From 2d8f4bbbfd11e2244c4dcb404aeb403109dcc51f Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Fri, 29 Jan 2016 18:19:53 -0800 Subject: [PATCH 01/43] Updated README.md for PHP 7.0 driver release --- README.md | 84 ++++--------------------------------------------------- 1 file changed, 6 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 529741bc..0714f075 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,16 @@ -# Microsoft Drivers for PHP for SQL Server +#Microsoft Drivers for PHP for SQL Server **Welcome to the Microsoft Drivers for PHP for SQL Server project!** -The Microsoft Drivers for PHP for SQL Server are PHP 5 extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The release contains two drivers, the SQLSRV driver and the PDO_SQLSRV driver. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PDO for accessing data in all editions of SQL Server 2005 and later (including SQL Azure). These drivers rely on the Microsoft ODBC Driver 11 for SQL Server to handle the low-level communication with SQL Server. +**Note:** For the PHP 7 project, see the PHP7 branch -Microsoft has published the source code to the driver on this site. With each successive update, we will revisit this plan. We've seen too many projects over-reach in their plans to be responsive to the community. We would prefer to start with a more conservative approach and make sure we're successfully delivering on that before expanding. We understand that many developers will download the source code to create their own build(s) of the driver. However, Microsoft supports only the Microsoft signed versions of the driver. +The Microsoft Drivers for PHP for SQL Server are PHP 5 extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The release contains two drivers, the SQLSRV driver and the PDO_SQLSRV driver. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PDO for accessing data in all editions of SQL Server 2005 and later (including SQL Azure). These drivers rely on the Microsoft ODBC Driver 11 for SQL Server to handle the low-level communication with SQL Server. We hope you enjoy using the Microsoft Drivers for PHP for SQL Server. The Microsoft Drivers for PHP for SQL Server Team -## Announcements +Announcements +- **Jan 29, 2016:** A PHP7 branch is now active for the Alpha/Preview supporting PHP 7 +- Please visit the blog for more announcements -Please visit the [blog][blog] for more announcements. - -## Prerequisites - -You must first be able to build PHP without including these -extensions. For help with doing this, see the [official PHP website][phpweb]. - -## Build - -To compile the SQLSRV and PDO_SQLSRV: - -1. Copy the source code directories from this repository into the ext -subdirectory. - -2. Run buildconf.bat to rebuild the configure.js script to include the -new drivers. - -3. Run "cscript configure.js --enable-sqlsrv=shared --enable-pdo ---with-pdo-sqlsrv=shared [other options]" to generate the makefile. -Run "cscript configure.js --help" to see what other options are -available. It is possible (and even probable) that other extensions -will have to be disabled or enabled for the compile to succeed. -Search bing.com for configurations that have worked for other people. - * It might be possible to compile these extensions as non-shared but that configuration has not been tested. - * NB: To build the driver with PHP 5.6.7 and later, you will need to specify the --with-odbcver=0x0380 argument to configure.js - -4. Run "nmake". It is suggested that you run the entire build. If you -wish to do so, run "nmake clean" first. - -5. To install the resulting build, run "nmake install" or just copy -php_sqlsrv.dll and php_pdo_sqlsrv.dll to your PHP extension directory. -Also enable them within your PHP installation's php.ini file. - -This software has been compiled and tested under PHP 5.4.32 and later -using the Visual C++ 2008 and 2012, Express and Standard compilers. - -## Documentation - -This driver is documented on [Microsoft's Documentation web site][phpdoc]. - -## Changes - -For details about the changes included in this release, please see our [blog][blog] or see the SQLSRV_Readme.htm -file that is part of the download package. - -## Known Issues - -Please visit the [project on Github][project] to view outstanding [issues][issues]. - -## Downlod the driver -The driver can be downloaded from the [Microsoft Download Center][link] - -## Notes - -####Note about version.h - -The version numbers in version.h in the source do not match the -version numbers in the supported PHP extension. - -## License - -The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details. - -[blog]: http://blogs.msdn.com/b/sqlphp/ - -[project]: https://github.com/Azure/msphpsql - -[issues]: https://github.com/Azure/msphpsql/issues - -[phpweb]: http://php.net - -[phpdoc]: http://msdn.microsoft.com/en-us/library/dd903047%28SQL.11%29.aspx - -[link]: https://www.microsoft.com/en-us/download/details.aspx?id=20098 From e5acb26bd3f17b84c2b75e4809c3c6e704706107 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Fri, 29 Jan 2016 18:31:25 -0800 Subject: [PATCH 02/43] Update README.md --- README.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0714f075..56cbaf66 100644 --- a/README.md +++ b/README.md @@ -12,5 +12,77 @@ The Microsoft Drivers for PHP for SQL Server Team Announcements - **Jan 29, 2016:** A PHP7 branch is now active for the Alpha/Preview supporting PHP 7 -- Please visit the blog for more announcements +Please visit the [blog][blog] for more announcements. +## Prerequisites + +You must first be able to build PHP without including these +extensions. For help with doing this, see the [official PHP website][phpweb]. + +## Build + +To compile the SQLSRV and PDO_SQLSRV: + +1. Copy the source code directories from this repository into the ext +subdirectory. + +2. Run buildconf.bat to rebuild the configure.js script to include the +new drivers. + +3. Run "cscript configure.js --enable-sqlsrv=shared --enable-pdo +--with-pdo-sqlsrv=shared [other options]" to generate the makefile. +Run "cscript configure.js --help" to see what other options are +available. It is possible (and even probable) that other extensions +will have to be disabled or enabled for the compile to succeed. +Search bing.com for configurations that have worked for other people. + * It might be possible to compile these extensions as non-shared but that configuration has not been tested. + * NB: To build the driver with PHP 5.6.7 and later, you will need to specify the --with-odbcver=0x0380 argument to configure.js + +4. Run "nmake". It is suggested that you run the entire build. If you +wish to do so, run "nmake clean" first. + +5. To install the resulting build, run "nmake install" or just copy +php_sqlsrv.dll and php_pdo_sqlsrv.dll to your PHP extension directory. +Also enable them within your PHP installation's php.ini file. + +This software has been compiled and tested under PHP 5.4.32 and later +using the Visual C++ 2008 and 2012, Express and Standard compilers. + +## Documentation + +This driver is documented on [Microsoft's Documentation web site][phpdoc]. + +## Changes + +For details about the changes included in this release, please see our [blog][blog] or see the SQLSRV_Readme.htm +file that is part of the download package. + +## Known Issues + +Please visit the [project on Github][project] to view outstanding [issues][issues]. + +## Downlod the driver +The driver can be downloaded from the [Microsoft Download Center][link] + +## Notes + +####Note about version.h + +The version numbers in version.h in the source do not match the +version numbers in the supported PHP extension. + +## License + +The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details. + +[blog]: http://blogs.msdn.com/b/sqlphp/ + +[project]: https://github.com/Azure/msphpsql + +[issues]: https://github.com/Azure/msphpsql/issues + +[phpweb]: http://php.net + +[phpdoc]: http://msdn.microsoft.com/en-us/library/dd903047%28SQL.11%29.aspx + +[link]: https://www.microsoft.com/en-us/download/details.aspx?id=20098 From 7cb7c799164e4e699fea6d09ba4de869b9689204 Mon Sep 17 00:00:00 2001 From: v-mabarw Date: Fri, 29 Jan 2016 18:34:23 -0800 Subject: [PATCH 03/43] Fix Readme formatting --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 56cbaf66..141e0cf9 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ We hope you enjoy using the Microsoft Drivers for PHP for SQL Server. The Microsoft Drivers for PHP for SQL Server Team Announcements -- **Jan 29, 2016:** A PHP7 branch is now active for the Alpha/Preview supporting PHP 7 -Please visit the [blog][blog] for more announcements. +- **Jan 29, 2016:** A PHP7 branch is now active for the Early Technical Preview supporting PHP 7 +- Please visit the [blog][blog] for more announcements. ## Prerequisites @@ -61,7 +61,7 @@ file that is part of the download package. Please visit the [project on Github][project] to view outstanding [issues][issues]. -## Downlod the driver +## Download the driver The driver can be downloaded from the [Microsoft Download Center][link] ## Notes From ff7d8a3c93f785d6232326c4adbfcbf6f7f09132 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Wed, 6 Jul 2016 11:50:54 -0700 Subject: [PATCH 04/43] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 141e0cf9..d6ce71a8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ We hope you enjoy using the Microsoft Drivers for PHP for SQL Server. The Microsoft Drivers for PHP for SQL Server Team Announcements +- **July 06, 2016:** PHP Driver 4.0 for SQL Server with PHP 7 support is now GA. You can get the binaries [HERE](https://github.com/Azure/msphpsql/releases) or download the exe from the [Microsoft Download Center](https://www.microsoft.com/en-us/download/details.aspx?id=20098) - **Jan 29, 2016:** A PHP7 branch is now active for the Early Technical Preview supporting PHP 7 - Please visit the [blog][blog] for more announcements. From 26e9d4f7db27609a01eec1bc9ca6c47493a87c40 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Thu, 1 Sep 2016 17:13:33 -0700 Subject: [PATCH 05/43] Merging PHP 7 to master --- LICENSE | 2 +- README.md | 149 ++- binaries/x64/php_pdo_sqlsrv_7_nts.dll | Bin 0 -> 391896 bytes binaries/x64/php_pdo_sqlsrv_7_ts.dll | Bin 0 -> 393432 bytes binaries/x64/php_sqlsrv_7_nts.dll | Bin 0 -> 406744 bytes binaries/x64/php_sqlsrv_7_ts.dll | Bin 0 -> 410840 bytes binaries/x86/php_pdo_sqlsrv_7_nts.dll | Bin 0 -> 323800 bytes binaries/x86/php_pdo_sqlsrv_7_ts.dll | Bin 0 -> 325336 bytes binaries/x86/php_sqlsrv_7_nts.dll | Bin 0 -> 331480 bytes binaries/x86/php_sqlsrv_7_ts.dll | Bin 0 -> 335064 bytes pdo_sqlsrv/CREDITS | 2 +- pdo_sqlsrv/config.w32 | 8 +- pdo_sqlsrv/core_conn.cpp | 185 +-- pdo_sqlsrv/core_init.cpp | 8 +- pdo_sqlsrv/core_results.cpp | 188 +-- pdo_sqlsrv/core_sqlsrv.h | 362 +++--- pdo_sqlsrv/core_stmt.cpp | 1519 +++++++++++++------------ pdo_sqlsrv/core_stream.cpp | 54 +- pdo_sqlsrv/core_util.cpp | 82 +- pdo_sqlsrv/msodbcsql.h | 136 +-- pdo_sqlsrv/pdo_dbh.cpp | 267 +++-- pdo_sqlsrv/pdo_init.cpp | 72 +- pdo_sqlsrv/pdo_parser.cpp | 21 +- pdo_sqlsrv/pdo_sqlsrv.h | 30 +- pdo_sqlsrv/pdo_stmt.cpp | 175 +-- pdo_sqlsrv/pdo_util.cpp | 76 +- pdo_sqlsrv/template.rc | 4 +- pdo_sqlsrv/version.h | 10 +- sample/pdo_sqlsrv_sample.php | 74 ++ sample/sqlsrv_sample.php | 69 ++ sqlsrv/CREDITS | 2 +- sqlsrv/config.w32 | 7 +- sqlsrv/conn.cpp | 434 ++++--- sqlsrv/core_conn.cpp | 185 +-- sqlsrv/core_init.cpp | 8 +- sqlsrv/core_results.cpp | 188 +-- sqlsrv/core_sqlsrv.h | 362 +++--- sqlsrv/core_stmt.cpp | 1519 +++++++++++++------------ sqlsrv/core_stream.cpp | 54 +- sqlsrv/core_util.cpp | 82 +- sqlsrv/init.cpp | 114 +- sqlsrv/msodbcsql.h | 136 +-- sqlsrv/php_sqlsrv.h | 95 +- sqlsrv/stmt.cpp | 743 ++++++------ sqlsrv/template.rc | 6 +- sqlsrv/util.cpp | 289 +++-- sqlsrv/version.h | 12 +- 47 files changed, 4133 insertions(+), 3596 deletions(-) create mode 100644 binaries/x64/php_pdo_sqlsrv_7_nts.dll create mode 100644 binaries/x64/php_pdo_sqlsrv_7_ts.dll create mode 100644 binaries/x64/php_sqlsrv_7_nts.dll create mode 100644 binaries/x64/php_sqlsrv_7_ts.dll create mode 100644 binaries/x86/php_pdo_sqlsrv_7_nts.dll create mode 100644 binaries/x86/php_pdo_sqlsrv_7_ts.dll create mode 100644 binaries/x86/php_sqlsrv_7_nts.dll create mode 100644 binaries/x86/php_sqlsrv_7_ts.dll create mode 100644 sample/pdo_sqlsrv_sample.php create mode 100644 sample/sqlsrv_sample.php diff --git a/LICENSE b/LICENSE index a39c89d3..d332c60c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright(c) 2015 Microsoft Corporation +Copyright(c) 2016 Microsoft Corporation All rights reserved. MIT License diff --git a/README.md b/README.md index d6ce71a8..639a03bc 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,130 @@ -#Microsoft Drivers for PHP for SQL Server +# Microsoft Drivers for PHP for SQL Server -**Welcome to the Microsoft Drivers for PHP for SQL Server project!** +**Welcome to the Microsoft Drivers for PHP for SQL Server PHP 7** -**Note:** For the PHP 7 project, see the PHP7 branch +The Microsoft Drivers for PHP for SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PDO for accessing data in all editions of SQL Server 2005 and later (including Azure SQL DB). These drivers rely on the Microsoft ODBC Driver for SQL Server to handle the low-level communication with SQL Server. -The Microsoft Drivers for PHP for SQL Server are PHP 5 extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The release contains two drivers, the SQLSRV driver and the PDO_SQLSRV driver. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PDO for accessing data in all editions of SQL Server 2005 and later (including SQL Azure). These drivers rely on the Microsoft ODBC Driver 11 for SQL Server to handle the low-level communication with SQL Server. - -We hope you enjoy using the Microsoft Drivers for PHP for 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 release(s) will contain more functionality, bug fixes, and more (see Plans below for more details). The Microsoft Drivers for PHP for SQL Server Team -Announcements -- **July 06, 2016:** PHP Driver 4.0 for SQL Server with PHP 7 support is now GA. You can get the binaries [HERE](https://github.com/Azure/msphpsql/releases) or download the exe from the [Microsoft Download Center](https://www.microsoft.com/en-us/download/details.aspx?id=20098) -- **Jan 29, 2016:** A PHP7 branch is now active for the Early Technical Preview supporting PHP 7 -- Please visit the [blog][blog] for more announcements. +##Announcements -## Prerequisites +August 22, 2016 (4.1.1): Updated Windows drivers built and compiled with PHP 7.0.9 are available and include a couple of bug fixes: -You must first be able to build PHP without including these -extensions. For help with doing this, see the [official PHP website][phpweb]. +- Fixed issue with storing integers in varchar field. +- Fixed issue with invalid connection handler if one connection fails. +- Fixed crash when emulate prepare is on. + +July 28, 2016 (4.1.0): Thanks to the community's input, this release expands drivers functionalities and also includes some bug fixes: + + - `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag is added to PDO_SQLSRV driver to handle numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, + `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` + If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. + Note for exceptions: + - When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string. + - When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off. + - Fixed float truncation when using buffered query. + - Fixed handling of Unicode strings and binary when emulate prepare is on in `PDOStatement::bindParam`. To bind a unicode string, `PDO::SQLSRV_ENCODING_UTF8` should be set using `$driverOption`, and to bind a string to column of Sql type binary, `PDO::SQLSRV_ENCODING_BINARY` should be set. + - Fixed string truncation in bind output parameters when the size is not set and the length of initialized variable is less than the output. + - Fixed bind string parameters as bidirectional parameters (`PDO::PARAM_INPUT_OUTPUT `) in PDO_SQLSRV driver. Note for output or bidirectional parameters, `PDOStatement::closeCursor` should be called to get the output value. ## Build -To compile the SQLSRV and PDO_SQLSRV: +Note: if you prefer, you can use the pre-compiled binary found [HERE](https://github.com/Azure/msphpsql/releases) -1. Copy the source code directories from this repository into the ext -subdirectory. +####Prerequisites -2. Run buildconf.bat to rebuild the configure.js script to include the -new drivers. +You must first be able to build PHP 7 without including these extensions. For help with doing this, see the [official PHP website][phpbuild] for building your own PHP on Windows. -3. Run "cscript configure.js --enable-sqlsrv=shared --enable-pdo ---with-pdo-sqlsrv=shared [other options]" to generate the makefile. -Run "cscript configure.js --help" to see what other options are -available. It is possible (and even probable) that other extensions -will have to be disabled or enabled for the compile to succeed. -Search bing.com for configurations that have worked for other people. - * It might be possible to compile these extensions as non-shared but that configuration has not been tested. - * NB: To build the driver with PHP 5.6.7 and later, you will need to specify the --with-odbcver=0x0380 argument to configure.js +####Compile the drivers -4. Run "nmake". It is suggested that you run the entire build. If you -wish to do so, run "nmake clean" first. +1. Copy the sqlsrv and/or pdo_sqlsrv source code directory from this repository into the ext subdirectory. -5. To install the resulting build, run "nmake install" or just copy -php_sqlsrv.dll and php_pdo_sqlsrv.dll to your PHP extension directory. -Also enable them within your PHP installation's php.ini file. +2. Run `buildconf.bat` to rebuild the configure.js script to include the driver. -This software has been compiled and tested under PHP 5.4.32 and later -using the Visual C++ 2008 and 2012, Express and Standard compilers. +3. Run `configure.bat --with-odbcver=0x0380 and the desired driver options (as below) [plus other options such as --disable-zts for the Non Thread Safe build]` to generate the makefile. You can run `configure.bat --help` to see what other options are available. + * For SQLSRV use: `--enable-sqlsrv=shared` + * For PDO_SQLSRV use: `--enable-pdo=shared --with-pdo-sqlsrv=shared` -## Documentation +4. Run `nmake`. It is suggested that you run the entire build. If you wish to do so, run `nmake clean` first. -This driver is documented on [Microsoft's Documentation web site][phpdoc]. +5. To install the resulting build, run `nmake install` or just copy php_sqlsrv.dll and/or php_pdo_sqlsrv.dll to your PHP extension directory. -## Changes +This software has been compiled and tested under PHP 7.0.8 using the Visual C++ 2015 compiler. -For details about the changes included in this release, please see our [blog][blog] or see the SQLSRV_Readme.htm -file that is part of the download package. +## Install + +####Prerequisites + +- A Web server such as Internet Information Services (IIS) is required. Your Web server must be configured to run PHP +- [Microsoft ODBC Driver 11][odbc11] or [Microsoft ODBC Driver 13][odbc13] + +####Enable the drivers + +1. Make sure that the driver is in your PHP extension directory (you can simply copy it there if you did not use nmake install). + +2. Enable it within your PHP installation's php.ini: `extension=php_sqlsrv.dll` and/or `extension=php_pdo_sqlsrv.dll`. If necessary, specify the extension directory using extension_dir, for example: `extension_dir = "C:\PHP\ext"` + +3. Restart the Web server. + +## Sample Code +For samples, please see the sample folder. For setup instructions, see [here] [phpazure] + +## Limitations + +- This release contains the PHP 7 port of the SQLSRV and PDO_SQLSRV drivers, and does not provide backwards compatibility with PHP 5. +- Binding output parameter using emulate prepare is not supported. ## Known Issues +- User defined data types and SQL_VARIANT. -Please visit the [project on Github][project] to view outstanding [issues][issues]. +## Future Plans +- Expand SQL 16 Feature Support (example: Always Encrypted) +- Build Verification/Fundamental Tests +- Bug Fixes -## Download the driver -The driver can be downloaded from the [Microsoft Download Center][link] +## Guidelines for Reporting Issues +We appreciate you taking the time to test the driver, provide feedback and report any issues. It would be extremely helpful if you: -## Notes +- Report each issue as a new issue (but check first if it's already been reported) +- Try to be detailed in your report. Useful information for good bug reports include: + * What you are seeing and what the expected behaviour is + * Which driver: SQLSRV or PDO_SQLSRV? + * Environment details: e.g. PHP version, thread safe (TS) or non-thread safe (NTS), 32-bit &/or 64-bit? + * Table schema (for some issues the data types make a big difference!) + * Any other relevant information you want to share +- Try to include a PHP script demonstrating the isolated problem. + +Thank you! + +## FAQs +**Q:** Can we get dates for any of the Future Plans listed above? + +**A:** At this time, Microsoft is not able to announce dates. We are working extremely hard to release future versions of the driver. We will share future plans once they solidify over the next few weeks. + +**Q:** What's next? + +**A:** On Jan 29, 2016 we released an early technical preview for our PHP Driver and several since. We will continue to release frequently to improve the quality of our driver. + +**Q:** Is Microsoft taking pull requests for this project? + +**A:** We will not be seeking to take pull requests until GA, Build Verification, and Fundamental tests are released. At this point Microsoft will also begin actively developing using this GitHub project as the prime repository. -####Note about version.h -The version numbers in version.h in the source do not match the -version numbers in the supported PHP extension. ## License The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details. +## Resources + +**Documentation**: [MSDN Online Documentation][phpdoc]. Please note that this documentation is not yet updated for PHP 7. + +**Team Blog**: Browse our blog for comments and announcements from the team in the [team blog][blog]. + +**Known Issues**: Please visit the [project on Github][project] to view outstanding [issues][issues] and report new ones. + [blog]: http://blogs.msdn.com/b/sqlphp/ [project]: https://github.com/Azure/msphpsql @@ -84,6 +133,14 @@ The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. [phpweb]: http://php.net +[phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild + [phpdoc]: http://msdn.microsoft.com/en-us/library/dd903047%28SQL.11%29.aspx -[link]: https://www.microsoft.com/en-us/download/details.aspx?id=20098 +[odbc11]: https://www.microsoft.com/en-us/download/details.aspx?id=36434 + +[odbc13]: https://www.microsoft.com/en-us/download/details.aspx?id=50420 + +[phpazure]: https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-php-simple-windows/ + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. \ No newline at end of file diff --git a/binaries/x64/php_pdo_sqlsrv_7_nts.dll b/binaries/x64/php_pdo_sqlsrv_7_nts.dll new file mode 100644 index 0000000000000000000000000000000000000000..ec4951ea947d0f263fde4d94016ef27c91c32129 GIT binary patch literal 391896 zcmeFa34D`P)<2%E6auCQkz!P;R;@;{P?chEY@mfFB$eU@8Uz%rXjKpzQNvPfQQ~6| zXPg;ls^d1}I9A7XSd1erU<-!LT_!F#>iE>SppICmlHd2-=gD$U%cA4FpZER$!$+Iv z?&qF!?z!ild+xne7cVzunM|f^{2z;%Osnuq|H{SR{r@S$eylQUY>^ZZpoO9{5g)=U_?z&lZg_m7XILCKg;Z@faR!lgr@Y-3IUoo^_zdl7q z)fZG0ztjJ?v3JJ*FSz85JC8*8&LxZQ9Dv_N3oDl$Dt<>Vvx(nv%dFye%(4vpZu-a4 zJM+bF@J@rR*3MqSIwA7ZSSk6)s-gG z<+t`Wt)IMdQasO2(=mnS%tK5j4Vc>oB;enU8vMx@pYVM$QY*QJ6`}(&7>363lK9yryYEF%izlYXcabq2#{_zR=Onsxi6Vs&pD>Rv=51n)Q zrFEAAtRlh!G8~WJ&xpg4|H{E$L-lN?o1BO*$w5B+9*Wd&oDV=1SkCb1rY9q@DOf1bU!{d?h8wX)@WU8*EK}0FT(rJ;B6Wq!{=c5sC`zh&%a&{+pd+v z5tivwFF(}zrw z({1m|^s&{+`8oc&FrriWJN&Mk-#b#S-+Q&3-_Q8ZyG~Abe<9b`q{;Ag)yVmqH_PGB z<#O1(LoTnrEr+|#km2nL%jH9(<`N2 z&A*bve%8NQHa^|U0PPx88 zHFA5+3+4KnE|c>+K4A5+@fCSTF5mpTTwc9dPWQhihaGJG{AQvIpM%vGX_4UxpCGsA zJzFl%>8HLb=Z}6U*JpcKP7hC&>+`btL=DRM{eP9~bH5_@*LI~G4*yy%U;9rv>|pa- z=nJ|2CYIlJv|K*&Pr1D=rjK};yz(;o+b@;ti~d3mo9~h9_lM-LcYvJV`>7m`w8~*W z!;hJb$H@0`|D#M_tbIdHS6O}Tr{wnRYvpiwrVPLBZaKZ{F*)q$ki#mIZ%w5#|J43k zuHU{xF7Nn&r8E4ikI4DMZ2k>p%I!JW{BJ9k%lpgaux+?pzWHMr9!Ee9`!~tu)h%+m zo55@2^5z&hf0*e9k;QU4Umw;ocr=L;<@%c0de_0$KPnsF>cet*zJ9RZFQ==&k;7f{WcVBp$>|}cFW5HA@*@1QTz(eQ z2mKWcKOCRW$?Zkhd}n6-Sf1?avuZ@+{Z7hG+i7cIsKl?TrzOAF>{9T;iZF0KXE91jGMXt|%tDNp-%adcxjqLQ{}HzSik>IukKQ5I9~vR2o7ws$G*HfOZj|#!?vul|kQ|ON`)l)L zxjsA7Z<>yk=^JMD88cf?MPHQL>$*v1PX%lr^w@c38D?VH*9 zJo1tZk7Jq~jxc+R|95hIHijSX?Q*)E*<+fSJZx%{+w(GdN0`3g4$J*5ogm{Uv{-IG zv|g@1`e(VkozXMG@ZH76Z*5Snul54Dy>S=I@Hhs``MpfPZesI)WSyKp%I9B}-``hm zziX}BKR=_7y;;s*%jBb%@#mdxxj&IHa@cl^9Bz74uD|(cIX(J{3||wopZKS;{$D7E z-RtD?9KI%|AB5TbUd!OMogl+!TPVY?vhf&Y>$|2>Ilr6bAHnt?yvybM5jI}J$II|E z{Zh`~yk73Vm)S?m7t8o+V)8|0^QHGox&8>7@1kSnbjNhL{!`g}(#7--+iP-p|5bAN zP@&vkFSAG3cztZWtDY*??|wiIN4Y#__6qMVIsXu5KQZ4Tr-xf*`27q&UPk}$pXBn< z*X8o=7CG$q$oWG|KXkOq`9oZOzaf`znjz=+4w3O4K37gxnS8MG{xf;X^^+zhU;T`p zj_ophAx57lvrpTr<@`--erjgxE&C_(_=qxlLzL~$MSd&SU(43pO|5c$Vb-7K;c|J~ zP&w>U~Ho8fM$FWB4Zxb7@T})r~Gy7PWt#`VZ{AgnB+wYazzm3_uo7w(Tc#{mD zeTQ7W_B}bQz9Z-FS|Nv34nMP>IaqrUwts44^NEeEpVT8{`upFN;fsuu=@(-BA7b;N zosFN!H*$U62Dv`-&2rd%x7>d-ldt}eTwi3BoWJWyxjqNC|1o%@3?D1clJQ|<`n+wk z++LK)&)Uc2_SZ0bVVKRQO>fEh$0&09X2$Q)!E$=nr7}Fz*!ocYn~bk!rq6Y8_@9^a zhZz3ctiBMd&%xw(=xVutUCjP7i~H-`D(7!v`gD}hkMBR5nZ1GA=lTA$y;8l^a`89x!OKRzwj*OVi}+q_Ip zx3TqE^BHn}$M5B^_glHVdIJk9a@ajq4%agMEzH(e{%hs-YMFnGm+2>JlbpZlZ*qGo z^VjqqBj*o|m-Blk$>9haZ)R5B?2_|mvHi?uW-oNCkoiL$DAO}KMy@aHli~HACWqY? zxxB4KP7n2y)6Hf%%=IU3pRqB2mS(pMANM!4zs&mg2N|BGZ7j_E`|NFU`6gx$SGUON zp(|K@4KjQYryO>(^`v<(tM4f}zk~Dh7P&p|yK>mxA(wC3DyN$ny`pEz>5(THJWQU| zvh{nE@3-X3^tEd;JY6ry^*28*r-zvRubIi0CT0)lV&gmfcey|AC*=BVjDCJ5|GaGe zaz87>+r;#L_mgt{ewN>Tn+#9W9dfw2LN4!S>z8JxUqy^&QLX>wG+f>gDp>KJ0!>PIpA)`kQ9S;Vw4* zBTOH*Gkm+5|3!r9C$*d9`lD<-h8D@`4u(JPujF(u)4$wozO#>#^E=r7QYa*+N7?#4 zyg^QHV*44HZpi=58=F>UwC^*h*pM3~X5c7&Wie7GELu9w4+DKfnWu>Ckc zAK%R1#m&}ZW)|*Z@P$Xp?S~mYL%D3cT`uR3a(`I1pH<7?ZDRHi?*H!3ko#|8^ShtH z>s=tj<7W0A?;spEWI))BVi8 zZ)W<6`ImBkYI*x3<#cb3+#d&%KMuAY^0M*W{HR=CnE9jHuae<6uaxuGGX44lw*KVj zcN}cJ8)f_(Ve?O?986Z{g1mC{rrEH@eyYFl#Q*Y?Mxqt zjF8K#Ouulj`W+mfZ2cc*{(a5te4hHSOdmVj&j_>jqjnjeZa$va`rN_Bx1I5SnA!Ka zKdqPHtNFJwy}hT&_%ieT4R$_@>u3It<@##b`Y*)zdmgLL-OSp5Nv zgZWdbi)8vVjh5SM`ce)%m_86@`+NMnqW?j;{KJg?O>F#x+4|Pb)<;cjy)%K;7h?ON z{{AvPB5Z%y&g^ZWljZ#G(`A0NjhFjlX7>;(!$n}}o{+M|I7?>IwFkCd|T3OWB1$rLzgv5eoWE)*ZNt1ZGGkbb$u-3rD39(TWt|8Q8&?=F$+3o-oj{ctbSU;PZeTBdJ0*!=2c`il2y8NOPkfAIY@8|%+& zR+-;a#_w-2{%B(MugEkRAI~y$>%$L(uW)JoMLC$YKSI*zS*59GC{aQ{BRm%*@{4VD`qYU(4lH#y|XgSuOLYFgMEl7iRWf^(YzNUC+q* z?W}z>t3S%t`~Hb?eO@Mi{A~Y0W&R8fX1_PzB*SBSP!3T_^T;9w0 zPyM%yZ#!GhN0_{6zC~`&_D4A!I$o|X^gB5mX7<)vR{tWlKM`f)scEX*UUY&SR@wYu z_sH#sj+MjF^>R48OAgl#mBUTv$nABp^+@zMIe+M3Ic(36%h&!&PFL5+>F%c({eH*7 z<#Ks58-F$iZ_{v={~S52GW^(?yl7r7w;yKXDRP2bAD_=63?EGlAN+o$C|e&mn&kG) zY<&~DL&jg%av7cwlULFEmzvD%@{mAKZdK24kaWMTU`jDJI z%;Am6@H+04!#0LrFFViI{2RIcPndn)cA}iFhUKuC+275KUmR>cjWT}|2jh1~t6abT zSs8y#N6YElU&VH{oWJ=iIlr0d+g@gmQty+?^Ye(!1#-IMF&W>rQ|0#T3+4Rk>vFnz zh#Xd1<@`;oJvXb5&&N&7zc|F!PtC{3?b(=qZDaa#gsrz7Q{?h$P!5OZ$?)4Qlf!nl zesVl0m$x(d9{z(|KFaXb%-7e0XFv+=0j zA@fUwkDqF}zhCFt^;XCF0%}n15F@9`j`iO(=SA?46_Uyl8;qh`f zGF=Xbf6M6Qkl|C={%P~ma(V8L)Xc_8I*B@r1?^Zh+BrA-_KzmAm^`T`hlJ4_uc$oSIPCC%JxUq zZ{_q_WP1??$7UK_Lb078Gr7h zb%?EBZ12nY!;i@I`>k>~a<3frvh}0N@E>)``E9Su{1YA{r#CZuhxu1> z{wVWD4vmuFPuc27f2J{z#y}baX$+(>kj6k718EGTF_6YU8Utwzq%n}jKpF#S45TrT z#y}baX$+(>kj6k718EGTF_6YU8Utwzq%n}jKpF#S45TrT#y}baX$+(>kj6k718EGT zF_6YU8Utwzq%n}jKpF#S45TrT#y}baX$+(>kj6k718EGTF_6YU8Utwzq%n}jKpF#S z45TrT#y}baX$+(>kj6k718EGTF_6YU8Utwzq%n}jKpF#S45TrT#y}baX$+(>kj6k7 z18EHW1Pmw*pA{>?^NUK9V8710vQ1i8jm1h^5c(7$C6M!>{__!~ZCw(JA!QYwq(aI@ zN@-YM+);32EVi$v%g;j#B}#imQQ_WLEVeq&WHM=krlO6AvZST1FRB}PD^-^l%~I5L zsP@cR)b5cE`~;3X3xC^lM&NH23hJe|UjitMvJ2$0hv{VpiL!%5*#Y>gp|D=|zLc^j z%4NU4PPF?Cega2+i@)tTU*c~^qU@z9Wsj80Zqdtb6=h!$WyARUX`<|LDP?yrk^1|X zUbb13eL|G|GyX;sWjm%M`+J;R_Ex>@ZK7;Ilx@J@&k|*yNGba}x$I?n*%_kj6{74k z{QW#p_WG2vKDq2K^s;B`Kjr$5lYWBzPC6RhYgk|6?J(W2Z@$?sikh?fYa(pU zo?7waxF*}QF4+pwf2XIPG+U(C;zv%uNl(B1dXXN+kDPvyQU4sh{<%DTq@I5B0+AlV zPipyKzY`0yOdYS+>9|q?Q;PDGKwVM(Bn+HFm%81h?o`x0RZBM3Ih7?FeJ8}MkA4Z} zG#!Qk5wrdg->c|TQKw)Kt93+O7}J??M@o>neX*Xq{WNFKinB~_kuE6Ac0Y>YN zyoL?AuJ&?KuQO1d?NVFxKBvGD|1|JU`U$RS4-vAIw`xwzGU{HeY0>GgJ{P}?~+1i$sz@T~!uus^oD=KfRa1s^)9b0ZF$}V{JDbe<=Wf@048v6(PXM5*jS{{Z^H{k z)A5_HSjOlvOACmA)oK_0(EsICOFpkV%Q^CrBW@TGJTJ?oZjMw4r_(&9sbUEp=VAFL>vsi=o(>aR0RmX&YUG<5Fu(QH&&k!8|W z%#For8oFXW3t%pNRZ+KS-`o_7MF%1>!&h4RYDI8#2F1RM*v1y$(Ms@+q6bi|_QIuT zHE2EVeBd}9_sHKeO)hn7%zFQ6IY3xl(R7d6=2Bl*R8P@NXY4KT8lkV6(3daJHzvQd zr9$0VVOcr9sDe;U1X6-Iw_cuYay9JDu-vu{`NpdQch;Y2^5s>Q#wykJXn$A3hL|hX zGP)jbI{0=WmOvoHYR9<J~&2cGHDp1csPVx%pJ)%8{g0X1LH$l6PdE08yq zyIY>lXl$uFqI9FS?ygwOqiz7cox$on&`j%pSuAE*v7vFJj$XWzY0@s4F3{U{u8v+2 zH}1JiQ%yx+J~{=WR#1ORL>RrAUsMPr0w7?V=xEl$cyA9;opIEp@|=z=!T@y_a&hmo z$P6TvS7jM&@EKwtMKLt?&$w+SQ;qYx_PsGnk8>>SQeEf%KwrM%>2}vtgLudY+0*pIFHO54I)ZJN6{EzwI4k2lk(oNc? zI}o*aeLc!Kr#YuOFL6$pHr1OH`ka8i2RSnET47C40AN zlcKJtPEA+Z%UA8C`6w_M>dx$Z%sUWQfg1}0Hx@v3u{5qk#pPvZ`uYI98J5OJ5p*r? zM2STUPe$W)iNLHSEst^-|Qa<*HKR0d4A0mp0&Bf)#?d zv1lp6m^^eg8wpu70#ipq79FJo&&ksop}+%CHfpI{d(%s8TF*9GD%aNGm3Z|9QFF9V z&?jbH+(Af4v``Z$3+Op2)OJOEji!%f7&FV2B@tpPOj6@eVmaY$HVup$fxPJw@}Lov zcV`JO=XTNzY4)=I0@UxkCKhw9?gFmF@SIpna<3ilj;8X7`z++#f}D&`nt;Uae6j?w zNj`b&V$uEbsyL$8{}p_qa|t83fEUy@)_=sp=v)HcCkP4!PzXpB=sE+m90uuwqmZYl zJEUKM2nM95Bsn^Q*f^nzwNrFRf8&WmdN?3`z6J!Z2#)_XX7MJ%O*7u4O2y5o)Z#O> z-Egxg*<#8Vx=L?RjG=NZbdkVKkGa6WIK9GUMujuALs20lP|W%o zD)2xsQ4c-wZb7-DFrmxQmwxZn)77wZ6=4AooEL}S;#3Gu6NuI!XvHLiE(zVZ9)+|A zNF5AXdkY{8F?{=DVzP}{-KfUEyMoVDy!S)ledApV$atSq2E2cJWjDO91quwjClvUt z+T)0g19=Yt*>!;c@^Xbxe@Bss`xtLt3+xjl=)tTr4eU?-Fa`UmeSZlvVZ7R>sM-Z@t)RgKRI}U@ytil@vbt0+mi=>!UFwIVA+QNR$kU`f4GUP(MxjrV7FkWJ znrbrVP1YLvfS#5$Vp)MOk#ZZ5qBP7enrwpIP4yH_j#=M;7PuU8sU+SE){j^|UWkR` z4(e-sxM2yGCx+VgQmn+s6xKKcW8x$2Qq;jl8ZkgvYk|y;^K_Z^(D?$f=5abtmuYuC zniy#pq6&lJq@6xbYH^a{`O-);YrF6SVXC;UKLZrcs_7<-wgKne#ozOYjYCzf_0^&B zjn$#5)^`6{ruDDDPGY3Z`CuR2+z!DTuW+UoI!|=7$f$6p_9!Y8RK5xo7#v=M&(!`_ z?YF2@VfyFC#Y2iuGtm8>~ZkOC@#L7=~G_*H4TvdOd$f zO0Sa~9@ajD@Q+t32FY4dsDf6zQLTu39FN6{zF&)KjJ{uu&(yy6+Hc?c+$z)h?$gls zL6>*y`$yVkP==&#->hWqf z5K7x7ioTy}R4d}1`jgT3>roA+w|n&&WAXYO)ZwYqrlzXJadQ>yH)|Cb!v@rk^!pB} zpWi>W0IF-O)zzS7mbO@im@F#>=GE_6;L9yLKQF^~sLRnZ*GAzi1?!JkOZ0t;>0Li6 z4^hKg);*QMS|J4trm$;@3dAs{<%OElPNvaPScEXS*58zu>ruZFmiaGfU_FLqxJ%7d zR4D1enFUI4YRvLtU%L_h#QjEzg_C)Pi9EiKsS;@V zYR=B#S!6>fvaHDIz~@pde^(7dSrTEe2JX%Fji5OdVys9J_MM1mQM*mhAs7@dI>wt!*kM_CXF(*s0t! z8VqG32I@7s?0geAN^!K#Ju;P}-lZ5bY)YmDzU<*G(afh{vcX!3IC?oCz|ve-ud1yi z^u5($Y>0(YvG&9{VqhHRr13^8%HKakgRL#WFS@x39oSW+VsZEu)-X4n46b`F8+H!r z?p@SL_02ecDY3QA`cZ?B9~L$33kNl(FWgSoRSyBgn-c^O$LOoIVjYOP%5@;hH7kHf z;I9q!jX-^X3Lv-y2z%@RVHiM&TAiz4aV4>EOsP(*$8I9lySIpB2dHmpJPk;1@E>8a zG@gX7=P~aBV65gY0_PEOo;Sy1CLo5alw_ZbS%>MIc}TLv2ufG|`4y_az}5OmjwcH( zJy)~}<>UQ#Ie?QtA)WWJe(pQWsqaq& zts73#F*ppez%HN|^^Q;McT2P%UORBq@%!b-vfnK6E`zqt4_vcf=& znXUx-NQ^ZWRg0MIWNAB1QF{x$==npbIg&BDYN7pg5v&T9YfLK7qQA$pbX!L z;xTLan>xdkw4)$MuoB7?$*7XHilX62-}!pBN$mvwE{s_RB5y<4F?HscwYT^@%vTPE z1eRdVG;5Vrf+0_dPC$b(Uk;L@ees#$TkaHSCF5jRJ4$4;ECC{7(ei*Fy9qilih76M zC&<3Q%>Z)odg`=y9dUS>Hr6eM*^_7K0GDZ{_v2MxJ?$ht?N^DkL6jE%2V2Z!%2yb( z-n4}*f=K zx+G0OH!6YWit2$>>~6*NNjloZ?6jcg3vO%CV)whK*x|I(9!+s zZW=muPTIfw3R8!o{)4vT2GTrX8C^d=Q=iq>DGgs{){ivl>|k-hdcRzkhZ&GZiu#I6 z-KaTyDf7iSr($mvcBA7FNwQtHA{C@&IaSX>kd}}YN#73a_;+2;d$FK@u~RMYTUojh z5ZavTM#ZtM?s&)tr!FF#YDEjl2n}Oo4dycK_gKseJ}6%2ngJ=&x84=Z-T-72_dG)5 zoR;u zUf+Sj-P&VNAE-YEN|JXRhz%4L_;BYKAi7RoIfMbv98{+2YK@kTN4P;7J*0DxS{62hN|Ajl?Sn z{-pgN#-BT}WwIZB3g&!b!$?ziLl}ZlG2CL-%C*_1UF$OH`aNGDW`(Vq7dA14mfqa6 zlh>?=Q}{oab6dCiKW^pqcaaI|C)F?fY4za+DcaSR9OVdphG6-w_GC}O+Fhb047vUF zM97bFF>nvnrd?wg%m!ha>qDJ^;oaM@bUZ+&uXWtG)sbr4dRn&(zah*`+ONt`C}{ou zB!EfwC#@bX20i02HCBQrQkw$L&4*Zr+Ia^B91v&VD|iuH zDqVS6`_(BJQ*@v}!8YZfFt!Ldg0alN*ehoUpw2XaD%4)Q{lFND(~t0^m8L@Td_kOk zWd{e%S3pVh80qiKN>IG;B4;4nzO-xs1!Wn^s?CvL>?0bm=B z?prMi94v2uJKvt@>7>%R0O~6p%Btqa^nc2CpD3?W)p#&!hU(d1F@ARlUqXS8JOeZtrZ1Rwt=@D*gI7l!=*96hj06N$68XYb$V}}LX z1KPt&X$xpffzT!o5q^^c$oPFJTrql9C{az8I35J8(i13!&po<4H(zf9F5U07WtWmH zDx*!n?mG34LNLo5=MQiM9E-l&>2Ykh0i?a+MG&sa^5h1W!49K#wGOF6f`<|m)!7Nw zn1k>r5w^oVx;u}U>0}XZ*nI-kGDOcTjHewBozQ)DV!$A%1SX>2H$ltBfX-;k&Nr=V z%kH0C@NoY4pAL)hT}bmq!=AnK<}2{PE`ej`$cY7W4+qQTYkq8T5U-Tb-Wg6*2%VEg z)`=*QKlfP5MQ z>7ZYMLSDT&9yKGHLgfChrw5${ zsc=DRE`xqG#n_Vo<2q7Xg$=kc<~+~>rcm7A;|idN!G8U1nq+3sH1T~Kk0eO%muxo zzO0=>`5@~bj#-COrUU*XeNp$%AaoZBVUFQ6I^U&YCrQ)y%HZQ+xn&#l4VS$|A1|$n zW%N!lZP{BCN1NqV+A|h|kB(V1-b#6cqe@8rTW))Z(nj7?F!xw&*lM4WLIThBD_ji= zAy%@g)H8I79f#eu=&?xheViO@Bz4M{mPJQ^n_*~)o(joL!aZNfa$`6Q910=Pk(>ze~Iw|5G z;_W5rLDOoXm`y(w4$QCCT`cBMAt!(pnxI;-c{XQ^nAi~NO*kR(FigZG@Sxa1$1NW9 zC9!`OPC*5*FtuZFf0|s!VBA0pG06Eq3OVZ*CP^v0BS!iQ<{m-56PQGgAl(VnyqMGQ z%OpWS!z`lV8K48qGtr?24J`yO=%=V}XnX5_3<(KtkTk6QeGE`YAJ5Upf8n24Pm%u; z21)Wf-^~~vnnI`7w!0jimRsH?ez$je99vzM(Yrwc->Kt+*}asJ&V0))tpb-Yu!(7u z=+u^-fzv(&_K{;_v5s3PgBU6I(ie@C6KI&xXn6q{2)~yq`e;!OYP6IZqs1;T9V7P) z8Y%7CZ{dQFBus)sbU_(EO-SoA43cshB-7SutBZkJ?OE}+YV^;H=EV5;=Fo2AV+W!* zNey}=&#Pjyo5(=(=*5aU5%cIC%%jQiv2)HC5tSStTW%N@&!lU4ihXC5x;*1G{?V2-A@09t$BCBMlY{|2MS{;Bv4}s?nBvyk0>t;;TQEcSjvZxS6EiO zMCcXHJ?qP+O}%6vYW9z-f7y?&KW^mL``HV8g*Cytq{%<7e{Lr&B}W@EACv+$K!~0J zruiStBrOVk8+H-%5)Tc^D8u&eh455C@Raa3Dc5SY@uQ z>o-0)Fazhl!FPYfDn6k=pb)%QB`UXoF#|j{EJ;HAw$o#taM>J2uhRo&1B+be=$j-6L8|h z*e*HrS>Q^YAk6tN^ks>lyEtau4R#WXBHGy*Q>+BC^kG0jc!2I}M9BD|RZc3vT54T~Jqhh`us-|8<{L%59^A7> z@afm{FwJ7&=2%;I5=Q6Vd2{eN_f%3ybI;c2ghNxz28wDEW&=I}&?C~j76x@ z2cBaswCW7$Oo5eQmZD-3fviu=7J@0Y7tZ`~{3rTj?2ia5jc&jl7_*** z?L-*+y@C=wqo-Re5j4SkNag&Vz$biBTj(|p@V7^WuDBa6%oWfw#c_L+@YmbsQeTc) z$3H1f4fqa)k7uwNe6m$rDU7*ww)*)+vrRr6g@<>r7ul#A)c_YqWOW<(!lS{bLF*?2 zkS2`cwY~$JRHZ(}?W=2PNT?f1UyWH?Axz2XGyzapJ6;DUm)h2SNDH-0z3tjW+h5il zMB5mqccK0S3FadFAij{=?VG7Ij5O6<<_ao>bQed=>Oy&a6+)+HgGzyZ z5l4G@b}mgr#>F3Wlt7yd5ZfwB>7osbakIuGx>@5~+^m7!neTxf+Y3)H4l>KierVmpnTO`{`^r&~{X*J0L!B-lKGv5*V%ohPCIMfdg81 zQ}$rLQ}G$I{^cQYb;e0x%olUAOu=(9wA@38Pd!D7w&^Irv8VUfIabkD&mr9yri&0c zQmA2ay)ML63df#{od-HPE?jM=YroP9exI)woUYZQ;Oa7z(K-i^v~H<8nvQ!E;%cZG zM~Z${8Q|Wi zy>dOx#v>Bvte@XVI8zVNW{WnH2lD?J%QR_Mh&EFW|-?R{x?hsD)+9b4`#A~?Pz^f?qC>1Kx3q5EQ5^=|%kdEEoV--i(JvJ4) z-fbu3@F6`r6ihNx)G+&KrSJ;8+H%TN^r_TblX5e(Amoc1Pn&BIL$~*2Ty)T z&bi9IJo)*2-f$+62rq%%WMX_-`}@fN7Kb6p!~~t^5JTrV>M`I$Jtwa7SVWL?o*aCE zm9c0H%qlee@_yE`$xS7 zDFeY)L4pmM>b}-&iduooQUcCG{Fb0Gm~AkF)@>=~im{JCg; zGW0L#AJDmDbyyO|^^m{)nOGyCTt6a$&_nK{55q?(W!)2Z79(xs;HCw1`T8!x2Q+fw z-}BmoDYL2UKXVg!Pyl#l62O5nfH@*aBcbaNf^bYRxEDQ00EG23kLnpwMr=a=DHN(2 z$Iq{+l_Y*{6+z%$O-2HTSz!?BbQ^75?u+j&Rem~s}NTVqrem~tKAalFv7_fHC`VhWF zsZ|sfYqI@}Jp=w!eH7B-Q?Xqxh6+xd7Gm$b7?=NG@_HLav6ywz!_-74&6@UIWAkVW zoYc$Zh^Fi6?!hlzvKQzD%Q?9Lv|dGgi4g89qXVi6K}Cvw;+TB`(PVFkSDTJs+omylBlO5eYRAGNYTEL;6<-NM9_{WwS#P9$jBgtj`*5$^#lYEh}k}Ca##d zx)*e)Ums7S%AE&;sAc7wjA$Lb@)C>S9`IcbBM?=(+X>aovXXXFM8*B7;!&vBvO@Q! z@z9Lx&ckh8ZAPGTM04&u z%Ssqi<5I9=(C~y;w=rK**?7U-P`b4uqagZQ=2M6H1M5_le(nieMKkABum@jNi2re4+Epd1Vlg_2?JPr}9RDlM zU{zVn`W!}?D=;n}=fph0oHt9LX$LRBtwkHKUZW%^uTKL+%gPP78e&{Q4PD{%1g;Lb z=XVYN?3J~)wKEIn(`R%lfdy|VmNgrd)(>)G)@gdBh0b7QMsRjkW$=nl&&X45a^M`| zlEHbnXs&WmpmLs~_M3_*MRj6$Y_BMrIoMawaARS{0vyLH&yV6SMyaX(#=)jKO>u0m z%Wd!%W-J_|1W`0l`MxJuHOmvMe4p0UBr=2j@==?XH3WE}H7A+8Y4ip!n1!@|pNX`$ z1hOwd*p1ER3l?d+#CoUS+5NyQfxL>q6^q&{=h0jS*w&(i0UN>aQ(<%Ku+>rfS7L3{ z18kEi&B0;2ScmOd(rp8I?Ujp&;z{t1{dvH9yi=g=jgbIh1J)NkfVY~`?u8GqqNboO zL^uofYb3W10UqdTN`lvjLO%oc`sv`EK_HyiGkAwn+7GbSO5h!#gZH-|_7ZpjUkYJ6 z%4E=vAbaOeW4!CW6WIGLYSeB&qDSocDQzkTZ$V#yy*CdR*dxZn|1=^z47ef2dG?Bt z(+l@GFe*mQDC}`yY}qB?`)&ka_!N5vJy177X)hkb(D$xcz;`?>q)Fy8K>Q0zq>@(P z@29~xREKX8!7#pO_#BjW6o>CD9ll(596lPf27JALKKPb>EAY4S48ZUhXw?J$nkY@! z_fkj4eS|pNw+MNj932LHSE0nugugHTE#MnOF!bpezOK`e_Ff_2NQLi*-a34{jNvg4 zut6*eQMr*yoJ`{fjxfOs=4spX%qQlH&V)z~1umHvAYmX1+(QMrh@*`H_v;0;UZQ{@ z6q5O8QvPsC{#kndM|TQn2u;b%4$Az8l+2^`%u#5)0=4r<@C1BuV9b>Nx|IBf>iI)^ z;$2PVeg8D%9i5W*Ur=zA;F0hDoAMR_aM9O9-A_{fLsRmvp!^~u@n5pyTPb4)@u-23 zps2Xtg(T#~L^Cg$`69~vLQ3Z8dS({{3No(}{i~c8sEl{9mxVfmbyA^{^;!*XV+oGN{`PpGxxjCvoF@D-a+Zbp;QS)}=3AKRp~jLD^SxLWjmuUX z?eoZDcrPiXe}>3``KRH7Eaxg*3Sz43?NnRf7pr7-y3}z{A?9c084X(2Wacy>%UPZH zW4Uby{wnuiJH0Wx6UKm9eW)^=d!LM}fWw8N)lno{`b96u_ta=j`OJTT@8kC)JG%j!sWS@W>mc}ncb`#XqSxVO19dA=G>+LG_oY^jH zthesygKJwX_v?4~4shWOA;6v$mJ@9oMqU&RUX*_jEj1)t3IRO5B@eZsDfH{$g@PJGP9V>n1*Vv<(6Z*u3U#fkRm&P%<-VzIz1@C(TSA0^nWEUZUZu8#kT%C_{q~L?=^0m*v5glDWph&Rl zJ8ft#eOQ0ni6sw(kGL2%4)HZmPgxL8$wbO>7wklf*VD==v~w{`)!qq3(ngCHZ04 zBNFzMV9rCNE5e?K6Svr?a6v@if-cx=my@>V&d2%JyYNNF8&r28CH9F~hw4#!m_3! zYo{l0ZHZ+~WdW+%>*$IYj47CRa4>cmDTmejOqjauLW=ypx;&bN(@d6e_DnX zU=o9crvQ!ntWn6?cSV+9zZK`PvQvTkEG;xLdAI zMdRAK0!6)}=uyH4yeGi;0OzF+cr&3Bm6)%NTy$bKO~G4SI4A_ePzA0n&B%072i4Y! zva1GLmNbKA!r!7|WO>T^b{h;oWDu&;T#nZ)jZ2WL!ZEtga)%#ZC=6w=N)1yLz7utS zjnwkE;yY2k$`P*1t#mjG7r+4?E~l-4MU*=>wS=NdE!O2yYYTvy*Pvn*dnmO!ucGYg z!M>x6_4vq(1-R$JP9@w%4yDw(4i7F_%5^%Bh|?w8svHxGEsb=`7`Oq0DpG-auj&yL z5EN951XwV0xrDKcb^<7#`Wzt$#uP4?py1|SDnQqpFU7A=a%tuFp zzR!d%L$+#Iz37HTr1vu+fmW>A`?NI-VSq?7eBo6n&GR9aIN$*qN;_U5wrdoWtWwu| zK-34&7AOY`@MckE?6r=1M6E8spO|&g9VlRQg{*X7sB>4-h>lq=N7CvHVu3(b%z7bZ zRL!s~DqrLFY2oJ)7y}b~zuPGRcIIHtp0AKXC1Td^0@+x^*XCC&tsQ7E=4+VMOSHYT zekC5m{d}~4PS)Y%RDMZ(&?Uul_x+5o&>h$lch@xR$q?7*y^MCXKVd}NiQg1#vHeI?AT|U~ zNbW~A3W6mzOno~2Aew6Ft9=X%$22nGh)t7@T7CrDfKz*-jeX8HCB{UfPQ=Z~9b)2) zyQsFT3K{WcTvG-0E_YDmTX9GFZ<#^4_0ynK@nuALQm=r?>& zvHgmLh3Y|&c4D($R)XV7JUU;as!8Rv^*p_bJWhZ!?b)wrV8|Oh5bu)-XG!rM=dbvF zQ*!>?cYjXYPk(;ciT)J)H~RD8?w``1)Oo8~a3Rhqfhw4{u<@~543_}SIF}1+NSpGz zh8-D7=~{Bqq%pIZ(iD*fdZ`oi+2@3%@Z$J9m8O_~6q05p_~Iy#MoD zmNi?6u%6(}dCJIZ^1xuAtgo+2?Q8O6?S{K%w9k0;2z+-|(XxF)r?X)bDA*`um|}lf z$$}3|o?@R?j@t|_ftPiVC#>hm zUQ@a;W_^4`WuAKx(KEWUk#pO@eRa%y?8UmsBB@VgRT`dq(ZC90 zGk9%Jk2u4V+{x}xzuB95XhoJU*!9A3h*Tyn6if{5OV_+~4jxCKKScIHV!$|;E(X0| zLI6VITGAjQ+dm90J@<1Ix)z1V%rG}kY;=w*bgm&ff?0)?%Cg3jh5K4Nq1Nu+TX(qA zu@wjDg8fdQ_J)h1QHxXkGTH~Fp%DwF6xN{m1%~h{fK}nN5?4bTgcnX<7v#~WCkwaZ zPVE%D452j}_rQM+^CONx**%UOmf$i1L48M1*kyk=W_`1fSeh=@)cN^RW0rmq@kxGu zigF2l{yojb`YcWSeB~_(ex8z?r6-EmH`~X;9t_lhAKJzaG-($PhPplgYac}5ERCCj z*+O0{iQHsC?2ty~8D|FH330@Dq zd^db1_3dyW!J;(7+)CC-^*rzf4*Ze+d_@DTbK)Yb3=1?}h5kY03idlaic;00)Ff== zKQ5Fu&&b+x-2Dm$!30?tpQ$`aVTOBy`{HK(kxKBYyr@}GM}b*pVygO73C_zP%VBw& zt_Gg1IQHH^^7VA6ews$MjwQQ<(&3?qw47Tv6K^nrfwjh zd5Z-1#b})Le^Sv688e9*1^WRGp(I{ru;1B;Pb!JS1hAwe;@Z?W`;n5UEh6?4N@69Y z=t|WOm7)>05HkBh{gd-?PVi23YZhqf$FYS>~0_-59@1u`Y|bt z%kbcAVFRhx`p5|`ip8u=iwSle?Z)Ui-vGZJNy*VOhcb%MGv$i}_%wP>6o87^;3!Jb z!5@HFAg=?r1@{x)|Ct?&ja_>330S!*8R(B{Fr6WCQEuN}n#lno%TI_-dSy`D$mJ}AO9z&H0bv%RKaesQL*Gmjz=rU3*TtjwNEK#ixEDvC)RI49TBz!46rkg8Clcj*J? zJ7eH{{|QPB5~VOi)G5Va|EYN}2>K3*S?|QOraK``%zRJNE=+7(E!6^c| z6iMk=xbOa0s6QwDhyA(TeNg=YGZB5Z6MbOTQd)^VSOw0H)90Cw0nB|F=nt+!7aZI| zF)GERss&ds5GJlvy+Bw`-ZS)qGpW=CqSS;!Fd2?p)7x_D`NJS<$mBuBI80XMHJ0nL zF$O$E)!$hD)$+BaekX9$SFkwfEGa#cEAD_?!sCO>WyX`fjse5^19{Z4H zq!&%py_nv;-4ydU#ia6|it(|(0qXq-rEUC?d5#O*ikmKePUc61R&IKfOOUdw73vdgYqL`y@s`#JEhz&E1w0p>~MDsdvUo zkeCRP_~1O2V!j}^p47p)`KJA@EdTHU3au7}#ub7qbAI>?TqvYxmOBp-!)h$2wpngl zh4F{4)3+ema@%7FS&G}Vq2+MTwG7>P^N~K=*OrsFK+ACkV3F}auhwUdjy=d~C_R?} z6q3c&hSD?9&>F@gKB@GKqFllRtyioOD>tCmsfNr6&cNW1Gr(YSx*mY=&6HBW;`|00% z@1n$RVafV&{rh(Ef4zUBsW+DpYz;r?^y}P#7`F~>vVSM}_D#RN@1VrspU}UTCj6NG z85;dW00qODOJCCdvQCF^)H zpsRiFw5%!5Z0PK)I}R6+)E(Z?sqaJXqLH}xyS+F>DC|2?Gi8qEtRahya}7NeX<5_m z%xb4KGOWwk|Co!$u-Xk&ehH6;HN#t$R<5+HtXf?YYpn@Xt%ep#-Yj?=R&96L*SoUX zt6=MbVJ8eftu?}NiG(*jZap^BvJx+aROM-h433|6yxiC8McRLimSJ;Ke^S}o4iMji zA0Hik#EVJd;qCtLBf)7$c#3Eb{|nLd+P0RkH>~?L8yirL<7FN&@=#z)_X@iHELVTnkP3 zEwPa{%TnX0=tW#9+Hzli|wRO05h?ZKd-gBGoor%{JK; zg!#y*b>r-mT8*myu6x3~r4`XWUeFqRLwLfxr5!p#_k?*%>!YX4ji+>;5l`9qPf_Ng zc*=W}Lg?_Ql`u7|H86tPA>o^Z8~o^nZa7{YFHmtFfW&E|!$GpQv`6%sEb*G|7HtrV zVQ-l*o{~gR_9qlWDb}ADll1-Nfrjdc{?G#VcMny5&H{ z7&BFa=#jYTxJZk30>$`Vyq<=A!uU6}r_Dgf3q${azXmN03iSKeJnFF0h)ey2OT8hl z#xbp6!RgpcZbWr7POt@GSraz)lrnU)tS1AKvG1^$)i*2KR1;fAh33C5Ol~s}BP>tG zGOvu5f93jO4u|I}ken(Y@gC2N{h3ZxO&3+seL<6*t8n%MZR>VJ zHLR_N&n{>Rw7e&Fh^Y|li?GSkZa*MI`I(9G3YDY{T$H?Bl&m|$&|B@-fy)8%Y|?R# zHnuOZM)ttatw1RwOYa8l4V-&{cqMy0*4;R$ZkRVezLrW;(}F+cK(ili%AdP`E2GSTvV<3%zGzQWbNMj(4 zf&Yydz*Bi@KHR-=ZD6~T_6?IyDF|@M9uU6|AM06qMrF5}U)n;?%HfhJTExRb0bgb9 zm+xT7N!AMZ8zru|@785S#fd05z^^bZrRxm3T~l3&pnd!v2cw88^PIk9EQnO?Z&qWbFi7wJ=A5>_615eSwRu| z?Z2SNf4%=Od2Z8 zJM-pDIzKWIMph4`^ki%#xB{c|sse5t;LgP3K0b>Fmrc@l0M8?3Mvs9Rzb?NjFjyq# z`3^&Tw0~9LB6t-dzmo6EMgat%cWa>qKAq~92veG$j>M;qI>{?u^KfidDLq67AFEu9J)gN0h|R_ z^5bpb(jXP}<3dr<#>6~dKM&-;3%87Re2?Bxs^XZKFQW5B^v8(y1Tw_;c6=X1XID^7 z0Jus~X~IFJfP-32z(G?^PkqIm$cQF9QQzSyNmB-ntsl6zbhMq2M?d@SElvhH1xBfx z{e3y;SQIqJ<3E9XoP-xp3A#7tLof->0oxG`*9k;pbM!}z_#VZ;|5zj&Pj?l$eu@8Z zBR3mQ>q+(m`~7Vrji*1KlO9ik;;ADzJ)THHb(bFh{~Aw1tDvWYs!4SzA=QOe4Ku6g zM#q29oJCi?`HoXm!-bso<;0}#QF~*TSqpEV>33ZR-E)GIpB~7_)17L0p%c%j2Di`@ z(XAvNPeDnV^_Cfhw<4d%(ds+H18)u|{GoB8)Dm3Yz4R2ie58eno@W%j8=0KyFz3kG zg_dQ{LrUWwbf>s%Ew>7@MATcQ<~fD*Jl2H^FT@pSh3BD(II=uk|3dG$AA z@)~TMk5wF*J{)VnuH(5Pq3#Qmph`TJikF=uuPn;GU!__V=+QWFS zknr>oK-3YSEjOZGL6m?$_y;3VKsnxp>*K^1UZ&e%Tp{N>E%{yd@96Q!lPB^F#+Gjd zVG&b)KfPBXq3&}8JPsV5a5@0(*;MKNp#^jV@YnD*0Km~Rh{_)uffG}^SBR6y$SIEL z5=rXUQwhviIQp@U7MXOOrMjC7@c-CcaWD=2$NV3m9sZxn|9>zF{{;S@VHEwp!vD{H z(2f5eFrxl<@_(idozDN??$8Ire)-?j@qd*6X;E1&$$#Bx0%v6_;m9Ly;Ku@EOdbY2 zq=Aq(!_~v+JST)pB^-TvkF3U7f%{sby&Uv$ZwoC;u*$(1Ai4yTO4PlM(+k4sh)!=- zIm|x1>qdSO!qWpj4~3{&$P#VdT0*1=YtpuHbenB^$3r9{Ns8Cyy3~vEqvyI*9gL%B z=@*xiqiU9?1R>Aukj;j8Ovv*BlIK)L7mr`m`ysR~6>qD#BXLtfF@s45=wGc2AbMFA zZqr)u86*UT089veVOWEENBr8?9uM#xj8OU!d4&8DUO+t&kkl5#BOJzw;P8^7>8?EGZ2gZQ<4?|d zAbS-ah)t*nhpT;-k5m%Zli(#QL#e3ifYQ`6xBjQ)4+P(Tnr^HIgHFg9yam=OF2%cT z6}n*&@8ql!$zv-3=9>^N#HcpVyiiC-abHKpeWV8(U2?BGhg$qRm*PWFSKGlNd? z2k*eh5DsK?Dqe|MKOP4sLOgi_n;zb=CReg^gXiGL{$|9B(+?O{G3y$H@S+7yKm6sn zOjGc}T$eb$e`6&@ivye2BHlQ9aT#Kr#$jjY(i?Du-=pFfHjZp=(rU0bKyPC!x@T*& zH(mYI0dKoD>UB9z6fGPlx`t=alvg?d&%)E-t@+6dStiR0oQYnPp|o~n$J%gJTK}bH z%ZdrHh7a)I4FYbw8?Y`@nXv_4qVr#KWx&da!_IKIZE1sNAchZ)F4LK3`IN;^9u5aA zNc6?g>G-Ko!Zy(qG_H=*m=pM-6(C^6%fL|ZhP^cCab6`%@X7W zR*3Yv*c5cfi|9QIo&!?yr)3~(I8+Ge7MY^m{|#Xara@tyZy()lhmYX<-<&QksGxm@ ze0l$aqN>H#L%egri?K4qbV)lwovf{~CU7NmTt`#Ltn5M)rZ9wW)_IL8bp|VO{?a zms5ekp*jks6Rb@aW8L3OGlE%Y8y%NDB3<-iaO8f=PfIO-%#SZWB(?lM|I}0aG$ipd z5#2IF`pvO8*XfZXfOauwB7d%Fj$|7WIux%^hk<3%%RM*z>YkS!iN1l3}PK+QRAgK@BDNj%31a}j;SL&9jC3dn03PNY}0buZa_X<*09kqBG07j zv8Lm`P)gR9NHdgd6hB)p-gkN;GI-aGfGco*-zm>y-vNtth%2q-rM)92(fvE6f#)^Xu`Nyo;h{c20ZDR&v-5Qn=&~E^IW2 zC<+P|8)?-VVHZ#of*X|WWh-s9we?Zk+RCp_Ta|}eO$d?*NWdqE4{U9{ajgX(2tL^V z_j~5v-MgD7Y9GJ<4?mK=fJ~#K>Q@u8BObMsRP?Ny-UuN*7*$U5c~+>tFPi|2)#X zKm6yQt)kdyec$iNt8yR?PUk#m&53{@$%BSG(KQcp<7$&Gc~Bgef~0uRYmyR% za5CJ!Er#$zuxs$3bHSEG>!Em1J#teFp#u;49_vbDeI^f@m)7LBQcX^@vAv^JliBG_ z4oqv(&Vvqqrdu9#;qBe>ppSUE-76jxmk21G^Po1o%j7}Ze{S)hZ6{>%ptcwO&-0+( z@}E&p^_KtaLr_-mkPQ!{v7k{1Dc+3;^f^Lar?H@$)aO01plg5og;~(0fT(8{)Sv+C zj0Ii6z$aa>penrkVl3#X0xpmu2i4PpJtYU#;&3ZN4mt$TrFhT|*tMpRsN|q)V&~%u z_gjlIS%Ifv9oi9*5`D!E(vY=PC6HI zr%OWD-LAM0m~EnnM3qIZAVE0O3mNW5QABwCLqd*(+U9xZ@%#*em06qC-k zWRK$Am*z)1X*aH?$wi&;q#FTYiYEoJ#HJ8w=SlAuaSEmH%#u9mMZQe(q!vjDbIAX3 ziaFGSgM&Go3N~evioS*19(dB+4o#ap>GHH@Yf{aY*;rve)$BX>q&52iSaGUZJ5SmR zoq0Dr>8xA3|b2rN^uKEz~ul z9%4Krs)uGO1JZj64Q3$z%LQC+bZbfcOMc!4wFEvosohwxK=#PWj3<$LJL6jT^4xbSY4AH(OA(syc@AF3p z@z8`PQ@yk7>JI2l>hwZ*XG5>j<&egy%ehSqd(BC(Xe}p4N~iPxw@PO}`NKc{xIg@1 zkIu#7KLw4YNipLWk53V)j74-|h*{(u-&ib2#?mYEzP9~>fO zPYzyzsKuiz9ASh*SInYpmBlQEiUplPMF0zKTBg4Ns(MJi&rDvGK;?rHL1Cy zvU1JL4%D1v7Qezf8#Q;yETDSUoWQ{~h2v6OA;mEG+L8I%WEw6UU-pt|%tR;BnZ`>a z2zy7S@g(z;V$nmA5~eXpw=<2auzG-L6fiTzG$tUo3#M_=%FdWZF{iaKjav`vl4(r2 zssoynOyeB9v!OYiXFP=TL8-7v8mo|Pe zQayu^NZVDd;=M6R#Z7C5!m! zl^tj|$s!_nXQSONS;QBRC+?C%_Fl;oVE~`b8Xkv)(HU#_1@n`v;Z8{jYdGsRJ8KAH zNdarfVP=XooPpdfSVK7kN0BFJs+E){a*(T7L&V!9Yw*qJfZil)D8xG(ded3MnY1!O zphyv_56BZZj_zu|aqs`^4}Z972x&y|hxw2wQ~QlO5AKFP`1mq4!*Yt{)9(4h3qMTp zhc78lwEw|k7HwQIQp}=rdEzHOPGc4s^2E~l-73O5VH?wMBDV{s z(Fa4;k!kFpah%8#XAJ6+X$+Xw0nJIK@z2ZC(45XRvbe?hKgTpYE3y#a*tx`6xkL`8=&5r&b*g*4TRdBZmY0?8Qkx)WD{%a_r!&mdrOnJ>m37A3o<5-d?|wi(+n z6PB4h`6a7Wj!4s~sTY@&9*y!+>gFDwdWFy900kIi&h39n;d2V|VDEkq;p;6vpJM6W z;d3;L+VGkEYr*F+H(B^>dew%{-Q0HC@cFAL1k1!{6gG_(J|_vhu+t#f{KxS5@<4@8 z55YF+(*?*9-rwO(@9}vZOZN_+*(_?q=OfDnpD*5M;q&_KHhj)`C>@^{U24N;8N@pa zpE&~We;S{+<(l*T4=H>$BM)|9_}hDY4rl4!;j>{tIzF!yQy}l+8!UVt^@}5Q|Q%TtQTRvTBJgbxPtD#WY zpYq?_&-yRvu6_o4v72b(q8;oTFlMG%>3+Y@tS>XYpY?P6x{H^V1k}XxmrgT^m6)3I zxFESELxqe;?KiJyj+p|Mb}L6 z$K9{~Wo7hlDErVw|JFf^jfK_f-%1FeTC|7$U5W!Ud?P&fKheMM*IVPq*0`{Rp z{~*_3{0>R)-?{Rsy??a&HODVY#?Ni67k2oc>)-ns{kt@$>;4_fcd7Avus8kt2}~uv zy75!|5wRrm#k>_9)@4FwR*4PT472P4ZavJq@-_y3xO1qJkf-XsdRNj|8};MH zOM3;$@lDAm$KM9B*bn*SFfgz#%VS!iLB^?4ZX%iK9_0s#=bC~!^`xeB$28-UrgAgu zH|HyQism=YSI2wT$?Gh(Z_B;1KW&P#=iz#A{@*yPSs{A56Gd&f!uJ zLG)rdd#>g;HXxGR2k}&6Id4vt*XEaYxi(9_l zB3h$_w+%U-72;MWx9#Af9siQCL*Rn#kIn&C4Z+cbUefy!LP?DeqBe-j3jGoFZFEH( zGMl{0bVYpm+Rj`71l5BZ z`?!}ne+gMtG4VDH_Z8jqxhw6yBI#r|e`LHnhcD12eCN#T8onanOTo7Vpmc!mU0jgV z4LBWMrNKvOD22bnfF;qQ9@r33y7_j7!$`QgvU#DFahZudn*Pe?4rEm!VKfQPkIKL73-Fp{4Is&FpW22zS^m>-viu(E z0#oIG+_8Me`9117eq+w>kH7iF=lA1KoI%_iVEhS{ECbBlFiZ3~iBB-@ODzBAVn8qc z&)>EI_4Q7HGW*Z>Dj#ugJL~x@WgK2vJnAo*X6$*>CWzdE%jp&_@}_>uDbe_`dfI zflsaPnf~wg`M#G?P8FICO?V%}UGH4ZqQg@R4`LcJ4z*=u(rIcvGnw3}Qt4hAKaEC= z5pxVKlgiHn8W1=T{)LkCB)nA_jap08G_=gsN(qX^Tt&%%(_C9KL6LOV{d)y#_Wt*; z{chOW*;>-{!CKiM$ZsYnU8L{Kc%MpRy^{F)plL`X7y<+U@*$!mBASJVRqFk#B%)F^ z^!;(f71;{1mv^DJweqc@LvYW1S=LR6kW(?zCP7PZ44TUMpz?qJk}Mo1K?pEvuAx^r z2Cx!$57YP$dt&jj?<)+1Ms-RGZyebh(z0P8^+|CavTa>4p<8 zFzUvFB;)8LoqR{C?&N(>-)FuLato0q6M7tv_as>CFjOpASpFeHJ0ybPhXIECUISid z23nwo-XPlR9JA}t}bxY_s-R^tOvf2wuvuk5WO zMlIUJd1P6C#59a%rP`N7Z`=RB-v9qJ{{N%xC*>*Q{fT5^W}Lhvji%mbVRh>6{hxaH ze$CaLeV?NL5Cm}?nyY<3L@SwwP-dAin=4g(W0?Be?NS7ZPdn%e%)QxAMsZ$-eZnm?8jo8j)+@RmU0-9n~@jZ zvu#Fzjt=F^aT;zbzqG6JRPLjkF~2sge5j}LcO#;Rt^N};%4;+HpJ_#{?GId{#5ybe z0*oFwdRF?5Md|5~x!cH;cVmB~dI5orV&5f3jG&I@)pNqz@(-5-s!(TW;A1E`Bgq;$$Kx7enjqkH19vVf_%I;o|)RnnL*+(_`eR*f!f30`lPiK_-`w72Xa1}t{!b+$Vk$D$0kr?S&*&r8y z*K)IFj6m2HTtdDtd?@tc-x~|aBWtHmY4^-Q*ZI_pmkBtO;a>P$JUYYeDhwwq(7-ZV zzae*zzet-7pD|QstT%EIwO!2Uy9$;ez5P%6Uq?_IGvN0zbCn4{NU5RlAu}O8TW%(> zPc{24Jv;A5ww|r5lAhH~X+J$IJ*(@XXYZjexK&+xR%7^P;G>xLef3q z3?%0(w$9MfV2L;W_(Qm_t=ua39}4^r0+hxbp?wL@N=WJ|!mMu^hTzlG5a5pZH&a8< zW>hIapC!f%j30en0Ckvv78ngHdJAY0z8d0@Y}bNQ$cCBqCZ-FF&+oJ#zcQS+>PqGV z-svEj8i99TxETHMSE4^_u~(~qLP*XLDnVbI;gd)`ZI!VxPUBW!7I;29ew_63B@mjt zG_F-Io{pbjBEzhl{VX>AaE0_U|3Wn&#l}64(g;c3{Xo6@Y@+q9p6|Z&e@Xnv9Fyrp zhJ@*5pHU;_hLI}I%2q_E#oZf&JS#EKcj2xB`#_JZ0AhYKD>=}0oNGM{^i&KjFeL-+ zGk!0J2i`-?PU3Xqk^StX%s0>?FJ1(nfYzqQI1z9rxj|$Mfb1hc@}MWO7ST2{xxrg0 zQruuX2E+rx&59Ir6@=jn)G!nndmr8}2s?xC`#-S3_g6pQtGynC00l;G;2W9--!*c4 z;C*p|1>Yt62VXb(&d81Vc}h7sdoTiQ<^>{+&d|_=_xiV84%{F{&7``GQfq-DDCbTE zR@!s%O+`L_^Qp=gBL^5|%X_Hv^Jc*u%LTW3C4y7y!hh4M1Nw5#Ab~PrvEB_A2m5SAbJPI8%p&n~OJyxJr2N3h~)B(gHasUC2EmC1p z1QlttHd=1$N#`J@<%)e0j^9#u2(B&$a*RUm-Y^Z}H`o|PD7Zqy#S>F-FWeLcRAcLe zBSq*M%xB&-=!B|fZE4rC6&voPy;8RK$OY-`J%T~~%G%2aMXYaAee|(S1T?jE^K5BW zNAGZ%C62BYQK`=2hBsRh$K5@hg(Umiv3==tR8aIA0d%&Es#ap($pV!&@RD5Kil!=JC&)cX{z=^K12=_VL@=mUnQW;hez;W~&u71+U+a#esqF=kYwGK+Qiib8y^6Z(ii(24mT0 zFvj4`zjwmNzan4G5D&ruB1*B=at+-C=iwZtNrZKbh+15%1xJYPV6;Sw&Igm4ZBB83 z^?GeIcQ)q}BBEbzLtxtM-T#E9uWvW1Q%54?SKv3M5hnH{V<7N-V#X~l#!dt$tRs!T z%S^$;@9-&v3}Q%35vH}@ZL6#kaA2tdR@Q`D!*Fzp?mA1JLC0#6B$&Z3dDj-l78@e{ zmGB7aZzkgVO~wNkvSA#twHe2wMY*I!|1`8ClM8kNLRuEv1EP7K1C0(;doVL?XM5vm z(^0INeAf6GL@eI_!9^V0_Hf<+qAVYbmt^MR9*}#31Z$IVCw>)7AB3JF)u0xMN_>r2 zNfq?kVuRzOp)w0-rb{!@BQw(ZY3X$RPs(yf)p#I%06!LO@wWp#r1S&&E0kVvA!7NZ z`LjJ5BrZ+Qh6ht)cmx?&i?8&?!VnIgkKYbik6sUuaZE3#-swWve#tYQ6>AEe{#7a* zM`i2oHCbU-J;v`IbUJod)MaCRJ2M!qn8_H* zlJm&v%*zisSE^PQJ}X4JXM>E*1I-oQSi(PpB4qxR7~bMSp`;% zQb!m?DUb6lR&@dkj*q%ZBjf9^4x6kKuMv!fNbWW3>=akM#t$#_P2oqhA9nnFNEMt z*h18neVSltaujQai7TvkDO_!V9PzJ4*GAr>aXJoJ^5N{|o|OZ`BHjzs1j3$mWO^0~ z1o-~ z@%k8E&Mc?GVca6id#lIb%4r{daiLRZ^{$5EEEvV%K2Z|lgE|NF|TWBY@x`~K|b=N-YP{ebH3e@*#+E&ZyM2$!Wf zL6zxE3FZCE-*?=P=Go~V{30`LT4Jk-f%p5H&fq8h{Y~$0dzs}k`?G&(v4=^0LjDqI z%uJ`6RPR&ipq?c+{zm0TNWD{jHBj-Z8pWIx!>Zv;!osG^Y++yKa|WNXlK<}Zu&Enq zvxenS+VPcGx+7~x+R4fSZ(}FrpIO*ldG41*wI`PT+i=(E8ETH4LaiT`?o%Odg$6*s z2j_m{s{52yfHw_LC9uILDy3k^k~XD)rP>=Ts=eHKsr@z71G)2U$t2)iJScf~x1XZa zHc!KCB3=1?G);x_3dTznleHOnnrjR6j+9OCOku;tAIK(dtq3=Kf0CF3mEWl^z!X4R z^oax7(pjT-Hw_OVbC_i6{yn%`aTe0Kk}hi1B75Dlk^by?eiYez9BcTeB=lBe&e`mA zMLrcEN)nWx8u?qG@*pbm6-33zj5^3d<7X-nyNizYGCK_)p|i0(S-8L@kb;YJM9_q{ zA|FEU1T8i`r0IFVqBVgndyLQ-|DGO@9&yHV^T}B_pHDoSZj6CGoA1N&{^gGE+lv;Y z?H$@5hdqT2SM5cM($Z2Nwk3UGc*i-St(v6%%fU8?V;Er@xVrEHt?A8dZP8w#hElNI z<9NF!3VdA(V32o>CW=~3h4BYlgb*5!(;_=|Xi;sCRBvo#A#5SPpYU#BiJt!J2;cV$ z1N=YRgzv8#+0NDLOFM&YoxB8WlhRpRtw~U2=JHU4*OZ4VHQ@?f%m3)@bfu`RGVb##+UcD_)zQ=RR z-@qmUMSFtf*uIU~3yD5_#<*B+U#)C>p68ZLQ^}JkoYq9B{i$>g>MQ0m-RHwpAx6> zntp-Xm^D>e#nBaaz4$@xOrWs})mX32%REo5(VBMTXf7p}dse2OSDE@cQEG+Ukg|+y z7)^AvL$#3HK0cj7+RIN*qnP%)PfkkYDqFARSFU>3vo`5r~ZNYL!_^4ocYj~LP!fD{5m=EL~Em{=nbn67I zxw^c`({M9pBj>Z9TQc0f9-bJ`Yw~zJ6~@ZgUyPqUV&A_7jGxKShjbhA#ZL(6rn&Rs zcpohH=YjKHU*I05{&kh^}&WC8#(s) zC|FRpX^)h?@Df1qFCb-CwZ1bvjk{0Tc4C{>sF^A=FO*9SWJr!wX6QrKRTDo6U%2H9 z)jyx~4{xNX>0tr8T`We(gqJmk#7D)pCTM~e)`GnFuz)^R+6`eX-5Vbm(C6h`=hseS8;HXi3JJ>V4k~ytBMPY5B58yqY z7r#Cj^}7Q)?s9ijmygX0=fJBhev|@vvITOqoc)8s5rW-V?AghJ&0a7O$FcWVcd)ED zoP#}kJX?10$f1VP?^!W6AAhub{}LbmotN*gUy$$gEPe`+iFmtR`8>C$0jJwSpY;p* zO2TczyR>|-XEE=jfZiK!GWm}1h3owJSfs||;=g?4VlGmSP%1UxZ!*Aw(>*#Q6$5-A zSp#^IU7k-wyNPHRmP8MO(1~)a`7noP@k`PUs>*kUv3I3f5}-6ZF4-zY1w2~cvJSQ= z00aPRGNv5Gi3d4$1^2hlFY)ICG!&vZx`_pie%C;FrI0H5)nN7Z*rpg}OSVW(>ax~bD znfC{=ILG&!)t{JR^+(!R{b1X9h@AUyJ%%^Y0e&xx3vx?0-11+@@3)`R6Tkl^ zHsSxv`27la1a!ym|Nb?_@2gF7B_HLYl( za_+2Ydl2*q{v_4i)-L*x}5E{l6cF zc3% z)KLV*bj$1<6biq`YCPE4 z{Z#7t59U2h)ikYYhxXDu)fuff*I5W7=e1%WnRJ7jjYD*fj zv7rz)nm~@!nP$XR96yEM1X)u>X%NGKa@9qfg04+fMO*v{sErA9X;8+irZSp`>cBB^ zbFG|GggodsQG&i3_2Y<@ipeyLv4iOQZI69BGU zJn6s`7${mf&?1c8r!@OjiWk(?R+X;{4=(wKXXVp8(W$YDU}~Ha)F)tS!0+d|5Ox&- zOpPtGF)ZfPz+wU-jtvf$!7mO8_2J{zrOCS8_tjY5rbM%Utfg*Gg@=EqtXnAHzmAX4n1Xcl;iXoV-oL^3RDW^cewu{zl zFcBpzoG3W+mstf>T549&9#Y_l7RS$I?dGHs6)OBJR72);ishbHU9=?-S(6pm0)3+! zZU8x4ve{pyrb2AgF=Er!8z|^DIXn zO~0&y*k}!3+?BlFQD5IC#L~$(& zNjfQ*+o4K5r{qVyAL5li%sMiCrc$K=AKJ%Jv3>)l!z)3miiETD*UJ1eXJY~M&y*Iy zIP&0j?`e2UChPUUCjAUO4Lsu2bnnx@alzVoZ&n!k;xTZS8aGobatB>a@!`mIh6n9# z&I%9WD>K_&1+fB#z*!(1&ytr>3BJL1WAL4mXUDfUBTpYxbO`cl{I1Q%qvCO;+Ke?= zNKu)mVI|&I>*K((i7;i4j zz#b}K=i<}OAf9)8IK5$>r^Zt(i^T48-P4HXT?P=gJY5Lhu|BpHTpioRrET0%? z@MDs|F@noONdj2k_;yq3r9}vRfuniX3%+#@AiiIN)c~Zha0-$&8bqiqpIGP>LVy5m z(!#4rTA*a>Y1oJ~#{)tweoDD)D`J2(n&fi80n2u`HCgXLR14Nnly5IFMuyPKS$ZwK`GFzq8mbii7aLxR&k zsZ2$TM5~%xz$_yqpmcT`c1jo zvbWl49_nC)89`YEvG$rY&mUT_Z=NeQkksJZ=Ya-iLNO#p&mZ2qv%fWrQO5cnt&4RdLTVIdk z6Tki(3We;2lBFsCtd(Qe=u8|%)?v#!uRu0nK3M?>}J?Y(} zO+OohpB;k`wHb4>Q8lAI(5#JK$cIaVz|@#kd9D_<6Ii?DI7tU|{e6fvKFR;)Bqjlx#HGMfo%p7u`n~XQy{` z)>Wwv_%G+s_Dn0;$MypMZKE>qkF#RpUk}t_RSj3b49yyXGvO$PAsExu5cCD*Dz7yPLAZV1Z+-^$5y92y-(aF^tKtj%91{6*3I`JoT;R%0S~HYZwrT z#P|a+YYl@ta~MYSO&^A@Gf)DU#8)y5$>G7>k7*+c{cL)=hxgDdLFpn2*z=)rstL@< z(p+m`+dVrQS4%cyyPO?910)K(%clB-VELN4ec(1cbI$Of2vjiN;ar&MFLL^+gO!~v z!RlfModjJT&?bhWsao&Pi8U#xUjh&Y1aK+@^Mlzr*bqCL{kwgD0?Je$CZT9%PYRSj zGq+E5nKpCIhyec1&JIMinJ56IO%}v-KXx&?4>Qfp?=x-U(jiDssr@RI;Rx{&y&4QZ zJw&KdlfV8`Ysr%NU|(;yNdS*e*c(4p#m6PQOMd2Zv^Wj{NhQ1ucwWWFjK#CL8uHW# zt^D~}M{v!7b~G1_t4&!kR}|CQIH<2FdCu7VEdYk-9KX=YKb`$h!h5W=FhW`wfoG|F z71Op)(>rRWJS#Hgi$Qu;qA{9fQjv9)j73!T`M-@pN}39^$eVj0SYYM!JfK$2L-C81 zTPdSU8gX^#osEI$WNO8`qiQf$S{&qj2q{PM* zP_{wqRlpsZU-k>t{9X7(+;XdVXq*@ChGR3G+D#!DFhK2?4>-tFqZQf4ec$9zTSoQJ z=C7+Pr-4a@}uSIqjnH#hmWul%c*hM@jFiDn9*APQ&TS7x3SAYkcLaBU+HQU^pp z3m6EJ3GbCAJP@`7b3TrzM%+P=;PtmK2Jh@18y=1&yf59K)*HAb@eI0SZ_uCEz_KH= z9PN)3cCmiy>jV0h_K&(Uv{w8qz+-9L+H)S-g@WtO#wC#*WDb~h85>2&+^nw(i)Clv zY!v(bg8J1XdCmly4}F^$FNPo0eJ+Q-HQ}9&UoCcf0p3^Y8!9oiW8PB^rMFiZ-{3A> zoCk0xamx|dNxXLSz68$xSrxANJr^bFpE9dPtsxx$4E#095zuMv523I;*)XO0SHGI} zr!WB97VcNSx|PpzGX3NsXyXQcrt9}37yf)4H(h5aNcf)#M0%qn-dBmulK7iShylzS`*Z8eXCw*H6at zbQ3|oCD+ev@$<$bv~ztOf=SNf{6k%50$w-%<>4Rpd2QRN+0VF$hbo~rXQR))(O$0^@j~)w?ua0F9C}_?8Z;@VG^FE&-ZCD zA(oQo5?(J@6+)TfJ`L>gPNA9}dp=ee0uB+i(7et1A`ifbd7pBU_92EF6*I86TV0CE z6W)GgchN{A5-ky-&0%tpCvH@~PlK8yHUaiAtqJe|C*FP3;mo_tzn-0|9`1 za1@qPe6WnF$rM80NOwH!g~T=nfzcN=J~9{Fjt=)j&XV{ z$q>$98IvP~Sm<6BTFye6KDP#^m6%D#(bn3WNzd-U2lUy)R6-HXrCaW1JJIWE5b4Wp ze1={4GVrJSfyIPZ>PL#0O?XENM6uf&B{kGcaXi2`%oGut@b3FD>x`X0&v+Nxe>ODF znC0YC^tySPJ`K9)?I9ey4xnNmw7E~d*2jzRiu8LbeLnc+U*$1+T`{Mk&$fzSQl^F> z$#{bLs{xo?U+lloKgqBB5Ne>X7e*0?PJxO1?EGqdFPyirzF~frK=7(~2>+JHG*@-3 zVmZRL8OQk;Sahev>?A90dk>dkUU1~TL~UeWBJ2T~E4RnnvFOByAX9>`GV&_em2m@d zLHdXn7A!0^rsN9guiXb4kPL0Q6B;}$k^VQhqAo354g>}f2ljsaa;I8=O8kkJf>?#Y z!$v|fQoIGPvBW1l4S&W%WZ^J}=hlbileu}7`c?UXrnho}uD=J$KlC)*jr{rrZilB~ ziIliJ4+ekv+Q(ZDexU_{_a5_miZX)lIQ&T`MDU1lORx+Z^PB~YhYIdOEjNf z54Pb&xE&_?P~Re_IyR~e{5d=*?y3Smiw(?TEvKUv`dNs9tYX?XCH??NiCQC@KIj;Nj^wzM(D)twYNA=q`K|gQh5;Pr<%xwlARn)j!RDx&Jc%)J{fQ z^jkO~^ySw^Yryz4HvYxk3=pzzF z*RSuZ^+P-jolzSxvQ~diBc8=gf~W`c(8UXe?MqbZzb%}OI{lH)`{K|(Ht<=#DaD(( z+j_TZlp9}GmhbTVfK0IxQxK8k++pmvHX(xTMt`D7@yVga8v}$=j?!XF3ptI9llu#? z{OB_hiz^ zP(htzcN8{aV)ES-vHHjvwNZePPW7_PXBVgKNL1hexzEdj|0+Gj&M zx;63_2#7vNn7OSu_1~ga*yue%xNt>qkM19ah;g_UX?A;7jCa@Xh55uCjg?C(3n$d? zb%pxXgYL3VdoG^!C+8Bx#btos8Xz+mJLZg6KgZnKljT{w1!$-`E#Xf*3moUn{YGHM z(|*07;Q{!&2V83tI}mUyoQbIb5;{X`9)q-*5Nxx_(%8OC0m0M+{61NuJ9>qn%)_5rMj==|{y$cX;> z6w=7x2hq~BD&d@*3UNNO5(_tNlTm>MK9+suNb(q^>uN%2M2Y8*l_ zHs)RUv6@(t=eUG>a$vYCP|;d2_HDFQt-psWFYcF7LU~QULr5O?tuC`kucJ)qHK0Fh ztN=#`qY=hT;n28~CkQd`zTFA{$y+#?Qi93D%xT264D?kd=xLahBe>k{%gG8I4>b#{ zw}_KHVtOXUE@3#@>r)hp9mCNT`p#qf9{el4=}qdc}p)Q&bx%%>^)R5swZs7 z$AkcfL@DW-WXCtfAjSt&FDhXgiYpY@QPKmLgiIy$W|Q%qGIlI&f?~fz1dy+=Ji@dn z;?&_;i;3E+De?S?=pM<(HUn&2Av-+E?|Eu%q_JOQk9Y3K)qY2T!}E)#**F5-6x6MG z0cMiq6z$OQU}mdV?F*40Pvt-=rZu=fnAX4{ME}%6a2amey5Jy<7)#?c09dX6ZFhy5 zpsoa#Z$xuY3{RvPlP#to$nVhTz5USWT{taLTn(bmU8uVfzst>EZkMp&V#;fr=CGwW z8REJdjtO~g_)|hG^FoQ4590r07Plow$EYG`G}OM^(=hEl$27}V2tF3 z9L~M@IJ5Gcp#e!A>`*ANV8TuAb8^p zTT}Dc)MC}td%ILql6M`N;zVV=LQ4spfTu6F)~b^v+4v2%7L(!?MP*}+gA@YHvpuhB zZ-LpK#WBPtW57F~Czg?pb^=aq(OPafk~HYIxfr6bW)aMP*IlS zo)fZ?nl{ltnL4&Qjr%I4g1Sd0I*$R=F>oQhfDyR1VFN}-c0KS0w)g-B{l>~txVd8| z8>-XKFQpA2MEP@lx>ii;7iPieu+JO%2Ou(NDS?y%XqFU-PSIj>-B{6ok6;>bD^ar^ z-jbV)&-Q&zB0Wp(eb4(`D0JmN)g&x6$|pD-tAQ~PVipL|su+kawt!Q|^u`Z9`y3az z?4;u05E1Lve+Tnv0L6vUx8xP};WNUA*FUub)j2{*|I9XH>&F7ahqle>(`{@Fd(w61 zYEJ8uF{t568i2GJcS2!>t3~*D6pS?#HX;}Bg}lKiMs+$O%1=iWfa&>oE{J0~y~^rYX!Roe zLHsEB6vQDK7;L<6Vg|y{$7bgRWApptg4z*UY+`|bbv?@0CS+mtG2le0oUzdUT*Q5X zBeVcJIM2$Ld7r&;!YLGt_E1_8P4=?apguVmov4i@65%{h2b8&3`iwU)rg1NFo#9hT zo}>OmvRjc2ni1XMbYtkJLNk$nD4LmW^gZ9{Scb*9Zw}QQXYawExg)h1*pueG_be!3 z#Zve5({ORu9ZM$TM{{jXV5g6$5aS`x9_v}TR8Uw8C6TE06xK0uIT|u?r$W=a>{xtS zjDJ|Y|Lgus0)+?u_tgUk0o&B5!G6epv1ZY#kD3&aD&jaq>mabs14`=(U@HV}2vg`? zSQ7KV1{2<=Z^g!Zf#y)`;S>rwiYW{<-uXmESJY$0#^}8=y1$I8(Je-Feh9eqNsKmx3AL5=Vep4wuQ8QS22dGk6b zX>iy8z5?REGVD=RWv?3>aGur}_tdtGo$hqDjFqE~hn&IKJ!)Onp_kNW!-8@?a5)?9 z{?K{Z&Tx{-O*~XCfoYUD-Pd9=VhC_^19V;(A&ePxT@ZhjYzXD%UgKnR$FuS!@F+Db z3l?LR13wL$KsM9~b46Ig=Lka8j6!%utlvIXL>CH3*r`WscGRceMRC)ODk8NY?nP`K z{LEU!B5t;U$$l)36Cg^;?EelkyvzVzyu%0q#C6pO)|iPfqJkM4q|a8P?TrL(QOL^< zgROhu!r@+*&2i;SwZXEqP{9I)A@l}$QD^(-kbB8_ivc4;Ov)Y%;SoyD*AxMs30-uO z0KbXBx8$ZJ=z+0nw@|}T?WDn_vRN^HJ`UCe&)|lw8q^6r>Zkm>@+IKhr_a4oUsJS> zyRM~gAsaG|c1&N)i%4rk`0Kdtc(JLG=<9mU>oTlL;`ONGncZf~w)&hN=K{)_>a9vv?*RC=`vvZ!P>z0h`aiiae6reQbL# zwB#g=KtUBk^RL{@y@YY_@u`ct*v*A6qnlairrb`Z76guvu?Zu=1p&92jZzDOWUoZF zHDdRpIit@aLBsY?ab%xs&T)}_+3*tGmlHZ7vae5gnBLrEz;35C=1+hI=eQT`rCKkX z4F;8UTBD_GIXy-49%ByhWg&1g!F`%iT+Dm239jV*;aq}i(j8=pEc#YK3kE>B<`?j+ zg3}iIKx*}XK@@K@#=!u=nkS(-ntn!-DxpQ@eBge!j=~A(6~`V!7g5rH8XJcLG!8?T zF*RC^=eBcY2^G$a_rnq82cCu0bb~0cv?!7zb1TRP^whOY*YqEerx^p}zzT9V5}jO> z!^SOtoxR4B)FbI_7nhdgE=!>tD> zFj=|FToS>H>ljCAt7ipyZ~YCa)eqL5*6Q~z5RZ{CSHG{lFT?QQTh{7VG#0Dh!kM!A zSsZJKvC{UDX7p1Z%`!jIjNMfnxHiLg6gGt0>A8aQ*EnVpl;N0E?Z=1)`bq>6i{17R zfyP)2^fm0Ju}GUS^l+TQpF7hT_uAy+U#=ya#yYl)ixhi`0~st(vkR~|+1DoGS4^pW zNgD$Q0^5X}<$sDvbvV>W^^a3bR3-=Gnc0WBC1rBJ(|L?K4kEWLa5J?erJnw-}1 zj5osLU2azjjGyk5;c@>}4Nrlwv=V}mdKXde-u;X9u8!|I{73oeqHg3T*`3h`=G&lO z@I6n1fj&h(&GIxnD-SMD1HAzvpYDTOH?%R?(8ie5bcAu=E&_GnKJ%AK}B#+p&iBAWnCHePy9uyc+H;AEqDOlR0xJt7@D zFK+kg=Au~U-mC&+YVOTWR$Y|6*@?)u_}55d!9N7AZ@;0sIMTSFSuxJJ)rNnr5F1Tbv~~~W-6^dXzl1(F~&VQJDjH>BJfT9^}&V{)Uhlc z7%hj9>l5Dgc^EqATl;Xr#j{GD3F-p^P_?S1Vwlk+$mQfdRKZ&Rx`pO!z`Bf!RE=U+ zDYm2Ry}*HCsFv_9xS=O|HXrWS^IyEG_zEng4s6l2q8(gkr(0w;880-bi>LEf&#CTX zl3PPzf#mE`(spDu*_I<*Zaj`@4=kSsO9CwNrKrmX5^#9(D98@FmBl=H!9vY5=)2sx zM{jjI5s+bsNbpbcZNfWwt}>O=@C(}*n8{(F2JDnYF5w-FT;-JFxudD!8O!RZAGeFf z=St5k%WIwS26S`yRC~>ufg8VOR&oSz&wbDz(O~3Bg5U_x0pdw`ka|$lgGZ)Ih#NM7 z+RlS!B27X>!J!zOOqmtgE@T4_a)h)5)f*QmU><*!z?i#?(ST?F5Y-M){j2s3Cuh5( zkU|faY47QCh?K3c2Tgh zG{}yy0S}9w+=(}oEIo^-BaML^=`3Kg4TX6};TNL|;dKM;4hY6$ZWLe5@`(F?4F_%y z5G*Hzu;)a^*_ULquFp`6cBDz@HyNwQBlJDVEEghHH%hlI56f5w%3C~la;(R!W+leC zIHyml8KJ8Jy6*sXEp&_U{Z~V}8QiYWEqTu(E>C4FcX+3LN$ZIIUG_fVJto-`Il(mB zZ;^xZRy9#o`$KH)Z^oy_4AuU|S5*6wSB>_oXS7tiKxcO-EP|;WMotSq(lnf8BXb!A zQS9Qb0Jg!8{BY6Xs=>JmX$)$D*~OwN09Q(C3t&I1)!*pO4vAuK1cik-ylUudsqkFp^0mKtmZglqL2lpp8t7UFgI8rXT}f9A=#+T9%mFK%Pmsn-r1&RSAp?u zmdp5$@W8E3we69%`@y_1z3rC;S8VDeRp$?rbq>v_6ZOp_gYuC<1!DD70cuJII4&6T z{sVS9kii?WLSwUFYtV4ZO#D{6*7y@U)DaNES|t$b2)A96fUz?K6=SDrJOZIQ?)9(w z2(%b~1?#8&KIZ%7P!%i#n&vg1fO^iuzl-n>N3;0W_*+SRH*59~V&8%NiN5AiUbEt393An{Bgq9?5xT_5_V-}9vN7*4K} z8u2G|TExf;oy0#w;i`q>7>KyV@e{}r9CUkyG(M$S=@^nJb~gErS8Xe)fK_AOi* zKS36h96%n=#iKL4+P{iDgoz=g?Z^KnjO~H=jFRnW>$g}2jTWjupl`=IDr8&$RT-)I zAt#sFf|lE4K|>v$LAP;9^$fb5Yb^za$u=8SNpt5*Q)Sz>XZc?RS%Y4(kag#oNn~-q zmv5dop;z4N;kt;>VDKDu%w0kNk5vF_dL2#^;B~r)vvr~8wzmicj0cZFZ!sKw4YhH7 z8^y`%)J_wi)QF%(H2fUyx1npS@GfJQ_zJ!d_Q11a(2q#AFZ7Ms;bULRDW z5H&y{s{J|44I%~1)j?{&F$&EV6$xxIS_VLpoq%^JPD2>PjDwz$e=v3B9H%N)>#R*w za{f$K)b)!$f_*HxvandFOLUJEb9oy65Po4^yVkQ(CY*WNlN$Xp`{-XZ$k>BEpi?+d zT`J0S<6LZGLH7@BR=6o(M^8NsTRPJ<<>RYGnitBijMllU^(u52YOq|M?IKWj`x5d6 z$dCkuD_o61eTaV*I?5S!&^utP$ewO2MYd-omN1yOz&4Mn@jBK!k|tLLLCWvg?a`cl zpz~X7)wF3@`%R^IgMp`TjabbHqgcj59Woe^*Q5{cwy8c~IdvG zt}Lvy$ghSyy7yJ-WgoMbl6PA%S1n9U;}kfmCiU%#4`Pr}TOZVB)iaazOj7kcxk+J5 z^3Fj$5+WNNQS0GV28V6nO#CFg-zOXed1oM}I9ocRzBR z+qg7<+~UwlkReq^!w#{Qe*JBr*bn^#`CZw9D{pp17hK7`3RfiVRqmuA`=Yg6zU;;@ zwacyolYe1+mgDnamSgaFS&ozOZ|nIf(USWG$c#SA9GIt#!PpVr-=lkXRq1y`Ubuh) z{qz3BUl7tKD}0rBc0gp%V5IC|u)2E9)x;=!*atVw?Z0X#pZI#!+sP;4fXwqp+OtA` z#$RXXNdKa}f5+}sZJdsgoyzc4dy?OfT%;aKsHe*7B8&(QFCl8?7(--$DQbS}6&Z== zH*%<%aq-Dm9Lckwuq%3LFhUH?4o8@ccFWn}vTf&1Xw$kn;`7 zt4awksH-a39@H-^tV3{rN+;rQy{$Sr6f`hgY^>J{Qga%AOQgcox_ycuZ8 zl8%aM338#O4%hntJ>1+^ldIm*D3=*(c>&KKT5+$L59)|H2cre0l1hD7^4u!E3w*pY zK-YKCjoB)#@I~g8_FFjoG0bxZnm!aiwNV6}$}EWVDhWBjs!?!+6y!xE)YU_NgU#9$ zYQ-z*o=M@~W!xgjC7QQxl@al8utwzR6M^R1W#>=CelFu*Ez&Mv<8d?J!n5^j?ste@ zpcz+jKsp?1Cs+}*A)wZ1v9qWBh6t|I8*bY(Da#RbwQxLYxEG}hjz`4&S_TDByBrF5 zsU!sa4&>|+DJNBC74`eJBvtOIq)_)Lq3+=pb;}0p>+z|pK2HBKH|iBnm!qYotsJICY5cSxwq%Ol)mY>hyJZA87QvTgEFOKv8jG?bj78=bE@N%-i=pZZ zTnTT^r@s7R1;*$V+rNQ8@G$&~;ompRXKT#tzm|UVu$1Z3qASU0H((G7rofMN7Luti zcz(l(y><1gSE1{Wv)Vlk$AVsA1e2#lXXfM7uKhjNUmP3wMzmTq3Kfz-_hm;3fT0}2sfz!375q6v|=ktnUyn~mAf`I#{(QvN)+ zj7wjyfiEDMug#w)yEUk1Zw_KdQ4g?1(kSz+sG=kLF_9_?q?-OLR_ej<^P7fP4d=Sa zB-*#(Z^AofMk=QTIZ&RKY$snmg>3a0ldIBMYWiQ$g8}FPz1`SSJoj;Q#SzbWob_Qb z&-)X$Z_Nmg8Z2*Ga5UEA^K#KkIqpg{VO9?}e*04DbI*#+4LCXD$7nR>7dMv*>hbEO zx|bhz8uvif$3j_9@=PFhncEnMBYq)Gutu_pzQz<(h(p_k?ViQ`P*M8?{NchfB#6s) zZa&Dru5x5PM%}D4HVM*Un{ft*;T1GO{Bs-x?MC9oHQdJk-b;B{`h{37Wc=5&fFjWnudaEe|#6-B48Hf-FYCUcX+i5 zRDX3ArE?FhK!*;SlM3nO>;iu8$1eo4VAQnz#&~mNTcHD)UjJ!!Xb6aU{0kIs8vK(7M08B@BugLqpo8H{B&07QM-rCEHkq7;*rvoll}9dR*dBeX?iezW5PPKux9`@|4V#gGAt=-f}XwH+zGzue<%wdN4z?jBymm z>?-7C93Wxg2k%919G6W3z{FS|YE;2O|38ehYx#1BTMGkjX5 zzK%Zl8;#?UX~~}CiN-upaCqNr=D02`l)SIQ=sJHkn&mme zIa;C#HgZR#(YJ&Pf0sRI}?S%K& zh>ij)onqN^_++mXB&h0UAbkUNez32)kUEJ?iI?rlh2?(&7K>lU= zFUG@Oms(2u`> z>6JWkLBz$4#t4DNvv?Q^aM(%i#9v; zg+EbPkIrbZ+o{D)ct4rS0TQ(j#$|R*HP^?fDC~Lzm$s=f>H}XJjuE~h;M~tr5H5g8 z5#|%7h71r+jI9BJZieSanHaqsV_*$RKH>k}Cc)?-Cj653BUlA=gV8a7x4jfBlheer zSa|_~uHX(00_*!EnJKgi1)%$cw;EAY2=i2dS-e-|2tr&;NY02iF5EyY_@G%JW|!fG zy6(Zp`yMiGml)RwygrJpIgGGave>iuT~Iz!-$UwD&tjY!GxdG-4mt;>exOpI9H#C- zYLsX3BS?{Ee282Q&X0;6<9RtN@as$JIAaqL8GkcpFUTop3i^ZB7%7ygF2ePJ{Kb?C z=!XPcTLZDtu<@LQQG?}I&Hc$yYmgGJdQqjHSO)DVX+lQ|bz3DK1-@$kr zA7MX_aWKd4a#V?wIes-NWsVCo0_GX+Wv%sQSzpuDM7j7#5AgwsdR@aW@N3)cB8=3 z!n;rd^(L3mvVGCo`;oNkO;U1@U-#u=A;($A5q|xd+GU7D2Y(m{$B8B_mL2fxm5sSL z)G%=ENMHuCa(#hVc5Cu@c%x?p`QS;BJ+3*&ME20G6qnQ-9@*0;d{|REltNo@3xcvL z{e*Hz*o6y=?RON5y`R{hb7gf;vV~ia7?H}$MC=f%Rg8`DtzzM$)8_Xes6G(ahbI(V z@92#25T`hU4MZn_BKGbH4aeVo;lZ@}q$B|k*M6MvbNa3VQm|K$!@%H-9a?O@vt)Nb zUkfF~XZ%KgFW{+s7ZF-M15j^PwiF z))8j-TzKOZ`SreNi$3rdgI}~UNLj*La1mFet=Q#Z$5-c$k4?1tqkVBYSHMtX39p9t z0fZDo8D$u+#kBkuU$*!rQmcj!t0kb=9t0F?VL-9-hmkN8ltI{7-w)t?OG2*vK=ep| z%UE}xA;b#q&MbPG9U#t*pPt7rTGGuk46!xJwokW~5s?BT}_P$KGNLOx7Mk#iw#05orYfQoBrEHHOF$sI66t4%?sBxyB*8*eRFIbr&0r%+RSaX{`PJHqY>S z)PmYJ7_CjhUOto^ztylg#jNtw4t?HE{8TcGW;y{i=MQ^zz4bEUztUlHM=c z`v9Ps3qtdj{g^w@hW_u?!f9#gEpOpRj=|K-ga6=mh9UhOXFaMr6=Nrzv+k8~J(27Rtwwh?6~0Sp|#zyS;#z`y|v9KgT<3>?6~0Sp|#zyS;#z`y|v9KgT<3>?6~e-;DuK1JX= zDEx6h0si3}!CzpzG}rbhfbMU4jU!znoSEj8dxzK7B3MoXRmnK%zf#`9`dj(EC6H0y;uNcX^ z?4x(z@8Yd!U40MJL4@3SE_ARK9bE`y<3a#^{Ri4_G<|d-T1>mUkO=Mtyom*o;>)F$(2X!OHb(>5|w#D63h3L?jwV)K4AIo(wXr6r_<^7*O)om;3rss zQ`W4C{N+vrP>uO<;g+G5RpiYb_!y5%cmnl9o)zev!{f16mQ#{pRc**ql)o3 zKa>085qte;7%DQr)+j~;fi1-7K92BFVzqB9`k6ZEQ`dV$Koy8igR;?F7|c|-BX9G} z4Z#I+;Y09@8U&}7s6YdZbOQ?k7@c-;t5lL4dVI*7Z#|_IeRCpg>xA*dv^?Y2_oG{? zPk%UFt}Jm`SC+W6DA3f!OjV$!_?h@T^Y$0zvd}Euj)#_pa;fnbnQ&_&ej?SDd(rjLFzxMvq3@> zybVU%&*?r*HaivOayX$)`Kc!nnDNkopJ(#-7CCZEj^BQl&-{Kr(~tJyAJ*~Ec#X@K{KalLKvyUC7VSL)Bj4S0FvVUTE_Tf@_XyN9&ERt;kg@Bi6CpjDLY%uH*U=Q zzNd7ueq#hp8}$LkuYb~ABgyiBhin%Cyy<+to-e}C3p`CgB|Z9Gry2H!HDDhIC>j}nJSl_tfueQDuzGC=Z`HJED&R1ODSzmE|@0{}0j>k{FSbY$q%ADA~Kpg66 zZ4>^D@-11yfuqj!t%a-z-O%$%U!=dnKWk_&CSFiCL*lTk->||K4;)@;WZ9?1 zvNvOutjW0ZQ2;mpXnK0DzHA!nZJ84nPFkqyMST>GC0vLLzD1fo0RlspeqGpLY__Vl zcO&_X_P*-+(%S2!KUF2qRlz z#YZu3a2d^!nX!3ShAO6$-gV3p{H6ShA(fgD`WUS7YTVW(F@86||K8a0U2(bJ$2g+{ z?^y`j+TjEoc*Rv;z;Dv=SN!s=fH!d5c{SM!hWA{5pL7HE{PY(crn<5CcGZmnqu>5@ zqg(j5!w5MA{~$a3uPy-m(|-R2;P0OX|I~YA7@q#F1%F`YehouM_(^ZZ$8C1VFOJzT zKZ20|@^@bV@*3b81Y~0V=M<(9cP|O~?f={_$Q3Om=bsN(mC9T(E)m$bf`Io?KQi$8 zn*s2LzlBXGZ`1&_fY#yGEp8YVmB0b*%_Nmz(*6=?1*p#|BKepidCWUKW+C~l_j(Iz zC-6PEiMUO{x8)|lcj5lQmy-tH#R|U0r51dZy@pTt4RELUj4=qX3F62hN&y&gHKlz8 z9nJaFJ%_jsLuzq|0;36b$H3YNNvMARkDjrX0ncqvOC{l1KHq}p*xteO2Jn)C=TrsH zPjm}QNA?09(HBzpVoDnVUjf*Z(ve+;InDKr_un4fiJqcUov)n>ChOz=@()W5qeVV-&mO|{wWwJuZ}!|W7{a0E+8*D|xH{9dBHWOSh%SLYs~Qr+ z=@?iL)NjrYZ270=+TdBt2!9AaqUlv&A2;V|O>aXDh-l5@Vx!=og34k;`e;R;jEm)- zp}F>GO$q4uN6Q-o!cTZ74yRZ-2@VZdg5X#FspmGiHMMDRk1P4Q$8JRM6Kp|20>C|k|jSDR51H*vu`q@5K7%AcJB>Y6%?)D_EBYT8A6 z9eEDNEQH+hAIC5xC{Qj15a)coo+QaOgy}-;gSz7ak-zThlYkF zHRw{lm9dOoGT0zU3eq4=F9$w>M`|g4ieeD&{w=oTLRDAhTdYePC=D5~yP-GZ^qwSn zx~m0^x*gA5<+-p4XY{hG@q7r#TZMf;rsdyAx;6_K9KNGRT^O)@aTGQ{yr59p{na>>r2JSL<4UC2NlUyVI&m|5A zXys;XF63$pqT>~#oSmHsDg%YKNi{|<=EbRIqddo{rb(Vns#&OBZZZb3;$St@_0=HM zWCiCBCk<%2aVj+D?x*Qu>Gyd6EcsvaU3VUgBqTMDtxtZw8FN-bzLYm(y*~>Jxxmtb zC7K+419E!fWF8SSn00_Ls9i^sF~C1uj4_-dFJPGbg$>r5%<1Y?;TM7W)3v~sEeJW# zBE=gINO#oxe~EbLeB)^U4hx^ngzpc>5x$0PnOg0>DAT$GOlt+1)_u%0@{#~Mp-sTB zt7+`GPjR+iA&zs;?*JQNKP-Gv{ACDyN5J21H+!(TNa&|2ZoYr+Wpjr=Y! zV*TpXxhu}Kr`9+~^xod!py;5VuxBw{e(nXYl$a2l-E`||FG7Y2AUu(y*?*BHJBu#+?no;Tq1SGGYl9ha*7!Zmfb{!K5Q%mKU_^x*hW9QrY|) z>hy+ko_-jW=^BJYd|0k`S?(=titVQ(DvV{2ZElZjcj01x7ET|7R>NhGh|UaqP-4<* z^3O2|n~p~Z{X&GZ--OHM$esZIfC>zZgQ~AB0WoSzji(^dH~U~ZA;I55}BvkA;d^>PfDb+tzW z20$gj)7yk6izOWfKVccaar|tS=Qv0!<=F&jk$O1>(nUuFiTX2_1INhCNwf`pQ{wcB zo=VZ*gKFR^Ip()j$$K$Cfh4HEdmfjQdC9lxU+4qWpB zdECEgIG_QmaYlKG;-7~#9Ka@c2X56TTMp3607%%88m}Tsx8LI&td+$WQ{=o7?-8}|0gkY zW7Nrf)Zn8zV!sBJ(l>cIepTF)?mA*tnBg#vL#h_+z!idm3IV>}HaaS(5Wb7LQ>?Sc za9ux+Kt=;uy*EREyf_Brl1)bfG68&3-v`;3$Z3Mdh=|>Zp+V6x0W&j1b)1ZupQ=h% zOo?J<_;;u@6+aRxH}HjX_n>UpVKqnH*_1QZ=t?zY=s@^&V21vM-^4HW{X7y<4%!am z^NrB!KrnWYoxx|u`*15!&<$ftJQ6L+1wv`)#R!ZNWDE#}7@KaqCJJ2N@kHu4B6T8> zT1BMNYef{Xch?DGKc5_J<4ve90e#>5*0gtt$qoPU=a9AI%jt1BB^CeROhq1jQ;bB7 z!bhmg8AWBz8ZSJ<7l={PyZjDQIJ)AOU4c_z&500DL>%xddM2CJ0PG#j1Wa7Zh^w{B z;9CZ@m(YD}Xc9ej!p*xfMO0K&W>ZQYX zgS6MQ*WmsHjILDJ(uMH8WL%5JF2E884TAHoCTZDF&R2~!1!9Lrhh;7jQR84)8A2SD z`2-BEZCke#|Ap9q!nf}HiKg?yFP{V(E1paAY3K0*|0*g3jBfu!X(~9ex*Ji-G5Y&Hk(&?d- z&WlVZon7&1DLU70N_To~Lw82(SK8RYn5^It2iuUPERU^akifT0+tBlR>uavYl){K^wi)2K(D0&iy$bvkOZ2G@@eDZoGBg~#q=M}( zh8tllR;sgF0=md=U(1Ooy!~YkT^Z&xC86Lm*R*}B| zcXa)GuKy(5Es(SxxLYW9opARz+?^*e7FRuluiub0x4GdQX{;FlX0`_nw8|@Iu!2Vt zDo{Q~W0l+ytK`;LB{xJX@sCBM?WKX17DQ@kIpjMv&_ZL7P|jDw0`8T8FzXto+iC)0g7`=qx9uRkDMioWWkbM(W zKJ4`QcOi@ML^AJ06F?L3fQkMHP2ERUPNwM)rc;vSX`!vx#l_aOG2E`9HO2fp{{ zM#Mf5HDI)(5?G!Fxln}69vqw(h-CYYMR32FY!bLHT>QP=5x_*kr+9U2&F1umdWX6p zeM7|lqU3Nq+sZyB`-nwdNt5D53da`BB$%aRrj^BI+bs0(%)CA4-^PHlzl~3rsrI*t z`=O?Ns|!7Q*j>>!-tTs#_Y>#iy)xxT?I%CTCKdj8_LH-ISb3@XBTT(wKaWuZ0U1W~ zQT@f4EdTrKFOk+sphl;z;L~wHa{Ha4mT&|4{3@w;*#xqIs?x_6S1ff%8&LU0km4Ua= z97tV=XPO6Q)@c@Iyz*IUTEY^m*Vd=W=Q)+YLmbug5&JE}fM{85=3uuzyQ;`8ag&bJ%FWVdpW7eswji+_-@(QoR?d0mxuLOn3TyhMY;s z&!MmO;PeyaL|(=ildzX$o4W&LU}*&pdzn_k$=NK33iy2xLK%vNC)Q;N#mD;(@y8@) zDOV<%eu5W$v=_@#ipn4%VwEw)5Q!3nh9X!I9!z$0|TJ_LC61TP}MGNM;EpG3 zz(WPmAKni{e=Qqr2+>gQ?^crG&rxk4g1M3pY&kSvmI*K@FawrHvqJWz_+Et@JK+XO z7POt);^&>+Gm&)4gtwQsTAVsZU+x+u5e^`4?<~`|X!>?H*4bwNze%vh5G+J(>w;16 z4iknlA#f3v6iC^{l=GPaR#;Uw<5T|y6Wsl8u=tBys1&4lMh3b#*r9l{d!|Fvl{au4 zrV(rsoTB`Ad7 z7U}QHT1DR>g~@>aU>#+Qf?how|M=)znxPwX%U`-7@n63SF@ze{>Z zY*=dlbN#CHKWwl=HUWSwXPUlKZ=z}=W4SAs21oOt5yS~j%Rr!_V-ZjQvZotsL0}Qw zMbi!A6)*!=umd3M6|vv8haBr|s*hg5CtR^kMiSx*LV1Nib~0YE9#^A^R4s!eIp8$t z;R+H2bdt4SJci=2n>mIifaY!{QoMTqGDN|6<3<>s>~QPbuquaPj6V~2xtaT%v+<68 zB|Oi+mOm4>u-(CtA8_yqfrA}6^f5*A z^Q7Z{XCWz)8G$Bxc!L143ek?_{L(UK4@(&9OmH2k|9aN1c!ra#tc1*>f&*#Xf;8v^ z7;di!8_eiPtZ-F4Fwh{#23jV={Vd-26V|db24Nk7omNDHEWsT-&2!FxtNt#4NM0Ae z>s&Db6^0kf+vq!P+uy<6nLQ41p|j0besI8gis)`=8@E<+LXK=Q#O>7?_jR{4khIjAA)3(85Xi;Ess7MNNqldN`N9Q&5Tk zI6|Xk5s(Pl4r`)%-w9Ed^QKIZfMCJxN}~hxD)Z?cH)8M1FEP0U)8-Sa@c({D5-{~~ z>A<6CoO&3x3|r-_16&`0t2htJq==6MB@z4VZ)LD@V!^89m;Q9i0*UH?CPwVl44bHT zmWcD5tMrZ$`*kV}Jo1_VmM}weamo?lCh<8||3v2M>xdZGQ`xV^3rvNBdE)YQWZ*qy zRK_xhN{%v2fwXMH)HqQ%ck@iV97U+or|J!0`&hdz`UXIu6-7V=cyzDdJ)W)_g_+(Z zO{D6Ve64u>eD3sO9feP3)I$*VkFph5U-qS2Yy~-U@uTM$eCTGq5`_eFp1>6^yrj}@ zz|$^UuJuiM%o!q?Ilsr#E`0GbK6cBgzz-o#kyoN}3c7v_gc6YVT8b%@O+-Y2C^;BZ zke1XXNP~!SXqS{YCMvD+d>`cHHu)d;gx=uM_fnM9x4ZO@0!F&8BlQir`lc{vn!I~4 z)2zgbo9lOm_Jm9X-Yt=*g^`k#RHI702~?N`u5;c99NqnHj4#?j?Z539`2P_GBto6H za5)URpuKJ4nVM5zlc??4sP^k@bGvCFPnv6HE>_4Vw~C`z#hm>%sQj7E|W01M&XqQ5z*>XZ}5-#xsa3G`gEroAypV)K?_;Wz7?~h@%)*SpnTH6W ze@3qH8(5D(0e>>Jmorg-amS6ABt!OXV^K;pt@2GQRZEZ?v7aeo=|=kGCxAjg);Ir^ zj>w)PJ7G&PVxPA=-7=q3V8nh9x17yo8CFGM;$)fr-1>0H`a>Ws+Rr42F!%c~v* zifjiMM22nSd9Hmh_*!6Ki$f&K2T-7COD($!Y|aA84rtDnC(xX+H^rOt6q~bsk~C-R z)v7s?_6QX2aDvjo){OymAOcQ}*wfL3cs*xHJ;;Eh25o`$Q>==VKt#a~34h>W4YbUO z`#qCU1@24OHi$DrAY5TC!wp0&2#}CoMg`b9dl^6kr2Po#$jd2nU~)t)yIqJ@a6FYB zJ#Z^ARBzvzE?b~~1zZrYWwrJo*Jh8y!p$3;jDXG4oq^#F{7!I(hf;hNlz}6_Y;798 znoSX#eMH?Xbj+Ad?MRlJt8s&s1k7>FSFplTpGSP?MZ`_IP)dk>x%pf3%Ttg(4}~JC z(^3+%sH7pFCrTM{7Nh82*7hKJha!5o3h6eV#5w`06AFccp|TmA=Y{eF_XUlEi?~w6 ztu}LTMdRR6RQ-dPTPV*22ajnSJPzbwSvGh)#G4k&-FB#*##jpQMacfr3}EP=qfI{* zP!^DZpZ632!OuV~CJ`zc?H@6;a7< zY{Cn%uXYTRuPEezkdl<=&2#W#9JA`q$rg+wLs7T|0ahuc4>Slqdf=Dm*w-d#Wl=WDQQ^=c>y0E>~4LjjK>4#+A|sjBCB2 zA+|rxr2eSjN^1-S+HqX+O_Vq))X3TDYKcl%xaebY849c6pH?knzgGIO?`H67i|;y3 zhrPTG)22NedoB+&q$7|aw5)$ggF>u3@w5YY!W!Fn?=Gshn(CJOO;o*JDX2QhguB3a z<_#|NVPVmz>F@EAR}t%23oLPjr8N~?-e93a7^4dvSoEY)Tg;yo6kQ?vV^}JwNo(?@ zs4hT|u~=Ja{1bB+&5n#k*Wkw7N>@B1kmAOJd*QO;HYdr^VjxocMGY|nsvbj*QLb+- z*9~abj1Ws^MZyj*1^~t^A1lKyRw(B>JZnLXic?<WG@}U zkUDFOYT89Wdy5yKm6%#(@k2c`0%W9B1*T4l_`A9ED@FzOMSNJJHW_{>30=G0`Iyq` z?qv)0Ub`MK8TOy(uLL>@v{>e%2LuP@R3SYg;b(9rDzX6vKq`3CPe@qb2w|-;azIBA z4uq=48P`!45wbU3rbYt-q+;8j6OSMj6Axw%SiXX5g#p!*Qn+m{<7*?Vw*N#0pbsl? zR1LMz8snmg7=mYmn$OVmhe~9o5*I6rfJT|7uK5x-!TXuz4gT4Q9}jv9g|FtsQWU<5 zvu6=Et}y4##8;r^I5=35`;f-oH#=U|j` z&McC;FT~HoUg>j4-^MqMlQtVG)>ejOM9%gLxOwV152h zRHyWH4y2eyBp8A~fo9wZF(N)B&cZ2kW6Eu$9U5Nt=}`+kM-ZWgLiUq!tA=aiaOUF} z4u(L5X@CON88+3U)-t{Du2IBd07g9tDil@pQm_eGzrBiipeN8h2nNO9JD7#8HWtk0 z??xe9)SOqw5rB!V$#@D}itJ(P6(lNK9S`+U8|D;V@!>C%O-Rmk0fd*U4o2pr(TK#K zi)t9$9c*O^^jl#&jXV4{lXEk99FpV2`qyhwTgcuM!MDlF#~Hu>D_J0VmRz_*>ik_w zopV89M*lYJ%xJsLFQ5yG);ZOo+14WL#lxs^W#s(?LxP9#sfS$sfqW7N zwWkJ;wPN@Q`P42YSw)4kRvcaAhh$%2=#EWB{wS0YW3=yKUN%m?mBk99y%tnvGTLg~ zDyq88D^eHSSD7YPHKfJxEpX5<*pVv?`xT~aLJ9}}Z6I73CgDtsJrFt-+K||irk%sV zS9e(=eEkStt&}V9ErLK}!nXmp3chv2QsCQyEMu4npNL9b>E)om318aGe*?Zxqcftk z`&T@2zLh2-3V$cX@mCzf-?^#qIf<`$M5u@)M90K$33?_g#q$9fL5fOU`i z-oZG?Xozg#9P}kVAe{uSMok#O1x9bGZ$tLk!`1LAFjl?71!(<(ugzDlHD7fa->Q*# zgZZk{cp9(POwZ2r74n1a<_Bk+AB-`Us1MFFKNw?F;RB5^*vX5yo+6CVO$#0JOVc=HZC zL+*U#z)_m-Tmq~Xz-x?YQkS_>x2fuCq&hQg{>ua#)6u2>q{t3&aputR&2v$tZSL^w zn!TBRIDDhY@$`;baOjxkCy`ULw}TI+$M~qv=M-zYa?y)gVL5v`dmziK^eMb+{Dxu+ z{79IUN}3y$63VYkw7$pL0lEVS%NFBC^}z>TBE?MW9)nRNOVj4Q7RFSz(KGs{KZ@LP>iD_FuE1ObR`j&#~aq z$AbK;^E~<|*DYDFA>Gmge_89}zr638@XUO^mnsosd||t<_KVplRBLq3lm%{3jW5Ir zLBdSjy}|%*cKR_ z8;nlDq1nwImMR8d#xjxw=s`w0Ba+yM*Ow^B3039J6oblR1h zfrq~V)QYCC-dSy&irEB0z>E*s7{KQ=(oh!&>n4wa&uM)10{ni*3LaxTih_%mmkb|od$a+os^Ez1 z@E9m-#)KioM~Qf~(akKN%6Lu{kY^T9WsEWlsE8MEoIYe!@p3lT-Jm@dkGrnuH(ik) zsU%K%cR4_QJaC0`A!YBvw9fpy2>tjN?w`lsO#Jbgf6TwfyiZJHI-jfX8@=ayskF*x z(SL~6Lnu@170#t~z_}p83&{8{{2B1ZX-d}$Kn2E`BG*AGk(OYBh9xvU@NLE+NxoDR z-jq;Liaj*^3CO%tr4&Ebe^7s^L|Ir_LzLYjGmw}6q!TsC9*gcZ%3=-A3NgLeW&? zWh!lfPK0)D(Zn>%0y?H3gQ|kO1iSMsP`_1ERFtB|cp#pkpQ!Jt8IWP|qY|2)VqG;x zZ%zrqb|)y)C~U*A(|HNP_BRj^>rAzA4Zt)8Q6dZ3 zUo7J&dmvxZ&U(&b5yN(jD-pOqyH5bX{`nz6PZ?`%f|4b*)<;l$8@1k@Q0rr?brmI- zko|nKR!N(Sy?UwDtuI!KZAw0!@OnATet?JZF)+sav*wLQPWUoFpkVq7f-oEBnJ}p} zaWUXG*B<-P%xJsk5Q0k-1h2YPyCv;BKmZ?EG#H?*NhVx_w?ud8T92Z=( zyp&KJwW-E_s4LK*@n&(7_8AV3C9VYC&yTMJ!adwra2HW}hT)4C6k}264&DM{gu>VN zacp_O0mtRRa_qQ5cgvyKdSx23?=())@LFsI@Xl>ON^fWbObPZZ%wGVroIlXGsBw6! zlljB4*}`2Y-dGLXpy$CeqAOqLXtf!l^gK8R@)#yhj6UHKMJAVc<7SgflvR|&7==ti zY^(GcYk?ve5XHMO{2a#Jm?5?P)!9N!MTGIL>tirFr^I;0Xxu6oS6;00-TK6FuE4=` z+l*;kHifJ!M(}M>=$Auzql1~wCfxuw%v)-aJBq4*8L3`o(~QBXRcf#&Kl?6x4i`9G zfOa!6^oyyYw{8fI>N3EENaCDr-{be1@h>PoW|ztlEuX~ePYpLj}_-u^b~%p%C4(M4+htOd!=F3 zAi7L|`WH@rq~NhEr)xsNw=>}&R>@LuCJOchvzA$kU{|%wnKqS;EyR3+QzM=|o8ZPE zy$mfr-UUM}JwLEwljU(4BTB-Bq#Y@k{EYWa;`iVHbtG=oGj0#o01;t<+qn3;+RbqQV~pBqX94>(56)?CglW5rQLcDWK=;mRhSWNm*Hgay8dk66aTSf4z_q9DA*yg5D=R4qZe}>=*VRsdtJ|~_s za1360s1qEEp@&vV56!;_9WseaCbR3;GGQze*!9=5cY|3`v%KtOmf?#?>(U{>`{|Ko zgkfmta#+N%&uqQj*lh%*vg6pTu9}w9{oT=bJV%P3^uV(8z~c09Hj^UDa9~Kt)yDp8 zBpGCOI6X8xP43c64VhkEL7sH}G(c6ml#C#OC&i*j#|4Zw_HUfrS;4lAhbrQ|aC<@t zaLO4z2R0iz;c!b}Uycu^ar=7sOBa5Dtni%+iScmtLcSQDq}6WcTFiGXBUJcC%GE8` z3-DE%-@1@1QSvmZ+l`wr%z%IikT@g$pOEiXaH9qSWbBaU!Y~W&zL2ZTaF>NFJ#01I zYhjV~*hY#W(28N0y&bW4#3R;Y{?||ijw*6S?;v&fp9#x0gv0opGEvAr;vz*y4r9ab zu}@Btau_RAUU4k%4d%7~YsWx42HG*uj)8Uzv}53Z4+dnv!i_j~qQ9O5Fyj3cgG?p0zaK-5+{AnChv8>& z%GxjWIn_S9&HZ(#)^K`oqiEfB&YuYW3;$Uw!78EM5N&q>)~h&grLV;KN!XLoxMKsR zmf2zKbEtiMEQX<~N!iR#ZmK*<{S}<=l=2Vui{p5lWFl=-suDfb%&&rS0Ow^^C^$-# zt`2YEf;Rgz0B;Bv6p9=MZDlIebmmPrM{p0Xpn>)B-rWAv_@xo2aeDIp@ zz)_N!%si^y(a96gN@*)B&0VlG_c+k*0yLo@=t5JH2X#UbXvw*kvn5GbiNApSKOH6i zwxi_N9wk5RDEU_&CEqpr$nf_+O8(Ndh6c%tspR}?kCK1U zQSwhZO8y}jiyW!`kB*YR=qUNWJ4*iaR{5#@)g68^z}YyISx`rFxH(q9Q#=uzk|ZchQn`>>awzd7H9Sm~M0biS3sM!+Ug44Xfb z$6*B#`)1xX4lWSt#wG&xvc$5Y(UqE%MDM8&470_uO#&JSo0X&vOoVT**jmUib42W& z1QfJ)q}+U8M?5AVhbbTN5%<*-jo-J}!fh)n&lp(WH-23*z3D3yD|;2|0lpLZD9Iy3R&+z2Alv}guyiUo&-CH*gu1Opu)%HahAc>+kPmNG_Go5Lo#*9B|XRK zRk^dg+7pdO$K?tOeBpZ)m^y!^iYD#P$)ei-I(($Ymq`&l5lAH2KUix1|FP%P@sRqQ zI)6hxsD+2u?=BzcDLDYD$gfF$5HV!!_lPI)jrg;wV{ z9C|CjLQ*U6b%E&}MZD&Fjt6f(g0Ls^OPu=^9^J{nyp3s2X1HM zzQ6y#!!=@QFv<%# z%wSd;5!e&L)F#P4`{n}K^l( z*Q9oyrsQI?gzA!#B*;~UU@b7hDG)#xrdO$?)1^kn`Ge&x*fyU>=x ze@$Au; z!a6gEyV-6XR=MY2j$*{WmTUKrZ1L{!4hkSye8_$VK~yf&8n##m*>W=rY~9j!;l^8# z3tvuH-QkpHj8KJO;EznsnXdhdYHv@if1dAHnomM(ZDcu~jHQ8+h%YZ(gXRWDMiINF z2M{|hon0}o`W*O(LOhDfC0a(krgv%NDl$hO)tGHG0rin3;ZJdh_^mMn57vU?ThL6{ zt4W`8Kl!rDuyNV-0y+NCyPU^AdJ;Jy`&Fn;KG_QJV4tZD50@CH^pSz9ABPe#a7zr^ z9YkiY>!VeqrB<;6eQ3N0V_2Lpc?ZUou6f6+Dtbq&n4VZgh4J@%siMHFqQZD-s#FoV z!feIw_N25T#42cSiz;fRiW5{7A3{ATt>~Fp#YE$3RmEG!s#Z)iJgSN(qg9lqR&ge) z`0RYC;vA{sI90{t(JJQ6QHT;3EH%h9cAqK@xlU>^ZRxjQLm%iWj#jjOcS>6tfE8n! zs_0#~k#NoHt}3dCRx~29B2K$TNL94k&gMLjFKJUy5zx^WUxDs7=L#qfT+TOg?#7R? z5mw!^0m@@cBXJA`D9CInBt-O2h_ezrX+;Kv<1RSnQeEKD??i$6@pQE2 z!W8L09rXV^ygFLoKLvyFkGP-5Tct3OAj%b8@bQwO) zz_EwSbewl^Ou5EmTLgDde`u_}3~~EP*ZMA&Vg|67$k~vj2j})5+(Xi~T!OSP(jtR< z==eVj)%$X%y@XnG`1)F8g?~p+xBy1j`4T!&jy)i!)nXT3g(F&Jla{eUTM&bHZrT9H zl;bo7*y8FO_-mh@?Lr8)$nr4a$t3#zb4Gn_JCke-$8kwhMVpJM;-+{zPAs!3SQUCR z7HjjPRn2xG{wON5o0T6%dRAnmC(;!Dqh+g{Sf@Fh6YZ@!yw(= zKReP?y3yamTe;*Cy+?nIryl9~79@zl+7!JG_iSai~j; zOLwp7;tCGIS?bI+h^p;qp23d6)H2ZsrDIkP4NlW4F-TV%w-llu;qF)RZli2dxnDhu z@6DVhu@ZsznfHwD2O_fSVJY`tj-6{d2f+i+c^rZO?9qp38(mMtS17E&_~S{uv)j9> zNv*&bd5dXlH;!ajMH-xn?s8JNNiZ=H`hy23@gTdM2!%&I)uXb*cyx>cUa7DQ;3p^Wn+_(}|Mu(ZVaF)goE z5jcsS3r`ZT4xQ;Mh3W+*kTE#)%kxb4_QSDvb!|46pdEUUulfFqWwK4U0A#|Syg0x9 z+}Ps00s+5)r6S`ZDriFX=Q=Aa6&Z!7Gl@^pY5+&cT&NBXywlu4Pi~{D2i9FU+d48y z^Qk^dT+i0C+X15K=`49Yi}78&+Z9+1!E@jn;R6wOXhH|in^C%jvo zd<W0^^iP=*-IM9wtAj8IV5EcY7iO*-tNJi_S=5AirnY z3rI_5APWKXDrP-|tnkYOAq|ZY5Uw^sa2mUMN?%l(AUKWR-;Dl8`^yTKF`in6H0O(s z2P1Mcv3VN~Uuu=Z$n9zdqzx2*D6g=VGf)d#-VH71?;TiT00TcsVU4A}UT%FCc;sFF zPK$sMOY>}$xCnq*n(xCMz!dK_Mi65*-VHY+OS)lS8R~iy+8vkvZS!cP%6YRmzV7x- zPQ=$rzWyLyPhhyWGVM;JwZ_+z%(@O);g`J#aY>wIMsZe!s|5Njy*ViB`{4)ClCAmxM zeJ2SfcJQ-Tk>{-(jPB^{su_@7>aw}NWzSVcdu~vgP^_(v^byA=dR^i31(x!2lmcq$ z$J&01`{n~+d+bE_dF(|02~mQG6&tqTt@p?r<1^L?C}J#&JBa{u%$&~xb6I%hU8ER9 zq|U0@cemfkvJEUA=-Od7K#3wdBZ};W$0}r386y?5Se&hpU1c1a2v;Qg98(HCwm-aE z-G@cxNKyXdU4@5K%iTJ394n17LRlnj>8t@u#1I^enQyH#-|CIGNKILIF$#!sEr`L| zR{$}+ro1I2VDBfem}$2HocMq!l>reV7V=I*699xowX^Pdcp&8SI4n}=*Xl7md(}un z*QJ%(W~@O9NiIE#h1c!Gf*CGKQd0MilI{uMnp_*TRXFA75NDz;{+KN!h1t?#+&^6F05a3%)X`?P}K68?_}& zHx{OKMw-dqF9VFeW99X;nY9Co5P=xp589ID+v<)~f79P$$F#6kv|No+CX(?C(8+8nAlER=YI?+^uPw0@VALKlz>Crw8D5%~lBCZFLXr zS(yJ{VB~G)EBf*1%VBE;c^+^o?^b~#nuD}j&PB_!LdG~kd76F|6sXH1_PbGFpfY!F z@%kC{qBOLXtq5$*$XMQSSZ*7oNB3i+@LUv!M=Wkz!=qx?BuLrZdcx`j{&E-^3Tuxs z;<4PizYq#?TE*5vPmHd6oMydP(ztX4%rvyZuoieLOKbQtLo40ru?_u7oAIUZIQT!- zI~y7r3-_vHVUE|Rl^*nmHT^zi6nvj5>p|K9b3dYM#Qy4Uu#4gi&Xpwn!4fkEP9Gj; zj;22-pW@{cBli2umzjibSQ>t4VvN<{;kVJ6`(fr*=ugrA=y#Mz{E83g{ss#tms6iw z#Cr-sTeyhtjD=z7KKc$C*n*4g{@>z?HBj-#2O`>Iwz+lbkDQF)JrCSlgC8v0BU@{L zLyPa&Wf|DxgzkcNMp$E+N*#EOS(cGmdujxWFho)7D3;qP@2CRaU6#>SCcZ|oC;)2I z+RhdzmQ$-xwdqTiWn`&()C-`6>z2&607@7BK7o6feEe48&jSBoX_^0X{sACMdb%Yo zEsc)LaGQ}q;4D8bgebd>5ea?*rx4aDZ~(Fx%KP$v9Ds!RNKL`1TrRGSY?}v!bDV2S z<`Fh^2l`WYr0ElBc^1ZwXHF43BN{6qu+PhizCa=I3t-%Mq_Yx}9mbhmd1rs(AjTUO zDGuZPYrresIVHnVkG~b>-$&+iD#(Nvp1?DGomuyVG%pJGml?V@3b@Y81@^_;NOS!?-y1^1EFG z>LIb0@8!#F{7Z)CCjt+g6(6I82Ei1iC4F)aI2@t3dWA=L3_<6f8yv~utSw>9bOK_$ zg=Mzty|Jq1Rr{m(%4W^67oUv1*jK&yhuDiBk8J})HQ&(gSQ(n+!2an`Om?Q(a(tWU zW~8>&KCpFYmZDM19B38-mWj@LZ=l6$yZ=HmPx!_Dfb7hzKT^BnG{i*ASm%mt&^lDavbfkuN(lvL9{L+*f{hpdIbjO zeKkbD5?tueig8Q!wT&=PfV1E=+_Ya4u}^xKC}-0ehX^L-2~&zsJZ6P$o(IHkVW43u z*xhOQa9&5fB*e zc`LVbj4CW{IVFb0mmeY)hsnT5#^S@fh{dR<`?oIuKAxh|faYh@zCmHUo#nq2GY|1_aa_{-oZ-vqdXynLuas?g`*Kxm~E2;ZLM5Ep#)MF+>*tqmn*xm9L)_Vf= z8J-3sT`OIT5JOA3GuAt*k8QM&oDg;|6A>LfrA_{?!Wr`Iw}HEQSZoj0Ar}E*%Jl;( z-i8K6l|D?1*aM_LwFDsc{Jnu0>=5+8@MQLDL@csT)fCt1sl&0;z7D>VJ-Wad?2*;z z(pY!6tC`f8nI)vX%oAuV&;pH_rPV#AJruSAoG>Wpwfq{q4VQ6N@vb$Po(sFepay6f zVdHvh*&y2+J#J)dg$tm6Mzm2^&2*2&p9QC{n7yXc(>V^bVpn0;`9S|0EiosE&QF2N zCEb&pAbLW0@u3V*w{pE`MyQbFsYr(I&J6Xi0(wBoo8PJlrNDS&G}##g>|vAMV{E9qWpLLo!b_BhHAiGjqf7xjv|Zy z0(y1y>LVP8nZ-$cA7}qYbWya!CK^{}N{6lZMs?Ukqr)h+ZYaVS)ZdfCPV;3gpcolG zP9LhtolDf{eOQs(R<_Hn->NO5%;?hJvMt!r8x!@ETeZiqZ&6nzh+7res_zOD1UFA8$}~HA5yS@aI#HlC(?(wgfU*9e z{Ms4zE-rZDx7ESJGMvW+n$vvMxY+8R#B-oI-FGaamiWd+-={rMb1kw2U$)=XV=G}` z3exryieEy^1JcAOO6-zOR*?L17x8A=Dy_T}WTW3x6z&^sK^92GxE$Z|9U34R&Dd{1 z3$8N1leCBN9iYzi4XU{(JKGQUeTlsWztImejgNZK%zqYo?RWrrZ5 zPfo>83ro2@p%kt>jWkut?Pe*G_99BrN92ZFQjV*3uZXJslbgF>FUvaxDO80E(6 zim9AtmL+M|p{(Ni2{4{@N~{Rp6~Q>M0Ho!>#wtRlF0;LHg@Y67yZaAB!k7%B^H|B;N#|Ll)0wTy?0bV zvi6K|z4iCg5VS1;Y2Nk&FlLGhNV~8{ZoGxXi$@7aeSs>Ib&bHSVqJF3C^I(S!8!RR zqViELcEE~Z1;M#w11mC$NC4OYM!!^rPu6rVrT{GZU}14fkVhh#e-2F1M=Tft0~l4h zv9pT}c2taQ;Ij}+@JrEaJjEJ{S%Y5ewf((18?%l>N!e$kAQ&$vZ7?Uw*4%v-Df~TS zzzC7DFUW&va5p}Z1t?(IHWNiemnR5qgcSrd7K3v=!Ln@4_V>XK+~m3%%ks#XJd7}4 z${Zeo^(0qC!1_3?wow#6f;5Ep&`O)CT|9S*X%-JFrktn259&CraFf=sA4;g9x&9n+ z3d}QEb3thona%Dg(DY28EiK$T<-G^xJ;<$)Br*`=S_=%#26rEt?JdmmG_+&@#SJ`|*jx+g zF-~I7GVnItw8f}lxa<8iAG+hb&7eHiSf17#36`k+VL1s%w3jeWw;aMr(hyy%T!^;DmY3D zm7WinbC()Qug*%SAHXZ@PmkX95`BPS`}=iRJl>Wow(!PE7-8Gy-Zv5XGhsfk%nh%+ zZm>1PVw3nWw1$RQh&F*Y!J~Z%G-sf2XYrCgmPo`>yb%QB#E7E|3_%)|hzvML@9{ij zLR;OblD69D{Z%AVyu>!Q-uyy;7vl=Tb_ZaQaq6CmpHJf=)cCzF$k+09~V6N;&V z{fZP*XBJas)DHuzADWA%CpEhvoNaq!r8xl%G$(-b1xEdCf=EHi=!3G!{3!_<^T50T z;Tcl2nRY!&Z&VJ+MOhV6_r!P!`lASn;J@W_%Aqa++U ze8t8^)+p{^r0c#;qDySAK?1bO@WZ5jvc`r`%Vn1tOjNw?O*D*%nA1(b05(UGbYe#6FEk ziZHIw4^KBMN+~e@;UW2f(pMVezl=nh_p(*BU(>&=t!^tH0$)S$dd9r-{fy7PQK&7= zcYdqd_?dB*S)0>XEF@qrHfwVlw@PjBSDPJ(?P72UHs8zg6&N|3oc*nemWHg`tr{}M z_?aqtqY)+f9xOVw^;YtdC(VzljK1olKbRj?86QK?HmltY%|MD2z#fi=RzOq_MFws8 zXCqaPQBL)^m!Z}x!@9?MKd*+pKb*@-U-F{-&^9zF7Mq}?0KMP21ABi67v=NLiS_>5 zxK+JB_7i-<7#;}W$r<7km;S@YAf$vAv^B$tFIMM6 zN}3<$jS7C@yGMdu!1XXfUR)*D;S%@)oeRdSB`{{`+!6m$Jy2kLCf#hm{GU-$`sGjw zf$<-)%q*#oLU>5XV=rVIynx3C3sO1{N#bmSx zl7=Jf`z7F6^oUYD%8yNR2zB;m4p3i=6>DAo9ArtT#BH!eG&||pZm~|Pp3b?itxmdf zE9bswpp}PD1HLC<$z=vw31coahiG=Jljh=+*)A*v(!|Q6H~XkBmuV2OKy{?7YqLGI z2M*n1vB>KGe7=#3H^9{1;dp$lh27jjeyEw0UCnX5yLdgK`9 zFx{a+56Yw>(eZ?n@~&V4#8`(H{Cy5ylIRazjp0~MCOU{*umQtwAl6t(o(NYPouJM$ zJrNd1!(^^DzJ;zSx;kUc#$W~cOP>&ObDelLfREPdnnxCnHe7-ZkTyJxHrU=6(aG2d z`)O~bL2vv}--U9f^JcACJe!u6g359Vi$_yu%GXW*OcU z%p*udi!@q@_>EEdZYX(;EAo?@y<|OrAA-tKn%x}@Y~5`3Pqf*xDE01;4tsr@YPO{H zbE{^vUe`RUg1qr8v)M1)oo*Qx>hVX=V_sm|+Pn__G>8xdF}3TSjS zb3xbGaLc@l(6qIh*^Vu+xVkqVvXrrh%Wt)s8HMJU!|gWQDxNs>BMdhUq9&Ol*EG}l zRRg|)Vi%I$TGnK2g@V?r%OUPeF;cDX@#8q3;!=ev~tLUP*B5Y~pGlZWXS20=>YM(p4e0d(NfE zG9JZOas5=c7}{1EFLG*ZR-zJlGMW&Ly^+j`+n?Go(2jw2476jQ9RuwcXve^R4+c{G zH;J-ny|-|kC$Jcs#@n%JyaJoX>&g*vum^PcE*&BG8iHwB$dg%Ky2Af;l5~di+w-_7 zhkw0Q#^My48E!r!Tv3`Qs65wDmyA8kNi>3iu1d@w9Sj4$T`O#6q-|O;LdX+C#ub`r z$XG~exrp`%9{nPb$K7vG=IabW6bIU9+F1H8|0k3ri>PZap=qlrT`E(T?~3hI|FlZ} zq!(vrxxTJk-$;ch{rd4-6jujceR;XQ*u&UDD~(0a=^j}EOvV3EpJ7kN+KZ&#Xr%`A z&8SFA=?&#?MPTEeabo8}f4&`R`B>+k*j+PsK;dvloY!%ZkAeJ8JWVuYt39 z8un(C>kXcD2F8r1bosDQX12#RXff<-H2lQcp4=1aNh=+hU7fA43F6A-Wi%}p$z z75pjw=~C%eOWZKMTv z=)8Y0J8H~y3^RU<4Do&Q$4MkswbzNm*-!zwlSZ%{^sR^8S2lDg##|_2v52;b&Y^Ip z)^I4p+!-B(au9RyBxnP20nj)2#qjA(vggK~6NhaJ`L zSaV^$h7B-uR`p14VVY-Qyjxo`Jo*ymmoC2j!t%myo=}%uPs2VKMAhR%ZJpsQ+*n@v z!6l&{x#;h;TIo9U_c|K)2bLBz--bP6-YhZhhoQ@1;7Lu!cuDMToJVlwYK8C!4iK3^ z_@Q(t??wM37gx5jA&rjD5022KMXS+spL_-ALMKX>LP08OpNpBXf*E?uoyp3ZTGf#V z^LMGQ{%sUn-9~+Dm~mBdePfSYANdIaU+U3OrSOv$MGx>L(Me)BJQ}~p_m0@h#4m^< zv4U3p0jDQP@2AK&sq)Uzo=yB!ZVK#Ic5HV+$7c-rlAV)tKlWN|Z)9Fc7JnF}Q5651 zU=5mw?NBP_O^eX26F4N5+UwYunODqt-{R#$o`);g>}1MgQGN`}D2uYvwbf;`Z^o|v zK&^1E))2w)864lxUuk@{ibMo=_+Tspa2^#7t)PqVqy+|NqYzGE!l+Ze`m=ncAnrhW z0LYaoUnJ7rce$7TEKHyej33+I8?3PdB2#)n2Y&3!09Ou&I4+aJA$j2(Q4(!%;ar9tnS zW>W1GM^p`ND?#D{Bo=QL8<#Td>Ya)Juf}c|*}<;`&8{5o&@lL;lh_Muj4yFqAbgrj z{N_obOJ7F=mq%f?F+go*?PEEDIU)wzGZID zA?-^ThL~wEPX8EBPTO2q!e0#wq$9b{Se1<5lBgWf8o%TeJH7gCj;OqF6C3k$tV>K} z!piv+VCF^oGi!~?)y6Z}#&3Sr1n+;bzqxu!x1cUNZo6h3XrRk=5vZ=vUbiqf3)<9+_jXPIO^%=cZV> z-VyBk7qE9gaABAl6d9k1wb!WpA~{qTvHt@wvce+xgJfAG8)bGC9CG-c&yrV@d;Xj2 z6MOzMJblNWSEKDa^?w4p{YDI?FMU}EW({LpZY^z~o%V#!Q_0|uVvE>nM6EWS zzt4g5`}a)XcKY2XIy@bsN&f@1r5 zi9!N~{U!wC&K0`8YsJ=c9Zf7~dRK%+H;Z&a&elS` zj_&{`JQywXZag2S0D(2Q$^l=4140-pjKe{yyJz(o*-G(xQ)_i=7 z7vACv-K<|BZI>t}*4&xzvS zDrtK1`o02A{SIcb3_A6TS}Y)Lz#o+-J#mNR)bt$@`=V>b3PA=&dNC-gT&z>Dt&0%a zYCmz>@!ZKCo96!)ER*0(yHr3Kkqw6N6s#{`s)eva-Dy?e;5lD9L#;unuK8jXhFK3= z?$-MtNJ6I|#(5FD6Zu$tA$>B_Egvx}7P;nkWVeQ_$4Fh^b^?WHIQ}f(;3a4T{xyjcQyUS(;-shVuACf8|KGNr5B=gIX8sMy~hDb%1>PE4hD6t}n>-CAq#L*Ew?it6X1` z>s-0clj{Px{!OlnzQ&rQLbI(YLV-%OIhQma(xfiL`qn- ziuyN97;pU!8JxXx7QPOa(m&@lB;f3cId??*oH&A|Tr1Z+xt<_bm41@kJLGzbT=V66 znp`VJ^4mdjJx{Ku$u$pGwLgKFYNqeDw$G2^jBEO??-%?tv#IW6=BLtvO-bLul}CX}yHgXd4{( z0@gfEBYvc9A~fe!c~F?c>X1inT^ESa8&+_Cgw_e) zLXR$HMs%ow>7@cd9jRsUzK-c##B$mg{dY7Dq6v`nWp2=ng0Xn_nS$9JULD8*r zEs;J~OOFnht5dFh<*NGoSh>%Z>%QUa^)KZ5kzCixb+KGulk2l`eN3*isbQBGP$28*B9jaB(8C+tXdW6b8k{gq|Z$$ z&zGh=UzqZIO3HJml;>UE#2O=gK1g|9obvoa%JVN%p6^U~zCPvol9cDZDbEX1pHtxd zE1n~LK9uW6k{6>L9tv7%^ghu-ITj}|_ecC$#)Y)+v>Kl*;c!`LWFePC^=@1AvuaiI zi*WOf4rs1kC^}}2y>i`!4Nm&SF}{2YYAY&&5=Wc!vNA|T`UkGSCoRx}yLMYLz4|&| zcbD$WF0ZV}+_Mq>1hmQ~@2TrN;vp1lVsI`Owi&~+ce^rdzAkW!lo8mnLwn4d(c~>g zRm;aVr@>S_kra~dF}BrMP{SFgRL#9zkA5Y9-Nmh49XnzW;Z}R}z24B^Orr?EyY;b| zE@^>VAD!vam$*)?2i)-ex^05n=H6Ha;SPas*Qi5IkQ$BQ@EGsL?j)r>_*ylc7^V0D zNP_L?4-;bkDz(y_KW)Ks0o~QmkUL>dLnN}xpT28}*04Oc`JVb#7=ZuphSMozn`(Wt z??cuXnTn-SFm3nZG=z)GLnj$saT1O4XKPjgTkLaDucogL;Ptx`%_KtL>@-uIxYXEGe-Pt|LLtU`ksLw-BN&?K*yVR72 zGMAT^Hu+9+>7Fjw*SNhibQRed4cHsHy@MyvP!Qhg!OmDx@i8=#y=TCg*SvB#c9_sV z&Hs&t+O&*@+L8)bAp1X^4gNq(V? zb)y4xk1b)wEo(M<2)O>^0{hcIYS5E?0^K+c<0v$1#%5b-uPHcSlmFvh8ej==ehH-W?Lv)ZD3 zRl4A7w5ERn0G#?JsAFvRKa28>FeY-eqYztIr=u+|MX-$f3Qz9=XJ~R zAU*m{ncR2FDaS9nh6082nkDmTbZ$5|#c>tI*?0Lxlk>cKgE^e6H$HM80`+61^-0`d z^WsYR>5^O9Uq;vU^VH~a<@vAh7B05E0bkppk$GCiTHMOu_J+Ew(1JbkJ*De>dHN+? zyrm7dchU!8cza9hZwE72ToC>kgf?Tnkos|Z2Ej%jN9u{sP8eSu@S85fEiYyC9=j*T7wj89O&o2KA`G_?6lZEGP!i@DK|C4R|ZUxpy`3b)HF-;Rd|ejE;lj;Z$&G# zJsW%0@7W0F;M~_pekt~pF#iO;$n~P{yxf;7FW&5h;wH~qxVGFjXu}O&{lq>LaLY?K z?5aL)f~$6$Wk-4F3CoU1#I+1(s*&vf+Ne|xiM$uyBqv5ue+jqq53{qW8ed*n-#qgVYTOWKSXI=PR963d3tZbM25t02Sd|#2 zShX^>pRr78Fhj)fQY*bzZ9o+8*un{(LdflOzYPFu8chH-MBDi=45bwYFwU?Q zAYMw587Jik%N~J&Fk;WMe_(r>uYi|h1N$(&$7L|%H?PV3vjNi(PPR&~lTye`Pi5cH zKOw2Fp!orieym{$H|E>{M??hOl%pU+;anD2X{T@1yoA%g2D#1$P$|x$S(?-NMp|It zo%>L#ual?;jaS||5NWiIM-q9pVH1Yz`si@n*2>OEB>mGd4=XaHJdknXvRv zOt6Qb0LP1yAHmnZ?I4y_#x!5%^?m&K{b zLN4V!uHhg}#{D@ZVC~<(jp1FHaXDx0)UDG{fAMBG3ZXNX3T#|jZ$gT31K5;vRtvCR z5*V1pCPz_Tigl@R`O^m?!CpIB@Mhw6pV#+y2FZ8*-k@|L!T-~d{DTHX~MKKck(S>y^ zi-)M_V-cf!wb2V_ykhTl#eqik9`nzG*l;@XD5I=4c7YCK50HQ24}Ubv%Y^KnDb57& z`pT}Lo`}yHh-CQBj8*V59bcP(_`1iEuSW?gIozRmGlqPT!qrran}6FxC`Kq|e<#`Q zLARfFB2SLBaR0cOmNQnwI|xHYTWMU3H5*)I@F?!v^z$VY8S|BP#2##30+-aNIYwj3 z$r$T_Jh&4~1QMCeEFVRS_e_)CMdr(Z#v}&0x&ea>!g?rVjbLYmwiKAv5}o?T@-K#$ z47>{6z4e{gt8rqB&Z(PVc7wsZ?l&y0GqxSFxlhwc3toaRwQOT?7Y=r7Hz^G!ABU6X zV)aH&h}HX{+!Dliy(sGjsS8#h4cW~%qWF{+n)ssD2;9bc0pG^1OEjLa(GQ;_hZBB@ zC%&*lEPZpB9zkgucO)FfNe#l~zJ394N_b}hZ^D%tZtvB;0X#J3Gs_>>0_gnC3qjfG)2u-7P z4(>QFG+Gy!cj{2&&GjfS=db*-F_W8_mUz0JGix0S_GUVwiRjdvyQ7Kt@QmXD;X7{w zV}b8rK%CP4=p#^2FrP;>jajshh{;Zz^d(Qw(+}MM^kk!fMS7Fq=PrP4e1&GQ)dgtv z^=LKmQ()p}7u%&F0QIEYjDTL1TbM*Cg*DSt+4pBqg$3t?mD>>@T6g4!&UC;gVsOeyToQchi5b! zG}I#vA3IPaoK50wN2DI#Irv?F{8pdBG%Q;kkp>t^hU~ZCLvF)E%F3+Qf|)DAD}c7_ z=w2FP;N(J8NCLZ%@Mnw>7batT3|f(J~`;e zaIKB=r%fDs*sO;wHr?Cc0m=d?He&B5#nE^M^Uo2dX`Z)_L1B~6UL_R8bfHR6#(}rP zKBu38qkkNZiG*WZ91a|7phKY~I9|bLk;XtIecE3Hk|WF3L=hX1Yp$yufg2D*VRAeRf-D|OpvrJ=dCUS=( zSDgL^M{fZzpSnvoPRl2VfRFH$w*aKzHHK}$Cmp58bbNF?K62R>Z0%8f3wY%!g|J5J z3%H}JBxd!jzKlO7R(nxc+FGq)n?vioRtt{K)%3F5{0;*;`1?YJ#2104>_+R&xS>hm z?lQv>wBCRxU-v+hqtSXPZmB{DS}Sk|Lc#O}e!kRkO?&BHZs4(}l z0)4uHSyE$X&c~y*&`bGF2sH5YQV+QGO=`A!4)+*bnfl7N&j|G4)PoULzN2`(Yu;Me zbr=i*#02c$7ju06mJ1jy^yo&W>nml1ZeR`y84w*Ap)HatFU3a(91(>KW^jD4Ox`#+ z0nd`6XEaG&)Sv}tuo?)hAxU)2qr9sogBde%YtXBPrmHW&&c=boRoJnD!S5)Jiio`{ z%7jo&&WrLmn}*7E*dBocU2r~DYGtJuug-`)9k&paJMG@wv;Ji3MsRRVe=>h9P(vpH z`1z0YDSCp*O_od1Qa2r6W#~m=>OX3^PKc z&w_Ikr6a-+(+T)b3{1_-^dGBhj!^BEeUm}V`g#+(sdS0|R*+I&#Qs$gTU-XUNLg;g4o7bmY&4w9`%l$@x4Kv-2D+y00n4yn$j9$J z`Y@l6c)HiPwpm2 z-LBaCm`fmG#vCMvdYyC@st%ps`|LCeJgn=bbZram+Zw|Tt+;X$p_P7g`)B4-Ddu&* zHJs5D_ET~lAE0p5vH6_TI{t#4_wQ9lq|bflaj$NST(6Sr)pEU7uH)o-6RuHcxZiob zq>h*C1i4O>E8mOsnIzY)oR|AJ<$C7%y#H3NizL?pF#!D|*J+Y2^LU>;N$Dil1M=n} zx#meZi{;Iwa$P3ZCb_PV>ngdvC)bT~-7eR6=RU*Yo6h znq2eb+DWbl&SnW;$#sid*WJ;I2oO9v_THwub_{r65gXP()&lrQ} z;`PQQYa;NXRpu8aGwCqLEp8JXK8SW-v*qa**E*u5ax4`+!^>p z6QzTghtrD6K<{n5A^}%iBtWSvi*%O-rF#--JP9rdO4q}y8I9zpi*)Mjm%3t5qZNm4sq(xaY7$Si4gPdDNST_%gi5sq8T3@} z@18VyGHg`v)6v0SQNjCe%SSm57F=iX!HosN=RS>6O;LW)ZlQRhyji2*5VbRS$VTy~ zTpi87Os7=&ye%^2X+(MH&Gi3F%0LO1@7>af!>5hqFMbK_zjolA@1owzI?(LbtoAF2 zT`6$p@*y1H2Xe($UFCl9wP94JTcsS!L%pBaYy?*q2}s?^?l+^9sq{YW^`5`7j86=?07-QCEO zViI&3Gm#=Bc4kVBtsVF!M?iHBGEA!x@$rFcZqc+Z3Dcr%;WWbka>IXe zI87%1YewMz%bPX-FFp~uO1G6OH%3N zt%ms70c2YrLSN(m!RYYMkB9PL>jNVj^nKM0raYLxu(R>0>xBca8a2Mq|I_uOr7!qj z8#TVjbJwWxh5j|8!^c-7Zt2g#LG{_UBw~NX;9XmO(_@9BRSc}la$ zGo)@tQ4$W`N^Y|)dERvo0&*>RzSMw#-yM1x$3%D8mjHf(l z2~hgEnE~}LhdlXN5r*{) zz5(bUsPy-nyGAYFg<0f#5;xQs`FdqdrsO+<5o0iq@;N6a-?wpNu#s=`=;gbWazyBF zt|ea|=PdhfQ}W#k(t`=`8mz_Nuqq2gB0blfs5xH)95M*UvBtcG@A6+Wbkh^{QvUE zNb(2QQ|EjTS&{Wr@wC6{S!!^~>942myH@A&y_sD6T0D3!VtwzEUVVn9N7_*@sosz5^Ke241~^8JvCRRqWR#=p-e@zS~@&4SpRK6P;pyrJAl<8yNm;!r2Z;0DwC-cJd(#FX{s zi^>4g^`m8!8he_)9SHSM$C8#v`Ij>8C>qXj+wK32-L`_^%?zG7hf<+z`>2;T#H z8}mP{sJv*}^miV|;}dVoM?IoLxi+E>Dg7KeJbUjnO*L3&Io}D&!>T6M(*pZ&)BM&Dk_!Xft4c`AIRq=}XoDa*NJ+?HK z@7fsvv;2Z27BMa7WMX3sMAv(d^dveeF?lvnEzUl{GmUoY}T4L63iw!;myVCZ$`Z@ zfdQU5e9Xd=SSaLO$FdclkLIp5N>Mn2EQ^~A6dE(H+ysNQ1(Y;(fR`E`M=6>n1e4m=#%&bqOG_S z-IH7K*f-@a=5;ab|HS;6#uR(LlI$t0cU798WE=Lb0fCL>L5^QRDFlb`vUqs>&hfKA zmOmLcjBpQ7xGv1Qh& zC?|XgSd|k#7-83pdCz8)6RsW&&a^?SOi;WLXf8yrVj&{^m&&VCvn!^BN>ji!s= zm$!9_5>G0b*Q{|zowwX_IRbLI#nI)(+4GOFN%F2qWT6y65RuE7Z~qM8+B%isBzoU% zF?JW9Lyt3D7xNDt=P4YM2eCiSfu&E>8>JWCz_8mO` z3ePC6;=$$Oe@6VV!3E;~hWN{sJqY-Zh`)9)SK>b|{*J-tw<&t}&@VQp!Ji-~N`yd| z*Nb`G?13J8Zjc$jXN361?D1%_GcNEOU$BFjUGjE}iMgo116N=&%l3Vfj~o!%@}N>T zBFW&|E0KSjpc+Y@{x>@>OcuZai`QI{O`a*M@NI;Ft9Obhr)R~>`^lAANqmzeN;HW_ zBJk8YVqf0XuU91Ec8@GkQ?A9G=>y+u7AaO(crYX}<9j&~urrQKEv?Ba(fwEe47^Q~ zb0hFdWgvK>UXh5HyG$wJ$0XKj5+97f(__iLL{=%(_b75SiKeW0c}KTrB{3OawitL6 za~xKbk63tmg3!hZg3*i4)L)viW-9$2kZaZCz8`_7GxfqXSta^Ul31ik9L zlKAyLkQls1GS%qgSrz{O*rQhj)SqgTqPuhS?PwF%67qU`zvJ8W_{Sm5S-Zgf>x;Bp+tNXLW4v@17jK()4-Sp#xyXdfiVq?X<$qPV;UIKz?cU9f6@T= ziHx}u)j;wIrxTl8$DG!{(3gYy=zCoJ@k7DzAsucMR}|7?EaZ=e{3hKP9Miy<2F5fn zrhzdHjA>v@1AlJ~Y+1k7iC?kSsZ6AdO6XA~mx`1>BkMwRwIQ@46QjSAe@QOYqcrGCE$^l(KI-A~|8t;CM z_hbYf+rV?%K&FMCUUf0r^Zba(()}1@+T5u9D z!)4{S^zXy3W5sz+$9c=0raJt=N%k(fVc5Lhh=!wGBz{;{3!m+XXS|L*oa?YY0 zb#={64Q?Zfp*ev}*Z#cI%p(TkGq{!`)uj)ZCs* zpp=`M8=6{nxF}T=s_AvywsZ=i9x+2y)AMBY*sgTXSUSILzJ*ucno88Py`i}+bzRN2 z#EzzxnslnJrL9i&YAbDCt=q0aU+3_BT}*(4gQilED6O?xVpXi2ZFOV@3yYg1K2#h?R_ z_J4UI)#g_}#|z&ChQO3|vbMQ7;AbmCLN8vPHA@9dJ+&%VtE%UV+_uKn_GZ@L>rfFB zP^GHTb!ljJ+iq1O8>FvluG^hRWoT5mRHfS6kRobmR0yhu*ZACPq-xio+0?Wp(kiB= zE}c%Tgo>p}`N*z_>Ry|GJiC*L2pLTKy`rh5p>jg!`YZmpMgGQR!|xadpAs&AT*k#}K#q&OfUYKL%Srmo z*Q~=*&lUobe>LE6_z!CQUX4FA48CUT0rHQ3)u$iMFXKOjc;i@(y%v60euHNqU6?-Q>9_Ei^7t_LOkef=ApKhSU4+wx>C>P4!ytYU{4SB< z!uXV@+`?zd+F|gS{+NZ&{0_v7ZI$uYi9z z`pFP}M;83X59D@Ip8iW51;gv!ZFLQ9eO+5xXT@3Hl1}WvGNu)?!tKqiyD%$s_40A2 zn&xT5RsJHjt?BxNyA#uYtR7sfa1&TF)U~)qo08zTmcyMVa=15*SMrP&8u<+D{1?fW zn-F?k%QRdmaA>RMbC$r^MuFXP1sXaA7IkYq)oA=v1tyCG#@7fOnxXxgPUl79H*^eK z-+~!#iVJG4;Hp+tmAN}$@M+-;Tvit?umNDfw5u-F60ycN>$SEtu&nvCp>NhZTn<@f zB#TgxeM5U9wR`1Cv+`GKQxj}x*wD~hqt-Kaj6558wkOi{jczMe5G{}l%Z4ScTNz$J z*4MS~Xk>N3f+FFaY+#rgodpu4SU2bN2%C<49Q zgGI`(g?@YX>dH&W(5@5ff!mBer6!R|Vd?g=aP$|O+fi9y0@_7WSHpZ# zS4OkC`o@G+2&R6)h+x*YXnFn;TJOb4)O?a)FmTNaC*3*>bvG0nlY01E{B2;UeKTUr@AUlWYZ zU)ydppPkwoVOT2|o!^Vy*y!^o#I@ljK-}^~%eAYiArX}7WsrwNvr6-l>9Z7Bnj_@) zZ>B%2^bW>h&j@Df@Ag!yDO%Q1sd$#^?Q(8`@v)6q=-z_;L1QcW+q#U@+4xx{7s)hR zI?wK9wK)o?^){k>;l`AWPe{ISGEY76M2j-8OP9n~F40|moAmX%^idB+C?~^Ua|5iU z0Sj=luB}ZhZqb}hj8Ua}6QIVxMQ*Bz!>jHe{NXaTG!|1YUm(;*yen#2y*r(#YP|yO zWKCloEUUpv8)>38dfw8~RNsn5tKJrbhS>ERD=XtVM z0AcXX+PuB?Vdg&^oTbjyaGqe;I-3$Z(1pUhYZ*0Tlm}lgWgEs!d!l{0jT3b9cCThl&4$gJUSq_k zzB1`VbRoKmMA2KjdCTezWxibMy_eB{DpA*f5pY|0a%AR}m;vcZ!YK+W^A4;s{Hp70 zZcp>Yif%&0F-GME+VYc$RP$~xGkCyw$~hc*IEb;M#api!k|$VR+nQSHQoB{ZuNs=o zNy`XbgK>!rAc z-3uhVs6=3o4)2SLe@Od_5|S>aVc#5yuU##$%cRpVsbQ2%c39*4M7~Z}=-6?4ERR2L zlPa7xvv2AvLe;@8%CCk%S0k66yW=HgX6o`QV;7iJXV+a?KE3`c)%mJ5`CKG&$1fGw ztHX;LbbhqHTn$S#OltkcE*89QovvTQP7N&;L_U_@Bl2~>R`av4Z2eWgNAQaDy3|7R z-cK;BJ_94 zx^nT(#SL!x%BGcVOWNJI6Yv8=`2mf+|8H|@Mir-CZ0-U z{V{#Dg-7~^UMIpCzaNlx;Uj>iju^hQH#PiR@X~-xe>Y$+Af3UZ-DgPS<^MwO#{pcZ zu!4AZBb@0zrSYByG-IZrN516)f>#Zg5C5HjE+CzaS2!qma{(#GT0r}~lhJdR#_QI2 zhXGv@gR|+mzZ7~gK<2j^&?OR_jrX9&`+~+hmIW{Ntk5e3?yypPP zch+;cPA!q(Y`iqW>Aw|_cn<=W0@4{gCHJocuM==O;tl|I5(&=WQP1vgbbbMut{Je0 zNN~ja-|ASdl<(Mo`t3*6L-<`Sbd>zv-MO6%FV+22kA}wHrjt78MH6*99Y@bm+tNE> z>DZo1Bx=+i2Q`^m>BhO=LA~*A!~66^%b6ISQ>nV$nQwQ^sEx@OD7j`jNi&wRVp(tU zF|1#Qsj0tLhBAfHx3?rZlCYv-a>h+5dikuT@19{yNQT}s+wYe{G|iO6-##PussIH$ zJx2HjQHP%)e+NiI6U}_Igc+R@W=o%JOx9raU!&$toaUf2Hik0HN}IWbB|2?@yvNEY zSka?iVA2Bpz$O(trO7-kQWcli#LMGVSH{a~Ud0(E3JLG+m6_5`qIivlk0%r!w#as> z5!m=CqVw!;21&r4$_1RCBKTzFv-2){MO%r4Z3Z4g2F z+2q^O($=0#wx(D^p-jV4ng3*lvdD!Uy)L~7D-HZ%zYAwBXmf4uLaaAh+f((4v)HIy zkI)i#JvJI)`3|{(l0?ZgZEI>y?^fGQ!kNL$$V{#3f*X@sThufaWZJNhgP}N-&tMLN zK0Ck4KDrVsI1i*m!G5VaF3tQlRNva%zO%(l2qU`#XPvSS z*EMr}u^R?kSos3HD!pzXyfbS{)w;z?&(kXkc3bGLPzK7Z5-e}#JUbmzTEi2t^NS`e z_V=1JW=yD&+(!gkwV3|4yUI52>NpyqmDM(Zg@xNs>Q_iC4tocyHxiRTpkQp~c&_4mF-;ZA_Ru&~Syi!q z(`%R~zi8@U`paC98a<$t@bafEi9L`ttQ3Zw?!(kOP@f$Kb@a=1uGDYU@`KWb-?N_6 zj#psv#uZto;joT5Pqio00n=5G3f3)rXBM%V)3r*PFoYGqzb>l&gUpAMfLs?n2-v7I zWzQ!AbZS&{T4_wDLi|-OdaW$Gh7Dh87A?Mpy?9h|40fBgY}jCB&U6i4{ZoxY?erxP zPLq^9yiRuzraTM`$}N2T7b=ify>(S`d!riOD7sB)w*mGxHY_!?St1$I3P#blD{+hJ zS;3bB7AqR38EzX|Af_C6;U4T&Fkh0! zIyHOTAqZd8M&;4edl^zzLLqt+ZkygB?^cw>H&;~ElvPx0uBe27yZNr64U_dIbalF~ zLFuTi=UEB~aw@x83LDe$;tGAfH&1It`RZ-A0r4ueKJ<12kDn?O>u*a{oTLBCrqA_i z-?u4^?A$j8?3l?`jJ(K~dB2z`?lAbQgH&n28m;#Cuk>HJUgVy!%-->@X$(e3ZTiR8M4Xrr4Km!y;5e9_L zVafZB%Jyw7iL@c1(!I9571NRRGD$)R&JolnD5Q$j{>?22N14hF{59C;k2;J@0?9ge zu)fXOYj8#7`n4ocS+=2UO_f_(x_)ihrmFQ-SL!1z7p|4{H*FyUT(Db?_K406j#{5D zC>6Y1gt6asEx(OduIbGHP7!_!H6HyoUaiKXEo2&gr5cZZ8?VEnNAapP9_cALE*1H@ zG#O$6kA9n8x5lFlD-XX;jYmJ})jmG1lkkAXqdeTlwx?9`AC|9I<8iMib=a-( z=vQt~==EtlZmK4)o>HMl9oc+`G~O(PlUJ|Cqu<8MFO&4lAAd(R9-nQzVvWbWviuEb zJU&w|$vV;BLXAh+CgXR!ROlPM^!!rtQLgc31Bd%_i&qIA{dBP>1TUuXPDYs0g~p@b zmakUh-Gb(o*x0M1*BvBF!sbd|CFo3_{Va- z8-AA_ID^M}@-)Kfe-4oJXZ<-EuNmRQdm|w6?vKD@xbuSGEd*p=(*Rh_a5%%4@?7{A z!K(sfd*}iT<59kae-*qHfb37wfG!}Np+~%<8gD@3EqE~+Zx6zmZa*OD9m#@Mid+&e z4oJKXKo^iMz*pl908-;cIhlA2Pa>S@x&YbN90W`P(iwWhD;=k_sKVJV*21rsnhH-v zjQ#zOYarQx9SmyjyQH~w2ljekPSce3UtzNyvi$OM{qkhF$KiLi^elJdh|hX_5b+Fu z5>Tbg8y>$J@x*TiB!2e@_{TMV;RL}i1$2>)E-VlIPb1zqw%dUb@S7(F@jKv;s=o*C z&Fv)p?pHYq8Xl$hNqE;)!SFsEUbNN^Cta%?V3omqwMMoW)a6->TvTu zr@KMod*=o5Vss!@`(dHet?7jKeP>mMdLf}~7sgN|)(h9gAeu<0xf(@uLt=X!hFCFb znni-i1fZ|?nucvPayq(ZI~KJ#0lgC2Q?V-Y7Xu^UTV%aQvp$8rOJC6dQ@K8S?Yd>& z3fEqftHZ77M(iC8zN;b8;pNS?a`wdW9s+b4jlm={zeaxjUV?^kS_$H7H@k8WJEYSP z-DE0(H&6+WZBYKk8Xcdh>1aAD-G*(Ai!T;o)H^@VqT{nrPubFE(@-cIVL2OGHT@9X zhSp5c6(-F@G%6-`!d%K_u(6QHb~}$<1iev-7)qy(%-br6ijNXC8qZd6=)Aqn1kcgw zSLyd4v?uuo;_al->9$}avD%V9;)|P5dO7NEF^G(hP(T*EQ7a%z`c1HjWY2u2-po}6 zqgFtOueoZ#(?FK^3=L$(8>I%arXN8AS<{VD1EKWj@ztsPZ233wBjulFcAnbEM#scw z&{@7@>5>&ImR_)AnItXifYFJ^)h`#@P#La9fq@O(7kdT11ruYNHlC^OLJe1`1a4=5 zRg0&S^nO*Zv+$(KXm{!uLsNr~5Yms1&k)-|N4|0;W7oH|!Jf3f!CW^1VctNSloA6r zFl;4k!A{q(EFnHBi7!QloGLz3iU1yUtZN_M#WuD!c#B~Ww)kb@MG$U5$GsabjhHUo z(SjQjv~kS8$)Sb@+`y|Yb!7q#`Qh9Of7nV-I5qcEK!zm^&CNY8X6bU9rhWmos;a7@rVQqXwRr!PE&jDz$||ljFPJrwoqp~53fMer6q=!7 z$HP2Sxw)dIa!tkN4I65>zr&hx>Hm<6!xomY!<@&2E z^Z@?4vZ^)ZWtBCXwrqqoWKDpwrW;>Xv1(%t-iz0)EnBy0%Z92f^o$A&nkS&9ip4jR6ELpH6>S*_GH3 zkUhPq*0(a*?c8tFuDZ}esQCMJ(G92_EjwEq+R;hL6y9sNy4~T8p1iuzJ&PxbPsd>B zS38=5N#EXL;)4`SJUb(A(&AA^KBV2(sLpB|Cfa%~%yxm9G4>j$9a>%&=gZTiI%GLb zh#sd^&AHY4k~U4@rC2VCclh$+XRUH z8Z{f&Z(3hfvkun9s`Z;Usr}~RZoJaw(W_+kE zkntf2$Q1hlZ9JXC1J*tkY{23@N9GD4mLyljW($jI4XKqYv3pCGiAMwvCr$^amlLgC zyE3$*IC6^#8ln}o(k**KJ9dR|yMTG^ZYTtyl}Oq%au7kzETh#H7MK8rp4GJg6oy+JZE$-svd+`sChwpvR@BaFHC)@DwO)t0Lb}7 zH=wHt*!8Mtir~3`#Jd_W3h#)y%<4@i4uKVTHS#%Y4r0Z6`| z0*nLF+48wXf)@iMUNfLeBsd%Ih{ijn@!aW|c$Du>gfrcPfPBX`02sx$afX%;P{{}A z0-w(2J8P!kEdV55Euc#zIO6@ioGIsHvxNU~K$ic)*}?gx;cxKWlLUV`An_XkU5#YR zyDof=KX4{FvwpHi<;-0jk&^2KIRrB(FvTYx+7FD?jQd#jvS;w=cKfxjl(@}fd zz1-OABPn3L_dUaaA4%r~T5Oi|qs~n@J!iWpaaHfE>}2!{HeN;{A>nXzPau2VKC(b9 zwuc#sDLQFn)b^2 zEE~o7`cKW9%)3k>rvH^?LO+upF%3dC`l3feKeMsu34p#7mNNjRpoSxdzl?fs%`=QB z3Wnve@S=)!IJ&-ivWl7ygL*-ky1Hr& zu0TP^GF&uOuu+0G%Fbzoo{N3g-0rEIN&mVt+O{IjbQ5a5&UIHG)?vnRoq800v)6?2 zT)z<(ogBm~hTj;FZM=90uR4Thc#RgSi>@*0mzQgp`oEza=UU|qqW9?nH*lMO_;!Bw zpM2lij;mv^cUoqlT*=A)&?W9F+?)+l9E))~u7Gdd#Rj{j0!uyKX0)pX$0X4SBX)Q; zJmsplu{WmENu0Ub(Yj=(`Mkmsh;YvKUD#@V?Kzih!2kM&itLk!*p-lndduXC@$4MWO+>_%CTLC4fkuZhwktd(8 zx_LqG3u#-Vj>^cG2akQA)IUlXARF!mMsp0*l2;8#RJv-z>f{#G-!NZUv^++*2T&b) z-G*)c34CEdopOe1aPz!6%n{0jK1m{Hpm349xoUqjQe*$IuV=sN z$(TBF79#3~MbGPsg5?%LHlpVNolrfpFH6$rN=V8&#;R|)L+jYGrgHHz)e!|g?qJC~ zYWj8N9G_^GZI5SnZrALo|MIpdk;|8x2X3OB#03So8sC-@3gCU>!mu@ zjgwRAQdAmsxBwzK?1ST-ROn#2hYo8E-lEwgn-g$M%n#==a1L-Vs-VZ@E0RS#ilqpu zX|QfBMp*l(YoPqX9>|AcLf4{<59WN5UcKWgGr77`1Zl%LF?CVqFkDD4m@8Ih=2cER zQ%iMt^+xH{QP@y(=3$~#3tkQQ6Ao!GI6G}7=Z3vor;kKxDgATq0XrVR@9+cHpq6(PyXBZWfM>+>o~m*R7&zufok87L7LVJe*;m4knwA8d;d&cH>!D zW%Q+0{nIGrrJ!poHsHapR8+2g}Mv7=+!gotC?T_mxUD6D&KLD^v-$vz7wcC`X}*yxO}SA? zte645_H`D4NkV27_Bu6QF5^#f%w-s=1LC0IO`pwcaLf{M*tVl-XZucd2OaKw^pi)5 ztn_2#))kCj(veE$Pv8PNHTY5jAyiphiuTk3m8&1Th*!IM0=tJYGp@OypVAj%=+ZaC z#hI1fPeBJ?;m@K-YlJ_oxAhMwX~oJc0b57`@@& z9g(m_ZAEKjS`Fp{E%>le zTRV=ZgNj|UMwlAzXEKMqGp6WETQJYT!m93T6hWn!UHE5&~Uxi6rUBhCP-lurt71AnaYtYKxxmfsSRMUf6 za5ynDQJ-kAv@AOQG*LIg?A-l=vC`}PDc-Q+^*E{<(bET24`f%M+C9h_gk1rxsOXJ_ zK5O*^{-O0sTl==QbW^%rPOzZ3x$V~KdV_suW_msQR7OUm=H6Z?k(+znrp#$~W_o*l z!ogAI#d@ZwXNTTWDOecDk)_XU?Hx^ao0^pAcP{oTZuSK_*bFUdwf=H6|7=V%@(?Pb zfS39*roKS^Qk5i1bgSpsuz9tzgIGb>uVpe!eaR9gU3V>3DHbQG(8CF8#z9ZGVC@YE zW@l*l{P`E9#m3qY}?*h`!G4am#FN^>_ceUI&ON z7VZgAoub@qVY;CO+BU974u49ic@GP}S&Ztg!v!ts)QYMkq^L%+ zrUcBmdMp>S3$7f~hAfDJ(WegPEp;ClZx>Z$JsH4m9Z-~2%lZl>48$4}`D(yzV|YtY zkMC&cFVDDj7-Z%W z6skTPsE)ZB@(u1n_4;abRun(isIPjVWvwfMV;`jn&oQ-qGux~Z=O4t7YSMA2SN2Ws z-l!2MRDr2a9dia;AWKV?Hb*q?4z&}LBeuT|OutxwU0m+MZTOr5 zHuK`Dz_a|_)|_28%sA%Yu9m#*VsX~Sn$`qP01ws3O`J8rm6&0Y!_D5Cy&65jLh!%{_PqqV1 zmU1C5%eu~3dUV<9d+l|s?B%}>wm=gtXSE1xKBq7XxP!Ct`b}6UXR9zlm2auxO@^_ z7jC@7Sc-l~Z0OrDZtzp*6j*PhmGtJ4GBzWdgFHv@39wbGT3!tuO6tExFqaSD3&J9? zcL5=WwKVs+s!k|GV%YSI#EH|I=%h-LO|9}4TLyh{P_5gOa#;HIRx34Wz!@ym1yx2K z1$oG%L%|f+mF^~9na!iIVq>$DmMOYQ_h!$NiuU$qWZ|31Pi?2O8G6Gyhy7^o_|BEO zik9t`XN8(jm#@dET(coWPrfXHxj(}4sz>gJC@%K*xBELM{QVTAh&OIL9?G-eI|p(* zNpEO#@IDQb)~O%$%7o$`Go3zmg`^MPx1rK0J9$fDSMU=rib&5jd5VAh zT7f=g|LIWmXRn}-=T6^&-<==Lb?$r*Po3iXmpYDjCC$Qb!N+o)1@DzO#^1@mzk}<2 zO*VNasQeT;0-Yhj`!^#)Sx%98v?0rA%#CSaOam`X1MI_$t6rby9Di4?a~Qvo9Mcuu z>EpT9Gt-imarjWoi+nD{kMyIQEr*M=ouC&F(TqyRzHPsS*MsMou2Y61g>$Vc#e{XGz|}19`cED2A6U1OY@v=;NhE3QK*41%5%`d z>*KQ?18hXnG(2p1$S2D60^c|rKNdmT;1QBXxs78#>-a7}a&<4ubM_z}-!y`6ct>fv zExe<6&c}GxiC_Nva-9|UjmD9u!L;*5Iig$_@XZ)x~*hv-LqaBHhO5_D~Mq!#T#*S~w>vtLeMQBMupQK?~n> z8;+%lV|_bn;SJ&0eSc;;(lk75J&;e7>jl1XHhw&Uw!tGLk46h8EDPhk{&7>Da{##b zmK&1}>nKgHg?Aj!3-8W#y74=q{t-M4H#=XHBg%CFKdOJM2H)WvW0MxniOM(b4*yA^!!jlU?70%0u2Jr-Uso`>)|(Q+hD z!_AhLazwcv;766C`+;bDLxZs^ESwXS)wDU%E5@8-KWH^t^bDUcWZCyvc!%*EH4h_A z!^74C`9wK`%eee_p0n^@a-HK@aP9O=R|;I?;?^_6NXs}|4$_Zuwj3*vwg-5fS#a(2 z)XQNDuOH80S%^=XhKDT=`9wK`%eW-cmUiblMfi>6C{MM8m&9|FJfvxO*z%B1lry-D z>p|K+;B{xgwbN6c0UPgLeE$)@up>>w!Km7`DtVU*{vh1ZYgD0xWJ@UZ0}pD1T=8P|=pr4N8E zej_=`Q*GfTnJ%KN!k$UR0@Q&j-o2|~K zKps(gBHuChKdXU?%C(grShW1!Jx{;8{9hJaacyy`|F5hcYgS&pt!^g{nl8pCtXk5G z8(ViKuI|{`yqLTHuHM$()ZDNb`{u63(ZNlNS6{Jq+0xkJOE<4xdNnSCYDipn^>y`2 zmtWm+-g5dEpT8uwdm;`J+2Sz}Wwo2F5fn zrhzdH{2eurGi`!1tzn`wZ8_kw$;q68T&Dof1L$W^%66hJl3hk>n>hbHI1206Dho0W|Xg1Bt;O zX9WDkJ^>pmAm91X-H9~(HkJBr6T)cEeF0&OI&1)8`|*6&LlVZce1A!|;*%0qdWy(% z_19Fe^BzFTa}OZpxEGKz6n#tla{(EDHX!ps8R+f>q>R7T{>k7;KY7!AMEl8?ZXF=w zOA5w01*r+~+?&QL{gf2qyIZOJeR z1Cl23>7E57OZiod zc)D?)5?$Fcke|pf)tT^+$k0Cu8J2%4sFy;}re10>Iid_Zz?<%h?}(rAbR`iokY8Aa zeAGYEVHyMZOlS0PFeJn2T88)*N%J=FrrUi){EVlIMaV#YCGI50ZFq$p%R@Z6>uh;++amvx zQzkm6EO(u$Idh$f{dvjUTG3T!NVd*lWP9Uhg1WjLxYU)qRAiff6dt%U0V!Mc4<(#< zbnmle)4C%6lKJ`0jH;>5jOA0D8L6rLg~`da{p(ToNK>+)z**2R%W;>_bY?wNG=$i` zg5G@9{tj5O+vQ7Ms`Q#}5qjtM z<5&E!Pd776=!V0i=4_-LE`Jqf^kL2Uvf-A@&vS~m&FY_#oL*am_Qd%|&s34m)L{cz zFBKgPZNOT}_~oC^RrM6PQ1dT_Z9o`Zsrr|{-cL&(Kwqf@ssGXxY!rGIjq&G_}}yv!#Tl>g|% zkLUJbZ4{RPK41;4dl@asu9ETfK_x;=_A59h6OVvh4}{JvoE3y0;* z$a7|Fo0^;o@ESnq*PoWLl@Q#q5@eyki#Y^R@fOGDo^4oNx`i8I`!hR#C zf#LX({3e}&BjJbRO*#X^@h9pJt@o&-l0}o9MGa><3zxsrIVI;*XV%b+{-VCAy@k*q z+j-Z_;pdEON6$U#k5?SCV`xttuk!KZdae}k86DPnwX{i0a1aVRcTtXNqfA40F(6}z zPxq|$mvBwA+Fuh@xqrjP$Gt-$qG_8M1-{js&$^6=hF`1Y4Vg15>tN*j2gXaLLlNSJ! z3DMt=)yxLp{?j6NLC-u+jo6!sX&M$Q4H%xKnL+A6c#+$!v-q0!iC4DFN z&Q9jMvCw%V(wy8d(>WQko_xp5q3Qk8`lj^4wozPwH7kDLUkv`m%T!HIW|bkEY}xel za?xK@;4Eq!hk14(=Gl{-85J_mera+>=r&uvtU6k9-emL#^PS?Hlbph#g8uxz3B9?= z@wMaPXxnS>t{wIBKGY@F(ZYk{oxXKar>=aB)Twt}D*lfG(jC(N9L}lIoL6B!g?hy~ zRmqZxDC_s5Ozv_f=e)<^+K@cl+2DbpjXa9MgKhy}*JVQc+zmo|Cm^4`qx}PbOrsL~ zo&QW*9uZo)rAQurI6Tz9DBK90sqiS=mpV?EbYU2l&%~?nsZPN|);Rl+9;cr&ZQmM) z6@DStLnvLH|CGijEGckG(o>w0hC-(V?Yac*x+FEbpSXH`9#b|i)Cs!MDybJ@-^lmY z@B4Lp55678I*_pzFmql5O@6{~INhrLJ!luDOyPb|g&95}Jx0x^4{O;?-8E2ZIp~vM z$gx#0{MN@a=hJs1jILDu%U{u#qvz9WtZ)n6akOn^%~Qg?0+4c%4&6LJ#+(7jvE`q1 zcpOkLh!vbqn|#{yX%lAJg*Tr*n&VXC7kk3YalwW2=~0Ix{~_FL^XcSIbDX{S-Rbed z{-{uxHxDr78it{IR5BySnY2yjQOC2CuV_gAsZ0J!4`-HL{7Zh>4Z)tla(L!jQZB~< z=_d}|RzQgf>fXpuG81;`3b8>iJV|&lJ<}S?0X%&-|jcSjjhfZCOU6dW=?|feJ^8g_Iv4RYu5JC z-?2Zzyki>X9pUwU>`0EY3BUZW`Lc$?yn1Kqm+DU@u?&TN$ZyLwVC8G*F)4>0SLCet ztmLZ|(B#YTnl5%x)$bOcY}FB6mU$Xd7yUmMTh?y?NpZt3eA||x=RuPP4ed5kZu0>h zBNKTTNBaCVd|hNJH4%VCkcsOLW{la3RR&3Q6Y zwk}Jy*aae2m2M{;x}6Y*?w%}i85vAF@nE<;?4IE4PET^usBd{_6J5W_aqh>jQOh+S zJi=i)nA6RIJ&N!7C__of1dPYB1~T^X16l{2Ai%O2cFZ&79IBhr|5|m|;Ee>;nC#ii z{CUJOJ&z!qLHji3%$PsSScbQhuun5DYCJiV@6@JpoD_b^Z|Js0o7=HTDy$#jdrUae z5HB1SPMdjX069YCh&2PFM*Z^=>L!&?JL|HlDgnR34U zR`LHCkne9Ve4F3Dz^k0DyYb=jonIWxcfXc*>LZeG7m#u-(r_st<+w=0)jFIE?R;-S zc$6EuUF6)lPxAjpKt6p6km-L8$dtbWB%j&uko;c`NdH#=ng3_sspGpO|Ca-*azdPO zqsjk@Q2t}O++VNDJqbv;-l*YCfRtmOhIi<2^0f2+0fa}n+Bb`wUjw8Y1mx5FPRV~I zAXC-?lF!Y6eB=IQ?LX@#$^S-FR>toFWceQid@1>F4COzr%l}P)q}K(=eBG_#y@2HV zaSb2R;Rp)k|51cTx!OG<=QjZ9o&)65g11ZlF9&4GdO-4d3n26Vxb`o-P4a)mUWva6 zkoo@-VAlL!1pC|ai(q?O9=X=u4;|bg;wTzDk4oM@4anff0I7rDYJct*MF&M1&IL5Q z3FpIaT;KaduCwkE2Im3tX#*f>HUSd*9zf>zhk(p)!TZJk8bH?fJ=%ZA-ID%8fVBPn z0?_b=S2?fVkC5-fp?t@6KDGc-#yUXCc!&1i4@en%H2eaf;Z67${9!kAkI1+F1CsyC z0r}JnNZM})WU5C1Njv{vB>(3F(w_!o{y(k#PjpNA9|AJ}bMJlW`9B`Ye-eEb^U)4S z8E?{k+e6xa2#_-MY4|iC@e!opPv8%`&VLs9z7I(Eb3i`50Lc88pyDyrMnKZu3&`?+ zSo{AAkojNnE{R`#r=-6ekokWvppgMym*}9(3*Y&4n;XNY%TtMdk0zjtP2}s%p0h#|_X#axuO8ygojNi3i((eUi z{+|RiGQg{x$-joN9JN-!yawwKBAl>a&zq*to8ZiAm<&4_>@HXr&|Xpv9nw*TB0cxN zSVM+)HA&nPACvt543JMJKj_Qt$*bmZvNw!rRd~g?*mN~-oaEVH_E8e z;nNOi^kVc-f^-GgPedCL-`AIu30#H^XX1Eg;_C5nzSpM{Tfro!=dU@=x)*bts(+U} z&z}W*_48PV9h>YFsJR$Q1asmHJl0^~IKYXD&pA8KIlBQi?d9Vgnc+dEai|~QFTBS& zvB~4GA8VX*)bNH};V?B<#=I};ov-u#9On}J3Xf{pnXyoq(Nl&#hOt#mS9AC&nAaEj zd!elIiS1UB>UQ>Q!u;(EBjz5#5&o`Sf3ZL%k}V zX{Zk1PD*R#&;QeKySNa{uYhyC)3s%IS*jijdW0S#HNN~?w5 z#E%NS(*gN(1z@OuiD%>|+@GCbJ33&=Hl*hkNo{Yq>OPUD_=CP2QTaP+(dgCr`n8q6 zz8#XkU+Dae*ZuSPfPe4&<)7lWw<0Z5??ocdN0BeOM)Vg(j*?TyJF~Yf0U`yyOGA|lr)C4J!(OXNV5!(Pp<+`yq5EbmXA1e&j3me zg7!QkL%6(;pMY#fB4mq)%G)W{<-J3s=xoyE4M?{&OL-ePY`ra<>$g9q9Zqk_{!G2a zExpC`e(%q}UgS6eNP5Jf+nPmhMg}t`Ot;Fl+LEvPVWAV#?~A_CA$NMU@BB{XJ3rXaCZ{I$vkmUC=u$S5p3laSI)9Bn$;@}ZRVLkf&+T%t|nzo-lExmd0b0+#ABKXtD3@Jj(1 zR}RQp@H*``Q`0}J-=iJX@EG~5lCt@_=5z5pp}zr;oD-)C-;z`F(Klcp z0N$H%AC~tH0DjzFv5*b45>=0+QCJ=ZoA-OE(RW7)(c()czZ_U!4V7=AQ+ky!ISL z`Ik<1mNve^IW4EuIc2D%|Kz^ey)%1 zVE5UQ=8n@OA8%bE^4||gcNg$TL& zdRr!=Z^F61@MS_)oZkbN^QKO$`e0?rN`1`<%b+r^f;|}RC=OmH)9p{Q%4=)!vzW_-0A>ffN@#(&;{Xf!vyN*(} z4F8#V&Z#-Bb8_;e+S$6E?jIo!$yY8Ec|Lcslp*8kk`eNeU&$ia2$y3| z-*06s;Jd}5Sgvye(wMqspdy4n=99m^kSy10S$a^$Of%yWk)@(kWMMqr+ahEk&+r+& z6$mrD4O9g1XJn{)HmHw()-t%8MTWb;o32O8@R0WZS^J4k_kjqR$lr{aB^OU}7Bws? zT(JD?!kIa*ER<6`vM;d~c&FEmtS{#|Usm=P2f&liLB(SJzP?IieP*rbi}7>^jcil$ zoGIJXu3+yRD0!=W6gNXBoErd^Oac6<)TuqA(BXbdu5$1zeL~vVP?^Z_7eKoDb>eRX zq}vEQmV@2y$WQ5vd-&9Rs1Wwe$AO_&ddg*z*CT+$e|m%Xe-B9iaqa&LAY<}2NqhkypQdR4Hv!4_58D4{ z?f=eZ!QcB@;nxF5cQ@pqjJ9r>S5wBKTj+Ipf#{a=LFioU&jn7hYMk?K{HlMg^=bJ{ zoz9sw&YAR7Uf=w=Q=PfbV;?kZ-m?xBIz?E66=J`1e$@;o7xSpv%f~sp@w;8q3i_wQ zHVHeqvX{fwiL?&VI!Nn;(n>jo-f8M`Jc_*Gm7!Noc6m`B)HOA;_BVccG5pKAyh`cO z?nNCobfxGtb(QF}58kBWIYY($C-u$hRi~cfN{@Ko z;psCtCr8f4+=*%+>Z}B5oY0w}upKw-H|1p3QrV!v%L1n>TcPY*;@o@xQh(i?{HoNk z_<2&N-*b!9!TSJB-S+BDw*A~(17a`IoHWixV1K+mcQK^&L7DDA-OHBN))C7@g|*t;rNmK zCSAx*qw;Fni-880epHG1_{&XWZv5tPP6>WJzxV4_) zm6wtwod3*;oc|2GI@kF!GQd8iQ};Q{k8vFD%-G51aO)p}4>PKuD_OU zqj=|FfAmQeanZ%DJ?HG7?CgI& z-+5PhniIz!2Hf4!2YW*;&r7L02aXjvrJo(=oPBVdGw@$BN3;Fe?0>Y8PtSRm1CD(Y z^a(80*P%Mj$N9&Z$Nt&LUw(%Z|J*p|o%od=7urETkb&of@I)8b8&HM526J|Gc9HFX zebKZWXWD(xaTdE|mZAy-zntYp!ipYbqg`-zPKn$>)C;>*t=N!z%VhmHr0d}efULiC zte^Q`3;J1m*{T`$;2(qSWC3tH5MGL(aqrM!y|0t@SM+73bev^?=Y;qTPpgo{ZVk<@=Pgh8u?`wH_HUB|Cy0)*1 zJdCINd4xRVXXp#lQAEoE7@h?GL{VURhb#?NSqARyf=wAN~$@`lDN$XeN6#s7k z$=CUo_|F8SpET(n0c6ZX(4$)bNdHpp59ghEvGcC{vv|(Jf1lB#>j~*8-h9x`Nzx1i4v; z4cLvE56F9o>GHoJ^&+Y7LHUH1Y5Eb7u^5nUtM;e0-S}t?%-051b zzGYH3XMb1ZIQl)2>j!{zwjAVR&+%kl+z>f0?f~vgG)b1j#o$GEDIj%3XCS%oM+}DD zpCkG+a$b%!s_&R9v^E_Txvl^tehVN&mi|!umjRL|)6pdW`SdOA5BCR@(;GKpc(<9e zIP$ztH}ERJkhFiSX-)a2*fn1PNEyxs?1tHmIPZ8`8VQCI0=n20j3 zAO4N#@limNcdxI>)}BJ`C#x)KKgd+<2hsc4?~r@-YAty>wLGQMMV@npM4rn4GvuMY zW*hRJWsO^M))eQgL-U+7(sOa&%qe(p%Dn_9$$YgB?F;LU*p}hv7OdO(`FTz$m84y_ zE@M~Ed$vy$9e!kz^zV$Pv)cpt*k`({N5Qu<#P{=>Z|N&UH`5A)&+7rH8^+Te2Bh3S zh>(MPy>r}Q+gZpi8h&S`h>!3Ww)@(3A!=P}>bJZHo8ZKY#ydOktDTgG_lhWc+zSv4 z!ySqi&h*m?oYQcR?L54bq%9Tu(S~ySrC!Bfr_1)+pr>`Ve1_=gUjeBz#s3bxhntPN zY>rKHrr=(pJlIqE5dS^=`YA^q?+cu&{HI}(GN5F0GW>QLse7+L-8&U^ZyxI2T-3ed zK;7#w@dmd1^h%;s5 z)fcOsN0z0jPX?O)A_SEkAU!#}H>{uSv2$y#h&>1izyS5AP+cbgDk%-45^9R&;@1 zbmn+xC4NPPq>Hk#{4&1yYsCY|pDAktO_{K~P~)8r(6Ptlm@e~Dg9vz;4vXtOe}4u~ zy1ff^JwQBNwXO#V?YHX#Wy$a_;2p5ju^$h2LtFQRv26NAA^URdd9Y<~({h$7Y2@!6 zI?UZF<-F=Fk+tz`k-Zs^?)_T!0}--Q7F%}ZSN;8Bw7ErSb7!N?y$Wsam4P;Qd=z;& z9?HXKz`qgllu{_|zOBQ0u8=(ZPRsntnB<`fknTr156{|pxI^qHlttyi_QPI$I&?p~ zLf{L7b0e^CxdcPSw2 zXPx$cb(#1{i*7L>d9BobyDTVchX0I7xEo=X6ST6)7+kLNM!MPB#O>39c`65Awa#NA z?do+{t=`}H=5s~X)6Wyx&jh4P0+0H-(Uv_WWkOk$?6#lxIb&bn$=KI78TuarV)H>ktndLQqzkd^L}7l_PEL-z+j$~^u%;S_{76dUS&h>pEHT@=Gmy@?w#F zHz3^};4yFSwq;)?^`ElXWupAk@J^{>oH`3sR5SAUwPI#a)(=L=`lt@;K22mj=@OCc zHGtGJ)6jh#kg^`tep@!m685V^{~!N(b24i##_W3GW)2jJFL5WRG2Ptx&M~D6`lF-1 zZKwkEA9c@P(QH}kmg;bV-Xmg@=X1|Wp|gCGl-uh7NtZZu-vA^XI#p&BQs0aW-W)UA z+|h)qy%iC6_c7n7qiTN*+E=zazmw(Toe$&Jqw_N#@`S^bEZdOp&^%M^(?mFR*LwoG z+i+5*?z*85wqN6lv|aU?^`fuhOGSUgqx*PRf4aRIIgI{->mi(rwAMq_J9XRrF7BY6 zBjq(^i_rfBAj@mG^qo3=A=3BgdBatgOZtypF>?9?I(;$HcP|wFb=8voi&skeoN2sw zO@GTq>tE*C`A+Rsc&Cfs8&8&b_Hce#pG?KOaj`YvY)RnTI7aW;plMIx`Fr;}q|J2c zbG;>V@$L}&3V0_i-wAQ=7VAf!#-mObBfnkxeCEDwlHacaGLHOB*?G{kVSPt|7q0Hu zoP_ZsA2yVUICF>ly_U`HS2Re+q%&auKTZLk{J5qzgBdkN3! zOMNwIR=)xE2K=6nNMm?Lt)D5|q&c{&8T0=+DcqKvoLOsVJ_UOBrN%oae@~#7kBV{e z@H*LxTZaBHG!7{KH|ugH%*Q<^s{yCyIMcUTs=7Wa-2@|i*&_q zv>Y*nhvg^&zWM&0O}_~1%dqRhdY`T%3;nJL`ou53G*kYbd5j6VqJ;R5M$qR-cpTx` zbjg%vPlBC~THMQRyA#RpX!Tx;xiDQ9zF8A?l)DGvE`H?^@x)CmBYUQzc_wfOR={C5UZ`=CxjcCo1Gnrbj{AzhW2J%VSzQ~ z#x(H1K?Ai@aywa<`!p<_D*m+^mTMT-uv5dZ>|r`4K3jNf>9TW||I>Nruee~<>NRW2 z@J38rw(x9pOgfwmQ28j;(1iEBTJmH3*`T{LPo)pXm;Rg34Tp#Q{iEb#(v6fKgBOOY zTHL0VbRyMK*X$-zsn(R+T3_FuN+lYWxbC{v)Xuu}#zb2iK9KHiuWM>fG`PD`P3fkV z9d2EVPOwwrmnhOYz1+kx1{4)fWUM&!NKvg*Q zDk_NmfC`q*6&NoQi2Zmf-0QZuzZhP8pN9j0x&B(YzuTpL-Djt z(}n5eUY1FRQ?1OAT_t~6|BS3JE75;2Vh51lW7nUXq00XAq`vfQk@Bt8^#^-mRDHxA z6$O)52y`0+_Makfe}$yu{E;qPZzOc&|6apC)K8oKhI{7dT6akvaP~vVC80QXsM8M^ zv(n`v*P!;h+W$-KFV+5MwZBpOpVR*R+W#x<@6~=Y7ah=kqtN^-gr7!t7HU7&IdtXP zKZx&C(lv(g_k{2dXuqL{(L^*f{Yud?J>O`N{*V15<4nJ2;)`1)-t=D+<@F2pbxHbK9sd{IznOm2 z#1|zc{R^7DPVS{YtmFTzJ|E1$6NpdfJhmQXP;u%#Cito|!zt`z;E3tBhe}0?Le_p4@jUR#dxQ_pwj>l~v zf%qXE|63hDH56Z(7W)6Gnq4fFflKwY{XZgecbT)cj8hrBCjX^n*J7URlf-{h z#~%;H$94QSbo?Jf@%=jfNge+}DBitU=zksY%-SpX2WQH+SI2)%r*~DXA76Bfr2ndp z|5J#5mySQIg>;FG9O(fe;2w@PilxNyx zA0%bTAn97}G&Pf=Wo}IrE+fm$-X-MPrcG(LT_NNOS?(3ObghGseXcdj<^MS|Pf4=e z_x;`f`~H7_@j0K*bDr~@^F7ORp65BwGv~Cal71n({}8*sZI%6A^YroOv->To?9XQR zA7uBptFoW@L4W)&*iY;CZ58|YQTqJ{*!{X6Eg@Wa`T4Q?^Vs+~JbeP?`%~Ec``P{6 zD*H9;{(Wrxyej*>7UDs>ucR#dHP-p^~djF_jl0muCSk3q~E`r z-QT{-el5Fy7n?pvS5IuP4~QLHB0j+~Io+unqjC-M4Q50B)8kiP?@vFdlCK{Bi%Y;J z{)*L_&iMXtg5b0-E`l_x^c)R~)n}UaY4n++t3ba^UF!Gs@5@TSXqmo$EDvqnx8LXi z!6EX2;XZPO(myyz}*#G}PZm5~>WriwN&9 zUy;-3z7{@y(h!L`RCI6SIeiGct?kR7Z4gU zMG_(p4GU08LgmUz*Q`&350-iRRDTc})hCWaL-m#8MfOLJ{YZaOZfB@6@STO#q~pQn z$0rys(8J#a)iw2M!jBd^4S#o;SIB}ZWxx7-zyL|G?yf-M8y4h4uVNxy?C+fF(xHZz zANO6DJS0M*^beHdYW~5|A1S{|$5#}hKB55sK>zBZ`KTR<7ECJav!BcXLaQ?KX)9yeFzN>k?50^C?gbqGhKOLSb$V1mncHy3MpE8 z<+N$7eJ*`pxzfi^5)>9F5ApY@dhkb%M^lopZSlP}`;qiKXs#swc!sVNe>HY_=Y>XZ$tcG?=zS` z2wDN2TZC_rxARCec*QyV?raCbgI@xP(4-ja9ACh+pg;472CTV=w4k-%bsEe^1f31$ zUd8(2JMsP!ECr&WDK7>Rp($qr+o36+01BWfmjV}|wP4q4h!a{2w!e;YfEIx-0#P{# z1MXcyNGvpSA76R`W_w{X_$&~xi%0kmFwY&Z9a;g70gUz`3^*T%Lij@P6QBT^JS4;d z5Frc$b_ep{?**O<5cn5@xmx5a7q(Fb_%u$ihHfLg7R>yK@_`nCRRDu9(bNyfhQ9_Z zc!V)_8Xj!>7~9Zd@CrZ)o3p`x0kKGn|AdfP0D~}UaQ&xfE6@x$9S}hmg8MzgcO1}O z;FEwDS_=++jyj-mg7eFe-`$Aw1tBwm82F1`Voe%AvxleSDbS_RloJ608z>(K%%CZs z0vKrW8t-xdCuqup0C#B0(}4hJHFztag3be<2BM&~V98%d12(9@CxBdN$}`_!SyO1r z3xRvkl%oMc?Z*hqTEGmNa!-t=bb+SqL;+2C7~l!525;wZhyt3yZ_vgYa7Yxi2HY9H z1Jc4C5qJ^(O%Td04}1}ri*&W%k%klAzxNWroE+8h!C zD5#xaejN@`L5snTSmVQ!`h(?s*oHV2U?F~YrQDA&4LKwlaDl%TJlupsylDFbKL%9L zd{Yhy1{Bb0@O3~8jUQE!5Fna{2fqc@Ax=SK4ml1aLTkX%Cde1G0=yU~fF@=f;tCW( zi^01A4RjvZrYVO!ffj)y0HZvVAvhHggAQ771i9`H5BQ0nJ__8&J zoPgGXQ@e6V33N8tghBk!0`OGeHM9!sB1BpT5GUBo2IUVe03WqOIYMi|_Z+ZJG&JeP zA+>v;OlTOebx-6CS_H1q3-&tc!~)5 zf>wdU`g4dK)nJbS95NKz3+&*;Au?zYxbZ*^nE@>TKLcW*i8F_c1Ja=j!FLC9NDlQM zg7ypW52C)n7Q+B&{y5Y-kbWJuf!_k_pj8t%WIu2MS_|%qwI6e#qrv9^MgxDaT*4uu z%kT%E2g;zeV7V9acoF#mkMTj7Uxf`a4oUV!Jwl5oa>x#Uw4W=`lc2G-srnLhActfI zpV2&^fa4ryw zFwxUEWFH_qi@E^60ixm0pUxqDfC3yV2FC&J2$KhHFaz}t&46bEPS9%bpMV9l*DUBr z=tAUo4%*2NC==N16~!U17Qz1%Y+lSE%c7xAq8@)le-i`SiV)9A4tWs^{X6VggS@Us zn9~Rkc8!Do8KfJJHkJU}F5p=3{6zFKsK-L^*(4586{Edv;*j>q98!87$AV2#(9fKJ zKX?GZ97Vpsp@8Xe!~;$Mv`3J~%^Wf*6*eP&HF$6u)-Ol=DsZQC4hf)n1RH0-Zlomw zPXb6j!hmyuvV*V({1VVY^D}Xd0@;ULTS05~BY1^7AD)PEbw4e-LTUf>mgBl4aHw%d+A z0eSQS7XbmtBiVs|7dSy}0Gs}d@`nu~a1?L?@vFf{sQ)?S5&RsOfiPqzhjauiXd4EH z0pYMS8oUp1f`1|S37~*&BnM%E6lgJcE8vCrwP0o!j)lJnoCS=BE(G&;quoL)z;*Ya zU!rM&F9D{|{9F#n06dYd2K@bA4)LS+s0VP~0bU5B0RI5kK}Um=0YB($@DZRFG;4Qs4lG7^{vo6b$PiuyJ_nqjZ4%t= zFw%g(7`z(Dg?~1<_7U_W)CO=UAV7Yj!TCTQv<6(e0R0BE06YZ9hV}xN0j1RcD6ac} z7MdL6km*1fv>H6?IIbfo=RC0N1lk|`qro+PMO%VD1KtD(pfzCWZwODrfLokIy6_i) zR{$0`ce23-g=qf;NEbXC$b-Kc%sGYnf`2qP88D~e!AF2HoNI;P($h%yFycJJA@hJT z+CIVjB9txKEdzD~j8Ko1*8t|wls5x*w0_Uw`~_6hwqn#jz#x9gvj9hE${BzdnsO1~ z2Tl1rpn@iUa7bUE4(f$+5g>r3Tmm>kS6)}1t~qaT0p|wlj9zwq(yl*kO)m#3*RhjO9E*4dR(#=XHb!-v{nNlO|lU z04SsW;7BtrsZ)adf?GD_qW!{t@P5D&x)6L15J8KuzON790$q81U%Kw^Tfh_klp8lk zIY0}*OMw~CYH*-A?1WZxIs^g)#wOutR;6!hUeW9$fMSS^$;;0k=^m;Hf>49<&O)tQVJPq1E6seYk|& zK^cNWMO+dMtpeBRhxTw6_JD1HY-kbKXdsv5P=D}`fcrhf1HJ)_ho+Ssvc)_2txd)dhp&76TFc-S=+Ie(My^Y^+F=oNj@h0dzXv&v?LTI{%-7Rmd ziwLdo;gX|rF4>MX@Cw28e9``(1>pWb5wsZm9dHp^1&#wsXqcI3^FYZ{*Z__O1kX@z z;PN$zDCYuC5Qg#rpbo+mf(h0{h15V)BLNI?J)<=XEfW3e&2(JKV0|C%^V6q5pkERP21L@G&U@f3ToMbVV zi~(GbcfnGm0}OqPc)(qkp`4+;z|Vj@XhAgE8&CwT0V{sQT0+ojaP#HJBeV#-2dIOz zG~f{{&@Q1B;H!WIwI>E;2;{;BFYrttAG-1yY?aq&tGwPDUH5GYkOP}3&jt#iDc=Q3 zpeYYpjq?YZ@)3X!+b9e8n77t*=n@IIMfqt z$OFFw%n^nk&n1I_@z7r2pMZI5jbW#9ORfN>&ylY!C?~)PS`E$zWQ8#u*Y85M*YFbKo0c>_uhx| z2)ghWwEaUU=NHi6QHK%FON0jtj-U^MrfU>s7a%XtbbX+uM^SFCP@do$z(r^+SaSkx zo@%hyN#x}(lpDB4A?o4{+7EaWkPn>=c0L9B-y)A-brH@5{30kDJoFswg!TgO07M)^ zk_Vo40sEj;;PZef*AQd%TvA_y_6aQjyIe(F(BwAy2SCj;#I+XpS$7Z*=D??O$Pc>< zTcH&Xx#TpEhq>pqV3`*2*F-#zVLxyY?izldSXzf!jY4Hqw3(AcFy90F)-U~bz zP$8aZ@OHon{(0aMAP4^ZGMpE{cEqCquLtZ<=GkDA7dQu?#o#pnkNShPfGOf3FJU_n zg?Pl^XuuBsYOn@Kq4tBjzk;nap4VKm0w_UR*_45NXa>Kz8Vlq?=Wz^32d)7rq8j`X zut1!A0|PP`h((-U-~_-FacaQbcm`xBv=?|Ou#VaQ?rLa25}`%lyR{HMG{HRTlYya_ zcU=Wu2NXePgHHe#p*3KK+6JTqS`7XJxJUiLlj>lOF=$mi1Jb!Z=9b4i;bL$GAcD>V zyYdk~^#_;Fb58j!;0J%obsAufJ7~(LKs2-$_(($oqK2+K&p4f2tpB4oHEW+29%;h!dI*CYT>mOEuUOb3Zbe`%wTM3n-u~&jCs2hBO{$ zK<2`qa(y5cnzAX7LXQQn8V?(w)nGCKbpxFzH6YEsv5ol$?Z6U11|0x?3JB=Dgg%&m z5GX@lVlw8b zhn@=-1mJu@x-Q@VpbR=3oB$XhOg8u}kPCm$Km!sCY=@2pUj_US<~3Lzgt~xU3{C+q z(qq95gK^G6Ghh`UZe~bg!21=*C;aPthdvoFhkrIWCj{|8zXpp#kzZ&9I0Hz8<|z%x z03Zii4Za5CK|cYThGEW4XmfBcpcvW-91aMY!)CB_3i1g5Sa2Gkh0X>KnTm4?S_zH} z$2kUF0Je`nx#3tb_yE9%)_~)tqc4SKW}s}pN0~zxfNRV|eLSZ#z6^c}_-*6qxXyO;htQPU0*dWeuMgYsRU4VPgBJg-124g!4@M6FeS`E$x>L5%Z_#WU6f3gSZ07ZzC0e?%k zPa*H%2UMSgJ-G&CCSZOV>4NL+g~ofL+2E~!>F-$Qb)Nwl37DSckptjIRG-27ul)vO zIbd1@d-4p(24L<5#1DP|$mn?c0Rz$p$j7l>;H7~1AMpRhfb<8_F@`AyM*?%{xHR|| z!080Nf5zP92N53QjePI`zzE}MkbbTl{}C@SIUxB&VhG;Yd>4`7ZlCCZBc2AcA1 zARqcE*!vLT!dO!fcnx5R@g>UnKoK^Wx-+80mhdo`v4kf%F_T9#+NAn2-Lwi z66F*?4NX~e1m`@)h$v46ilCQ)4+0FvVJN=^Vlb{kxn}|T6KJ~rF_C~W8!LmROxImiLesTQ>H4C*@a#aDt`T|yny#@)*Y$kz8a9LJ`jnv`ErcBpYbjPy>WxAfB44N`s$B>6-4a#)A zKvQVSbdA0gXv%aAJqFJmbX_~$`g3@`piI}Gi-o4^J_P_g-1pKom(~F>^nSR|fQ$hG z=)E*}D$vCcVZf__6VP;hBf8d5FWl==E(P+T>Dn}Hfjrzp(ltxy8YP9eU!qLc0ntEH zrt5z!#=R3|y0*qTXv%a=4KePcDARQ=yrAiN6LhT#OWadYrt3a9LQ|&eJ(NIGrfVcT zfu>B?Q&@+4Ey{Eqf^=xgbj<=W?z3sfl4FF5rgHhJas}D_?&Zl1jO_|O| z{{)&cox467_i2=G0%~Y=kpbBW#Nb{f53B{;ac@KCSRM?N;<`!aE}j9b!?lmjM_d3D zLDRW~9|ESh?$9}d`Dc+&Xgb&LFkl_D<|g`zTj-DQZbc0~4g?_1uYX@l;A;tdErG8k z@XwThTF?Cd*4% zPGosE%g0#0$g-AY-f#NjgV^nVrgXkMR_+zOJ$aY)tYWzj%Wp2}{eNaz!Loqm(--yo zBU!d%S$je6pUH9n%UxL3o@diz*_q|@f9U-Ovvygt+fR!1{!uK8S?02Q?3{jo49hMo zH)L6HR=@wZ-}UUla%Pc!y9Uck&*--woo2(aeC?EeTg38)Lj86pmhYX^Z)dU`!mEM=a;vT z|E`e1dUI4REca%aow8dcWVacX`7E>ZE2-Fdm{j`GAgU;KzbVTNS;kM(>7S}4%SJ3W zXL$(AOIV)DGCNO&%8K1?%W^B0Td-V4Qu@6kV;pJQzNDwaLi{DiX{!*Vvu z1uUOsc^!M)J$CyU%k|jnrW3o(Ww-ySa;h5d%YV4sF0%2rVe{9R<(vO-x_yr7+uaG4 zZ?as*a=l~v{T3|uU|GzvjOB$ar?8yMav{rFmfx^k=eR!JHZ0q)Je1|}Z2H?-4q{ow z?%%}neAa&*%fGPx8kV22YcO5%ad4Eu^hv4I?KmczR2<;mW|o_E_0SUu{@ql zFPzP%C2Ln>mLC=9?KsbJKFix#-oSD+%QIQ_V%dr1PAoTLxgN`;oU1nA|MKndL;7-| zX1dU_+=X`odGGv2!mn%_-e2Ut^J`4+X|gdMv-h3fU|cs`wqU&ez&pPnoI~Or@H_a< z&m8@Scqiig<(*$2_^E!z`-4h;-@;D-KT^p)75t)d3^A7a?$~(vX%SAH|IY6h_*o)c zb$?%i5R$_xguoBET?Rs-{FD)lFgJALOqj zY9f_k4#d#V;PTl6CGv30;f*;9<*d<2e+tnX%9=(eJhHVR6Dp-FlVdKl;0T&6GOcpx zAWQ_s=EgrLR33su5g{=t&pVq8A!91V<`W>72I=Aos~nfUP{I^4%qJug+1Qaa*gHiU zAWKYA<~HMV!qr8?||T-iMrVDRQ8nxhv_C&LJn?M z_V*4BE;mP@x6xNOB@yN_3JweLk=xJ`+omf=y@Avu%y*&lsg!-4_c`K&VwHrUB=j>_ zVS-Bx-g4-gcD`6m3X=*^t1-zKet`3yuY&a+KqNG$h z5gO{O4v|m9xq|}1$sv&kVW&KlsP+58f~wv7l*s%;6#Fm`IaShley-Zabw$3rsxMo}$B0y+Z@WRdGVvw5b-7_1 z4^in;SNfqG5WA0l`d2nhy6R&$Yr57W8Sd17fUS*QpW_aaD@R43&H{CFVAnOkwud|< z6zA2z@P5RD?jMRNRR_|g+%WTu!7xm<=ifgR#~fuwI= zMlv+mXEHi&-9)z|2KMWR$w6^+67~r{+=rBfoW6bg`^!X_9aoCE<|>Yp48cz1ysyL+ zt>C@g1Nw;v_Vb{z`3EXQLK~69ji&KCr>_M0*Y_%Hbd_WM;4mc6mD*fsT3d98behdi zPNwQgbA@9U&^-OkV@I>c+E<_MN2iw5mrHrdi>gogGe?)Fy-QzCK2oKquLUN&lT5^f zkWQGqlAV?o1t9T9=cJ#!x61sc>;%7hd-U$8ev%=m5Hu7IU#Sn`Vhi0yM7`{6M5vs3 z27PHN|0C&*7}&pG-+q77UK^2zt&NE3hlW!z%dL$Fn=Zg$SQzzRZ6R&oxmD{;fr%w) zy|L5V{uA{k^?|-Lly1r;n)w-gsUoz$+jrKDq z?w6ltWNp<}{O_e%QDxP24Ij7qvnLzb@ZWEzt{(SQtw(we=*qOZM*Q&-(OuU5Mm;L4 zw(Wl}&5C;bdsbI_@=3<%&b}%C%@*qF?)%DDNjfF;C$H+|SI5T&ef@C{AV;64pX~m# zN7B0-T#qWQdi`+K!?gxoRGrFK`On5ves!N!)$Y$Ut8&YqR%iKVj{Ugp{HnHpCZ=-h z+f_b}ZEl|~dEH$iy`w8Has4yrr|!dq`dhyFl}qZ2(kd^j&ed7@nH+w?&W|6-S{hZI zr5`^^*Iks`+N`pzY)AZ0mFS$xc79P>##F#A(6e4!Q3s`eOPSPsL`<9X6YqYUd|n`vUXc#U~k~i|}{E z=NCLRP#P5B^RE~v8H{E?&V4$TFEC6W&tHfqBIsW-#D&`N_M^vtfeE@8ni=*D^9=}| z`mdA)jllfl$A5tdgAhZ1!@l96xX%vyS4@x$u4pPwpNQuROwq-}HWD(B#)W&vf7zz~ z`sO?66Y+eZP5sMlT7`Ss{{5VWJ3H%dNNpv9`x9~%XRiz;_dhToVB^75bPO1kDybCn zzxfDRCFbjuV*d9Zu-=dSW~I3Q{hR*x<2J&WmqU4{_C@zC^h!w|f*GMm?4VN>qZmRu zis4}+#0@9yh2P@3_%Fw;8><^O%42|7XvgSBK6{fud0=4h6geH2#SpEm(vYc7umYpG zZ9ed4-_j5ckqihAcc%lb73*&hQV-$b?HnQvl(!N63Ye-Pfx5F;9oBvc{y@%P1|RDQZqP>j7=!e1#5kO#_@Sb-x@>K}xG z0EmBs3LifWJ(F^(T-uI=mkgOo!%4_mA&G5Pi~^(1GaRvY?#tRwD=>*oX* zBL1PF3XH8`%n??glq4A2-AB;hVz9Zs-d6u$ypljo1xcoa>c_nis1wnRs`~^gF#Env z2l>+tlpQTqEHB|lniBTKAnA(`azFZlh#F4^!ELGK{z`x3YnogVgg1J0%sgm{e@Jjp zAV#Bgu~rHZj2BTDv)8?apmmBjE$o{Pc8_j375Xv?tw#SMNG^Hrpq8)3HDRPzt693Yc7QW-En zsz5`P56~}tLv~QVp~1nE!xYYRp&I%+0hHZk8L44$_|{ zvWoN3Sy$PZ@`S#H_~>qhp?mpxSN;NmC=6Uf*^S^3XStNxCB|z9+AJnvpOf4>jGk`g zeuwDWiBa_ZQka6iL>WT@tG>e+gLi?v5p?Aou{^{#I0ScBKFBNT3ui5#hJODoh^q|q zRK4>>PlFd<@WjlZG%elx8h2E6zffdTP8D%Cpzjm(4SLj2oXjq|*O-6rqoc2(zm~w) z68KsI|3xJrqCf0YP)F1Nj+iwf1aR_N!_iVl-3AUr ze*ze7K{u(BO@ATiWl1-wBj^Z6MICh~IEpURp+6o}Fw~Da(fCaxS4)4`7;OWmkUF?i zfkAd~6x31MQ-x1#NOTW48tQ1N!}NrcO&yi)my&&`A9Y035mP6dIvVPz`@$)tj)po6 z9+QX{b;JYU@SUg{2uI-zM?)ROAUKQ*Rn*C*j+ZMO!C*Kd>WHbsr$3hz>3%ag6iP)M zW*8jpa5xGvoIL6jQb#=kj)pqokyKHKcmQ71(NKrMm6Ir_lT95m7Ko-!-Z(f~>IlZe z5m84l0gj3~dDPKT$Lm`-dPlbu*6Y zrp(mMm8qM2Qa972Zi+p^$PADz>WHc1MI8loRMd&4j+#2z)XAexA$2s=(Nc%V;qa*= zpbkSFkxm6uzu9o;oF+tpiA;1enoxuPxxaAyUWi0fCzd*2|Gt*Mmz6*heBM_nWZt*$ zZ&%v&RsJt70iG^zn12~8KrcvtPmk-M3Z(nf@5eeI*uup{rT4{}eL8_PEWqf+Kk%=G z02Q7HQy!lBM`K&fuYoygG1{l!fw@0x5Lfh=<=Y}ddR_J)qlg6VKr#}_l?*3t*mi~M z3{C&`@*cjSE8Amr3)m9chB8*5YeO%<^p9>)8!P{9fF}p>DM<+N$7sn!#O6-|h#WBo z5nn=ot29j)hQTNZgYg*3-f#(V4%;2y!7uSy=0;M1+=C zUY-i%E(kWbfoa*&zhSV)M;Cv2Y3XeZ(WQp<1Yr?Y4bhc~oH*&ypyBs>tq;^WA{RdR44SIYnUN)~6 zzB!i%dIv-Z!UF?>LVLHtaCMJRA3yx)Bh)I;AFDNo2Ky?lFa+E~8X9OlMc77w(KCNv z3|q6`Dfez8v}W4q52sj2Q6`tmZDqE;%=c~j^y*sa$WNr&?ei(x z+6!$Ry}L`T>}B1NqOZ5i%2943vy$4_*)cXY_P*V1r5~hNp7{^5>G=5s?W8uoHjZ*z zD|=h-?pAhoc0wy}X?GthnL~G(ozzb1Ah&h=8wpwqKcA$~*P)w(Pd5iEM@BBRva_-6 zZq?o1+uO>~R>t@`$QYTl+uul1$b3FUM>*r$O)9jp^7i$%MXAaitx(4HR(6iQGJ8fU zw3l}Oc&Ya4`azrO)%Cq5*vA-Q*wd$vxStdLJGr_I^7LfI|AuXPXJmoCf*&V{XW|{> zUE)3C{o=#pHJPQE+Dx-7L6&6};nOv3@fe&?kWiRVoT14m&G5?f%T#2BXR0!zGNUtN zGqW>uGV?MEG7B?{GwHD;%P5PVr9Ym@a?BzEtl@?@qSIp2)M+Vc*=adxd1(b{g=xiU znzYh1ZCY6xNjFMo(jC(a(~HwJ>80t~^s;o4VU)qoFv}2RSY|L8jv1m1mkdQlct&i7 zIwLQmAOi#cx_j2bxZ*fXTxlE;@rWAfh~pG-;c-!Mv2p6S?6|zR0_-o1)5ei_e!N+{ zC2}f?7sq?)@){K%9j}hhj?alNh%b)U#B1Y8f>DB5f@K1eAW9G?cqaHIgeRyHq7&2! zDG52cQqUx56QWaNQ&Up2Q}a>_Qu%3SX_jeBnkdaB%`?p_O_3I!7L^uVkym{_VVH`C zH+J|#`G~Syvcy@ctf(w(N9*(9s1~a|)n01CV7_h+{MANkzS>MJP+O`QwF~xosr}R` z>TGp^x=^iA8^sCYT#!>0a%O})xgb}bx_l{6i>i2j*Y{^sn>Ih*q~7i=!vT)bJcxpcF3bJ=E+ zYL+TUWl~*IJyXL|qf^zXw1mRbRJyi6+W~C@wEoS~1*m^fx@WpqdU(1jJvLpPo|B%Z zYY((7&~`xEfMr1I7&V?OP!UIm6uhVrO7JGq7Nb`Xx-?_+fgmj)kL^jrH)mr)j8@sbumi3 zOikj@j4rj`K*>rcgV zRfK6>={TOLqhwOc-%j+|c&2N6zv4&DE<~(J7N{c|MKY1RgG{7Bn%aQN;c`vr`?&S} z{T>L-&w1?C75{psZiQhSLzpmCXOY2ZUJVoO=utuwra=vC7?~K4!PEr!dPW%>B;+&o zsAo-+nj_^heB2}xwq(qykFiOkVdYP~f&sxHcresGR0>-&t>}IO6SH^w>6af?`1B-D zArKGf$FyuxSLndl+A_lKHbPrwEH=9_4i%fs>}~&uWp$a_)Uw(pykWz|BZVE9cKXeh zK?D31e)153)2M-hfur1d2<;pPSPii2-`&cFac~f}W7_I-WL|BKM#)2_`1{D2NKTu| zCCo7-29cclXb>D@ZX}08jvsvJkTUCW$1t0popbMpJsC9| zE7`sNv$vbtW^dun;dM!Kcra@8Z+#5r2klFk`QXkJ*}~_Ie-~%heA+qjs7tdc?ItHL zJ<+JfgP!7=C$FD5C}>z(?~>=$q{nR^4r`b5({RJ<@xN{JvWb5AW~beQSd$J%X59HU zXy0Fvn|_Tnd3e@r@6Y_e+|cHdTBh=*i5Y2ka{C%|YV90rQg6;9>uv}3Gk>;LE=XSG zY-sq$SvYx(a5<@=9CwYd+6Fwi?%#7rm@PjtzFq4Bb^n-_eHO!yxP_|`=I@{ z`iwh0z7=m6GlX$f4^j`DKfq6^RP^ZD)h8stx?()W8qc9!6_fp`Z&yVKey)wF2D(;M zBW;(uW{Fm5jUbF?4I`YDhK4mc9Nti7Fym6b&2Z=UViTA;b!v49;HUK;k6Fo>PM+LTQ5qOkZZ0?>(Oj_B_5^d6nS;8Xue08hV}4+ZaKolQPVpW*Z}Rw6o}@+XU1xfo za!)-g?b$wY$U)Ix>zIcN$|7Iv;7mNy%r=a>tZvlaqjx#JvDaty8rWid-EE#8t!I~f zYxF`jEVphqw{8&!t~Ik?`l7>P@3l9Ig4$0W;38g?`K0@bAD=DGJ!Aap<*%!HJQucV zemH;WMDr;NTz=Yq$vpgMRNvT!&z^RyZ~DiIMgc-?_l+6X&*lF%bxy65SGTv#*qXBF zzK`|c;VoXfd^_#>1}5dGX2B1gY`uK;rw0`U{4lKFtNkyg6h)5@U!=TTE4@wAAr8xI z+gRKxXE*RxMF$kSwMMq}OI52j?Zgw_y_Ov5&V7gaq zGPCFWBeqrP$k-m);3GZqXFI0luY(=-Ts7EbdDi(y?+_xy?aHB1{M1~lD}CmH6xpk$c1c*(DE(xj;-Kba@rud95X>=99F`FzyrEz^87 zPh(c^y!XeTvVh)?+eK~5H9o{kUOHo*%M^2|b86H4Y2J$t{9>Q_$}{+oPwf@1%j&(;&~|rT{c}V3>(>p_j)WAZ{tjj!%RHK^2EEqG%rywm>I zn}J7u{Bc*z;&?M@H#=qoJ-Hk3&|f4vGfu)P^@9r=ZAykFoot2n-R(Owc1*YKw)Tvb z(7{(~W#{9-Sa~~m`&xCE*?0?mWQ=`xN4xhrz~c={kDcD$*pqX#o3(A@#=D08WZa7J zplP<^4QJ2=s?i0`ANaRAK(uAFWb}Iy#?i`Fh%23t(RG1i%ljvEfo=>s!SZc%fqlNL z3;amj$|`5BuD{!qGU~@GJvncr6KagT>#MC({QQr(XUK?p{8NdinpoaiZ0p#$c)($U zMPYYsmp|EjZQ@_t3rQ{91`M3g-1WL3dSJ(X9sD@`(jeKaox9L)~XhocHl2zk%zc?$6?p}COoaE}@-mt-F zgKg6rzZ&%BmEY^Uf#a|BZ5Ux2|1k2>&>hU z)5U|jE`AwN+)_|x_^RV%Gt-wLyq&d^f0Es6Fk$#W*CidBS-mR!xz;F&U5uu2{;oTr z_N8MUUcKA+pxKcc+j^dq&b{`t(}I|U`AnoCZV(L;_5Gc()V6&y-QAk7N?|b)-3^*? zt>x_(ta#K##%bEv02eo5Q+nT6$+K3)wGZD`ch+}d7Vo;Sk-@=A!2n#V=r{ZL$Xn1a zOz9UKg4YVVyD|*i+X`)Lgxzgz(3#n=n>KXwKiSWI?$&B+$T#DgGP3z6)k z$daVJ5ktOPJE-RQzuH|{9~N~oHPmU=x!D&U?|tNucy#>0i&+`HHI{y>n|@%at9kP3h`>$&~q~?DP`v zzm6Z3I19zQI+&D*n3EwL`Ic>tbaU&iw&@-HYn)?^dE?zYA}u=^+Uy*1Zn)2_(^lSp z4(xp^-H1GMj@xi*+~W45N~fg`e&l?zTQA2A+oz6CYO-O;@dnF0dL2lwB{4Wt-h+LM z9&9XApY{wU9L`&wA!7il)O%IilhC{37WH{NJUGl}8rG=AK9@A&@Cn-JR(?~G$cXiQnJY7h8K@qh?l-^hm-l5A?L7od+XpRM*NeHA_gd)1To`o9 z<=g1R9KWm=)4$Q9Ck#Db5bKBi7nei6>E35nd*`gX!J;iTJ0{(&*EJ}`_4!>%*!`hB ztnZ7X3y+Y>Xyv8vV+(tCFux;s9_W8HPdo}BGv5j$K%p7*)aZ`QG^b(;7e zPg*CidRbeX@A0eE^}(le6}M9A#v3GgXmS<~8S`jm|8;*pe)!<}{8o0oay)+0j%quv zQ(~m~ifb`7TRghvR<=0qSgA?Uk8TCcPcI8u+3CB$wav=RwWH2XJkjRu_!hs$FW%c> zYlM%7Q~ZcuU))ac^tiN^JJ6}Cev@m>?+4!UOjU|^>v~XQ&wJ*cY$@~GoQ8O~(lKr^Wzr`M@ z^>gFy6W1pW)m}sm_a=35RwMoIv zkZuj11aIjzG4jR8txIeh%Ujl4c!_^m_9XZ>=b|%BZb#(vwx4;`b{`cJOZ<`WPY;0F6&|D>Kb*-NJr5katZVcjQ$YS4$8(YuV zZ`gX;wUzxk_#a%cY~|4<#cNw<)*Zi28=pDfZ%&;_R(qyQCM{NFJZe1sS>rhtyP{4` zN*N^V`qRbh-}OF6W_k}g^;^`@9J818LY5pz=$*wCO?vCUcGWe03V(YyvC-Lsy_v`w zHPL@REbqS?`_b=gFeBQ3qW@;P;obrrxVtpB_oFIKzYM62Vr7C)?i^kkxWm)Fy8MYXtd%Pk@9K-*Ermb@HzvR2`@ zS%umCdGRkc1+18O&hn!3sOg& zkKbNI@*^@D#|>5|Weg4Vd{WcITK@k`um44l?kH>i>V*f#8#p;!y1hMP>V@ATMhxR@ zu~vRJF0hVC+Hd=&FUzqmY8byb&^u=g_qdzDM7-wGG|{y&d$K%#GQZk_Ge3P#_@hOI z_j+<3T-&$I*sx%U%Qfw&Mwf=CEibvXWYX`d12LnG?l@gCo|aqJ>N-)l9S*}OO*96UwXyK_kGf;)=d zZ|51CT=~UVx>S2Er^#KnMY9gscm6hE|J~g0YxkdCG%BR^1LoMCaQXOgoF>Mm^-iBR zjeXi{m#^nGtFE_R%$t8=gh%OmMNB|?_n}44BKD`6P4n*fFn(=EyBbrQc^~cFGO$&o zw)T-Odrl76cH_mp?{{2JOi|kBxE=bgZNv6cYWEts_`9*r159(bZOa}uv0!8Wx2lNN zstt`8-_rgKzin2qp-t<;0k=Ef-t)xeM3~Jl)a^sK{yMo+C@ZsEoxa(*51 z>xyIAy?c+`aAHg@tQt0AL%qCCJd&;NB?TcCBbAVb^>=Z*+k=)Pb_odNL|>l?JW z{c7_VnZn`fH@|QaXD2J>b@$X>JYaA6O}(OBp^W|V(Bj@Rea>#K-8SLyD5J(1Wh1Pm zwbXC_eAvb-uX$qsr43JAK3nhTwdJC_#oZ%=o-%$DzUS@1Ls{HxR!rUOi=q^_>;ovpD_vLWfwt#Yb~&vTp8M zY}{0Blw71fdt&0Ky5CMwcIde7sMV4!E-&LE+mB!(xfbv0r-m?++X!9_bS-k}f9wvR z`nM64+T#Sq?48vH!rB!ZH92UF746MXSYJ27=4fLp>@Kvmw;%gKYpi>5Uw`|zvv;2I zojPlMxYvk|mmcI*ZYvmOrESH3LZ>%-9-shNVQrE_u5IsvQfHe@cLKKGi^{M0Q;l{}Pyg)bUN_(MocGcT>z{5I9=K&&ooOnk zjrIS^`tHtS7tUf6@9Q(y_3s!sf7#mHgUVVtf+W++o!?qAEqy&=Y_B;yN;z<3-0g;; z1BzF8>^A5Xai>Ak9e@6DINiA9w(awNhSAFL^X5i}U9n^$E!w?1MR^=yqy-v^MT3g6 zrf;q+xJ+c*H)uP;`m_kW=DTp@20SiKH)j01gYou?4ikO!?UimIqtdZFftA74BlmCL z9-A5=&2kOtJz6wy=i3F1Jx6RGd11?)MV(KoBkL~5Z8aaa)n)I7fr)I;+?e?n`ZdMZ z^!SriuGQKwY34sN)N0AJ$NdxDX0>`ZhEjDHSU|sm)D5553mF?n8{C20Ip|+%2`e`L z9~lc?VST>%W}nzUn%9>}hNXB8Gwu0&+T7Q@8VGwlT=2_atA0r?6D~E}@G#BS((d=~ zJB^+`blAd2d4Fy_GqTa5pSKz1EE3ngH0#x*TI!1vdHH`{$qh3Ti1vkdS>ti(!Bgu? zefqx`+ltBLm@dqY+%ZCZEl~dExtZ}2CU97f!bT?*%S$-G#v^?|&F2Ol(F@r}u| z!#!0YW>KSFrR`_>sHg578MS{}acTC7jJW2Di)L=(i~0Mm7MROk{$Twoe~@qEyt&gI zvLw6bIR$X*TWPZEHIMJpuKZqn7VgET>jy5{(YH|L!v_Wi6)k_L5vS6~il@hSJnh~+ zwom8yow%iPem*Aabeq31F6>}u)7h@O+|kF@*Vf9$)*hdlcJpPd7MI3nF77$cV#j*X*}cB$=dbW8rwaLy-bB9ds=^m0)Dh=u%y=?LRh+OkqsjJL4HgXX5YUwGlsMRa6mUk=L z+GqbNTzP!>?(??0qMt8T*SqSwGG)yRUd*7d-*>Ne+F*Rs*N}75{^`Y&%T~*S?>#;= zYKA-^to5?-R=Zd9e>`S$LW8&tLq_E6behDxRkl~=FSs@>e~eLLq(-smqDhA#hGVub zZ2K1{B-VqezcHP&Hfx}zMQ6d&bfzcsoY&TGe~p%IjB#)7PvKiy)JS&g9oMmmL-SLM zo@L(W?6!*@<C(>E+x?hb-*woo)G_rQJUc$Sv)$9Fa8{2fAsg+C6kwm>drAHM`zC1>5>|F>qu%rHz%2zX}*^s z<0r<6I~&Daa(q&E)WygU@yh!b@0v^v`zfd)XS^zQmWkk;>v{i)HY2WU#?4xp_g9lH zX*oS5x$>)9{p{kee3$&2|5j;olZ&Qz7$&mjGz^z}mk+hni$1Np6c|)#EX?evf8<(# z;c)%6pyRu1!N&ywl{{W5uaCH5{AqTzAhpSf}NsZj%o;EUv9} z{AEpz?1JvUb4-Q(&n&1LA^TxwjF&|~)&|#gxBb37eP!*at;SsrX8p0bbLO;KS?5-L zbIhxm;cee3r8XnmH|%;V%}9K5o72t-#rf6-VQGGkj|V>PF+Q&G6X)F;M_GE1%szaR z+NZwN8PSTe>z8ZR{e65yl559Xb@!=FrtXXB{qWWG&SUwlhIv@VPYcmB?6K4J+v5B8 z2mCnakLg>d&u{)mpDl|glr9)Px7nk(uHTeI_q58i^*pq*&l{UF+YEYd*_yT7apvju zsxD96Jbr9#-|k@dAla-@yVupvXxet}@h7_t<}Z0J(H4%}zc}WH+`QJxb`mqopHFnK zbZi&feXzsH8C#ZTnzv2f?0ZkzYSL9p*Y%R9YwaeSX+5OR$ow5+`dS!hPfZ)w_4l^d z6%*=@aGtuYj9kr4=SE5{6LrqHfFuPl@-CXD$ zF4@|3?zu7R#_ykMY5C{(nKI5qNV)TiZ0`HAm zeelYHenV9L{YQvZ53VqJz%Elmx2t!Oj|fk-U0}zIr2$&;a3x}ftB0wF%y<3rR|YtJ zao%E7A1}WuE_M>eR$^nLyV!kOesROa4$rQPTlqFFcKyHX<-sT7S2D9VQsV`@*{hk^ zG0f~AD+cYX4RD*t^es=0%V});spROzTNdmSiZ5II1EnDmK8jFlKV={zs)&Hg*tN95 z>=5sMuSS3E7K|rt`g^Ge-EZEMx?elVb-yR0zrM5aM;MAI(pS)woAAvp!R- zoY-b4nn|*ytjgR%*KWKBpWB&_2``g$j zt)3vC8~yzvXYuIbI?HFAZZ^dHaj*W1MiyrOHTimK#6ZPLG3bcWOcY-cW^t=}y03kpu&sXQn$ ztg(sH$+AymUz1_Jwez+OdwO-*Ec4(--w#ZlazoTfo^Wu&NbmUvTlhGvo`2>1#xSyVCDSJ09S?0;o78vU)Ry*#Pshh^oHnh^OP7_c(q0W} zt9rVjZ2#n)LsnnA8y4Q|-kol1BFu)oJ-e-~U)as8m#-Jyt*yG_-!tnqbDuYK>6I&C zfj&R>KDFM%ZTNoGm^N|Y4QyIZd)Uu-OW#*1ClV(dh>Kb~<~t8Jmw|cxkFK3E&RFF# z`LBqL2lfUAPC7a=)THh-@vp+jMvs`tMh|pj2`m587cTvfLN$+sm7X-!v#;QjM&t6) zs-`?4M#MS)`p8$;il^gRJYgH2pzX-4o5`{6``@-*YLPm4LVm8OOYOeiKAfVqC(S$e zZ|3wWwP5#;j}HzSIL9!vVWWC6HwK=(yP*EQ5m)z=zMMB<#;;-0E-nWKH9sTV z=RC@`iV? z?r%{?G%0g=aId3n_zms~&(7G|H|4vyNRuH2jBc28gP(0h{n6146AO3= zxEzBIM>irly=&AiZ#H}ka71#tVAKesCL9a2AU5i`-xwQk@YT%wLnA!F56iZU_l(f2 zE;?-FHL~&i(AeK*9{1~Sar%mov!9#wZ4$S}nB2%i!<(3y;M(JWJ45|omV@>GsqWmP zq0Ij{J}$YgTkJ5#J-6858Mhf>a~WD@6&bn5xFxNUmE3uVL?JTeR^ygza*O0Hmr`y? zHjBtLD~xL*?C%*iOTXQ1&)MCxyZ_9Y=XuV2pL4$FeCPB2yg#qc_lrfNEdfU81;Mm5 z3uLe?6TJ72qp&)Fo)StCz^b5dx_a0@h{BRX*xMij=QPCfS$q)d%pi3;AqbKFD7O52 z#5(XZ0fPb15S6R~xrO{3Wf}2~1N{2`Kyd1YevZ z$X0olD{`O6e~U?E}er~F;;U)VO*j}q&=O22fLwVL_dM2a3Q ztcB!H5Nu=Sj`k-47KH$gW7aO>96{%^7l!w-On0?k#^-+jEnp@YlA+7?bYk%U*jgiQ zr(kZUH)nnzT;ZIMC-K;2TaZxL9{+XRgNB) zId~W4eL$xla!O8&JZgLU#20o758%Yb)Z3@%vPdx&dn@m)(GR-cP%w~MGtHM@zc(7K zSYdCQ)N7{s<7``^bL_>RTQcQNc9tSt53;DS6>MVAi2hR-*kI6 zn6fOmitWC#@Gy&o6rNdioj;FWJM?}@aIzRyq(oxWadK&)CeXn`g2Pu%)yymX^mh!A z92el_#B8qSq;B>DNx_XzTeF{10zzj7)lM@H*mzzOV5-%>kRUnR8VuCLcC;hZK9X3NG9aqY(;zkGvp5P%X0RX<{=? z^gaKr8q?S{)WoG%igLU6F4oEuq(qmRau4=M-!IJRB^70Br$25-Xc@i=UzSPA;Bp2! zD?AnO0}g(RiIMWiN22)_C3$@~^{YmwU!*;2G#x(PZ}y<0AE;ex88dv(R%3JM$5bH% zR=%>`e3u-BjclacLt&N@=(-!IEW}jaqte@H8`JL@p}!jnI6Y9+pl(~$fK)SAELwZ^ z47E2K1|xkOeg4_M^vy;XB?Sz%{{@ZNtQlaoF8`a+|C50#RRJijjFQw%e=}JSdm(&U zY>%Kwd4ughX$lj&TEE)#shF=PpB+6D@H#K6x^AGFUUIts!9Z&V#muzUAjLA7 zY^4_wQ^NJx?2zy~jkOGO5iDIZeZTZx1#Q9bksH(gZ5>+P?sw7;1Ys%h$NeuN96TcK z4qh-)Ph~zh<6t}&$NXrN9f|KOOJEG;$;z)%nPi-i^O7mq z5{|^gTeT0o;TsUpc!G-v(xrJ$L$G^3fPj0+?oVhjxmRSms#1+O{01lAdR5LNzRW&8 z0PfCy$%`@lWvHceHir#gtVH)7h)RZ)rg>uKOHrtxwGV*`3C$Gmwh}@r9|j>>R`RyR zq5obq`~n^rT|2tN@Bh`f^(Q%SNNGSFFjNkFGrB;IKl-nVF26DM$5#KR_?iv>A8L&; z@=UvaEfIiwSj9NSIVW+EnHIVzC%>8hij>vf4-(>D#o}itgGk!Wdd!k$Y`4Z=RfI7g z0?4>ttQwLhVaYx0PR186q^oziU48D!;HNj9(7MPiT-=@hL$m~DJ@621)J*~nX6ihv zOhJWI6aywxGYhA6-H$UrZhtU=1F#y|S5S;;19#h*~%)GC%U7>`Wx|l@s1*v zzO6o-%5R$V89bpTkC304AN z+210)KWfzY|Bk?e1e1-9KxK@c3Q7m93!s#hm2}Xuz}`*CF1Ay$i(qyrB;LAI4|6W{)eo>2p2vtc8yg@re@cv`G+=JxrP7aaU7PPvaj!Tepmxt;U^iUpDQk zymUBVq(qG;M>WN#Uplb8-_Q+YQ{wKrb9ut#dl!2!ZY)Qa(~4L(V$9y)vls8`DqWt< zsC~|Qpw~VjuN_$$Dkr)YvzmL6Q>w!G>Nl1Q`e81r#6FWOf;88J`LLH##5Sh$

ps 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�kt!$^gD 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%0D$jbFG4H_l!(j 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

v<&Av%WI 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℞P*qmts65EYDi!kEy&z-%WGo-(+((`-GfV z)p%=T)7D0pZ`%f6Gf_~YmT@}G>6&e%TiUp#rlGFZ3Hcy&UNL@ysY&ynPx*%i_hEog@)FaE#CFMO?8dlKub+yYYlV5Tyq&s>+8WIuB|n# zuEt)KG$8lVD~;+w3S&}EuX%Z&|9 zTTx4w9G*5a>Wr1mR%{H8t#x1Yxi*6Z!M!ewoIdbuO`|Jcro`<7jK!b9Iy@&W?oTCL zg><%Cux9@(aJw1f`z82YYI8a(5w=l`_muWgr0ZL5b9%~c&c4eLKGo(tFd1wAETmm* zq+8ku>b20INj7IXGe*U$%ms1x7t(#hEF+FWw4;GpUrfMXf;5vxW%t{X-Y|scackU(( zL$!4^o4gwdM{^;jXD$cL=`C4Wyx6O^>&ITw0x@$y{&B6Z+k|e_1QN^P0Fr~jQAYpJ z+x}cCRZ9JahyI!(6#@_l593(5ZVX^z;wNpM3LId2r>&aH5>e5 z_7enGJcEfqjd|!f`f$pLB%S08t44vE^^mcUmdmN(ETf@c+Z045Z=lH=U4W_W%YvJI z;7_7V(C2ExoR4V$^2a8L^sHI=`2|S7s6~iRm?tosP#>2QIO6hWR^(rYd;Ge!Zbx_6 zEN2x{$u?X3dfwp5AA7w7a5?ct0hcfFaoSo}>x;$cW59<1C&cCthEJ_WtvE_;|7iFl zU>}E$8(W%Gvl3qlW;3^e#HSGgVk@Pa{D#V_I9 z3aH?f7|&nRT`hGqEy^)KpXM+I&w#jS{LJ z0=*4Q&D$l8vS7s1l0M<|B%6}f0IejvDlLq6p?6t%89tXi5**7AF#@cT@~Q>P+y*Sx zGbq}(_-bk~qpi0JHA?1#1S6S)L=7uCb8KAW7fx%Jc|h}vJy5? zvnpiSsKm1^iA@8OUWn=8Fs;*WusQSXxZ~I6#<~J)h68wR&c+%5cjq|hi12ew!#bkW z=G>34;}nmd36w4H9^O|N>jmns^k>LPg0%ulqNTc6$RPPb%0C9HAt}!oQWl2N#+uV+ zTe3R;dRiq-A5L?2EkHeh-#Wr|2A;A0>B4j0#lVm1f?pAS_tzi}Y44{v{Mztiq~&Gs zeY{^3Tc>ruLm7S}e4R|+gR*TG#)faMX}%hLeO~1aXu&9HA3(sFz>5S#OCn)5&%>U{y~h_xa`gKEB-&A1B{ zS!-Z3Rtg(id_FJhdw4*b>++E9hcyGO&^P!RuRu3xX{p&By`ok<2ZJG?xYY1MOFUlT znY0|}T3awB82UlN#gaeR=-bwe)g=Z;QpicMl&4C4(I`M#*7fE!y#=Nk77VqaSldNG zFy}`vs}-a$Dr9KFYU^nEORGl0-~5*dMzg=!3(4Bc;};D#U;?$U#LUn@s=WAlV~Wsa zY=%X2!Ez9=qm*^#T&nW4s#{U*^(^sJuJ*XSSJDWNMuMJ%m+$5*V=6<{yGp2vM**)= zN9%FSbgq*MG9UB{BqSL*jCF;%oO)pGOO~G<_`-LtnmX-c|}#t29iO&B%sHq z`CNz*t-`)DLWpK5hY!JOU{Y}w$ON}a}~ z1DfGl3Z*KJtD}j+t4U;v|O4z1Go(=$}6+#jeC6@Y`>1KU4 z*O&DNR8_sd8Qr64!v@mcMs?JvyrfI;LN8oHqL0x>HZ(N^H#e$*J#KfmEn;0sO#{v0 z+o8R2zQLhs^M?Q>P-qN`?JD`#vPjxY{^_N-HlZr{W(R0}1}Y_21^sL(fg0~me5 zd89vuzD6vQH@a9C<&trvlwqxg55iO{H8Sbyq7jpgQQ@w1Eh}I6IdA3CQg?Y(r7O?S z%Fqz%6{DNA?AB&%h6FZxp}=J&>I|u{qEVToVGgzyNW3d*J-XJu48v00W(XTJYRe{} zZ3ds0*8N*pDh7iS$A})U_*`nVS}_V?FygebP%ApNxbTY2Sy&k3WI9uTZYCxr(T;~c z8YhCl6l_W&H3`^C9w*&tRN3HCpLYW|E+CW|w2vU>zPVRljfch!4Rtd~Xuu!y015Xj zAVY<}d2>@^R6mikCI)pzM6tQZu3sy7JTUd$+9n@1nLuUVwmP8}=Crb#dzs_q{QE_! z4r$7H?@*15#tcN0tFKiriXYU(tHw_viWn~iP->H)Wxrr43fV-8{}xwJwyJrE75hH4 zH6gYRV)j&|ij>H2-f`1sPXZ9t5GIr>NyjT8Le#a)9~G4=mXV10GA*oYsKzb9 z=0Mak&wwCGuO(e@9BEH-km#C7D&EzdYKflaoanVNSQ2?&WMtmmnbom$bA?(xbrl!L&NcK{e)eU^dN+f^5E)nbiF zoq?ws6FLk&9$Yi?HS&lD^l0)RCZTEG)r#JNDIc3I(Y+tzhnPL4@G@uvG!hu5d@WO{ zMpQqD<;}Q{K1L?0vra>`Uk)T_^T1j!#j9e7p9=!WO~6*UrR`eb_LQ%v^txB9C|^+l z#I}>ptQ7)r9fl)09-t*yZjaL-Og+-UK>yu>jc+JkF(&KsQ4`uyZvW}1RY)4httP!J z35su3l^&XXKNk8e<$YyE`NXA`N3GGDn=t#KE=m_3i{=RF<;;veZ>kjA3(PgP2j;u- zW+@8-87K5htc~K>T4i4#<2Pffui4~tnX~HSk?K!6p;2~)dzpJ-rK_lDX^Fe6a%tsi z?A6LJ4)HFmTCoB#Q}SA;5~|7`rP$-K6*o6G)CsLS4Nr|!m*PMGU7%$BB5bS5?Q+{f z-GIW=)I=)_q{A9g--LHJZfM!w9N_gh?MHgC%nLN~*1Bz36Ko}Dv58rL%4c03L;^8I zuV5RN3SWR{9*(OBu5a`OXlYNNY|Dd95FM7XKm!|w4L(8)aQL~r5qGq=sMJE(7=Wiy zAtXS&vd&oAYT6H5S+TT)@L1t4F7Z~CEi5lBU0PY`Ho{CH=sE7<%3>&uyp$YtU9q%y z+0xHb2*Ic*EA~_@DX*mG|1x?;+aC$~{+Vb<_1^o>Y0mMuYiaZ*{!(%CVBdN_;_P@% z!LRST;#tMrjkpUDPRfH*h@)o}_b}ovzO3XOOUL_c#F@(*MjU<9i0Zfmar8`o$Nx6f z=>TpQFl_2gsywh(l;^%o>h5!5l1vRAHSoB zqi01kJjVQ)nmH!!e_B9uEwuMMFV1La+JyZL3`lhWp~gK99oYQ^LLWEfL+JNDC!TlX z*)>l*llX6fffWJ9Sg%kKdpFl?^WM5XO0JxlKWjGS=>rTK>?Vkw^FG4!RpR+xdcIOT zpT=|1wc^(Y~1)??$XfFJwnvHG@=@k}I@nK81|)vtu^*g7iQ zrLKt-A0&BtO3Gcrh|omGD7^gM<`y5;Zaz_N{J2=ECQ4Vk-hb5%0twdB>Z<`$wG!n` zL=h9}(QWDX);3AF7MQh7u_)VUX+eug-)1b0NwiWDw%Cr;i-`kkET~y=W7AGmZ3|3} zi-TfZ7;~kBEC_0`2~`E)9>0uYsmr2uh#|uR#WsbSxmi)>TmTR6ME~d#7rW<3;6XqYG<_YtRvF4Wi z7`fSsG+w<`+-UVCH40gs#e<>ZVqnakF>}V8IWw=HF^jX=+}p75AT!Nsv0iY)v>l5w z3`x}MNqZ?4XmY{D!vRk>Ek0NbV?{((jB?*z9}EO&Yad|~t-4wfX`zRVi$T|h!FcXm zie1{+iuJ|PT4nkSB)&jv6D}TWA#ABOVyEAXgQW~kE>W}M#&NNwM~x3eQ^{e_llz-$ zmAcMUPHdVuKpQatZ-=sv@&&dv!kSkukPPc$FD>e$RFrJaONtzG4F!H!N1t;}+P^~i zGinWOTmIxmROjBz`_YIzGPlYbY*g{)+`v)v7cX^ieR7wTGsj`z^lmW8 zg^&`l-@UcmC7R2bmO)Ph^eySA}YxVP(rq_*%lJu=yRmS=dtNMhov8FMp zQ)1(z)hpI`tGdNpzFGBR!=9A4bZObrO79|QbSjsYm$9viF)T7<=xnWLkfctPg>HgT zv81Z9q!RE|{1XRY#!}mQMp^VU8Z5C(GFE6r zQ(X;@4PH7&qK<12RcTeGw$PXpTjtJ121z4@*}22OsQ_P-n*JC}ypm|A49sten(K0Z zA&A{6+U`(zD=fs_l$d%A=KQ&Hf2m*751rc{KZ_?sP1fAWIeMGWvd;Xmbi;%e>O1_Ha;}$P*9YZK^1SGkBKtT z6@gwwho@j0yZ#tO#Fnhu35`O=Y)b(oIHiKF`nWk}Ed&Jau`U-)a!VW-s`B~-A@;&(KYObmdUIDz z?G_l8NRopg&F2t*TN{P4`Yw@hWS<# zh>^=Rb*osK#8g(mq7$CnJkcmoq}cdPs3WoP8IKKYu!fOvBs>)j>0Sy87{Q@iU=fDl z6#FJ*&eOWRaf8^q7f48pn5Z!@rVQjC3+;W|d>e3Zh|#dZka00!5~e%%H82xfFXtSZ z%!&isA4y`yjf<&8Y@d`iOk=D?!u(^dg%wYl!O_CZW3nnSecqfVPL)PUKjy0G`;Ji@ zN7MRPkOuN2C5xIg#@sjg_%-v3Qc@(Mk%}J+eT=kYZnn5})0H6!>Q3=v!H3#cq@}8k zs+vuR%el}vQCpEkULqPRKBP49>P2H2E47tr9+nkygTUod2I&^tk&*D2OLq5|%dl3_ zkG;Zj!4IoSg(BZKHq>mQflAB}nrX(S9E45Gv=vi^Nv=?+jg%%{(~X>d zRJ2*n*zm{78A6WqH#OZVs$r$25g%KHkbHB3fsteLfuPt;CAl1`rF?BFCP0Sm2-q>2z5FD>o6`;?!J%2NYVz9ChVB=t#RiDal}6cWF<2RbrXXeqJz;15mUpM zW+r9Ph>hE3^lRFhA^>a-ezkQ@N>HvMwrV5&gpx6uGEf66#W8G+8B+`>O@^7$gMv7Q z7gJdjs>);IYQkFLE61ZzinFGQDi!@}8yP&pR5)?@RPSh~HZTLGsm^qeLAd)txL}f~ zYVhbq_Q2C!jpDY29P9AA{v?iEPRB^uq8_O=8){l$`YE@}jnM&|NoLbjYLHztp_X(m zD5_l5Ug}Q$oV`)#bBxdcnP!R_7pd8V?WD9h$M6b)n!#J3Q-3pL02;N3C{WLl_aMw0 zpavas1-n6DSnq=w0-Yry7bkQ=iGac^la(QS0HIkPTgQnHa$<&Izp@E^5yK!&L8gLg zw!jjUcUPGi2qMo=b-vc=E;%>znw`$uN=+6}!K_k)yM*YgA2Vnqjj%IC?bI*gYXhxc0IuT$b__J-J+Ys#Um8SWni`t|>dGvgk#X-pEo{Z0XdKh`56A zXyQ1OPEf2d@0zDr^TxI@x&BG!R%#!f^~~j~onm9-d;~1`97>Gn0RE@xUu=WUof`!a zoXRBnqlE(1c(})}N1}~=Q+0qSwQY?$5-HnKo?n--Vh?T$PFeAMq+XTMn2(X7^|A00 zynt-FOq8hQ4dhFO-^GSHcrPT~v=~!9w}4NakmEjvD(qbG!V3x`x7bG}FJE#^ah{te zs)caTfe;!*&MOr`rQ$RMLJM$?f=r=#518`aoY%Sr;Zgc;is!i-JyIAIVM6#q5NE5! z8Cl)srYp+hrqu%`F#*}U1>rUXla|oB8&l;BS22&f%n}>nhOucYEuE@XV9Sa;)@*IW zFKm8gy0muw%xep- zn<)*asIp`Hi_VB zijv591cC|(hZbD;k&PvIyNc~i#1PjAolnrx$uTcMWQS=vW*H$M(+0Jg0b#FCeu#wB zP7@{|R@4b2Ow1uhI0^kMcfrYV1IzDN4@rQuCRVx{g>1BvN^d>mf#yqHe%H_J$z4ON zjf6`@xtw673NM>z!(fZy4M4ynSkX11K0v%6bYI|zKn>JqgfSo4j?-o7z+6isOi2s{ ztza~q=~tT+s&cvrEwMEGEs!Wvp;kulm%;`a>O7@5l`D>I$MBJ&46hGlM?-n+ESd1O z?i7s7cc%WUi~A$@E1oIz0?{c&FB7hW(H&Twh+x$<6vqNaw8^YMLQw#1n*dAy_UJshKe*x3G$p=07?}K)PqJaxpC-*%uiL(^7i? zMp6av+EqE*Hl>8vy>o0UAGE_5@g+n}D-mK*wHi`t@;?L0N*^QE3i=c(wd*`Q#>}78 zH^?_Fr>4n9t)609&(Qar3;YV6ZpQ>3_T_SsM7+o@J|M6vt)A-k#uP9ErYCBnLr>{2C47%&=0AH>L^j!9yY>kKqJE9*`2iF8Xt${w6;tJ@sh%s$58 z!_UYb7gb&7Cz*<21A4Nf3@>Na)cy9U_H+o_U5RDU`*%V>ro!La}?N(Hl zRw}s#`W)R|lL20JCKC+MG-z_|^ar;!@t@o-Q=1cn3vW#rs2ChGa!aGXLl~>6y^8by zp-7qd#WWGcA27%kghc@OaAFq*b6sD=ViG4kTm5iE12@+2euI%jpVc=)uS=h5ATE)H zmLk7$jzA-fiO}*fPB~G<3gs8vyqU5ZBAPmQ3h(OzTiu}v$$VrBNwp&{ai;8MTL^h@ z)s&xrlhJ+rthTZyp#_lQ5umd`!r({5%WR6o`VpKtfIf!@UNdrdj)g-AafvuhM=tN^ zIF%UJ%vs$Gm+7tSX*gQ94@gjMY!6Q}6F2vnJk$>RdyfU1@PPIGv1YMWJ@4ms_;RK6lr|o0bN)w>M!`1Mtwh35adanGt7Nz;gTmM0N4`&N%k9o>mVDz7 ze3uM+826+2WuHlNPKR$@5Bb+sf5^))@NUM#46hbx)L3LrGh``~KoTrF%Ax0YWj+E} z)ZXfk(tGfmC-bOqx>K1R8NLqp-T3v$cQYI_PG&fRC@Wq&JS<_D%OaeflHulZGFFIo zBHt_Kat7nT;Ibrb9rCX7Q(1}foAIP{M3W-?%y=p|ga`epG*&!_mK2u{yzer%Gs1`` z?w4Ut;ogT|Jf5Ju1cUy}IGf?!jWqFi!cKmREq??nTZShpQ)TG`+!6~6YMWIutQPmZ z3Kr7FYhQEzqKf$a>=8>DR6fD#lHsZh3SWmG)nPwi^;z-}O;5`(Cwx55#;*-OYd@pB z@n~v>cN%Hp(R3C0j<);}tOgk#ugrOs@%!0BabQuIoie-!cQgN+;h1^B3P8_Kog zJ4R-7iALEnY&Pzz@QX(y&MPyT%QC||1^Dr3w8K&+f#Eij;fczW#o6XyJbuaogZS>a z40{!KH7`-xczkEguV7EFvOBZs8%m}>%0FF(&6amcr;?cKZ_TUHR3d$|1uo&dTZZk& zUBRZb@o={0SFqcV{%9Q7eKKs&1e?;vgKf>PU_XlVQ{YcYz0;lWo+rbW;NBpgDQ!I1 z=KLp+hG1C7=>3*5C=J1SNQOTm?U-rb+{|olr|n+toaq3ZAgD#9M}hB*i$Chlr|o0Ykmd$0MZY~ zft^AAtMHeNyTY5&#)ECmuVBB5^pz5h3L~2MWmv!jo6^REZOyOXI96hP83*=Z8Ft(R zo6^REZOyM>&q4YO_~ugYbSJ!BGHg2TiYAmc9&BrV1*;k9{c&LLkYRgGuqkak*w*|C z_CrX2I1cPy8FtD9o6^REZOyM>pFnyS*7WL~?lhLqmSH8hoBKYcCz{cpx&K&Uh!pT0 zh;g7T4*a`i*h9FR*B6vN9(*ed9X{~sv%shNpO#@~<$W0GRG$CO-n#%qQMLc$1G_4z z*{W!G-;}(gbKBY3-Pv7GRJ@~j%P2rDLLv|s#Y;tIhNVTNMQKH*rlmz?YH3DgW@d$# zlu`?CMQTQ-MQZl{JhL;fAQ$WXzTfx#|Gths=j=S^Ip;j*oaa2}InNnpy!oz~SEuox zWHia}W9au3Q@@PRaFK|;_bxF@Om+Ff8D1z#D*H09**}0dD}r0S0D2 zAakIB@eegHTLH;~3{1zt24**)*AN4f1Xv6B5imB)z`O})H55Dm)&jl;FvCy}AOVmE z*bOKLn1>sfaKHk#h6hXpqywG?yb1UQaN{TgBLf0P z8<;S_i-7Hb?*JD8LE#2wGGHFyIlyMXKEPQ(yDDKfad_40qg_=lL&YO@EYJa;QICN>w&Qmp%L%g zn)`mm`k+DYwDEm({qw*6NcwW1J#tFSbUbhsgjXM@WCkUsPLGewo}L^;2St(U7jc8| z_Xm-9tY%cufQg}e(6G@1xJbO>AufJKwi$7fw&3eLtNQ(0$%vMtx7V zet+K10(U=mDToA;WjFobP zTjnmOq4HjrE5diJ1OJy&JN@B0@QGe{l277AZsFO}Mtbo~brR1+L-M%@?WX&*I%uez zaIKvA_0S}0H2L+=YyQh4r}}r)kAYxquE~%O-H0ryUOaIp?2kK{9LYshWHtw zm16=eZYwXn56K^_V65pruofB`YZJzXG6oEW5$A>#Kq?Vfni^%nI3-P7|2Y_}?2 z_48Km^yoNGx{lLh%1;;dAJvtQdh|Xx0iq>7KI%R7VJYq#`}&x48P5bQ<(u!t8*bII zDE7W`T6*fgtkF_k&0AHqi#l2#C!IHHS3z_Y-wR!yxw{cDmAy^757+J|Atrue5$imP zsDo&0UPMPU6!&d_hGaUl9-5kEQ@J&9-uEQibX_Z5uuAO-S{C|LTUWQ^UhgkGrZ&-~ z$5apL&al>=b#)P9l|T!Kj>dz4+DiAb7mbeV>4gJ{dR=Q9h_{^Ko^he_&(u;D73j^g zT0tFw-iK6f^suUOL;b6_P0#L4Uj%x>@6hs5yEE&^r}IPf*V{y}rW@)K?nf$#)Sk)z=t&Q++pS?P#yXX2jIKp4xps#Fdrd zhhE=^w)MBq4E`yf9Wm867%|m1T5H#2EsjP^^(AWeDTpbbeo=k&{gtf^)K@gfQ~nOb zRNohfNtVhGQ@@?p@P8tv`uqlaViRKGt1V)nD=)oX@#gy5nWp8Rftc#O7cteFkC^IR zso~cnrg}GPaS>vwcL!piD=)p?bq&-T4LP9v35coQ6vR|-E@G;8iH3g;G1a?9iwhA` zzKw{1uDtYm0|V-BuL=D}`P*v!*%>j_D&KypXLQ%?73_7>bNJP3yPiz>2>d%3D{Y-X{3e`;Sf``dyN+P5gpiP{+meaA-fc10(w2O! zp856EL)CcKl^xb_TS`$L^%wPPxmE|$vF^4IpJuErl!w|FT!R)U_0roPtld*2k7}5o zG?b_FlY`$>PF{^V^s=c;;wL(|il5>dv_wOfo+ntn^8_U$8s>-TRK<@8zo{HEVy|K< z%O)K0Q{dG<(KTrGd^$fwU+Tqw-ZjuidzD{w4fF+G<(D@=PyDgkH>$+Wo_3OKgyOgN zeu$p6AzI>}V%j$p1y=1F2&bp%dmGySeb@otYeO3Cr_SL1)t0`MvR{*aHxTP@sym(P zc)NN1=}ZIZeVf&tzYO({xuNcK+P|J|a&@}i4n5Y}OM5%nx2pZ6xIe;F-BbT8y>hio zQaQiFj;Hqej_~NYLfjMn0}W4UBvUk2;w_%Bq4j|FAcW7<#u7i(d&14H1-G&m-1b^< zCu+eNyHw2!DxV;*7F=iz9PvC3JnMPro_=R)bo5)t&99M{(v|?{otJ1cQ6BZr$LKHI zR=WtVBl5NWqZp}dQU?u4>bz*Yb=lZ}x@sfTWrO(HUJnh)wx0jONX-t{;zpyYY$u)OzbPi(O{1&U z_zi(|_XY7yeL_HeLSVYpqYHvj25zfe-gC2kT=luj;hy@R#<2o*Qkf@e)J-_=i+J6N zdc4b>wWzudn7HujHG}BsxhE=X#)7NUsa#6C*4VrLUuR!ko_9K5OS;~7)OKoPBKnZ( zr;F|hvTMQR*T7LZN{{M!2^U!lZZ2?o*?RrUfg>IWXe}fB)*5*!Pg#w63Fp_{<9qYY zdl-4C566aIEenLdVT=dg5;2uQmmYifS^o#B%gRvPd#8_{S3Nxm_ulEL_g7C}hK3DG^!kE?JB5)MqOjkKh&-bbqVznkdlmT~45)ADThM3FH?^@rd{bMB#(L)S zF2sb}kC@u>En@1MGQ=eB=MdA_Uq+0m^75Wjq06q;qdFm#O~Cf4+AjvvR+o zPQSd%{|w)cCY~uxk99h|{5-Gn3$*eJud)1*nGKg;?o~e1*TcUFvCap@-ux4v!RRv@ z54Ei|X!U%09-`0jqR(l7p7Q4n^Xx0PYqT_OCHP(Qj80!asSe6Vd(s@ICvPwzwCmHi zukMFnJrS|3mUKN?ABPGrzI9wcP}SJ`;Wy!B#Cl$e2}gZJGT2k28Bq%j(bl}EUF

  • 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%0Ad-l5;Rt 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 N(n_A@9q^mD{|B)3N}K=y literal 0 HcmV?d00001 diff --git a/binaries/x86/php_pdo_sqlsrv_7_ts.dll b/binaries/x86/php_pdo_sqlsrv_7_ts.dll new file mode 100644 index 0000000000000000000000000000000000000000..20d3f1fa6268c446b40678288083655f9878b273 GIT binary patch literal 325336 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 V!$I<{%{a!l(Z6fOh^KUF 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
  • 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 L$Nu@fVi^AedJjBZ literal 0 HcmV?d00001 diff --git a/binaries/x86/php_sqlsrv_7_nts.dll b/binaries/x86/php_sqlsrv_7_nts.dll new file mode 100644 index 0000000000000000000000000000000000000000..d75692d9a6f7db1a2f7da0cbc1c161bc91672c20 GIT binary patch literal 331480 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

    _9o-;T%&dSuh_iQ~G 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 literal 0 HcmV?d00001 diff --git a/pdo_sqlsrv/CREDITS b/pdo_sqlsrv/CREDITS index fb98084f..dc85ce17 100644 --- a/pdo_sqlsrv/CREDITS +++ b/pdo_sqlsrv/CREDITS @@ -1 +1 @@ -Microsoft Drivers 3.2.0 for PHP for SQL Server (PDO driver) +Microsoft Drivers 4.1 for PHP for SQL Server (PDO driver) diff --git a/pdo_sqlsrv/config.w32 b/pdo_sqlsrv/config.w32 index b7ba7e6b..0a598a51 100644 --- a/pdo_sqlsrv/config.w32 +++ b/pdo_sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.0 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -21,10 +21,13 @@ ARG_WITH("pdo-sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (PDO dri if( PHP_PDO_SQLSRV != "no" ) { + pdo_sqlsrv_src = "pdo_dbh.cpp pdo_init.cpp pdo_stmt.cpp pdo_util.cpp pdo_parser.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp"; + if (CHECK_LIB("odbc32.lib", "pdo_sqlsrv") && CHECK_LIB("odbccp32.lib", "pdo_sqlsrv") && CHECK_LIB("version.lib", "pdo_sqlsrv") && CHECK_LIB("psapi.lib", "pdo_sqlsrv")) { - EXTENSION("pdo_sqlsrv", "pdo_dbh.cpp pdo_init.cpp pdo_stmt.cpp pdo_util.cpp pdo_parser.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp" ) + EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1") + CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_PDO_SQLSRV_ODBC'); CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_PDO_SQLSRV_ODBC'); ADD_FLAG( 'LDFLAGS_PDO_SQLSRV', '/NXCOMPAT /DYNAMICBASE /debug' ); @@ -32,7 +35,6 @@ if( PHP_PDO_SQLSRV != "no" ) { ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/GS' ); ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/Zi' ); ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' ); - ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/D _HAS_CPP0X=0' ); ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo'); } diff --git a/pdo_sqlsrv/core_conn.cpp b/pdo_sqlsrv/core_conn.cpp index 02a05d77..97566d3c 100644 --- a/pdo_sqlsrv/core_conn.cpp +++ b/pdo_sqlsrv/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -42,7 +42,7 @@ const int INFO_BUFFER_LEN = 256; const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" }; // ODBC driver name. -const char CONNECTION_STRING_DRIVER_NAME[] = "Driver={ODBC Driver 11 for SQL Server};"; +const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"}; // default options if only the server is specified const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}"; @@ -57,12 +57,12 @@ const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};"; void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, HashTable* options_ht, const connection_option valid_conn_opts[], - void* driver,__inout std::string& connection_string TSRMLS_DC ); + void* driver,_Inout_ std::string& connection_string TSRMLS_DC ); void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ); const char* get_processor_arch( void ); void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ); -connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, unsigned int key_len TSRMLS_DC ); -void common_conn_str_append_func( const char* odbc_name, const char* val, int val_len, std::string& conn_str TSRMLS_DC ); +connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, SQLULEN key_len TSRMLS_DC ); +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ); } @@ -104,14 +104,14 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - zval** option_zz = NULL; + zval* option_z = NULL; int zr = SUCCESS; - zr = zend_hash_index_find( options_ht, SQLSRV_CONN_OPTION_CONN_POOLING, reinterpret_cast( &option_zz )); - if( zr != FAILURE ) { + option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING); + if (option_z) { // if the option was found and it's not true, then use the non pooled environment handle - if(( Z_TYPE_PP( option_zz ) == IS_STRING && !core_str_zval_is_true( *option_zz )) || !zend_is_true( *option_zz ) ) { + if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) { henv = &henv_ncp; } @@ -124,38 +124,49 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); conn->set_func( driver_func ); + for ( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) { + conn_str = CONNECTION_STRING_DRIVER_NAME[i]; + build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, + conn_str TSRMLS_CC ); - build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, - conn_str TSRMLS_CC ); - - // We only support UTF-8 encoding for connection string. - // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW - wconn_len = (conn_str.length() + 1) * sizeof( wchar_t ); - wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), conn_str.length(), &wconn_len ); - CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message() ) - { - throw core::CoreException(); - } - - SQLSMALLINT output_conn_size; - r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), - static_cast( wconn_len ), NULL, - 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); - // clear the connection string from memory to remove sensitive data (such as a password). - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn_str.clear(); + // We only support UTF-8 encoding for connection string. + // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW + wconn_len = static_cast( conn_str.length() + 1 ) * sizeof( wchar_t ); - if( !SQL_SUCCEEDED( r )) { - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); - // if it's a IM002, meaning that the correct ODBC driver is not installed - CHECK_CUSTOM_ERROR( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && - state[4] == '2', conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch() ) { - throw core::CoreException(); - } - } + wconn_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast(conn_str.length()), &wconn_len); + CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) + { + throw core::CoreException(); + } + + SQLSMALLINT output_conn_size; + r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get()), + static_cast( wconn_len ), NULL, + 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); + // clear the connection string from memory to remove sensitive data (such as a password). + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes + conn_str.clear(); + + if( !SQL_SUCCEEDED( r )) { + SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; + SQLSMALLINT len; + SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); + bool missing_driver_error = ( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && + state[4] == '2' ); + // if it's a IM002, meaning that the correct ODBC driver is not installed + CHECK_CUSTOM_ERROR( missing_driver_error && ( i == DRIVER_VERSION::MAX ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { + throw core::CoreException(); + } + if ( !missing_driver_error ) { + break; + } + } else { + conn->driver_version = static_cast( i ); + break; + } + + } CHECK_SQL_ERROR( r, conn ) { throw core::CoreException(); } @@ -167,7 +178,7 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ // determine the version of the server we're connected to. The server version is left in the // connection upon return. determine_server_version( conn TSRMLS_CC ); - + } catch( std::bad_alloc& ) { memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); @@ -318,7 +329,7 @@ void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ) // sql - T-SQL command to prepare // sql_len - length of the T-SQL string -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRMLS_DC ) +void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ) { try { @@ -333,10 +344,17 @@ void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRML wsql_len = 0; } else { + + if (sql_len > INT_MAX) + { + LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding() ); wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - sql_len, &wsql_len ); + static_cast( sql_len ), &wsql_len ); CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) { throw core::CoreException(); @@ -359,7 +377,7 @@ void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRML // conn - The connection resource by which the client and server are connected. // *server_version - zval for returning results. -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC ) +void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ) { try { @@ -367,7 +385,10 @@ void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_versi SQLSMALLINT buffer_len = 0; get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - ZVAL_STRINGL( server_version, buffer, buffer_len, 0 ); + core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); + if (NULL != buffer) { + sqlsrv_free( buffer ); + } buffer.transferred(); } @@ -383,7 +404,7 @@ void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_versi // conn - The connection resource by which the client and server are connected. // *server_info - zval for returning results. -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval *server_info TSRMLS_DC ) +void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval *server_info TSRMLS_DC ) { try { @@ -422,7 +443,7 @@ void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval *server_info TSR // conn - The connection resource by which the client and server are connected. // *client_info - zval for returning the results. -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC ) +void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ) { try { @@ -463,7 +484,7 @@ void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSR // Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that // the value will be surrounded by { and } by the caller after it has been validated -bool core_is_conn_opt_value_escaped( const char* value, int value_len ) +bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ) { // 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. @@ -472,7 +493,7 @@ bool core_is_conn_opt_value_escaped( const char* value, int value_len ) value_len -= 2; } // check to make sure that all right braces are escaped - int i = 0; + size_t i = 0; while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) { // skip both braces if( value[i] == '}' ) @@ -491,7 +512,7 @@ bool core_is_conn_opt_value_escaped( const char* value, int value_len ) namespace { -connection_option const* get_connection_option( sqlsrv_conn* conn, unsigned long key, +connection_option const* get_connection_option( sqlsrv_conn* conn, SQLULEN key, 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 ) { @@ -513,7 +534,7 @@ connection_option const* get_connection_option( sqlsrv_conn* conn, unsigned long void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, HashTable* options, const connection_option valid_conn_opts[], - void* driver,__inout std::string& connection_string TSRMLS_DC ) + void* driver,_Inout_ std::string& connection_string TSRMLS_DC ) { bool credentials_mentioned = false; bool mars_mentioned = false; @@ -521,9 +542,7 @@ void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* s int zr = SUCCESS; try { - - connection_string = CONNECTION_STRING_DRIVER_NAME; - + // Add the server name common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC ); @@ -566,40 +585,34 @@ void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* s // flag is set to false. if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) { - zval** trace_value = NULL; - int zr = zend_hash_index_find( options, SQLSRV_CONN_OPTION_TRACE_ON, (void**)&trace_value ); + zval* trace_value = NULL; + trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); - if( zr == FAILURE || !zend_is_true( *trace_value )) { + if (trace_value == NULL || !zend_is_true(trace_value)) { zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE ); } } - for( zend_hash_internal_pointer_reset( options ); - zend_hash_has_more_elements( options ) == SUCCESS; - zend_hash_move_forward( options )) { - - int type = HASH_KEY_NON_EXISTANT; - char *key = NULL; - unsigned int key_len = -1; - unsigned long index = -1; - zval** data = NULL; + zend_string *key = NULL; + zend_ulong index = -1; + zval* data = NULL; - type = zend_hash_get_current_key_ex( options, &key, &key_len, &index, 0, NULL ); - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); - - core::sqlsrv_zend_hash_get_current_data( *conn, options, (void**) &data TSRMLS_CC ); + ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); - - if( index == SQLSRV_CONN_OPTION_MARS ) { - mars_mentioned = true; - } - - conn_opt->func( conn_opt, *data, conn, connection_string TSRMLS_CC ); - } + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); + + conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); + + if( index == SQLSRV_CONN_OPTION_MARS ) { + mars_mentioned = true; + } + + conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); // MARS on if not explicitly turned off if( !mars_mentioned ) { @@ -676,7 +689,7 @@ void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) errno = 0; char version_major_str[ 3 ]; SERVER_VERSION version_major; - memcpy( version_major_str, p, 2 ); + memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); version_major_str[ 2 ] = '\0'; version_major = static_cast( atoi( version_major_str )); @@ -690,7 +703,7 @@ void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) conn->server_version = version_major; } -void common_conn_str_append_func( const char* odbc_name, const char* val, int val_len, std::string& conn_str TSRMLS_DC ) +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) { // wrap a connection option in a quote. It is presumed that any character that need to be escaped will // be escaped, such as a closing }. @@ -713,7 +726,7 @@ void conn_str_append_func::func( connection_option const* option, zval* value, s TSRMLS_DC ) { const char* val_str = Z_STRVAL_P( value ); - int val_len = Z_STRLEN_P( value ); + size_t val_len = Z_STRLEN_P( value ); common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC ); } @@ -729,15 +742,15 @@ void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, // Values = ("true" or "1") are treated as true values. Everything else is treated as false. // Returns 1 for true and 0 for false. -int core_str_zval_is_true( zval* value_z ) +size_t core_str_zval_is_true( zval* value_z ) { SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); char* value_in = Z_STRVAL_P( value_z ); - int val_len = Z_STRLEN_P( value_z ); + size_t val_len = Z_STRLEN_P( value_z ); // strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8) - int last_char = val_len - 1; + size_t last_char = val_len - 1; while( isspace( value_in[ last_char ] )) { value_in[ last_char ] = '\0'; val_len = last_char; @@ -745,7 +758,7 @@ int core_str_zval_is_true( zval* value_z ) } // save adjustments to the value made by stripping whitespace at the end - ZVAL_STRINGL( value_z, value_in, val_len, 0 ); + Z_STRLEN_P( value_z ) = val_len; const char VALID_TRUE_VALUE_1[] = "true"; const char VALID_TRUE_VALUE_2[] = "1"; diff --git a/pdo_sqlsrv/core_init.cpp b/pdo_sqlsrv/core_init.cpp index 8ba175f8..1b1e8e57 100644 --- a/pdo_sqlsrv/core_init.cpp +++ b/pdo_sqlsrv/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -37,8 +37,8 @@ OSVERSIONINFO g_osversion; // err - Driver specific error handler which handles any errors during initialization. void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ) { - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( long )); - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( long )); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL @@ -146,11 +146,13 @@ void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ) henv_ncp.invalidate(); } + delete &henv_ncp; if( henv_cp != SQL_NULL_HANDLE ) { henv_cp.invalidate(); } + delete &henv_cp; return; } diff --git a/pdo_sqlsrv/core_results.cpp b/pdo_sqlsrv/core_results.cpp index d4eed5d3..8b2657ed 100644 --- a/pdo_sqlsrv/core_results.cpp +++ b/pdo_sqlsrv/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -76,19 +76,37 @@ bool get_bit( void* ptr, unsigned int bit ) // read in LOB field during buffered result creation SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - unsigned long mem_used TSRMLS_DC ); + zend_long mem_used TSRMLS_DC ); // dtor for each row in the cache -void cache_row_dtor( void* data ); +void cache_row_dtor(zval* data); // convert a number to a string using locales // There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of // subclassing a new streambuf just to avoid the copy, it's easier to do the copy template -SQLRETURN number_to_string( Number* number_data, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, +SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) { + // get to display size by removing the null terminator from buffer length + size_t display_size = ( buffer_length - sizeof( Char )) / sizeof( Char ); + std::basic_ostringstream os; + // use the display size to determine the sql type. And if it is a double, set the precision accordingly + // the display sizes are set by the ODBC driver based on the precision of the sql type + // otherwise we can just use the default precision as long will not be truncated + size_t real_display_size = 14; + size_t float_display_size = 24; + size_t real_precision = 7; + size_t float_precision = 15; + // this is the case of sql type float(24) or real + if ( display_size == real_display_size ) { + os.precision( real_precision ); + } + // this is the case of sql type float(53) + else if ( display_size == float_display_size ) { + os.precision( float_precision ); + } std::locale loc; os.imbue( loc ); std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf() ), os, ' ', *number_data ); @@ -100,21 +118,21 @@ SQLRETURN number_to_string( Number* number_data, __out void* buffer, SQLLEN buff return SQL_ERROR; } - if( str_num.size() * sizeof(Char) + sizeof(Char) > (size_t) buffer_length ) { + if( str_num.size() * sizeof(Char) > (size_t) buffer_length ) { last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); return SQL_ERROR; } - *out_buffer_length = str_num.size() * sizeof(Char) + sizeof(Char); // include NULL terminator - memcpy( buffer, str_num.c_str(), *out_buffer_length ); + *out_buffer_length = str_num.size() * sizeof( Char ); // str_num.size() already include the NULL terminator + memcpy_s( buffer, buffer_length, str_num.c_str(), *out_buffer_length ); return SQL_SUCCESS; } template -SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) +SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) { Number* number_data = reinterpret_cast( buffer ); std::locale loc; // default locale should match system @@ -170,13 +188,13 @@ sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number ) // convert the error into the encoding of the context sqlsrv_malloc_auto_ptr sql_state; - SQLINTEGER sql_state_len = 0; + SQLLEN sql_state_len = 0; if (!convert_string_from_utf16( enc, wsql_state, sizeof(wsql_state), (char**)&sql_state, sql_state_len )) { return NULL; } sqlsrv_malloc_auto_ptr native_message; - SQLINTEGER native_message_len = 0; + SQLLEN native_message_len = 0; if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) { return NULL; } @@ -214,7 +232,7 @@ SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset } SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ) { SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); @@ -222,8 +240,8 @@ SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLIN } SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) { SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length, @@ -257,9 +275,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS { // 10 is an arbitrary number for now for the initial size of the cache ALLOC_HASHTABLE( cache ); - - core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, NULL /* hashfn */, cache_row_dtor /*dtor*/, 0 /*persistent*/ - TSRMLS_CC ); + core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); // there is no result set to buffer if( col_count == 0 ) { @@ -441,7 +457,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS // read the data into the cache // (offset from the above loop has the size of the row buffer necessary) - unsigned long mem_used = 0; + zend_long mem_used = 0; unsigned long row_count = 0; while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { @@ -478,7 +494,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS } else { - mem_used += meta[i].length; + mem_used += meta[i].length; CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { @@ -523,7 +539,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS // add it to the cache row_dtor_closure cl( this, row ); - sqlsrv_zend_hash_next_index_insert( *stmt, cache, &cl, sizeof( cl ) TSRMLS_CC ); + sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); } } @@ -598,7 +614,7 @@ SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN off } SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ) { last_error = NULL; @@ -634,8 +650,8 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMA } SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) { SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE, @@ -650,7 +666,7 @@ SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSRV_ASSERT( last_error->sqlstate != NULL, "Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" ); - memcpy( diag_info_buffer, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE )); + memcpy_s( diag_info_buffer, buffer_length, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE )); return SQL_SUCCESS; } @@ -658,8 +674,8 @@ SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, unsigned char* sqlsrv_buffered_result_set::get_row( void ) { row_dtor_closure* cl_ptr; - int zr = zend_hash_index_find( cache, current - 1, (void**) &cl_ptr ); - SQLSRV_ASSERT( zr == SUCCESS, "Failed to find row %1!d! in the cache", current ); + cl_ptr = reinterpret_cast(zend_hash_index_find_ptr(cache, static_cast(current - 1))); + SQLSRV_ASSERT(cl_ptr != NULL, "Failed to find row %1!d! in the cache", current); return cl_ptr->row_data; } @@ -686,8 +702,8 @@ SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D ) // private functions template -SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, __out void* buffer, - SQLLEN buffer_length, __out SQLLEN* out_buffer_length, +SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, _Out_ void* buffer, + SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& out_error ) { // hex characters for the conversion loop below @@ -747,8 +763,8 @@ SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, __out voi return r; } -SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLCHAR* row = get_row(); SQLCHAR* field_data = NULL; @@ -765,8 +781,8 @@ SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); } -SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLCHAR* row = get_row(); SQLCHAR* field_data = NULL; @@ -784,11 +800,11 @@ SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_i } -SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof(LONG), "Buffer length must be able to find a long in " + 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(); @@ -813,8 +829,8 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, _ return SQL_SUCCESS; } -SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -825,8 +841,8 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); } -SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -837,8 +853,8 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_i return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); } -SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -852,8 +868,8 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, _ return SQL_SUCCESS; } -SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -864,8 +880,8 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_i return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); } -SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -876,8 +892,8 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_ind return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); } -SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -888,8 +904,8 @@ SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, 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( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -900,8 +916,8 @@ SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index 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( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -912,8 +928,8 @@ SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, _ 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( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -924,8 +940,8 @@ SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, 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( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" ); SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" ); @@ -974,8 +990,13 @@ SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_i bool tried_again = false; do { - int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, to_copy, - (LPWSTR) buffer, to_copy ); + if (to_copy > INT_MAX ) { + LOG(SEV_ERROR, "MultiByteToWideChar: Buffer length exceeded."); + throw core::CoreException(); + } + + int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast(to_copy), + static_cast(buffer), static_cast(to_copy)); if( ch_space == 0 ) { switch( GetLastError() ) { @@ -1012,8 +1033,8 @@ SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_i return r; } -SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::to_same_string" ); @@ -1071,19 +1092,19 @@ SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, _ SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" ); if( to_copy > 0 ) { - memcpy( buffer, field_data + read_so_far, to_copy ); + memcpy_s( buffer, buffer_length, field_data + read_so_far, to_copy ); read_so_far += to_copy; } if( extra ) { OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); - memcpy( reinterpret_cast( buffer ) + to_copy, L"\0", extra ); + memcpy_s( reinterpret_cast( buffer ) + to_copy, buffer_length, L"\0", extra ); } return r; } -SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" ); @@ -1091,7 +1112,7 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_i unsigned char* row = get_row(); SQLCHAR* field_data = NULL; - SQLULEN field_len = NULL; + SQLLEN field_len = NULL; // if this is the first time called for this field, just convert the entire string to system first then // use that to read from instead of converting chunk by chunk. This is because it's impossible to know @@ -1116,9 +1137,9 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_i char default_char = '?'; // allocate enough to handle WC -> DBCS conversion if it happens - temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof( char ), sizeof(char))); - temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, field_len / sizeof(WCHAR), - (LPSTR) temp_string.get(), field_len, &default_char, &default_char_used ); + temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof(char), sizeof(char))); + temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), + (LPSTR) temp_string.get(), static_cast(field_len), &default_char, &default_char_used ); if( temp_length == 0 ) { @@ -1156,7 +1177,7 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_i if( to_copy > 0 ) { - memcpy( buffer, temp_string.get() + read_so_far, to_copy ); + memcpy_s( buffer, buffer_length, temp_string.get() + read_so_far, to_copy ); } SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); @@ -1167,14 +1188,14 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_i } -SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { return to_same_string( field_index, buffer, buffer_length, out_buffer_length ); } -SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invlid conversion to long" ); SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this @@ -1182,14 +1203,14 @@ SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, __out vo unsigned char* row = get_row(); LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - memcpy( buffer, long_data, sizeof( LONG )); + memcpy_s( buffer, buffer_length, long_data, sizeof( LONG )); *out_buffer_length = sizeof( LONG ); return SQL_SUCCESS; } -SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invlid conversion to double" ); SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this @@ -1197,7 +1218,7 @@ SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, __out unsigned char* row = get_row(); double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - memcpy( buffer, double_data, sizeof( double )); + memcpy_s( buffer, buffer_length, double_data, sizeof( double )); *out_buffer_length = sizeof( double ); return SQL_SUCCESS; @@ -1206,9 +1227,9 @@ SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, __out namespace { // called for each row in the cache when the cache is destroyed in the destructor -void cache_row_dtor( void* data ) +void cache_row_dtor( zval* data ) { - row_dtor_closure* cl = reinterpret_cast( data ); + row_dtor_closure* cl = reinterpret_cast( Z_PTR_P( data ) ); BYTE* row = cl->row_data; // don't release this here, since this is called from the destructor of the result_set sqlsrv_buffered_result_set* result_set = cl->results; @@ -1223,13 +1244,14 @@ void cache_row_dtor( void* data ) } sqlsrv_free( row ); + sqlsrv_free( cl ); } SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - unsigned long mem_used TSRMLS_DC ) + zend_long mem_used TSRMLS_DC ) { SQLSMALLINT extra = 0; - SQLLEN* output_buffer_len = NULL; + SQLULEN* output_buffer_len = NULL; // Set the amount of space necessary for null characters at the end of the data. switch( meta.c_type ) { @@ -1259,7 +1281,7 @@ SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_b do { - output_buffer_len = reinterpret_cast( buffer.get() ); + output_buffer_len = reinterpret_cast( buffer.get() ); r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ), to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC ); @@ -1303,7 +1325,7 @@ SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_b already_read += to_read - already_read; to_read = last_field_len; buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); + output_buffer_len = reinterpret_cast( buffer.get() ); // record the size of the field since we have it available *output_buffer_len = last_field_len; full_length_returned = true; @@ -1318,7 +1340,7 @@ SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_b throw core::CoreException(); } buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); + output_buffer_len = reinterpret_cast( buffer.get() ); } } while( true ); diff --git a/pdo_sqlsrv/core_sqlsrv.h b/pdo_sqlsrv/core_sqlsrv.h index 3867cdc9..c511b057 100644 --- a/pdo_sqlsrv/core_sqlsrv.h +++ b/pdo_sqlsrv/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -107,7 +107,7 @@ OACR_WARNING_POP #include #include #include - +#include // included for SQL Server specific constants #include "msodbcsql.h" @@ -182,7 +182,7 @@ union sqlsrv_sqltype { int scale:8; } typeinfo; - long value; + zend_long value; }; @@ -196,7 +196,7 @@ union sqlsrv_phptype { unsigned encoding:16; } typeinfo; - long value; + zend_long value; }; // static assert for enforcing compile time conditions @@ -500,6 +500,16 @@ public: return _ptr[ index ]; } + + #ifdef __WIN64 + // 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[](std::size_t index) const + { + return _ptr[index]; + } + #endif + // 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[]( unsigned short index ) const @@ -656,7 +666,7 @@ public: void reset( zval* ptr = NULL ) { if( _ptr ) - zval_ptr_dtor( &_ptr ); + zval_ptr_dtor(_ptr ); _ptr = ptr; } @@ -664,12 +674,7 @@ public: { return sqlsrv_auto_ptr::operator=( ptr ); } -#if PHP_MAJOR_VERSION > 5 || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 3) - operator zval_gc_info*( void ) - { - return reinterpret_cast(_ptr); - } -#endif + private: @@ -884,6 +889,8 @@ class sqlsrv_context { { if( handle_ != SQL_NULL_HANDLE ) { ::SQLFreeHandle( handle_type_, handle_ ); + + last_error_.reset(); } handle_ = SQL_NULL_HANDLE; } @@ -919,7 +926,7 @@ const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista struct sqlsrv_encoding { const char* iana; - unsigned int iana_len; + size_t iana_len; unsigned int code_page; bool not_for_connection; @@ -965,6 +972,14 @@ enum SERVER_VERSION { SERVER_VERSION_2008, // use this for anything 2008 or later }; +// supported driver versions. +enum DRIVER_VERSION : size_t { + MIN = 0, + ODBC_DRIVER_13 = MIN, + ODBC_DRIVER_11 = 1, + MAX = ODBC_DRIVER_11, +}; + // forward decl struct sqlsrv_stmt; struct stmt_option; @@ -976,6 +991,8 @@ struct sqlsrv_conn : public sqlsrv_context { // instance variables SERVER_VERSION server_version; // version of the server that we're connected to + DRIVER_VERSION driver_version; + // initialize with default values sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) : sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) @@ -1010,7 +1027,7 @@ const char ConnectionPooling[] = "ConnectionPooling"; const char Database[] = "Database"; const char Encrypt[] = "Encrypt"; const char Failover_Partner[] = "Failover_Partner"; -const char LoginTimeout[] = "LoginTimggeout"; +const char LoginTimeout[] = "LoginTimeout"; const char MARS_ODBC[] = "MARS_Connection"; const char MultiSubnetFailover[] = "MultiSubnetFailover"; const char QuotedId[] = "QuotedId"; @@ -1087,9 +1104,8 @@ struct str_conn_attr_func { static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) { try { - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), - Z_STRLEN_P( value ) TSRMLS_CC ); + static_cast(Z_STRLEN_P( value )) TSRMLS_CC ); } catch( core::CoreException& ) { throw; @@ -1118,15 +1134,15 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[], void* driver, const char* driver_func TSRMLS_DC ); void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRMLS_DC ); +void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ); void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ); void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ); void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval* server_info TSRMLS_DC ); -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC ); -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC ); -bool core_is_conn_opt_value_escaped( const char* value, int value_len ); -int core_str_zval_is_true( zval* str_zval ); +void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC ); +void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ); +void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); +bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); +size_t core_str_zval_is_true( zval* str_zval ); //********************************************************************************************************************************* // Statement @@ -1158,7 +1174,7 @@ struct stmt_option { const char * name; // name of the statement option unsigned int name_len; // name length unsigned int key; - stmt_option_functor* func; // callback that actually handles the work of the option + std::unique_ptr func; // callback that actually handles the work of the option }; @@ -1170,7 +1186,7 @@ struct sqlsrv_stream { SQLUSMALLINT field_index; SQLSMALLINT sql_type; sqlsrv_stmt* stmt; - int stmt_index; + std::size_t stmt_index; sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) : stream_z( str_z ), encoding( enc ) @@ -1183,7 +1199,7 @@ struct sqlsrv_stream { }; // close any active stream -void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC ); +void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); extern php_stream_wrapper g_sqlsrv_stream_wrapper; @@ -1197,7 +1213,7 @@ struct sqlsrv_output_param { zval* param_z; SQLSRV_ENCODING encoding; - int param_num; // used to index into the ind_or_len of the statement + SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer bool is_bool; @@ -1239,28 +1255,28 @@ struct sqlsrv_stmt : public sqlsrv_context { bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the // last results unsigned long query_timeout; // maximum allowed statement execution time - unsigned long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) + zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) // 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 // memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold std::deque param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter - zval* param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP - zval* output_params; // hold all the output parameters - zval* param_streams; // track which streams to send data to the server - zval* param_datetime_buffers; // datetime strings to be converted back to DateTime objects + zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP + zval output_params; // hold all the output parameters + zval param_streams; // track which streams to send data to the server + zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects bool send_streams_at_exec; // send all stream data right after execution before returning sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string // to the server) - zval* field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals - zval* active_stream; // the currently active stream reading data from the database + zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals + zval active_stream; // the currently active stream reading data from the database sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); virtual ~sqlsrv_stmt( void ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) = 0; + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string = false ) = 0; }; @@ -1302,25 +1318,25 @@ typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, err // *** statement functions *** sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ); -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int direction, zval* param_z, +void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, SQLSMALLINT decimal_digits TSRMLS_DC ); void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 ); field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ); -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); -void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, - __out void** field_value, __out SQLLEN* field_length, bool cache_field, - __out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC ); +bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ); +void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, + _Out_ void*& field_value, _Out_ SQLLEN* field_length, bool cache_field, + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC); bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ); void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true ); -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z TSRMLS_DC ); -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned int cursor_type TSRMLS_DC ); +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC ); +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ); void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ); void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, long limit TSRMLS_DC ); +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); //********************************************************************************************************************************* @@ -1344,11 +1360,11 @@ struct sqlsrv_result_set { virtual bool cached( int field_index ) = 0; virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0; virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC )= 0; virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0; virtual SQLLEN row_count( TSRMLS_D ) = 0; }; @@ -1356,16 +1372,16 @@ struct sqlsrv_result_set { struct sqlsrv_odbc_result_set : public sqlsrv_result_set { explicit sqlsrv_odbc_result_set( sqlsrv_stmt* ); - virtual ~sqlsrv_odbc_result_set( void ); + virtual ~sqlsrv_odbc_result_set( void ); virtual bool cached( int field_index ) { return false; } virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ); virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ); + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); virtual SQLLEN row_count( TSRMLS_D ); @@ -1390,8 +1406,8 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { // default maximum amount of memory that a buffered query can consume #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings - static const unsigned long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB - static const long BUFFERED_QUERY_LIMIT_INVALID = 0; + static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB + static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC ); virtual ~sqlsrv_buffered_result_set( void ); @@ -1399,11 +1415,11 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { virtual bool cached( int field_index ) { return true; } virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ); virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ); + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); virtual SQLLEN row_count( TSRMLS_D ); @@ -1434,55 +1450,55 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use SQLLEN temp_length; // number of bytes in the temp conversion buffer - typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); + typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; // two dimentional sparse matrix that holds the [from][to] functions that do conversions static conv_matrix_t conv_matrix; // string conversion functions - SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN system_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); + SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); // long conversion functions - SQLRETURN to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length ); - SQLRETURN long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); + SQLRETURN to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); // double conversion functions - SQLRETURN to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length ); - SQLRETURN double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); + SQLRETURN to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); // string to number conversion functions // Future: See if these can be converted directly to template member functions - SQLRETURN string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN string_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); + SQLRETURN string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); // utility functions for conversions unsigned char* get_row( void ); @@ -1500,11 +1516,12 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { #define MEMCHECK_SILENT 1 // utility functions shared by multiple callers across files -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLINTEGER& len); -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLINTEGER& cchOutLen ); +bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len); +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len); +bool validate_string(char* string, SQLLEN& len); +bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ); wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, - unsigned int mbcs_len, __out unsigned int* utf16_len ); - + unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); //********************************************************************************************************************************* // Error handling routines and Predefined Errors @@ -1559,8 +1576,8 @@ enum SQLSRV_ERROR_CODES { }; // the message returned by ODBC Driver 11 for SQL Server -const char CONNECTION_BUSY_ODBC_ERROR[] = "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for " - "another command"; +static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command", + "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" }; // SQLSTATE for all internal errors extern SQLCHAR IMSSP[]; @@ -1582,7 +1599,7 @@ enum error_handling_flags { // 2/code) driver specific error code // 3/message) driver specific error message // The fetch type determines if the indices are numeric, associative, or both. -bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, __out sqlsrv_error_auto_ptr& error, +bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, _Out_ sqlsrv_error_auto_ptr& error, logging_severity severity TSRMLS_DC ); // format and return a driver specfic error @@ -1595,11 +1612,11 @@ void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const co const char* get_last_error_message( DWORD last_error = 0 ); // a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer -DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ); +DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ); // convenience functions that overload either a reference or a pointer so we can use // either in the CHECK_* functions. -inline bool call_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_code TSRMLS_DC, bool warning, ... ) +inline bool call_error_handler( sqlsrv_context& ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) { va_list print_params; va_start( print_params, warning ); @@ -1608,7 +1625,7 @@ inline bool call_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_c return ignored; } -inline bool call_error_handler( sqlsrv_context* ctx, unsigned int sqlsrv_error_code TSRMLS_DC, bool warning, ... ) +inline bool call_error_handler( sqlsrv_context* ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) { va_list print_params; va_start( print_params, warning ); @@ -1737,9 +1754,8 @@ namespace core { throw CoreException(); } - - if(( len == sizeof( CONNECTION_BUSY_ODBC_ERROR ) - 1 ) && - !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR )) { + std::size_t driver_version = stmt->conn->driver_version; + if( !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) { THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); } @@ -1755,8 +1771,8 @@ namespace core { // the context to hold the error, they are not passed as const. inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) { SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, diag_info_buffer, buffer_length, out_buffer_length ); @@ -1769,7 +1785,7 @@ namespace core { } inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle, - __out_ecount(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) + _Out_writes_(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) { SQLRETURN r; r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); @@ -1785,9 +1801,9 @@ namespace core { SQLSMALLINT ParameterType, SQLULEN ColumnSize, SQLSMALLINT DecimalDigits, - __inout SQLPOINTER ParameterValuePtr, + _Inout_ SQLPOINTER ParameterValuePtr, SQLLEN BufferLength, - __inout SQLLEN * StrLen_Or_IndPtr + _Inout_ SQLLEN * StrLen_Or_IndPtr TSRMLS_DC ) { SQLRETURN r; @@ -1801,8 +1817,8 @@ namespace core { inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, - __out SQLPOINTER field_type_char, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length, __out SQLLEN* field_type_num TSRMLS_DC ) + _Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC ) { SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, buffer_length, out_buffer_length, field_type_num ); @@ -1813,9 +1829,9 @@ namespace core { } - inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, __out_z SQLCHAR* col_name, SQLSMALLINT col_name_length, - __out SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, __out SQLULEN* col_size, - __out SQLSMALLINT* decimal_digits, __out SQLSMALLINT* nullable TSRMLS_DC ) + inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLCHAR* col_name, SQLSMALLINT col_name_length, + _Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size, + _Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC ) { SQLRETURN r; r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, @@ -1898,7 +1914,7 @@ namespace core { } inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ) { SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); @@ -1920,8 +1936,8 @@ namespace core { } - inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, __out SQLPOINTER info_value, SQLSMALLINT buffer_len, - __out SQLSMALLINT* str_len TSRMLS_DC ) + inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, _Out_ SQLPOINTER info_value, SQLSMALLINT buffer_len, + _Out_ SQLSMALLINT* str_len TSRMLS_DC ) { SQLRETURN r; r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); @@ -1970,7 +1986,7 @@ namespace core { // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more // parameters or when the parameters are all processed. - inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, __out SQLPOINTER* value_ptr_ptr TSRMLS_DC ) + inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, _Out_ SQLPOINTER* value_ptr_ptr TSRMLS_DC ) { SQLRETURN r; r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); @@ -2055,6 +2071,25 @@ namespace core { // *** zend wrappers *** + + //zend_resource_dtor sets the type of destroyed resources to -1 + #define RSRC_INVALID_TYPE -1 + + // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string + // so allocated memory inside of value_z should be released before assigning it to the new string + inline void sqlsrv_zval_stringl(zval* value_z, const char* str, const std::size_t str_len) + { + if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { + zend_string* temp_zstr = zend_string_init(str, str_len, 0); + zend_string_release(Z_STR_P(value_z)); + ZVAL_NEW_STR(value_z, temp_zstr); + } + else { + ZVAL_STRINGL(value_z, str, str_len); + } + } + + // exception thrown when a zend function wrapped here fails. // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw @@ -2063,7 +2098,7 @@ namespace core { // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error // that can be thrown from it. - inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, unsigned int index, zval* value TSRMLS_DC) + inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, zend_ulong index, zval* value TSRMLS_DC) { int zr = ::add_index_zval( array, index, value ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2079,7 +2114,7 @@ namespace core { } } - inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, char* key TSRMLS_DC ) + inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, const char* key TSRMLS_DC ) { int zr = ::add_assoc_null( array_z, key ); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2087,7 +2122,7 @@ namespace core { } } - inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, char* key, long val TSRMLS_DC ) + inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, const char* key, zend_long val TSRMLS_DC ) { int zr = ::add_assoc_long( array_z, key, val ); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2095,23 +2130,26 @@ namespace core { } } - inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, char* key, char* val, bool duplicate TSRMLS_DC ) + inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, const char* key, char* val, bool duplicate TSRMLS_DC ) { - int zr = ::add_assoc_string( array_z, key, val, duplicate ); + int zr = ::add_assoc_string(array_z, key, val); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } + if (duplicate == 0) { + sqlsrv_free(val); + } } - inline void sqlsrv_array_init( sqlsrv_context& ctx, __out zval* new_array TSRMLS_DC) + inline void sqlsrv_array_init( sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) { - int zr = ::array_init( new_array ); + int zr = ::array_init(new_array); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } - inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval** stream_z TSRMLS_DC ) + inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval* stream_z TSRMLS_DC ) { // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment php_stream_from_zval_no_verify( stream, stream_z ); @@ -2120,53 +2158,89 @@ namespace core { } } - inline void sqlsrv_zend_hash_get_current_data( sqlsrv_context& ctx, HashTable* ht, __out void** output_data TSRMLS_DC ) + inline void sqlsrv_zend_hash_get_current_data(sqlsrv_context& ctx, HashTable* ht, _Out_ zval*& output_data TSRMLS_DC) + { + int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_get_current_data_ptr(sqlsrv_context& ctx, HashTable* ht, _Out_ void*& output_data TSRMLS_DC) { - int zr = ::zend_hash_get_current_data( ht, output_data ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); - } + } } - inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, int index TSRMLS_DC ) + inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, zend_ulong index TSRMLS_DC ) { int zr = ::zend_hash_index_del( ht, index ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } - - inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, unsigned long index, void* data, - uint data_size TSRMLS_DC ) + + inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, zend_ulong index, zval* data_z TSRMLS_DC ) { - int zr = ::zend_hash_index_update( ht, index, data, data_size, NULL ); + int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } - inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, void* data, - uint data_size TSRMLS_DC ) + inline void sqlsrv_zend_hash_index_update_ptr(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData TSRMLS_DC) { - int zr = ::zend_hash_next_index_insert( ht, data, data_size, NULL ); + int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + + inline void sqlsrv_zend_hash_index_update_mem(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData, std::size_t size TSRMLS_DC) + { + int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, zval* data TSRMLS_DC ) + { + int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } - inline void sqlsrv_zend_hash_init( sqlsrv_context& ctx, HashTable* ht, unsigned int initial_size, hash_func_t hash_fn, - dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) + inline void sqlsrv_zend_hash_next_index_insert_mem(sqlsrv_context& ctx, HashTable* ht, void* data, uint data_size TSRMLS_DC) + { + int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert_ptr(sqlsrv_context& ctx, HashTable* ht, void* data TSRMLS_DC) { - int zr = ::zend_hash_init( ht, initial_size, hash_fn, dtor_fn, persistent ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); } } - - inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, char* key, unsigned int key_len, void** data, - unsigned int data_size, void **pDest TSRMLS_DC ) + + inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, HashTable* ht, uint32_t initial_size, + dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) { - int zr = ::zend_hash_add( ht, key, key_len, data, data_size, pDest ); + ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); + } + + inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, zend_string* key, unsigned int key_len, zval* data, + unsigned int data_size, zval* pDest TSRMLS_DC ) + { + int zr = (pDest = ::zend_hash_add(ht, key, data)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } diff --git a/pdo_sqlsrv/core_stmt.cpp b/pdo_sqlsrv/core_stmt.cpp index 96559d00..a4ef0d9d 100644 --- a/pdo_sqlsrv/core_stmt.cpp +++ b/pdo_sqlsrv/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -24,7 +24,7 @@ namespace { // certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the // results of every field request, and if it is out of order, we cache those for preceding fields. struct field_cache { - + void* value; SQLLEN len; sqlsrv_phptype type; @@ -35,7 +35,7 @@ struct field_cache { // if the value is NULL, then just record a NULL pointer if( field_value != NULL ) { value = sqlsrv_malloc( field_len ); - memcpy( value, field_value, field_len ); + memcpy_s( value, field_len, field_value, field_len ); len = field_len; } else { @@ -76,36 +76,36 @@ const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT ); // *** internal functions *** // Only declarations are put here. Functions contain the documentation they need at their definition sites. -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, __out SQLLEN& size TSRMLS_DC ); +void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ); size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ); bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC ); bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z ); -void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ); +void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype + sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC); // returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); +SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - __out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC ); + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); // given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) -void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - __out SQLSMALLINT& sql_type TSRMLS_DC ); -void field_cache_dtor( void* data ); +void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); +void field_cache_dtor( zval* data_z ); void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ); -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ); -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, unsigned long key, const stmt_option stmt_opts[] TSRMLS_DC ); +void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, + _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ); +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ); bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); // assure there is enough space for the output parameter string -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsigned int paramno, SQLSRV_ENCODING encoding, +void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, SQLLEN& buffer_len TSRMLS_DC ); void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ); // send all the stream data void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ); // called when a bound output string parameter is to be destroyed -void sqlsrv_output_param_dtor( void* data ); +void sqlsrv_output_param_dtor( zval* data ); // called when a bound stream parameter is to be destroyed. -void sqlsrv_stream_dtor( void* data ); +void sqlsrv_stream_dtor( zval* data ); bool is_streamable_type( SQLINTEGER sql_type ); } @@ -127,43 +127,32 @@ sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, vo current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), current_stream_read( 0 ), query_timeout( QUERY_TIMEOUT_INVALID ), - buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ), - active_stream( NULL ) + buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) { + ZVAL_UNDEF( &active_stream ); // initialize the input string parameters array (which holds zvals) - MAKE_STD_ZVAL( param_input_strings ); - core::sqlsrv_array_init( *conn, param_input_strings TSRMLS_CC ); - + core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); + // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) - MAKE_STD_ZVAL( param_streams ); - Z_TYPE_P( param_streams ) = IS_ARRAY; - ALLOC_HASHTABLE( Z_ARRVAL_P( param_streams )); - core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( param_streams ), 5 /* # of buckets */, NULL /*hashfn*/, - sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC ); + ZVAL_NEW_ARR( ¶m_streams ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC); // initialize the (input only) datetime parameters of converted date time objects to strings - MAKE_STD_ZVAL( param_datetime_buffers ); - array_init( param_datetime_buffers ); + array_init( ¶m_datetime_buffers ); // initialize the output string parameters (which holds sqlsrv_output_param structures) - MAKE_STD_ZVAL( output_params ); - Z_TYPE_P( output_params ) = IS_ARRAY; - ALLOC_HASHTABLE( Z_ARRVAL_P( output_params )); - core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( output_params ), 5 /* # of buckets */, NULL /*hashfn*/, - sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC ); - + ZVAL_NEW_ARR( &output_params ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC); + // initialize the field cache - MAKE_STD_ZVAL( field_cache ); - Z_TYPE_P( field_cache ) = IS_ARRAY; - ALLOC_HASHTABLE( Z_ARRVAL_P( field_cache )); - core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( field_cache ), 5 /* # of buckets */, NULL /*hashfn*/, - field_cache_dtor, 0 /*persistent*/ TSRMLS_CC ); + ZVAL_NEW_ARR( &field_cache ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC); } // desctructor for sqlsrv statement. sqlsrv_stmt::~sqlsrv_stmt( void ) { - if( active_stream ) { + if( Z_TYPE( active_stream ) != IS_UNDEF ) { TSRMLS_FETCH(); close_active_stream( this TSRMLS_CC ); } @@ -174,8 +163,8 @@ sqlsrv_stmt::~sqlsrv_stmt( void ) efree( current_results ); current_results = NULL; } - - invalidate(); + + invalidate(); zval_ptr_dtor( ¶m_input_strings ); zval_ptr_dtor( &output_params ); zval_ptr_dtor( ¶m_streams ); @@ -189,13 +178,13 @@ sqlsrv_stmt::~sqlsrv_stmt( void ) // execution phase. void sqlsrv_stmt::free_param_data( TSRMLS_D ) { - SQLSRV_ASSERT( Z_TYPE_P( param_input_strings ) == IS_ARRAY && Z_TYPE_P( param_streams ) == IS_ARRAY, + SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY, "sqlsrv_stmt::free_param_data: Param zvals aren't arrays." ); - zend_hash_clean( Z_ARRVAL_P( param_input_strings )); - zend_hash_clean( Z_ARRVAL_P( output_params )); - zend_hash_clean( Z_ARRVAL_P( param_streams )); - zend_hash_clean( Z_ARRVAL_P( param_datetime_buffers )); - zend_hash_clean( Z_ARRVAL_P( field_cache )); + zend_hash_clean( Z_ARRVAL( param_input_strings )); + zend_hash_clean( Z_ARRVAL( output_params )); + zend_hash_clean( Z_ARRVAL( param_streams )); + zend_hash_clean( Z_ARRVAL( param_datetime_buffers )); + zend_hash_clean( Z_ARRVAL( field_cache )); } @@ -242,7 +231,7 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ) { - sqlsrv_malloc_auto_ptr stmt; + sqlsrv_malloc_auto_ptr stmt; SQLHANDLE stmt_h = SQL_NULL_HANDLE; try { @@ -259,34 +248,26 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stm // process the options array given to core_sqlsrv_prepare. if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { + zend_ulong index = -1; + zend_string *key = NULL; + zval* value_z = NULL; - for( zend_hash_internal_pointer_reset( options_ht ); - zend_hash_has_more_elements( options_ht ) == SUCCESS; - zend_hash_move_forward( options_ht )) { + ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) { - char *key = NULL; - unsigned int key_len = 0; - unsigned long index = -1; - zval** value_z = NULL; + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - int type = zend_hash_get_current_key_ex( options_ht, &key, &key_len, &index, 0, NULL ); - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); - - core::sqlsrv_zend_hash_get_current_data( *(stmt->conn), options_ht, (void**) &value_z TSRMLS_CC ); - - const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); - - // if the key didn't match, then return the error to the script. - // The driver layer should ensure that the key is valid. - DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); - - // perform the actions the statement option needs done. - (*stmt_opt->func)( stmt, stmt_opt, *value_z TSRMLS_CC ); - } + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); - zend_hash_internal_pointer_end( options_ht ); + const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); + + // if the key didn't match, then return the error to the script. + // The driver layer should ensure that the key is valid. + DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); + + // perform the actions the statement option needs done. + (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); } sqlsrv_stmt* return_stmt = stmt; @@ -329,9 +310,9 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stm // Return: // Nothing, though an exception is thrown if an error occurs // The php type of the parameter is taken from the zval. -// The sql type is given as a hint if the driver provides it. +// The sql type is given as a hint if the driver provides it. -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int direction, zval* param_z, +void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, SQLSMALLINT decimal_digits TSRMLS_DC ) { @@ -352,13 +333,17 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire } // resize the statements array of int_ptrs if the parameter isn't already set. - if( stmt->param_ind_ptrs.size() < param_num + 1 ) { + 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 ]; - bool zval_was_null = (Z_TYPE_P( param_z ) == IS_NULL); - bool zval_was_bool = (Z_TYPE_P( param_z ) == IS_BOOL); + zval* param_ref = param_z; + if ( Z_ISREF_P( param_z ) ) { + ZVAL_DEREF( param_z ); + } + bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL ); + bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE ); // if the user asks for for a specific type for input and output, make sure the data type we send matches the data we // type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so // we always let that match if they want a string back. @@ -426,12 +411,12 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire // if the sql type is unknown, then set the default based on the PHP type passed in if( sql_type == SQL_UNKNOWN_TYPE ) { - default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); + default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); } // if the size is unknown, then set the default based on the PHP type passed in if( column_size == SQLSRV_UNKNOWN_SIZE ) { - default_sql_size_and_scale( stmt, param_num, param_z, encoding, column_size, decimal_digits TSRMLS_CC ); + default_sql_size_and_scale( stmt, static_cast( param_num ), param_z, encoding, column_size, decimal_digits TSRMLS_CC ); } // determine the ODBC C type @@ -448,15 +433,18 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire buffer_len = 0; } break; - case IS_BOOL: + case IS_TRUE: + case IS_FALSE: case IS_LONG: - { + { + // if it is boolean, set the lval to 0 or 1 + convert_to_long( param_z ); buffer = ¶m_z->value; - buffer_len = sizeof( param_z->value.lval ); + buffer_len = sizeof( Z_LVAL_P( param_z )); ind_ptr = buffer_len; if( direction != SQL_PARAM_INPUT ) { // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_z, param_num, zval_was_bool ); + sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool ); save_output_param_for_later( stmt, output_param TSRMLS_CC ); } } @@ -464,11 +452,11 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire case IS_DOUBLE: { buffer = ¶m_z->value; - buffer_len = sizeof( param_z->value.dval ); + buffer_len = sizeof( Z_DVAL_P( param_z )); ind_ptr = buffer_len; if( direction != SQL_PARAM_INPUT ) { // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_z, param_num, false ); + sqlsrv_output_param output_param( param_ref, static_cast( param_num ), false ); save_output_param_for_later( stmt, output_param TSRMLS_CC ); } } @@ -479,48 +467,43 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) { - zval_auto_ptr wbuffer_z; - ALLOC_INIT_ZVAL( wbuffer_z ); + zval wbuffer_z; + ZVAL_NULL( &wbuffer_z ); - bool converted = convert_input_param_to_utf16( param_z, wbuffer_z ); + bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z ); CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, param_num + 1, get_last_error_message() ) { throw core::CoreException(); } - buffer = Z_STRVAL_P( wbuffer_z ); - buffer_len = Z_STRLEN_P( wbuffer_z ); - core::sqlsrv_add_index_zval( *stmt, stmt->param_input_strings, param_num, wbuffer_z TSRMLS_CC ); - wbuffer_z.transferred(); + buffer = Z_STRVAL_P( &wbuffer_z ); + buffer_len = Z_STRLEN_P( &wbuffer_z ); + core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC); } ind_ptr = buffer_len; if( direction != SQL_PARAM_INPUT ) { - -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4 - // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, - // we reallocate the string if it's interned - if( IS_INTERNED( buffer )) { - ZVAL_STRINGL( param_z, static_cast(buffer), buffer_len, 1 ); - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - } -#endif - + // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, + // we reallocate the string if it's interned + if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) { + core::sqlsrv_zval_stringl( param_z, static_cast(buffer), buffer_len ); + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + } + // if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR) // or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type, // convert it to wchar first if( direction == SQL_PARAM_INPUT_OUTPUT && - (c_type == SQL_C_WCHAR || - (c_type == SQL_C_BINARY && - (sql_type == SQL_WCHAR || - sql_type == SQL_WVARCHAR || - sql_type == SQL_WLONGVARCHAR )))) { + ( c_type == SQL_C_WCHAR || + ( c_type == SQL_C_BINARY && + ( sql_type == SQL_WCHAR || + sql_type == SQL_WVARCHAR || + sql_type == SQL_WLONGVARCHAR )))) { bool converted = convert_input_param_to_utf16( param_z, param_z ); CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, param_num + 1, get_last_error_message() ) { throw core::CoreException(); } - sqlsrv_free( buffer ); buffer = Z_STRVAL_P( param_z ); buffer_len = Z_STRLEN_P( param_z ); ind_ptr = buffer_len; @@ -532,11 +515,12 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire buffer, buffer_len TSRMLS_CC ); // save the parameter to be adjusted and/or converted after the results are processed - sqlsrv_output_param output_param( param_z, encoding, param_num, buffer_len ); + sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len )); + save_output_param_for_later( stmt, output_param TSRMLS_CC ); // For output parameters, if we set the column_size to be same as the buffer_len, - // than if there is a truncation due to the data coming from the server being + // then if there is a truncation due to the data coming from the server being // greater than the column_size, we don't get any truncation error. In order to // avoid this silent truncation, we set the column_size to be "MAX" size for // string types. This will guarantee that there is no silent truncation for @@ -561,11 +545,10 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire { SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); sqlsrv_stream stream_encoding( param_z, encoding ); - HashTable* streams_ht = Z_ARRVAL_P( stmt->param_streams ); - core::sqlsrv_zend_hash_index_update( *stmt, streams_ht, param_num, &stream_encoding, sizeof( stream_encoding ) - TSRMLS_CC ); + HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC ); buffer = reinterpret_cast( param_num ); - zval_add_ref( ¶m_z ); // so that it doesn't go away while we're using it + Z_TRY_ADDREF_P( param_z ); // so that it doesn't go away while we're using it buffer_len = 0; ind_ptr = SQL_DATA_AT_EXEC; } @@ -573,18 +556,23 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire case IS_OBJECT: { SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - zval_auto_ptr function_z; - zval_auto_ptr buffer_z; - zval_auto_ptr format_z; - zval* params[1]; - bool valid_class_name_found = false; + zval function_z; + zval buffer_z; + zval format_z; + zval params[1]; + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( &buffer_z ); + ZVAL_UNDEF( &format_z ); + ZVAL_UNDEF( params ); - zend_class_entry *class_entry = zend_get_class_entry( param_z TSRMLS_CC ); + bool valid_class_name_found = false; + + zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC ); while( class_entry != NULL ) { - if( class_entry->name_length == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && - stricmp( class_entry->name, DateTime::DATETIME_CLASS_NAME ) == 0 ) { + if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && + stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) { valid_class_name_found = true; break; } @@ -599,40 +587,37 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { throw core::CoreException(); } - - ALLOC_INIT_ZVAL( buffer_z ); - ALLOC_INIT_ZVAL( function_z ); - ALLOC_INIT_ZVAL( format_z ); + // if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error' // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' // sql type, it lacks the timezone. if( sql_type == SQL_SS_TIMESTAMPOFFSET ) { - ZVAL_STRINGL( format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), - DateTime::DATETIMEOFFSET_FORMAT_LEN, 1 /* dup */ ); + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), + DateTime::DATETIMEOFFSET_FORMAT_LEN ); } else if( sql_type == SQL_TYPE_DATE ) { - ZVAL_STRINGL( format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN, 1 /* dup */ ); + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); } else { - ZVAL_STRINGL( format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN, - 1 /* dup */); + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); } // call the DateTime::format member function to convert the object to a string that SQL Server understands - ZVAL_STRINGL( function_z, "format", sizeof( "format" ) - 1, 1 ); + core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); params[0] = format_z; // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the // DateTime object and $format_z is the format string. - int zr = call_user_function( EG( function_table ), ¶m_z, function_z, buffer_z, 1, params TSRMLS_CC ); + int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); + zend_string_release( Z_STR( format_z )); + zend_string_release( Z_STR( function_z )); CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { throw core::CoreException(); } - buffer = Z_STRVAL_P( buffer_z ); - zr = add_next_index_zval( stmt->param_datetime_buffers, buffer_z ); + buffer = Z_STRVAL( buffer_z ); + zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z ); CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { throw core::CoreException(); } - buffer_len = Z_STRLEN_P( buffer_z ); - buffer_z.transferred(); + buffer_len = Z_STRLEN( buffer_z ) - 1; ind_ptr = buffer_len; break; } @@ -649,9 +634,8 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire ind_ptr = SQL_NULL_DATA; } - core::SQLBindParameter( stmt, param_num + 1, direction, c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, - &ind_ptr TSRMLS_CC ); - + core::SQLBindParameter( stmt, param_num + 1, direction, + c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); } catch( core::CoreException& e ) { stmt->free_param_data( TSRMLS_C ); @@ -670,13 +654,13 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len ) { + SQLRETURN r; + try { // close the stream to release the resource close_active_stream( stmt TSRMLS_CC ); - SQLRETURN r; - if( sql ) { sqlsrv_malloc_auto_ptr wsql_string; @@ -712,17 +696,24 @@ void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_ stmt->executed = true; // if all the data has been sent and no data was returned then finalize the output parameters - if( stmt->send_streams_at_exec && (r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { + if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { finalize_output_parameters( stmt TSRMLS_CC ); } - + // stream parameters are sent, clean the Hashtable + if ( stmt->send_streams_at_exec ) { + zend_hash_clean( Z_ARRVAL( stmt->param_streams )); + } } catch( core::CoreException& e ) { // if the statement executed but failed in a subsequent operation before returning, - // we need to cancel the statement - if( stmt->executed ) { + // we need to cancel the statement and deref the output and stream parameters + if ( stmt->send_streams_at_exec ) { + zend_hash_clean( Z_ARRVAL( stmt->output_params )); + zend_hash_clean( Z_ARRVAL( stmt->param_streams )); + } + if( stmt->executed ) { SQLCancel( stmt->handle() ); // stmt->executed = false; should this be reset if something fails? } @@ -742,7 +733,7 @@ void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_ // Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the // user scrolls past a non-scrollable result set -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) +bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ) { // pre-condition check SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE, @@ -751,7 +742,7 @@ bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN try { // clear the field cache of the previous fetch - zend_hash_clean( Z_ARRVAL_P( stmt->field_cache )); + zend_hash_clean( Z_ARRVAL( stmt->field_cache )); CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { throw core::CoreException(); @@ -879,101 +870,100 @@ field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT coln // Nothing, excpetion thrown if an error occurs void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string, - __out void** field_value, __out SQLLEN* field_len, bool cache_field, - __out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC ) + _Out_ void*& field_value, _Out_ SQLLEN* field_len, bool cache_field, + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) { - try { - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - // if the field has been retrieved before, return the previous result - field_cache* cached = NULL; - if( zend_hash_index_find( Z_ARRVAL_P( stmt->field_cache ), field_index, (void**) &cached ) == SUCCESS ) { - // the field value is NULL - if( cached->value == NULL ) { - *field_value = NULL; - *field_len = 0; - if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } - } - else { - - *field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); - memcpy( *field_value, 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'; - } - *field_len = cached->len; - if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = static_cast( cached->type.typeinfo.type ); } - } - return; - } + try { - sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; + // close the stream to release the resource + close_active_stream(stmt TSRMLS_CC); - SQLLEN sql_field_type = 0; - SQLLEN sql_field_len = 0; + // if the field has been retrieved before, return the previous result + field_cache* cached = NULL; + if (NULL != ( cached = static_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast( field_index ))))) { + // the field value is NULL + if( cached->value == NULL ) { + field_value = NULL; + *field_len = 0; + if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } + } + else { - // Make sure that the statement was executed and not just prepared. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } + field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); + 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'; + } + *field_len = cached->len; + if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } + } + return; + } - // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they - // may also be retrieved. - if( cache_field && ( field_index - stmt->last_field_index ) >= 2 ) { - sqlsrv_phptype invalid; - invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { - SQLSRV_ASSERT( zend_hash_index_find( Z_ARRVAL_P( stmt->field_cache ), i, (void**) &cached ) == FAILURE, - "Field already cached." ); - core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, - sqlsrv_php_type_out TSRMLS_CC ); - // delete the value returned since we only want it cached, not the actual value - if( *field_value ) { - efree( *field_value ); - *field_value = NULL; - *field_len = 0; - } - } - } + sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; - // If the php type was not specified set the php type to be the default type. - if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { - - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Get the length of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); + SQLLEN sql_field_type = 0; + SQLLEN sql_field_len = 0; - // Get the corresponding php type from the sql type. - sqlsrv_php_type = stmt->sql_type_to_php_type( sql_field_type, sql_field_len, prefer_string ); - } + // Make sure that the statement was executed and not just prepared. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } - // Verify that we have an acceptable type to convert. - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { - throw core::CoreException(); - } - - if( sqlsrv_php_type_out != NULL ) - *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); + // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they + // may also be retrieved. + if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { + sqlsrv_phptype invalid; + invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { + SQLSRV_ASSERT((cached = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->field_cache), i))) == NULL, + "Field already cached." ); + core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, + sqlsrv_php_type_out TSRMLS_CC ); + // delete the value returned since we only want it cached, not the actual value + if( field_value ) { + efree( field_value ); + field_value = NULL; + *field_len = 0; + } + } + } - // Retrieve the data - core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + // If the php type was not specified set the php type to be the default type. + if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { - // if the user wants us to cache the field, we'll do it - if( cache_field ) { - field_cache cache( *field_value, *field_len, sqlsrv_php_type ); - core::sqlsrv_zend_hash_index_update( *stmt, Z_ARRVAL_P( stmt->field_cache ), field_index, &cache, - sizeof( field_cache ) TSRMLS_CC ); - } - } + // Get the SQL type of the field. + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - catch( core::CoreException& e) { - throw e; - } + // Get the length of the field. + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); + + // Get the corresponding php type from the sql type. + sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast( sql_field_type ), static_cast( sql_field_len ), prefer_string ); + } + + // Verify that we have an acceptable type to convert. + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { + throw core::CoreException(); + } + + if( sqlsrv_php_type_out != NULL ) + *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); + + // Retrieve the data + core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + + // if the user wants us to cache the field, we'll do it + if( cache_field ) { + field_cache cache( field_value, *field_len, sqlsrv_php_type ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); + } + } + + catch( core::CoreException& e ) { + throw e; + } } // core_sqlsrv_has_any_result @@ -1025,7 +1015,7 @@ void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_ if( r == SQL_NO_DATA ) { - if( stmt->output_params && finalize_output_params ) { + if( &(stmt->output_params) && finalize_output_params ) { // if we're finished processing result sets, handle the output parameters finalize_output_parameters( stmt TSRMLS_CC ); } @@ -1054,28 +1044,28 @@ void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_ // Returns: // Nothing, exception thrown if problem occurs -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, unsigned int param_num, zval* param_z TSRMLS_DC ) +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* param_z TSRMLS_DC ) { - SQLSRV_ASSERT( Z_TYPE_P( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); - SQLSRV_ASSERT( Z_TYPE_P( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); + SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); + SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); // if the parameter was an input string, delete it from the array holding input parameter strings - if( zend_hash_index_exists( Z_ARRVAL_P( stmt->param_input_strings ), param_num )) { - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL_P( stmt->param_input_strings ), param_num TSRMLS_CC ); + if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) { + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC ); } // if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams // PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it // with sqlsrv_send_stream_data. - if( zend_hash_index_exists( Z_ARRVAL_P( stmt->param_streams ), param_num )) { - sqlsrv_stream* stream_encoding; - zend_hash_index_find( Z_ARRVAL_P( stmt->param_streams ), param_num, (void**) &stream_encoding ); - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL_P( stmt->param_streams ), param_num TSRMLS_CC ); + if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) { + sqlsrv_stream* stream_encoding = NULL; + stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->param_streams), param_num)); + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC ); } } //Calls SQLSetStmtAttr to set a cursor. -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned int cursor_type TSRMLS_DC ) +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ) { try { @@ -1129,7 +1119,7 @@ void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRM core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); } -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, long limit TSRMLS_DC ) +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ) { if( limit <= 0 ) { @@ -1154,7 +1144,7 @@ void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) ); } - core_sqlsrv_set_query_timeout( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); + core_sqlsrv_set_query_timeout( stmt, static_cast( Z_LVAL_P( value_z )) TSRMLS_CC ); } catch( core::CoreException& ) { throw; @@ -1169,7 +1159,7 @@ void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ) DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." ); // set the statement attribute - core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( timeout ), SQL_IS_UINTEGER TSRMLS_CC ); + core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC ); // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which // is represented by -1. @@ -1232,7 +1222,7 @@ bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) // get the stream from the zval we bound php_stream* param_stream = NULL; - core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, &stmt->current_stream.stream_z TSRMLS_CC ); + core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); // if we're at the end, then release our current parameter if( php_stream_eof( param_stream )) { @@ -1248,9 +1238,16 @@ bool core_sqlsrv_send_stream_packet( 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 ]; - size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character - size_t read = php_stream_read( param_stream, buffer, buffer_size ); - stmt->current_stream_read += read; + 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 ); + + if (read > UINT_MAX) + { + LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); + throw core::CoreException(); + } + + stmt->current_stream_read += static_cast( read ); if( read > 0 ) { // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it @@ -1264,7 +1261,7 @@ bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, read, wbuffer, sizeof( wbuffer ) / sizeof( wchar_t )); + buffer, static_cast( read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) { // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more @@ -1280,7 +1277,7 @@ bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) } // try the conversion again with the complete character wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, read + new_read, wbuffer, sizeof( wbuffer ) / sizeof( wchar_t )); + buffer, static_cast( read + new_read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); // something else must be wrong if it failed CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { @@ -1308,7 +1305,6 @@ bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) return true; } - void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) { TSRMLS_C; @@ -1335,23 +1331,23 @@ void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_opti // internal function to release the active stream. Called by each main API function // that will alter the statement and cancel any retrieval of data from a stream. -void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC ) +void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { // if there is no active stream, return - if( stmt->active_stream == NULL ) { + if( Z_TYPE( stmt->active_stream ) == IS_UNDEF ) { return; } php_stream* stream = NULL; // we use no verify since verify would return immediately and we want to assert, not return. - php_stream_from_zval_no_verify( stream, &stmt->active_stream ); + php_stream_from_zval_no_verify( stream, &( stmt->active_stream )); SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." ); php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here. - SQLSRV_ASSERT( stmt->active_stream == NULL, "close_active_stream: Active stream not closed." ); + SQLSRV_ASSERT( Z_TYPE( stmt->active_stream ) == IS_UNDEF, "close_active_stream: Active stream not closed." ); } @@ -1359,7 +1355,7 @@ void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC ) namespace { -bool is_streamable_type( SQLINTEGER sql_type ) +bool is_streamable_type( SQLLEN sql_type ) { switch( sql_type ) { case SQL_CHAR: @@ -1378,7 +1374,7 @@ bool is_streamable_type( SQLINTEGER sql_type ) return false; } -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, __out SQLLEN& size TSRMLS_DC ) +void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ) { try { @@ -1474,200 +1470,204 @@ size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_e // The memory allocation has to happen in the core layer because otherwise // the driver layer would have to calculate size of the field_value // to decide the amount of memory allocation. - -void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ) +void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype + sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) { - try { + try { - close_active_stream( stmt TSRMLS_CC ); + close_active_stream( stmt TSRMLS_CC ); - // make sure that fetch is called before trying to retrieve. - CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { - throw core::CoreException(); - } + // make sure that fetch is called before trying to retrieve. + CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { + throw core::CoreException(); + } - // make sure that fields are not retrieved incorrectly. - CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, - stmt->last_field_index ) { - throw core::CoreException(); - } + // make sure that fields are not retrieved incorrectly. + CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, + stmt->last_field_index ) { + throw core::CoreException(); + } - switch( sqlsrv_php_type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_INT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); + switch( sqlsrv_php_type.typeinfo.type ) { - SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } + case SQLSRV_PHPTYPE_INT: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), + field_len, true /*handle_warning*/ TSRMLS_CC ); - if( *field_len == SQL_NULL_DATA ) { - *field_value = NULL; - break; - } - - *field_value = field_value_temp; - field_value_temp.transferred(); - break; - } + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } - case SQLSRV_PHPTYPE_FLOAT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } + if( *field_len == SQL_NULL_DATA ) { + field_value = NULL; + break; + } - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException (); - } + field_value = field_value_temp; + field_value_temp.transferred(); + break; + } - if( *field_len == SQL_NULL_DATA ) { - *field_value = NULL; - break; - } - - *field_value = field_value_temp; - field_value_temp.transferred(); - break; - } - - case SQLSRV_PHPTYPE_STRING: - { - get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - break; - } - - // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and - // convert it to a DateTime object and return the created object - case SQLSRV_PHPTYPE_DATETIME: - { - char field_value_temp[ MAX_DATETIME_STRING_LEN ]; - zval_auto_ptr field_value_temp_z; - zval_auto_ptr return_value_z; - zval_auto_ptr function_z; - zval* params[1]; - ALLOC_INIT_ZVAL( field_value_temp_z ); - ALLOC_INIT_ZVAL( function_z ); - ALLOC_INIT_ZVAL( return_value_z ); + case SQLSRV_PHPTYPE_FLOAT: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), + field_len, true /*handle_warning*/ TSRMLS_CC ); - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, - MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException (); - } + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } - if( *field_len == SQL_NULL_DATA ) { - ZVAL_NULL( return_value_z ); - *field_value = reinterpret_cast( return_value_z.get() ); - return_value_z.transferred(); - break; - } - - // Convert the string date to a DateTime object - ZVAL_STRINGL( field_value_temp_z, field_value_temp, *field_len, 1 ); - ZVAL_STRINGL( function_z, "date_create", sizeof("date_create") -1, 1 ); - params[0] = field_value_temp_z; + if( *field_len == SQL_NULL_DATA ) { + field_value = NULL; + break; + } - if( call_user_function( EG( function_table ), NULL, function_z, return_value_z, 1, - params TSRMLS_CC ) == FAILURE ) { - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED ); - } + field_value = field_value_temp; + field_value_temp.transferred(); + break; + } - *field_value = reinterpret_cast( return_value_z.get() ); - return_value_z.transferred(); - break; - } - - // create a stream wrapper around the field and return that object to the PHP script. calls to fread - // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file - // for how these fields are used. - case SQLSRV_PHPTYPE_STREAM: - { - zval_auto_ptr return_value_z; - php_stream* stream = NULL; - sqlsrv_stream* ss = NULL; - ALLOC_INIT_ZVAL( return_value_z ); - SQLINTEGER sql_type; + case SQLSRV_PHPTYPE_STRING: + { + get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + break; + } - SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { - throw core::CoreException(); - } - - stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); + // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and + // convert it to a DateTime object and return the created object + case SQLSRV_PHPTYPE_DATETIME: + { + char field_value_temp[ MAX_DATETIME_STRING_LEN ]; + zval params[1]; + zval field_value_temp_z; + zval function_z; - CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { - throw core::CoreException(); - } + ZVAL_UNDEF( &field_value_temp_z ); + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( params ); - ss = static_cast( stream->abstract ); - ss->stmt = stmt; - ss->field_index = field_index; - ss->sql_type = static_cast( sql_type ); - ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, + MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); - // turn our stream into a zval to be returned - php_stream_to_zval( stream, return_value_z ); + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } - // mark this as our active stream - stmt->active_stream = return_value_z; - *field_value = reinterpret_cast( return_value_z.get() ); - return_value_z.transferred(); + zval_auto_ptr return_value_z; + return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); + ZVAL_UNDEF( return_value_z ); - break; - } + if( *field_len == SQL_NULL_DATA ) { + ZVAL_NULL( return_value_z ); + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + break; + } - case SQLSRV_PHPTYPE_NULL: - *field_value = NULL; - *field_len = 0; - break; + // Convert the string date to a DateTime object + core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); + core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 ); + params[0] = field_value_temp_z; - default: - DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); - break; - } + if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, + params TSRMLS_CC ) == FAILURE) { + THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); + } - // sucessfully retrieved the field, so update our last retrieved field - if( stmt->last_field_index < field_index ) { - stmt->last_field_index = field_index; - } - } - catch( core::CoreException& e ) { - throw e; - } + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + zend_string_free( Z_STR( field_value_temp_z )); + zend_string_free( Z_STR( function_z )); + break; + } + + // create a stream wrapper around the field and return that object to the PHP script. calls to fread + // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file + // for how these fields are used. + case SQLSRV_PHPTYPE_STREAM: + { + + php_stream* stream = NULL; + sqlsrv_stream* ss = NULL; + SQLLEN sql_type; + + SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { + throw core::CoreException(); + } + + stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); + + CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { + throw core::CoreException(); + } + + ss = static_cast( stream->abstract ); + ss->stmt = stmt; + ss->field_index = field_index; + ss->sql_type = static_cast( sql_type ); + ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); + + zval_auto_ptr return_value_z; + return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); + ZVAL_UNDEF( return_value_z ); + + // turn our stream into a zval to be returned + php_stream_to_zval( stream, return_value_z ); + + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + break; + } + + case SQLSRV_PHPTYPE_NULL: + field_value = NULL; + *field_len = 0; + break; + + default: + DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); + break; + } + + // sucessfully retrieved the field, so update our last retrieved field + if( stmt->last_field_index < field_index ) { + stmt->last_field_index = field_index; + } + } + catch( core::CoreException& e ) { + throw e; + } } // check_for_next_stream_parameter // see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise // returns false - -bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt TSRMLS_DC ) +bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { int stream_index = 0; SQLRETURN r = SQL_SUCCESS; - sqlsrv_stream* stream_encoding; + sqlsrv_stream* stream_encoding = NULL; zval* param_z = NULL; // get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param @@ -1679,11 +1679,11 @@ bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt TSRMLS_DC ) return false; } - HashTable* streams_ht = Z_ARRVAL_P( stmt->param_streams ); + HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); // pull out the sqlsrv_encoding struct - int zr = zend_hash_index_find( streams_ht, stream_index, (void**) &stream_encoding ); - SQLSRV_ASSERT( zr == SUCCESS, "Stream parameter does not exist" ); // if the index isn't in the hash, that's a serious error + stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(streams_ht, stream_index)); + SQLSRV_ASSERT(stream_encoding != NULL, "Stream parameter does not exist"); // if the index isn't in the hash, that's a serious error param_z = stream_encoding->stream_z; @@ -1704,18 +1704,26 @@ bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z "convert_input_param_z called with invalid parameter states" ); const char* buffer = Z_STRVAL_P( input_param_z ); - int buffer_len = Z_STRLEN_P( input_param_z ); + std::size_t buffer_len = Z_STRLEN_P( input_param_z ); int wchar_size; + if (buffer_len > INT_MAX) + { + LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } + // if the string is empty, then just return that the conversion succeeded as // MultiByteToWideChar will "fail" on an empty string. if( buffer_len == 0 ) { - ZVAL_STRINGL( converted_param_z, "", 0, 1 ); + core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); return true; } // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string - wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), buffer_len, NULL, 0 ); + wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, + reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); + // if there was a problem determining the size of the string, return false if( wchar_size == 0 ) { return false; @@ -1724,7 +1732,7 @@ bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) )); // convert the utf-8 string to a wchar string in the new buffer int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), - buffer_len, wbuffer, wchar_size ); + static_cast( buffer_len ), wbuffer, wchar_size ); // if there was a problem converting the string, then free the memory and return false if( r == 0 ) { return false; @@ -1732,8 +1740,9 @@ bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z // null terminate the string, set the size within the zval, and return success wbuffer[ wchar_size ] = L'\0'; - ZVAL_STRINGL( converted_param_z, reinterpret_cast( wbuffer.get() ), - wchar_size * sizeof( wchar_t ), 0 ); + core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), + wchar_size * sizeof( wchar_t ) ); + sqlsrv_free(wbuffer); wbuffer.transferred(); return true; @@ -1741,7 +1750,7 @@ bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z // returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) +SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) { SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; int php_type = Z_TYPE_P( param_z ); @@ -1762,9 +1771,18 @@ SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* break; } break; - case IS_BOOL: + case IS_TRUE: + case IS_FALSE: case IS_LONG: - sql_c_type = SQL_C_LONG; + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) + { + sql_c_type = SQL_C_SBIGINT; + } + else + { + sql_c_type = SQL_C_SLONG; + } break; case IS_DOUBLE: sql_c_type = SQL_C_DOUBLE; @@ -1803,12 +1821,11 @@ SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* // given a zval and encoding, determine the appropriate sql type -void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - __out SQLSMALLINT& sql_type TSRMLS_DC ) +void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLSMALLINT& sql_type TSRMLS_DC ) { sql_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P( param_z ); - + int php_type = Z_TYPE_P(param_z); switch( php_type ) { case IS_NULL: @@ -1825,9 +1842,19 @@ void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, S break; } break; - case IS_BOOL: + case IS_TRUE: + case IS_FALSE: case IS_LONG: - sql_type = SQL_INTEGER; + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) + { + sql_type = SQL_BIGINT; + } + else + { + sql_type = SQL_INTEGER; + } + break; case IS_DOUBLE: sql_type = SQL_FLOAT; @@ -1874,7 +1901,7 @@ void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, S // given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate) void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - __out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC ) + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) { int php_type = Z_TYPE_P( param_z ); column_size = 0; @@ -1886,19 +1913,21 @@ void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* column_size = 1; break; // size is not necessary for these types, they are inferred by ODBC - case IS_BOOL: + case IS_TRUE: + case IS_FALSE: case IS_LONG: case IS_DOUBLE: case IS_RESOURCE: break; case IS_STRING: { - SQLULEN byte_len = Z_STRLEN_P( param_z ) * ((encoding == SQLSRV_ENCODING_UTF8) ? sizeof( wchar_t ) : sizeof( char )); + size_t char_size = ( encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( wchar_t ) : sizeof( char ); + SQLULEN byte_len = Z_STRLEN_P( param_z ) * char_size; if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { column_size = SQL_SERVER_MAX_TYPE_SIZE; } else { - column_size = Z_STRLEN_P( param_z ); + column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size; } break; } @@ -1924,13 +1953,14 @@ void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* } } -void field_cache_dtor( void* data ) +void field_cache_dtor( zval* data_z ) { - field_cache* cache = reinterpret_cast( data ); + field_cache* cache = static_cast( Z_PTR_P( data_z )); if( cache->value ) { sqlsrv_free( cache->value ); } + sqlsrv_free( cache ); } @@ -1942,325 +1972,329 @@ void field_cache_dtor( void* data ) void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) { - if( stmt->output_params == NULL ) + if( Z_ISUNDEF(stmt->output_params) ) return; bool converted = true; - HashTable* params_ht = Z_ARRVAL_P( stmt->output_params ); + HashTable* params_ht = Z_ARRVAL( stmt->output_params ); + zend_ulong index = -1; + zend_string* key = NULL; + void* output_param_temp = NULL; - for( zend_hash_internal_pointer_reset( params_ht ); - zend_hash_has_more_elements( params_ht ) == SUCCESS; - zend_hash_move_forward( params_ht ) ) { + ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { + sqlsrv_output_param* output_param = static_cast( output_param_temp ); + zval* value_z = Z_REFVAL_P( output_param->param_z ); + switch( Z_TYPE_P( value_z )) { + case IS_STRING: + { + // 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 ]; + if( str_len == SQL_NULL_DATA ) { + zend_string_release( Z_STR_P( value_z )); + ZVAL_NULL( value_z ); + continue; + } - sqlsrv_output_param *output_param; - core::sqlsrv_zend_hash_get_current_data( *stmt, params_ht, (void**) &output_param TSRMLS_CC ); - - switch( Z_TYPE_P( output_param->param_z )) { - case IS_STRING: - { - // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter - char* str = Z_STRVAL_P( output_param->param_z ); - SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ]; - if( str_len == SQL_NULL_DATA ) { - ZVAL_NULL( output_param->param_z ); - continue; - } - - // if there was more to output than buffer size to hold it, then throw a truncation error - int null_size = 0; - switch( output_param->encoding ) { - case SQLSRV_ENCODING_UTF8: - null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16 - break; - case SQLSRV_ENCODING_SYSTEM: - null_size = 1; - break; - case SQLSRV_ENCODING_BINARY: - null_size = 0; - break; - default: - SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); - break; - } - CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { - throw core::CoreException(); - } - - // if it's not in the 8 bit encodings, then it's in UTF-16 - if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { - - bool converted = convert_string_from_utf16_inplace( output_param->encoding, &str, str_len ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { - // 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'; - } - // set the string length - ZVAL_STRINGL( output_param->param_z, str, str_len, 0 ); - } + // if there was more to output than buffer size to hold it, then throw a truncation error + int null_size = 0; + switch( output_param->encoding ) { + case SQLSRV_ENCODING_UTF8: + null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16 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 ) { - ZVAL_NULL( output_param->param_z ); - } - else if( output_param->is_bool ) { - convert_to_boolean( output_param->param_z ); - } + case SQLSRV_ENCODING_SYSTEM: + null_size = 1; break; - case IS_DOUBLE: - // 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 ) { - ZVAL_NULL( output_param->param_z ); - } + case SQLSRV_ENCODING_BINARY: + null_size = 0; break; default: - DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); + SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); break; + } + CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { + throw core::CoreException(); + } + + // if it's not in the 8 bit encodings, then it's in UTF-16 + if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { + bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { + // 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'; + core::sqlsrv_zval_stringl(value_z, str, str_len); + } + else { + core::sqlsrv_zval_stringl(value_z, str, str_len); + } } - } + 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 ) { + ZVAL_NULL( value_z ); + } + else if( output_param->is_bool ) { + convert_to_boolean( value_z ); + } + else + { + ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); + } + break; + case IS_DOUBLE: + // 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 ) { + ZVAL_NULL( value_z ); + } + break; + default: + DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); + break; + } + value_z = NULL; + } ZEND_HASH_FOREACH_END(); // empty the hash table since it's been processed - zend_hash_clean( Z_ARRVAL_P( stmt->output_params )); - + zend_hash_clean( Z_ARRVAL( stmt->output_params )); return; } -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ) +void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, + _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) { - SQLRETURN r; - SQLSMALLINT c_type; - SQLLEN sql_field_type = 0; - SQLSMALLINT extra = 0; - SQLLEN field_len_temp; - SQLLEN sql_display_size = 0; - char* field_value_temp = NULL; + SQLRETURN r; + SQLSMALLINT c_type; + SQLLEN sql_field_type = 0; + SQLSMALLINT extra = 0; + SQLLEN field_len_temp; + SQLLEN sql_display_size = 0; + char* field_value_temp = NULL; - try { + try { - DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, - "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); - - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); - } + DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, + "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); - // Set the C type and account for null characters at the end of the data. - switch( sqlsrv_php_type.typeinfo.encoding ) { - case CP_UTF8: - c_type = SQL_C_WCHAR; - extra = sizeof( SQLWCHAR ); - break; - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - extra = 0; - break; - default: - c_type = SQL_C_CHAR; - extra = sizeof( SQLCHAR ); - break; - } + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); + } - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + // Set the C type and account for null characters at the end of the data. + switch( sqlsrv_php_type.typeinfo.encoding ) { + case CP_UTF8: + c_type = SQL_C_WCHAR; + extra = sizeof( SQLWCHAR ); + break; + case SQLSRV_ENCODING_BINARY: + c_type = SQL_C_BINARY; + extra = 0; + break; + default: + c_type = SQL_C_CHAR; + extra = sizeof( SQLCHAR ); + break; + } - // Calculate the field size. - calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); + // Get the SQL type of the field. + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - // if this is a large type, then read the first few bytes to get the actual length from SQLGetData - if( sql_display_size == 0 || sql_display_size == LONG_MAX || - sql_display_size == LONG_MAX >> 1 || sql_display_size == ULONG_MAX - 1 ) { + // Calculate the field size. + calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); - field_len_temp = INITIAL_FIELD_STRING_LEN; - - field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); - - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), - &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); - - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException (); - } + // if this is a large type, then read the first few bytes to get the actual length from SQLGetData + if( sql_display_size == 0 || sql_display_size == INT_MAX || + sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { - if( field_len_temp == SQL_NULL_DATA ) { - *field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } + field_len_temp = INITIAL_FIELD_STRING_LEN; - if( r == SQL_SUCCESS_WITH_INFO ) { + field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - - stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - - if( is_truncated_warning( state ) ) { - - SQLINTEGER dummy_field_len; + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), + &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); - // for XML (and possibly other conditions) the field length returned is not the real field length, so - // in every pass, we double the allocation size to retrieve all the contents. - if( field_len_temp == SQL_NO_TOTAL ) { - - // reset the field_len_temp - field_len_temp = INITIAL_FIELD_STRING_LEN; + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } - do { + if( field_len_temp == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } - SQLINTEGER initial_field_len = field_len_temp; - // Double the size. - field_len_temp *= 2; - - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - field_len_temp -= initial_field_len; + if( r == SQL_SUCCESS_WITH_INFO ) { - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, - field_len_temp + extra, &dummy_field_len, - false /*handle_warning*/ TSRMLS_CC ); + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; - // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL - // so we calculate the actual length of the string with that. - if( dummy_field_len != SQL_NO_TOTAL ) - field_len_temp += dummy_field_len; - else - field_len_temp += initial_field_len; + stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - if( r == SQL_SUCCESS_WITH_INFO ) { - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - } + if( is_truncated_warning( state )) { - } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); - } - else { + SQLLEN dummy_field_len; - // We got the field_len_temp from SQLGetData call. - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - // We have already recieved INITIAL_FIELD_STRING_LEN size data. - field_len_temp -= INITIAL_FIELD_STRING_LEN; - - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, - field_len_temp + extra, &dummy_field_len, - true /*handle_warning*/ TSRMLS_CC ); + // for XML (and possibly other conditions) the field length returned is not the real field length, so + // in every pass, we double the allocation size to retrieve all the contents. + if( field_len_temp == SQL_NO_TOTAL ) { - if( dummy_field_len == SQL_NULL_DATA ) { - *field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException (); - } - - field_len_temp += INITIAL_FIELD_STRING_LEN; - } + // reset the field_len_temp + field_len_temp = INITIAL_FIELD_STRING_LEN; - } // if( is_truncation_warning ( state ) ) - else { - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - } - } // if( r == SQL_SUCCESS_WITH_INFO ) + do { + SQLLEN initial_field_len = field_len_temp; + // Double the size. + field_len_temp *= 2; - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { - throw core::CoreException (); - } - } - } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) - - else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { + field_len_temp -= initial_field_len; - // only allow binary retrievals for char and binary types. All others get a string converted - // to the encoding type they asked for. - - // null terminator - if( c_type == SQL_C_CHAR ) { - sql_display_size += sizeof( SQLCHAR ); - } + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, + field_len_temp + extra, &dummy_field_len, + false /*handle_warning*/ TSRMLS_CC ); - // For WCHAR multiply by sizeof(WCHAR) and include the null terminator - else if( c_type == SQL_C_WCHAR ) { - sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); - } + // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL + // so we calculate the actual length of the string with that. + if( dummy_field_len != SQL_NO_TOTAL ) + field_len_temp += dummy_field_len; + else + field_len_temp += initial_field_len; - field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); - - // get the data - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, - &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); - CHECK_SQL_ERROR( r, stmt ) { - throw core::CoreException(); - } - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException (); - } + if( r == SQL_SUCCESS_WITH_INFO ) { + core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len + TSRMLS_CC ); + } - if( field_len_temp == SQL_NULL_DATA ) { - *field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { + } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); + } + else { - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { - throw core::CoreException (); - } - } - } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) + // We got the field_len_temp from SQLGetData call. + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - else { - - DIE( "Invalid sql_display_size" ); - return; // to eliminate a warning - } - - *field_value = field_value_temp; - *field_len = field_len_temp; - - // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP - // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. - // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary - // operator to set add 1 to fill the null terminator - field_value_temp[field_len_temp] = '\0'; - } + // We have already recieved INITIAL_FIELD_STRING_LEN size data. + field_len_temp -= INITIAL_FIELD_STRING_LEN; - catch( core::CoreException& ) { + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, + field_len_temp + extra, &dummy_field_len, + true /*handle_warning*/ TSRMLS_CC ); - *field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - catch ( ... ) { + if( dummy_field_len == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } - *field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + field_len_temp += INITIAL_FIELD_STRING_LEN; + } + + } // if( is_truncation_warning ( state ) ) + else { + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + } + } // if( r == SQL_SUCCESS_WITH_INFO ) + + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { + + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) + + else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { + + // only allow binary retrievals for char and binary types. All others get a string converted + // to the encoding type they asked for. + + // null terminator + if( c_type == SQL_C_CHAR ) { + sql_display_size += sizeof( SQLCHAR ); + } + + // For WCHAR multiply by sizeof(WCHAR) and include the null terminator + else if( c_type == SQL_C_WCHAR ) { + sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); + } + + field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); + + // get the data + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, + &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); + CHECK_SQL_ERROR( r, stmt ) { + throw core::CoreException(); + } + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( field_len_temp == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { + + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) + + else { + + DIE( "Invalid sql_display_size" ); + return; // to eliminate a warning + } + + field_value = field_value_temp; + *field_len = field_len_temp; + + // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP + // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. + // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary + // operator to set add 1 to fill the null terminator + field_value_temp[field_len_temp] = '\0'; + } + + catch( core::CoreException& ) { + + field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } + catch ( ... ) { + + field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } } @@ -2268,7 +2302,7 @@ void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_ph // return the option from the stmt_opts array that matches the key. If no option found, // NULL is returned. -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, unsigned long key, const stmt_option stmt_opts[] TSRMLS_DC ) +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ) { for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { @@ -2334,13 +2368,12 @@ bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) // string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and // stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsigned int paramno, SQLSRV_ENCODING encoding, +void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, SQLLEN& buffer_len TSRMLS_DC ) { SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); buffer_len = Z_STRLEN_P( param_z ); - buffer = Z_STRVAL_P( param_z ); SQLLEN expected_len; SQLLEN buffer_null_extra; SQLLEN elem_size; @@ -2371,20 +2404,24 @@ void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsign // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about // not having a NULL terminator on a string. - buffer = static_cast( sqlsrv_realloc( buffer, expected_len )); - buffer_len = expected_len; // set the buffer_len to the new allocation size (includes the null terminator taken out below) + zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); // A zval string len doesn't include the null. This calculates the length it should be // regardless of whether the ODBC type contains the NULL or not. - ZVAL_STRINGL( param_z, reinterpret_cast( buffer ), without_null_len, 0 ); - + // null terminate the string to avoid a warning in debug PHP builds - (static_cast(buffer))[ without_null_len ] = '\0'; - } + ZSTR_VAL(param_z_string)[without_null_len] = '\0'; + ZVAL_NEW_STR(param_z, param_z_string); - // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the - // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY - buffer_len -= buffer_null_extra; + // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the + // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY + buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; + + // Zend string length doesn't include the null terminator + ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; + } + + buffer = Z_STRVAL_P(param_z); // 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, @@ -2394,18 +2431,16 @@ void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsign } } - // output parameters have their reference count incremented so that they do not disappear // while the query is executed and processed. They are saved in the statement so that // their reference count may be decremented later (after results are processed) void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ) { - HashTable* param_ht = Z_ARRVAL_P( stmt->output_params ); - int paramno = param.param_num; - core::sqlsrv_zend_hash_index_update( *stmt, param_ht, paramno, ¶m, sizeof( param ) - TSRMLS_CC ); - zval_add_ref( ¶m.param_z ); // we have a reference to the param + HashTable* param_ht = Z_ARRVAL( stmt->output_params ); + zend_ulong paramno = static_cast( param.param_num ); + core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, ¶m, sizeof( sqlsrv_output_param )); + Z_TRY_ADDREF_P( param.param_z ); // we have a reference to the param } @@ -2418,19 +2453,19 @@ void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ) // called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed - -void sqlsrv_output_param_dtor( void* data ) +void sqlsrv_output_param_dtor( zval* data ) { - sqlsrv_output_param *output_param = reinterpret_cast( data ); - zval_ptr_dtor( &output_param->param_z ); // undo the reference to the string we will no longer hold + sqlsrv_output_param *output_param = static_cast( Z_PTR_P( data )); + zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold + sqlsrv_free( output_param ); } // called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed - -void sqlsrv_stream_dtor( void* data ) +void sqlsrv_stream_dtor( zval* data ) { - sqlsrv_stream* stream_encoding = reinterpret_cast( data ); - zval_ptr_dtor( &stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold + sqlsrv_stream* stream_encoding = static_cast( Z_PTR_P( data )); + zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold + sqlsrv_free( stream_encoding ); } } diff --git a/pdo_sqlsrv/core_stream.cpp b/pdo_sqlsrv/core_stream.cpp index 4cd5eab4..b85c63ce 100644 --- a/pdo_sqlsrv/core_stream.cpp +++ b/pdo_sqlsrv/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -32,11 +32,8 @@ int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) // free the stream resources in the Zend engine php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM ); - // NULL out the stream zval and delete our reference count to it. - ZVAL_NULL( ss->stmt->active_stream ); - - // there is no active stream - ss->stmt->active_stream = NULL; + // UNDEF the stream zval and delete our reference count to it. + ZVAL_UNDEF( &( ss->stmt->active_stream ) ); sqlsrv_free( ss ); stream->abstract = NULL; @@ -48,10 +45,9 @@ int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) // read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are // set when sqlsrv_get_field is called by the user specifying which field type they want. -size_t sqlsrv_stream_read( php_stream* stream, __out_bcount(count) char* buf, size_t count TSRMLS_DC ) +size_t sqlsrv_stream_read( php_stream* stream, _Out_writes_bytes_(count) char* buf, size_t count TSRMLS_DC ) { - - SQLINTEGER read = 0; + SQLLEN read = 0; SQLSMALLINT c_type = SQL_C_CHAR; char* get_data_buffer = buf; sqlsrv_malloc_auto_ptr temp_buf; @@ -145,21 +141,28 @@ size_t sqlsrv_stream_read( php_stream* stream, __out_bcount(count) char* buf, si } // if the encoding is UTF-8 - if( c_type == SQL_C_WCHAR ) { - - count *= 2; // undo the shift to use the full buffer + if (c_type == SQL_C_WCHAR) { - // flags set to 0 by default, which means that any invalid characters are dropped rather than causing - // an error. This happens only on XP. - DWORD flags = 0; + count *= 2; // undo the shift to use the full buffer + + // flags set to 0 by default, which means that any invalid characters are dropped rather than causing + // an error. This happens only on XP. + DWORD flags = 0; + + // convert to UTF-8 + if (g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER) { + // Vista (and later) will detect invalid UTF-16 characters and raise an error. + flags = WC_ERR_INVALID_CHARS; + } + + if ( count > INT_MAX || (read >> 1) > INT_MAX ) + { + LOG(SEV_ERROR, "UTF-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } - // convert to UTF-8 - if( g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { - // Vista (and later) will detect invalid UTF-16 characters and raise an error. - flags = WC_ERR_INVALID_CHARS; - } int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast( temp_buf.get() ), - read >> 1, buf, count, NULL, NULL ); + static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); if( enc_len == 0 ) { @@ -200,13 +203,8 @@ php_stream_ops sqlsrv_stream_ops = { // open a stream and return the sqlsrv_stream_ops function table as part of the // return value. There is only one valid way to open a stream, using sqlsrv_get_field on // certain field types. A sqlsrv stream may only be opened in read mode. -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 6 -static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, __in const char*, __in const char* mode, - int options, __in char **, php_stream_context* STREAMS_DC TSRMLS_DC ) -#else -static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, __in char*, __in char* mode, - int options, __in char **, php_stream_context* STREAMS_DC TSRMLS_DC ) -#endif +static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, _In_ const char*, _In_ const char* mode, + int options, _In_ zend_string **, php_stream_context* STREAMS_DC TSRMLS_DC ) { #if ZEND_DEBUG diff --git a/pdo_sqlsrv/core_util.cpp b/pdo_sqlsrv/core_util.cpp index 8d7ae4a5..8bc410f3 100644 --- a/pdo_sqlsrv/core_util.cpp +++ b/pdo_sqlsrv/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -33,9 +33,9 @@ SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage fa char last_err_msg[ 2048 ]; // 2k to hold the error messages // routine used by utf16_string_from_mbcs_string -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, __in_bcount(mbcs_len) char const* mbcs_in_string, +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, unsigned int mbcs_len, - __out_ecount(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, + _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, unsigned int utf16_len ); } @@ -70,18 +70,19 @@ void core_sqlsrv_register_logger( log_callback driver_logger ) // utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed // and false is returned. -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLINTEGER& len) +bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len) { - SQLSRV_ASSERT( string != NULL && *string != NULL, "String must be specified" ); + SQLSRV_ASSERT( string != NULL, "String must be specified" ); - // for the empty string, we simply returned we converted it - if( len == 0 && *string[0] == '\0' ) { - return true; - } + if (validate_string(*string, len)) { + return true; + } char* outString = NULL; - SQLINTEGER outLen = 0; - bool result = convert_string_from_utf16( encoding, reinterpret_cast(*string), len / sizeof(wchar_t), &outString, outLen); + SQLLEN outLen = 0; + + bool result = convert_string_from_utf16( encoding, + reinterpret_cast(*string), int(len / sizeof(wchar_t)), &outString, outLen); if (result) { @@ -93,7 +94,49 @@ bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, return result; } -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLINTEGER& cchOutLen ) +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len) +{ + char* string = Z_STRVAL_P(value_z); + + if (validate_string(string, len)) { + return true; + } + + char* outString = NULL; + SQLLEN outLen = 0; + + bool result = convert_string_from_utf16(encoding, + reinterpret_cast(string), int(len / sizeof(wchar_t)), &outString, outLen); + + if (result) + { + core::sqlsrv_zval_stringl(value_z, outString, outLen); + sqlsrv_free(outString); + len = outLen; + } + + return result; +} + +bool validate_string(char* string, SQLLEN& len) +{ + SQLSRV_ASSERT(string != NULL, "String must be specified"); + + //for the empty string, we simply returned we converted it + if (len == 0 && string[0] == '\0') { + return true; + } + + if ((len / sizeof(wchar_t)) > INT_MAX) + { + LOG(SEV_ERROR, "UTP-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } + + return false; +} + +bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ) { SQLSRV_ASSERT( inString != NULL, "Input string must be specified" ); SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" ); @@ -126,7 +169,7 @@ bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inStrin char* newString = reinterpret_cast( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ )); int rc = WideCharToMultiByte( encoding, flags, inString, cchInLen, - newString, cchOutLen, NULL, NULL ); + newString, static_cast(cchOutLen), NULL, NULL ); if( rc == 0 ) { cchOutLen = 0; sqlsrv_free( newString ); @@ -139,7 +182,6 @@ bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inStrin return true; } - // thin wrapper around convert_string_from_default_encoding that handles // allocation of the destination string. An empty string passed in returns // failure since it's a failure case for convert_string_from_default_encoding. @@ -219,10 +261,10 @@ bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_ return false; } - SQLINTEGER sqlstate_len = 0; + SQLLEN sqlstate_len = 0; convert_string_from_utf16(enc, wsqlstate, sizeof(wsqlstate), (char**)&error->sqlstate, sqlstate_len); - SQLINTEGER message_len = 0; + SQLLEN message_len = 0; convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); break; } @@ -264,12 +306,12 @@ void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const co LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); } -DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ) +DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ) { va_list format_args; va_start( format_args, format ); - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_buffer, output_len, &format_args ); + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast(output_buffer), SQL_MAX_MESSAGE_LENGTH, &format_args ); va_end( format_args ); @@ -328,8 +370,8 @@ namespace { // returned in utf16_out_string. An empty string passed in will result as // a failure since MBTWC returns 0 for both an empty string and failure // to convert. -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, __in_bcount(mbcs_len) char const* mbcs_in_string, - unsigned int mbcs_len, __out_ecount(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, + unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, unsigned int utf16_len ) { unsigned int win_encoding = CP_ACP; diff --git a/pdo_sqlsrv/msodbcsql.h b/pdo_sqlsrv/msodbcsql.h index a16bf59a..8bcf83d0 100644 --- a/pdo_sqlsrv/msodbcsql.h +++ b/pdo_sqlsrv/msodbcsql.h @@ -888,7 +888,7 @@ RETCODE SQL_API bcp_moretext (HDBC, DBINT, LPCBYTE); RETCODE SQL_API bcp_readfmtA (HDBC, LPCSTR); RETCODE SQL_API bcp_readfmtW (HDBC, LPCWSTR); RETCODE SQL_API bcp_sendrow (HDBC); -RETCODE SQL_API bcp_setbulkmode (HDBC, INT, __in_bcount(cbField) void*, INT cbField, __in_bcount(cbRow) void *, INT cbRow); +RETCODE SQL_API bcp_setbulkmode (HDBC, INT, _In_reads_bytes_(cbField) void*, INT cbField, _In_reads_bytes_(cbRow) void *, INT cbRow); RETCODE SQL_API bcp_setcolfmt (HDBC, INT, INT, void *, INT); RETCODE SQL_API bcp_writefmtA (HDBC, LPCSTR); RETCODE SQL_API bcp_writefmtW (HDBC, LPCWSTR); @@ -958,7 +958,7 @@ HANDLE __stdcall OpenSqlFilestream ( LPCWSTR FilestreamPath, SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess, ULONG OpenOptions, - __in_bcount(FilestreamTransactionContextLength) + _In_reads_bytes_(FilestreamTransactionContextLength) LPBYTE FilestreamTransactionContext, SSIZE_T FilestreamTransactionContextLength, PLARGE_INTEGER AllocationSize); @@ -995,11 +995,11 @@ extern "C" { // type definition for LocalDBCreateInstance function typedef HRESULT __cdecl FnLocalDBCreateInstance ( // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - __in_z PCWSTR wszVersion, + _In_z_ PCWSTR wszVersion, // I the instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags + _In_ DWORD dwFlags ); // type definition for pointer to LocalDBCreateInstance function @@ -1008,14 +1008,14 @@ typedef FnLocalDBCreateInstance* PFnLocalDBCreateInstance; // type definition for LocalDBStartInstance function typedef HRESULT __cdecl FnLocalDBStartInstance ( // I the LocalDB instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags, + _In_ DWORD dwFlags, // O the buffer to store the connection string to the LocalDB instance - __out_ecount_z_opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, + _Out_writes_opt_z_(*lpcchSqlConnection) LPWSTR wszSqlConnection, // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is // too small, has the buffer size required, in characters, including trailing null. - __inout_opt LPDWORD lpcchSqlConnection + _Inout_opt_ LPDWORD lpcchSqlConnection ); // type definition for pointer to LocalDBStartInstance function @@ -1027,19 +1027,19 @@ typedef FnLocalDBStartInstance* PFnLocalDBStartInstance; // type definition for LocalDBFormatMessage function typedef HRESULT __cdecl FnLocalDBFormatMessage( // I the LocalDB error code - __in HRESULT hrLocalDB, + _In_ HRESULT hrLocalDB, // I Available flags: // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, // the error message will be truncated to fit into the buffer - __in DWORD dwFlags, + _In_ DWORD dwFlags, // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - __in DWORD dwLanguageId, + _In_ DWORD dwLanguageId, // O the buffer to store the LocalDB error message - __out_ecount_z(*lpcchMessage) LPWSTR wszMessage, + _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is // too small, has the buffer size required, in characters, including trailing null. If the function succeeds // contains the number of characters in the message, excluding the trailing null - __inout LPDWORD lpcchMessage + _Inout_ LPDWORD lpcchMessage ); // type definition for function pointer to LocalDBFormatMessage function @@ -1112,14 +1112,14 @@ FnLocalDBStartInstance LocalDBStartInstance; // type definition for LocalDBStopInstance function typedef HRESULT __cdecl FnLocalDBStopInstance ( // I the LocalDB instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I Available flags: // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - __in DWORD dwFlags, + _In_ DWORD dwFlags, // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately // without waiting for LocalDB instance to stop - __in ULONG ulTimeout + _In_ ULONG ulTimeout ); // type definition for pointer to LocalDBStopInstance function @@ -1149,9 +1149,9 @@ FnLocalDBStopInstance LocalDBStopInstance; // type definition for LocalDBDeleteInstance function typedef HRESULT __cdecl FnLocalDBDeleteInstance ( // I the LocalDB instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags + _In_ DWORD dwFlags ); // type definition for pointer to LocalDBDeleteInstance function @@ -1202,10 +1202,10 @@ typedef TLocalDBInstanceName* PTLocalDBInstanceName; // type definition for LocalDBGetInstances function typedef HRESULT __cdecl FnLocalDBGetInstances( // O buffer for a LocalDB instance names - __out PTLocalDBInstanceName pInstanceNames, + _Out_ PTLocalDBInstanceName pInstanceNames, // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, // has the number of existing LocalDB instances - __inout LPDWORD lpdwNumberOfInstances + _Inout_ LPDWORD lpdwNumberOfInstances ); // type definition for pointer to LocalDBGetInstances function @@ -1267,11 +1267,11 @@ typedef LocalDBInstanceInfo* PLocalDBInstanceInfo; // type definition for LocalDBGetInstanceInfo function typedef HRESULT __cdecl FnLocalDBGetInstanceInfo( // I the LocalDB instance name - __in_z PCWSTR wszInstanceName, + _In_z_ PCWSTR wszInstanceName, // O instance information - __out PLocalDBInstanceInfo pInfo, + _Out_ PLocalDBInstanceInfo pInfo, // I Size of LocalDBInstanceInfo structure in bytes - __in DWORD cbInfo); + _In_ DWORD cbInfo); // type definition for pointer to LocalDBGetInstances function typedef FnLocalDBGetInstanceInfo* PFnLocalDBGetInstanceInfo; @@ -1298,10 +1298,10 @@ typedef TLocalDBVersion* PTLocalDBVersion; // type definition for LocalDBGetVersions function typedef HRESULT __cdecl FnLocalDBGetVersions( // O buffer for installed LocalDB versions - __out PTLocalDBVersion pVersions, + _Out_ PTLocalDBVersion pVersions, // I/O on input has the number slots for versions in the pVersions buffer. On output, // has the number of existing LocalDB versions - __inout LPDWORD lpdwNumberOfVersions + _Inout_ LPDWORD lpdwNumberOfVersions ); // type definition for pointer to LocalDBGetVersions function @@ -1352,11 +1352,11 @@ typedef LocalDBVersionInfo* PLocalDBVersionInfo; // type definition for LocalDBGetVersionInfo function typedef HRESULT __cdecl FnLocalDBGetVersionInfo( // I LocalDB version string - __in_z PCWSTR wszVersion, + _In_z_ PCWSTR wszVersion, // O version information - __out PLocalDBVersionInfo pVersionInfo, + _Out_ PLocalDBVersionInfo pVersionInfo, // I Size of LocalDBVersionInfo structure in bytes - __in DWORD cbVersionInfo + _In_ DWORD cbVersionInfo ); // type definition for pointer to LocalDBGetVersionInfo function @@ -1402,13 +1402,13 @@ FnLocalDBStopTracing LocalDBStopTracing; // type definition for LocalDBShareInstance function typedef HRESULT __cdecl FnLocalDBShareInstance( // I the SID of the LocalDB instance owner - __in_opt PSID pOwnerSID, + _In_opt_ PSID pOwnerSID, // I the private name of LocalDB instance which should be shared - __in_z PCWSTR wszPrivateLocalDBInstanceName, + _In_z_ PCWSTR wszPrivateLocalDBInstanceName, // I the public shared name - __in_z PCWSTR wszSharedName, + _In_z_ PCWSTR wszSharedName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags); + _In_ DWORD dwFlags); // type definition for pointer to LocalDBShareInstance function typedef FnLocalDBShareInstance* PFnLocalDBShareInstance; @@ -1426,9 +1426,9 @@ FnLocalDBShareInstance LocalDBShareInstance; // type definition for LocalDBUnshareInstance function typedef HRESULT __cdecl FnLocalDBUnshareInstance( // I the LocalDB instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags); + _In_ DWORD dwFlags); // type definition for pointer to LocalDBUnshareInstance function typedef FnLocalDBUnshareInstance* PFnLocalDBUnshareInstance; @@ -1653,11 +1653,11 @@ Cleanup: HRESULT __cdecl LocalDBCreateInstance ( // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - __in_z PCWSTR wszVersion, + _In_z_ PCWSTR wszVersion, // I the instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags + _In_ DWORD dwFlags ) { LOCALDB_PROXY(LocalDBCreateInstance)(wszVersion, pInstanceName, dwFlags); @@ -1666,14 +1666,14 @@ LocalDBCreateInstance ( HRESULT __cdecl LocalDBStartInstance( // I the instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags, + _In_ DWORD dwFlags, // O the buffer to store the connection string to the LocalDB instance - __out_ecount_z_opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, + _Out_writes_z__opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is // too small, has the buffer size required, in characters, including trailing null. - __inout_opt LPDWORD lpcchSqlConnection + _Inout_opt_ LPDWORD lpcchSqlConnection ) { LOCALDB_PROXY(LocalDBStartInstance)(pInstanceName, dwFlags, wszSqlConnection, lpcchSqlConnection); @@ -1682,14 +1682,14 @@ LocalDBStartInstance( HRESULT __cdecl LocalDBStopInstance ( // I the instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I Available flags: // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - __in DWORD dwFlags, + _In_ DWORD dwFlags, // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately // without waiting for LocalDB instance to stop - __in ULONG ulTimeout + _In_ ULONG ulTimeout ) { LOCALDB_PROXY(LocalDBStopInstance)(pInstanceName, dwFlags, ulTimeout); @@ -1698,9 +1698,9 @@ LocalDBStopInstance ( HRESULT __cdecl LocalDBDeleteInstance ( // I the instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags + _In_ DWORD dwFlags ) { LOCALDB_PROXY(LocalDBDeleteInstance)(pInstanceName, dwFlags); @@ -1709,19 +1709,19 @@ LocalDBDeleteInstance ( HRESULT __cdecl LocalDBFormatMessage( // I the LocalDB error code - __in HRESULT hrLocalDB, + _In_ HRESULT hrLocalDB, // I Available flags: // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, // the error message will be truncated to fit into the buffer - __in DWORD dwFlags, + _In_ DWORD dwFlags, // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - __in DWORD dwLanguageId, + _In_ DWORD dwLanguageId, // O the buffer to store the LocalDB error message - __out_ecount_z(*lpcchMessage) LPWSTR wszMessage, + _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is // too small, has the buffer size required, in characters, including trailing null. If the function succeeds // contains the number of characters in the message, excluding the trailing null - __inout LPDWORD lpcchMessage + _Inout_ LPDWORD lpcchMessage ) { LOCALDB_PROXY(LocalDBFormatMessage)(hrLocalDB, dwFlags, dwLanguageId, wszMessage, lpcchMessage); @@ -1730,10 +1730,10 @@ LocalDBFormatMessage( HRESULT __cdecl LocalDBGetInstances( // O buffer with instance names - __out PTLocalDBInstanceName pInstanceNames, + _Out_ PTLocalDBInstanceName pInstanceNames, // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, // has the number of existing LocalDB instances - __inout LPDWORD lpdwNumberOfInstances + _Inout_ LPDWORD lpdwNumberOfInstances ) { LOCALDB_PROXY(LocalDBGetInstances)(pInstanceNames, lpdwNumberOfInstances); @@ -1742,11 +1742,11 @@ LocalDBGetInstances( HRESULT __cdecl LocalDBGetInstanceInfo( // I the instance name - __in_z PCWSTR wszInstanceName, + _In_z_ PCWSTR wszInstanceName, // O instance information - __out PLocalDBInstanceInfo pInfo, + _Out_ PLocalDBInstanceInfo pInfo, // I Size of LocalDBInstanceInfo structure in bytes - __in DWORD cbInfo + _In_ DWORD cbInfo ) { LOCALDB_PROXY(LocalDBGetInstanceInfo)(wszInstanceName, pInfo, cbInfo); @@ -1767,13 +1767,13 @@ LocalDBStopTracing() HRESULT __cdecl LocalDBShareInstance( // I the SID of the LocalDB instance owner - __in_opt PSID pOwnerSID, + _In_opt_ PSID pOwnerSID, // I the private name of LocalDB instance which should be shared - __in_z PCWSTR wszLocalDBInstancePrivateName, + _In_z_ PCWSTR wszLocalDBInstancePrivateName, // I the public shared name - __in_z PCWSTR wszSharedName, + _In_z_ PCWSTR wszSharedName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags) + _In_ DWORD dwFlags) { LOCALDB_PROXY(LocalDBShareInstance)(pOwnerSID, wszLocalDBInstancePrivateName, wszSharedName, dwFlags); } @@ -1781,10 +1781,10 @@ LocalDBShareInstance( HRESULT __cdecl LocalDBGetVersions( // O buffer for installed LocalDB versions - __out PTLocalDBVersion pVersions, + _Out_ PTLocalDBVersion pVersions, // I/O on input has the number slots for versions in the pVersions buffer. On output, // has the number of existing LocalDB versions - __inout LPDWORD lpdwNumberOfVersions + _Inout_ LPDWORD lpdwNumberOfVersions ) { LOCALDB_PROXY(LocalDBGetVersions)(pVersions, lpdwNumberOfVersions); @@ -1793,9 +1793,9 @@ LocalDBGetVersions( HRESULT __cdecl LocalDBUnshareInstance( // I the LocalDB instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags) + _In_ DWORD dwFlags) { LOCALDB_PROXY(LocalDBUnshareInstance)(pInstanceName, dwFlags); } @@ -1803,11 +1803,11 @@ LocalDBUnshareInstance( HRESULT __cdecl LocalDBGetVersionInfo( // I LocalDB version string - __in_z PCWSTR wszVersion, + _In_z_ PCWSTR wszVersion, // O version information - __out PLocalDBVersionInfo pVersionInfo, + _Out_ PLocalDBVersionInfo pVersionInfo, // I Size of LocalDBVersionInfo structure in bytes - __in DWORD cbVersionInfo) + _In_ DWORD cbVersionInfo) { LOCALDB_PROXY(LocalDBGetVersionInfo)(wszVersion, pVersionInfo, cbVersionInfo); } diff --git a/pdo_sqlsrv/pdo_dbh.cpp b/pdo_sqlsrv/pdo_dbh.cpp index a4e4c734..27e2cc5b 100644 --- a/pdo_sqlsrv/pdo_dbh.cpp +++ b/pdo_sqlsrv/pdo_dbh.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDO object for PDO_SQLSRV // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -33,7 +33,7 @@ typedef const zend_function_entry pdo_sqlsrv_function_entry; namespace { const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;"; -const char LAST_INSERT_ID_BUFF_LEN = 10; // size of the buffer to hold the string value of the last insert id integer +const size_t LAST_INSERT_ID_BUFF_LEN = 10; // size of the buffer to hold the string value of the last insert id integer const char TABLE_LAST_INSERT_ID_QUERY[] = "SELECT IDENT_CURRENT(%s)"; const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( TABLE_LAST_INSERT_ID_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes @@ -73,20 +73,22 @@ enum PDO_STMT_OPTIONS { PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, PDO_STMT_OPTION_EMULATE_PREPARES, + PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, }; // List of all the statement options supported by this driver. const stmt_option PDO_STMT_OPTS[] = { - { NULL, 0, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, new stmt_option_query_timeout }, - { NULL, 0, SQLSRV_STMT_OPTION_SCROLLABLE, new stmt_option_scrollable }, - { NULL, 0, PDO_STMT_OPTION_ENCODING, new stmt_option_encoding }, - { NULL, 0, PDO_STMT_OPTION_DIRECT_QUERY, new stmt_option_direct_query }, - { NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, new stmt_option_cursor_scroll_type }, - { NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, new stmt_option_buffered_query_limit }, - { NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, new stmt_option_emulate_prepares }, + { NULL, 0, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, std::unique_ptr( new stmt_option_query_timeout ) }, + { NULL, 0, SQLSRV_STMT_OPTION_SCROLLABLE, std::unique_ptr( new stmt_option_scrollable ) }, + { NULL, 0, PDO_STMT_OPTION_ENCODING, std::unique_ptr( new stmt_option_encoding ) }, + { NULL, 0, PDO_STMT_OPTION_DIRECT_QUERY, std::unique_ptr( new stmt_option_direct_query ) }, + { NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, std::unique_ptr( new stmt_option_cursor_scroll_type ) }, + { 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, SQLSRV_STMT_OPTION_INVALID, NULL}, + { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; // boolean connection string @@ -109,7 +111,7 @@ struct pdo_int_conn_attr_func { SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "pdo_int_conn_attr_func: Unexpected zval type." ); - int val = atoi( Z_STRVAL_P( value )); + size_t val = static_cast( atoi( Z_STRVAL_P( value )) ); core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( val ), SQL_IS_UINTEGER TSRMLS_CC ); } catch( core::CoreException& ) { @@ -135,9 +137,9 @@ struct pdo_bool_conn_attr_func { }; // statement options related functions -void add_stmt_option_key( sqlsrv_context& ctx, unsigned long key, HashTable* options_ht, +void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, zval** data TSRMLS_DC ); -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* pdo_stmt_options_ht TSRMLS_DC ); +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ); } // namespace @@ -306,8 +308,8 @@ int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ); // execute queries int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, - long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ); -long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC ); + size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ); +zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ); // transaction support functions int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ); @@ -315,21 +317,21 @@ int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ); int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ); // attribute functions -int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC ); -int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, long attr, zval *return_value TSRMLS_DC ); +int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ); +int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ); // return more information int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info TSRMLS_DC); // return the last id generated by an executed SQL statement -char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, unsigned int* len TSRMLS_DC ); +char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, size_t* len TSRMLS_DC ); // additional methods are supported in this function pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ); // quote a string, meaning put quotes around it and escape any quotes within it -int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, int unquotedlen, char **quoted, int* quotedlen, +int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquotedlen, char **quoted, size_t* quotedlen, enum pdo_param_type paramtype TSRMLS_DC ); struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { @@ -347,7 +349,8 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { pdo_sqlsrv_dbh_get_attr, NULL, // check liveness not implemented pdo_sqlsrv_get_driver_methods, - NULL // request shutdown not implemented + NULL, // request shutdown not implemented + NULL // in transaction not implemented }; @@ -361,11 +364,13 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { // constructor for the internal object for connections pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) : - sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ), - stmts( NULL ), - direct_query( false ), - query_timeout( QUERY_TIMEOUT_INVALID ), - client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size ) ) + sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ), + stmts( NULL ), + direct_query( false ), + query_timeout( QUERY_TIMEOUT_INVALID ), + client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )), + bind_param_encoding( SQLSRV_ENCODING_CHAR ), + fetch_numeric( false ) { if( client_buffer_max_size < 0 ) { client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; @@ -400,9 +405,10 @@ int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) // object for errors. dbh->methods = &pdo_sqlsrv_dbh_methods; dbh->driver_data = NULL; - zval** temp_server_z = NULL; + zval* temp_server_z = NULL; sqlsrv_malloc_auto_ptr dsn_parser; - zval_auto_ptr server_z; + zval server_z; + ZVAL_UNDEF( &server_z ); try { @@ -420,16 +426,16 @@ int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( pdo_conn_options_ht ); - core::sqlsrv_zend_hash_init( *g_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, NULL /*hashfn*/, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + core::sqlsrv_zend_hash_init( *g_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, + ZVAL_INTERNAL_DTOR, 0 /*persistent*/ TSRMLS_CC ); // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_henv_cp, dbh->data_source, - dbh->data_source_len, pdo_conn_options_ht ); + static_cast( dbh->data_source_len ), pdo_conn_options_ht ); dsn_parser->parse_conn_string( TSRMLS_C ); // Extract the server name - zend_hash_index_find( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER, (void**)&temp_server_z ); + temp_server_z = zend_hash_index_find( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER); CHECK_CUSTOM_ERROR(( temp_server_z == NULL ), g_henv_cp, PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED ) { @@ -442,9 +448,12 @@ int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) zval_add_ref( &server_z ); zend_hash_index_del( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER ); - sqlsrv_conn* conn = core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, core::allocate_conn, Z_STRVAL_P( server_z ), + sqlsrv_conn* conn = core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, core::allocate_conn, Z_STRVAL( server_z ), dbh->username, dbh->password, pdo_conn_options_ht, pdo_sqlsrv_handle_dbh_error, PDO_CONN_OPTS, dbh, "pdo_sqlsrv_db_handle_factory" TSRMLS_CC ); + + // Free the string in server_z after being used + zend_string_release( Z_STR( server_z )); SQLSRV_ASSERT( conn != NULL, "Invalid connection returned. Exception should have been thrown." ); @@ -456,16 +465,18 @@ int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) } catch( core::CoreException& ) { - + if ( Z_TYPE( server_z ) == IS_STRING ) { + zend_string_release( Z_STR( server_z )); + } dbh->error_mode = prev_err_mode; // reset the error mode - + g_henv_cp->last_error().reset(); // reset the last error; callee will check if last_error exist before freeing it and setting it to NULL return 0; } catch( ... ) { DIE( "pdo_sqlsrv_db_handle_factory: Unknown exception caught" ); } - + return 1; } @@ -510,7 +521,7 @@ int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ) // Return: // 0 for failure, 1 for success. int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, - long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ) + size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -518,7 +529,7 @@ int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, hash_auto_ptr pdo_stmt_options_ht; sqlsrv_malloc_auto_ptr sql_rewrite; - int sql_rewrite_len = 0; + size_t sql_rewrite_len = 0; sqlsrv_malloc_auto_ptr driver_stmt; pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); @@ -533,7 +544,7 @@ int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( pdo_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */, NULL /*hashfn*/, + core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */, ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. @@ -635,7 +646,7 @@ int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, // sql_len - length of sql query // Return // # of rows affected, -1 for an error. -long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC ) +zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -644,7 +655,7 @@ long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); sqlsrv_malloc_auto_ptr driver_stmt; - long rows = 0; + SQLLEN rows = 0; // verify that the data type sizes are the same. If we ever upgrade to 64 bit we don't want the wrong // thing to happen here. @@ -662,7 +673,7 @@ long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); driver_stmt->set_func( __FUNCTION__ ); - core_sqlsrv_execute( driver_stmt TSRMLS_CC, sql, sql_len ); + core_sqlsrv_execute( driver_stmt TSRMLS_CC, sql, static_cast( sql_len ) ); // since the user can give us a compound statement, we return the row count for the last set, and since the row count // isn't guaranteed to be valid until all the results have been fetched, we fetch them all first. @@ -843,7 +854,7 @@ int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ) // val - The value of the attribute to be set. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC ) +int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -857,7 +868,7 @@ int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC ) case SQLSRV_ATTR_ENCODING: { - long attr_value; + zend_long attr_value; if( Z_TYPE_P( val ) != IS_LONG ) { THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); } @@ -888,7 +899,7 @@ int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC ) convert_to_string( val ); THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( val )); } - driver_dbh->query_timeout = Z_LVAL_P( val ); + driver_dbh->query_timeout = static_cast( Z_LVAL_P( val ) ); break; case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: @@ -899,6 +910,10 @@ int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC ) driver_dbh->client_buffer_max_size = Z_LVAL_P( val ); break; + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + driver_dbh->fetch_numeric = (zend_is_true(val)) ? true : false; + break; + // Not supported case PDO_ATTR_FETCH_TABLE_NAMES: case PDO_ATTR_FETCH_CATALOG_NAMES: @@ -954,7 +969,7 @@ int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC ) // return_value - zval in which to return the attribute value. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, long attr, zval *return_value TSRMLS_DC ) +int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -1040,6 +1055,12 @@ int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, long attr, zval *return_value TSRML break; } + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + { + ZVAL_BOOL( return_value, driver_dbh->fetch_numeric ); + break; + } + default: { THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); @@ -1074,8 +1095,8 @@ int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, else { ctx_error = static_cast( dbh->driver_data )->last_error(); } - - pdo_sqlsrv_retrieve_context_error( ctx_error, info ); + + pdo_sqlsrv_retrieve_context_error( ctx_error, info ); return 1; } @@ -1089,7 +1110,7 @@ int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, // len - Length of the name. // Return: // Returns the last insert id as a string. -char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, __out unsigned int* len TSRMLS_DC ) +char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, _Out_ size_t* len TSRMLS_DC ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -1101,7 +1122,7 @@ char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, __out unsigned sqlsrv_malloc_auto_ptr driver_stmt; - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); sqlsrv_malloc_auto_ptr id_str; id_str = reinterpret_cast( sqlsrv_malloc( LAST_INSERT_ID_BUFF_LEN )); @@ -1114,7 +1135,7 @@ char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, __out unsigned } else { char* quoted_table = NULL; - int quoted_len = 0; + size_t quoted_len = 0; int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strlen( name ), "ed_table, "ed_len, PDO_PARAM_NULL TSRMLS_CC ); SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name." ); sprintf_s( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, TABLE_LAST_INSERT_ID_QUERY, quoted_table ); @@ -1134,11 +1155,10 @@ char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, __out unsigned core::SQLExecDirect( driver_stmt, last_insert_id_query TSRMLS_CC ); core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); - SQLSRV_STATIC_ASSERT( sizeof( SQLLEN ) == sizeof( unsigned int )); SQLRETURN r = core::SQLGetData( driver_stmt, 1, SQL_C_CHAR, id_str, LAST_INSERT_ID_BUFF_LEN, reinterpret_cast( len ), false TSRMLS_CC ); - CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt, + CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt, PDO_SQLSRV_ERROR_LAST_INSERT_ID ) { throw core::CoreException(); } @@ -1181,44 +1201,84 @@ char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, __out unsigned // quoted_len - Length of the output string. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, int unquoted_len, char **quoted, int* quoted_len, +int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquoted_len, char **quoted, size_t* quoted_len, enum pdo_param_type /*paramtype*/ TSRMLS_DC ) { - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; PDO_LOG_DBH_ENTRY; - // count the number of quotes needed - unsigned int quotes_needed = 2; // the initial start and end quotes of course - for( int index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { - if( unquoted[ index ] == '\'' ) { - ++quotes_needed; - } - } + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); + SQLSRV_ENCODING encoding = driver_dbh->bind_param_encoding; - *quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator. - *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); // include space for null terminator. - unsigned int out_current = 0; + 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 ) { + os << std::hex << ( int )unquoted[index]; + } + std::basic_string str_hex = os.str(); + // each character is represented by 2 digits of hex + size_t unquoted_str_len = unquoted_len * 2; // length returned should not account for null terminator + char* unquoted_str = reinterpret_cast( sqlsrv_malloc( unquoted_str_len, sizeof( char ), 1 )); // include space for null terminator + strcpy_s( unquoted_str, unquoted_str_len + 1 /* include null terminator*/, str_hex.c_str()); + // include length of '0x' in the binary string + *quoted_len = unquoted_str_len + 2; + *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]; + } + // null terminator + ( *quoted )[out_current] = '\0'; + sqlsrv_free( unquoted_str ); + return 1; + } + else { + // count the number of quotes needed + unsigned int quotes_needed = 2; // the initial start and end quotes of course + // include the N proceeding the initial quote if encoding is UTF8 + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + quotes_needed = 3; + } - // insert initial quote - (*quoted)[ out_current++ ] ='\''; + for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { + if ( unquoted[index] == '\'' ) { + ++quotes_needed; + } + } - for( int index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + *quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator. + *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); // include space for null terminator. + unsigned int out_current = 0; - if( unquoted[ index ] == '\'' ) { - (*quoted)[ out_current++ ] = '\''; - (*quoted)[ out_current++ ] = '\''; - } - else { - (*quoted)[ out_current++ ] = unquoted[ index ]; - } - } + // insert N if the encoding is UTF8 + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + ( *quoted )[out_current++] = 'N'; + } + // insert initial quote + ( *quoted )[out_current++] = '\''; - // trailing quote and null terminator - (*quoted)[ out_current++ ] ='\''; - (*quoted)[ out_current ] = '\0'; + for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { - return 1; + if ( unquoted[index] == '\'' ) { + ( *quoted )[out_current++] = '\''; + ( *quoted )[out_current++] = '\''; + } + else { + ( *quoted )[out_current++] = unquoted[index]; + } + } + + // trailing quote and null terminator + ( *quoted )[out_current++] = '\''; + ( *quoted )[out_current] = '\0'; + + return 1; + } } // This method is not implemented by this driver. @@ -1240,10 +1300,10 @@ namespace { // Maps the PDO driver specific statement option/attribute constants to the core layer // statement option/attribute constants. -void add_stmt_option_key( sqlsrv_context& ctx, unsigned long key, HashTable* options_ht, - zval** data TSRMLS_DC ) +void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, + zval* data TSRMLS_DC ) { - unsigned long option_key = -1; + zend_ulong option_key = -1; switch( key ) { case PDO_ATTR_CURSOR: @@ -1277,6 +1337,9 @@ void add_stmt_option_key( sqlsrv_context& ctx, unsigned long key, HashTable* opt option_key = PDO_STMT_OPTION_EMULATE_PREPARES; break; + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; + default: CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { throw core::CoreException(); @@ -1287,7 +1350,7 @@ void add_stmt_option_key( sqlsrv_context& ctx, unsigned long key, HashTable* opt // if a PDO handled option makes it through (such as PDO_ATTR_STATEMENT_CLASS, just skip it if( option_key != -1 ) { zval_add_ref( data ); - core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, (void**)data, sizeof(zval*) TSRMLS_CC ); + core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data TSRMLS_CC ); } } @@ -1300,33 +1363,27 @@ void add_stmt_option_key( sqlsrv_context& ctx, unsigned long key, HashTable* opt // ctx - The current context. // stmt_options - The user provided list of statement options. // pdo_stmt_options_ht - Output hashtable of statement options. -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* pdo_stmt_options_ht TSRMLS_DC ) +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ) { try { if( stmt_options ) { HashTable* options_ht = Z_ARRVAL_P( stmt_options ); - - for( zend_hash_internal_pointer_reset( options_ht ); zend_hash_has_more_elements( options_ht ) == SUCCESS; - zend_hash_move_forward( options_ht )) { + size_t int_key = -1; + zend_string *key = NULL; + zval* data = NULL; - int type = HASH_KEY_NON_EXISTANT; - char *key = NULL; - unsigned int key_len = 0; - unsigned long int_key = -1; - zval** data; - int result = 0; + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + int result = 0; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_LONG ), ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { + throw core::CoreException(); + } - type = zend_hash_get_current_key_ex( options_ht, &key, &key_len, &int_key, 0, NULL ); - CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_LONG ), ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { - throw core::CoreException(); - } - - core::sqlsrv_zend_hash_get_current_data( ctx, options_ht, (void**) &data TSRMLS_CC ); - - add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data TSRMLS_CC ); - } + add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); } } catch( core::CoreException& ) { @@ -1359,8 +1416,8 @@ void pdo_txn_isolation_conn_attr_func::func( connection_option const* /*option*/ SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "pdo_txn_isolation_conn_attr_func: Unexpected zval type." ); const char* val = Z_STRVAL_P( value_z ); - int val_len = Z_STRLEN_P( value_z ); - long out_val = SQL_TXN_READ_COMMITTED; + size_t val_len = Z_STRLEN_P( value_z ); + zend_long out_val = SQL_TXN_READ_COMMITTED; // READ_COMMITTED if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_COMMITTED ) - 1 ) diff --git a/pdo_sqlsrv/pdo_init.cpp b/pdo_sqlsrv/pdo_init.cpp index 63e3ae85..7db1ab9a 100644 --- a/pdo_sqlsrv/pdo_init.cpp +++ b/pdo_sqlsrv/pdo_init.cpp @@ -3,7 +3,7 @@ // // Contents: initialization routines for PDO_SQLSRV // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -20,6 +20,9 @@ #include "pdo_sqlsrv.h" #include +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE(); +#endif ZEND_GET_MODULE(g_pdo_sqlsrv) extern "C" { @@ -48,19 +51,19 @@ const char PDO_DLL_NAME[] = "php_pdo.dll"; // PHP_DEBUG is always defined as either 0 or 1 #if PHP_DEBUG == 1 && !defined(ZTS) -const char PHP_DLL_NAME[] = "php5_debug.dll"; +const char PHP_DLL_NAME[] = "php7_debug.dll"; #elif PHP_DEBUG == 1 && defined(ZTS) -const char PHP_DLL_NAME[] = "php5ts_debug.dll"; +const char PHP_DLL_NAME[] = "php7ts_debug.dll"; #elif PHP_DEBUG == 0 && !defined(ZTS) -const char PHP_DLL_NAME[] = "php5.dll"; +const char PHP_DLL_NAME[] = "php7.dll"; #elif PHP_DEBUG == 0 && defined(ZTS) -const char PHP_DLL_NAME[] = "php5ts.dll"; +const char PHP_DLL_NAME[] = "php7ts.dll"; #else @@ -133,8 +136,14 @@ zend_module_entry g_pdo_sqlsrv_module_entry = // functions dynamically linked from the PDO (or PHP) dll and called by other parts of the driver zend_class_entry* (*pdo_get_exception_class)( void ); -int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, int inquery_len, - char **outquery, int *outquery_len TSRMLS_DC); +int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, size_t inquery_len, + char **outquery, size_t *outquery_len TSRMLS_DC); + +// called by Zend for each parameter in the g_pdo_errors_ht hash table when it is destroyed +void pdo_error_dtor(zval* elem) { + pdo_error* error_to_ignore = reinterpret_cast(Z_PTR_P(elem)); + pefree(error_to_ignore, 1); +} // Module initialization // This function is called once per execution of the Zend engine @@ -150,6 +159,7 @@ PHP_MINIT_FUNCTION(pdo_sqlsrv) (ts_allocate_ctor) NULL, (ts_allocate_dtor) NULL ) == 0 ) return FAILURE; + ZEND_TSRMLS_CACHE_UPDATE(); #endif core_sqlsrv_register_logger( pdo_sqlsrv_log ); @@ -206,8 +216,8 @@ PHP_MINIT_FUNCTION(pdo_sqlsrv) } pdo_subst_named_params = - reinterpret_cast( + reinterpret_cast( GetProcAddress( pdo_hmodule, "pdo_parse_params" )); if( pdo_subst_named_params == NULL ) { LOG( SEV_ERROR, "Failed to register driver." ); @@ -216,17 +226,13 @@ PHP_MINIT_FUNCTION(pdo_sqlsrv) // initialize list of pdo errors g_pdo_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - int zr = ::zend_hash_init( g_pdo_errors_ht, 50, NULL, NULL, 1 ); - if( zr == FAILURE ) { - LOG( SEV_ERROR, "Failed to initialize the PDO errors hashtable." ); - return FAILURE; - } + ::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 ) { - zr = ::zend_hash_index_update( g_pdo_errors_ht, PDO_ERRORS[ i ].error_code, - &( PDO_ERRORS[ i ].sqlsrv_error ), sizeof( PDO_ERRORS[ i ].sqlsrv_error ), NULL ); - if( zr == FAILURE ) { + void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[ i ].error_code, + &( PDO_ERRORS[ i ].sqlsrv_error ), sizeof( PDO_ERRORS[ i ].sqlsrv_error ) ); + if( zr == NULL ) { LOG( SEV_ERROR, "Failed to insert data into PDO errors hashtable." ); return FAILURE; @@ -262,7 +268,6 @@ PHP_MINIT_FUNCTION(pdo_sqlsrv) return SUCCESS; } - // Module shutdown function // Module shutdown function @@ -303,10 +308,12 @@ PHP_RINIT_FUNCTION(pdo_sqlsrv) SQLSRV_UNUSED( module_number ); SQLSRV_UNUSED( type ); +#if defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + LOG( SEV_NOTICE, "pdo_sqlsrv: entering rinit" ); - // verify memory at the end of the request (in debug mode only) - full_mem_check(MEMCHECK_SILENT); return SUCCESS; } @@ -321,9 +328,6 @@ PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv) LOG( SEV_NOTICE, "pdo_sqlsrv: entering rshutdown" ); - // verify memory at the end of the request (in debug mode only) - full_mem_check(MEMCHECK_SILENT); - return SUCCESS; } @@ -332,12 +336,9 @@ PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv) PHP_MINFO_FUNCTION(pdo_sqlsrv) { -#if defined(ZTS) - SQLSRV_UNUSED( tsrm_ls ); -#endif - php_info_print_table_start(); php_info_print_table_header(2, "pdo_sqlsrv support", "enabled"); + php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); php_info_print_table_end(); DISPLAY_INI_ENTRIES(); } @@ -371,14 +372,15 @@ namespace { } // array of pdo constants. - sqlsrv_attr_pdo_constant pdo_attr_constants[] = { + sqlsrv_attr_pdo_constant pdo_attr_constants[] = { - // driver specific attributes - { "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING }, - { "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT }, - { "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY }, - { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, - { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, + // driver specific attributes + { "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING }, + { "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT }, + { "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY }, + { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, + { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, + { "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE }, // 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 @@ -394,7 +396,7 @@ namespace { { "SQLSRV_CURSOR_STATIC" , SQL_CURSOR_STATIC }, { "SQLSRV_CURSOR_DYNAMIC" , SQL_CURSOR_DYNAMIC }, { "SQLSRV_CURSOR_KEYSET" , SQL_CURSOR_KEYSET_DRIVEN }, - { "SQLSRV_CURSOR_BUFFERED" , SQLSRV_CURSOR_BUFFERED }, + { "SQLSRV_CURSOR_BUFFERED" , static_cast(SQLSRV_CURSOR_BUFFERED) }, { NULL , 0 } // terminate the table }; diff --git a/pdo_sqlsrv/pdo_parser.cpp b/pdo_sqlsrv/pdo_parser.cpp index f7fd6374..f055ea8e 100644 --- a/pdo_sqlsrv/pdo_parser.cpp +++ b/pdo_sqlsrv/pdo_parser.cpp @@ -5,7 +5,7 @@ // // Copyright Microsoft Corporation // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -22,7 +22,7 @@ #include "pdo_sqlsrv.h" // Constructor -conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, __inout HashTable* conn_options_ht ) +conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ) { this->conn_str = dsn; this->len = len; @@ -34,7 +34,7 @@ conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, i // Move to the next character inline bool conn_string_parser::next( void ) { - // if already at the end than return false + // if already at the end then return false if( this->is_eos() ) { return false; @@ -108,22 +108,19 @@ bool conn_string_parser::discard_white_spaces() // Add a key-value pair to the hashtable of connection options. void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC ) { - zval_auto_ptr value_z; - ALLOC_INIT_ZVAL( value_z ); + zval value_z; + ZVAL_UNDEF( &value_z ); if( len == 0 ) { - ZVAL_STRINGL( value_z, "", 0, 1 /*dup*/ ); + ZVAL_STRINGL( &value_z, "", 0); } else { - ZVAL_STRINGL( value_z, const_cast( value ), len, 1 /*dup*/ ); + ZVAL_STRINGL( &value_z, const_cast( value ), len ); } - core::sqlsrv_zend_hash_index_update( *ctx, this->conn_options_ht, this->current_key, (void**)&value_z, - sizeof(zval*) TSRMLS_CC ); - - zval_add_ref( &value_z ); + core::sqlsrv_zend_hash_index_update( *ctx, this->conn_options_ht, this->current_key, &value_z TSRMLS_CC ); } // Validate a given DSN keyword. @@ -145,7 +142,7 @@ void conn_string_parser::validate_key(const char *key, int key_len TSRMLS_DC ) // encountered an invalid key, throw error. sqlsrv_malloc_auto_ptr key_name; key_name = static_cast( sqlsrv_malloc( new_len + 1 )); - memcpy( key_name, key, new_len ); + memcpy_s( key_name, new_len + 1 ,key, new_len ); key_name[ new_len ] = '\0'; THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, key_name ); diff --git a/pdo_sqlsrv/pdo_sqlsrv.h b/pdo_sqlsrv/pdo_sqlsrv.h index 6638a672..959f6696 100644 --- a/pdo_sqlsrv/pdo_sqlsrv.h +++ b/pdo_sqlsrv/pdo_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Declarations for the extension // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -48,6 +48,7 @@ enum PDO_SQLSRV_ATTR { SQLSRV_ATTR_DIRECT_QUERY, SQLSRV_ATTR_CURSOR_SCROLL_TYPE, SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, + SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, }; // valid set of values for TransactionIsolation connection option @@ -70,7 +71,7 @@ extern "C" { ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) unsigned int log_severity; -long client_buffer_max_size; +zend_long client_buffer_max_size; ZEND_END_MODULE_GLOBALS(pdo_sqlsrv) @@ -158,7 +159,7 @@ class conn_string_parser void add_key_value_pair( const char* value, int len TSRMLS_DC ); public: - conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, __inout HashTable* conn_options_ht ); + conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ); void parse_conn_string( TSRMLS_D ); }; @@ -175,7 +176,9 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { zval* stmts; bool direct_query; long query_timeout; - long client_buffer_max_size; + zend_long client_buffer_max_size; + SQLSRV_ENCODING bind_param_encoding; + bool fetch_numeric; pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ); }; @@ -210,6 +213,10 @@ struct stmt_option_emulate_prepares : public stmt_option_functor { virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); }; +struct stmt_option_fetch_numeric : public stmt_option_functor { + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, 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 @@ -220,25 +227,28 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { direct_query( false ), direct_query_subst_string( NULL ), direct_query_subst_string_len( 0 ), - bound_column_param_types( NULL ) + bound_column_param_types( NULL ), + fetch_numeric( false ) { pdo_sqlsrv_dbh* db = static_cast( c ); direct_query = db->direct_query; + fetch_numeric = db->fetch_numeric; } virtual ~pdo_sqlsrv_stmt( void ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ); + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ); bool direct_query; // flag set if the query should be executed directly or prepared const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters - int direct_query_subst_string_len; // length of query string used for direct queries + size_t direct_query_subst_string_len; // length of query string used for direct queries // meta data for current result set std::vector > current_meta_data; pdo_param_type* bound_column_param_types; + bool fetch_numeric; }; @@ -277,7 +287,7 @@ inline void pdo_reset_dbh_error( pdo_dbh_t* dbh TSRMLS_DC ) // release the last statement from the dbh so that error handling won't have a statement passed to it if( dbh->query_stmt ) { dbh->query_stmt = NULL; - zend_objects_store_del_ref( &dbh->query_stmt_zval TSRMLS_CC ); + zend_objects_store_del( Z_OBJ_P(&dbh->query_stmt_zval TSRMLS_CC) ); } // if the driver isn't valid, just return (PDO calls close sometimes more than once?) @@ -383,8 +393,8 @@ namespace pdo { // called pdo_parse_params in php_pdo_driver.h // we renamed it for 2 reasons: 1) we can't have the same name since it would conflict with our dynamic linking, and // 2) this is a more precise name -extern int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, int inquery_len, - char **outquery, int *outquery_len TSRMLS_DC); +extern int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, size_t inquery_len, + char **outquery, size_t *outquery_len TSRMLS_DC); // logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); diff --git a/pdo_sqlsrv/pdo_stmt.cpp b/pdo_sqlsrv/pdo_stmt.cpp index 823ad112..324afebc 100644 --- a/pdo_sqlsrv/pdo_stmt.cpp +++ b/pdo_sqlsrv/pdo_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDOStatement object for the PDO_SQLSRV // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -132,8 +132,8 @@ void set_stmt_cursors( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); } - long pdo_cursor_type = Z_LVAL_P( value_z ); - int odbc_cursor_type = -1; + zend_long pdo_cursor_type = Z_LVAL_P( value_z ); + long odbc_cursor_type = -1; switch( pdo_cursor_type ) { @@ -164,7 +164,7 @@ void set_stmt_cursor_scroll_type( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE ); } - long odbc_cursor_type = Z_LVAL_P( value_z ); + long odbc_cursor_type = static_cast( Z_LVAL_P( value_z ) ); core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); @@ -181,7 +181,7 @@ void set_stmt_encoding( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING ); } - long attr_value = Z_LVAL_P( value_z ); + zend_long attr_value = Z_LVAL_P( value_z ); switch( attr_value ) { @@ -202,29 +202,31 @@ void set_stmt_encoding( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) // internal helper function to free meta data structures allocated void meta_data_free( field_meta_data* meta ) { + if( meta->field_name ) { + meta->field_name.reset(); + } sqlsrv_free( meta ); } -zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len ) +zval convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len ) { - zval* out_zval = NULL; + zval out_zval; switch( sqlsrv_php_type ) { case SQLSRV_PHPTYPE_INT: case SQLSRV_PHPTYPE_FLOAT: { - ALLOC_INIT_ZVAL( out_zval ); if( *in_val == NULL ) { - ZVAL_NULL( out_zval ); + ZVAL_NULL( &out_zval ); } else { if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) { - ZVAL_LONG( out_zval, **( reinterpret_cast( in_val ))); + 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 ))); } } @@ -238,27 +240,26 @@ zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN fie case SQLSRV_PHPTYPE_STRING: case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented { - ALLOC_INIT_ZVAL( out_zval ); if( *in_val == NULL ) { - ZVAL_NULL( out_zval ); + ZVAL_NULL( &out_zval ); } else { - ZVAL_STRINGL( out_zval, reinterpret_cast( *in_val ), field_len, 0 /*duplicate*/ ); + ZVAL_STRINGL( &out_zval, reinterpret_cast( *in_val ), field_len ); + sqlsrv_free( *in_val ); } break; } case SQLSRV_PHPTYPE_DATETIME: DIE( "Unsupported php type" ); - out_zval = ( reinterpret_cast( *in_val )); + out_zval = *( reinterpret_cast( *in_val )); break; case SQLSRV_PHPTYPE_NULL: - ALLOC_INIT_ZVAL( out_zval ); - ZVAL_NULL( out_zval ); + ZVAL_NULL( &out_zval ); break; default: @@ -274,15 +275,15 @@ zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN fie int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC); int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC); int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, - long offset TSRMLS_DC); + zend_long offset TSRMLS_DC); int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC); int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC); int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, - char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC); -int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC); -int pdo_sqlsrv_stmt_get_attr(pdo_stmt_t *stmt, long attr, zval *return_value TSRMLS_DC); -int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, long colno, zval *return_value TSRMLS_DC); + char **ptr, size_t *len, int *caller_frees TSRMLS_DC); +int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC); +int pdo_sqlsrv_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC); +int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC); int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC); int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC); @@ -329,6 +330,12 @@ void stmt_option_emulate_prepares:: operator()( sqlsrv_stmt* stmt, stmt_option c pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; } +void stmt_option_fetch_numeric:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); + pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false; +} + // log a function entry point #define PDO_LOG_STMT_ENTRY \ @@ -430,13 +437,10 @@ int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC) pdo_column_data* column_data = &(stmt->columns[colno]); SQLSRV_ASSERT( column_data != NULL, "pdo_sqsrv_stmt_describe_col: pdo_column_data was null" ); - - // Set the name - column_data->name = reinterpret_cast( core_meta_data->field_name.get()); - core_meta_data->field_name.transferred(); - - // Set the namelen - column_data->namelen = core_meta_data->field_name_len; + + // Set the name + column_data->name = zend_string_init( (const char*)core_meta_data->field_name.get(), core_meta_data->field_name_len, 0 ); + core_meta_data->field_name.reset(); // Set the maxlen column_data->maxlen = ( core_meta_data->field_precision > 0 ) ? core_meta_data->field_precision : core_meta_data->field_size; @@ -519,7 +523,7 @@ int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) if( driver_stmt->direct_query ) { query = driver_stmt->direct_query_subst_string; - query_len = driver_stmt->direct_query_subst_string_len; + query_len = static_cast( driver_stmt->direct_query_subst_string_len ); } // if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the @@ -527,7 +531,7 @@ int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) if( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { query = stmt->active_query_string; - query_len = stmt->active_query_stringlen; + query_len = static_cast( stmt->active_query_stringlen ); } core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len ); @@ -580,7 +584,7 @@ int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) // Return: // 0 for failure, 1 for success. int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, - long offset TSRMLS_DC) + zend_long offset TSRMLS_DC) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -609,10 +613,9 @@ int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, } for( long i = 0; i < stmt->column_count; ++i ) { - - if( zend_hash_index_find( stmt->bound_columns, i, (void**) &bind_data ) == FAILURE && - zend_hash_find( stmt->bound_columns, stmt->columns[ i ].name, stmt->columns[ i ].namelen, - (void**) &bind_data ) == FAILURE ) { + + 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; continue; @@ -675,7 +678,7 @@ int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, // Return: // 0 for failure, 1 for success. int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, - char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC) + char **ptr, size_t *len, int *caller_frees TSRMLS_DC) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -703,8 +706,9 @@ int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, sqlsrv_phptype sqlsrv_php_type; SQLSRV_ASSERT( colno >= 0 && colno < static_cast( driver_stmt->current_meta_data.size()), "Invalid column number in pdo_sqlsrv_stmt_get_col_data" ); - sqlsrv_php_type = driver_stmt->sql_type_to_php_type( driver_stmt->current_meta_data[ colno ]->field_type, - driver_stmt->current_meta_data[ colno ]->field_size, true ); + sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast( driver_stmt->current_meta_data[ colno ]->field_type ), + static_cast( driver_stmt->current_meta_data[ colno ]->field_size ), + true, driver_stmt->fetch_numeric ); // set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding sqlsrv_php_type.typeinfo.encoding = driver_stmt->encoding(); @@ -718,11 +722,11 @@ int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, TSRMLS_CC ); pdo_bound_param_data* bind_data = NULL; - int zr = zend_hash_index_find( stmt->bound_columns, colno, (void**) &bind_data ); + bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, colno)); - if( bind_data != NULL && bind_data->driver_params != NULL ) { + if( bind_data != NULL && !Z_ISUNDEF(bind_data->driver_params) ) { - CHECK_CUSTOM_ERROR( Z_TYPE_P( bind_data->driver_params ) != IS_LONG, driver_stmt, + CHECK_CUSTOM_ERROR( Z_TYPE( bind_data->driver_params ) != IS_LONG, driver_stmt, PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, colno + 1 ) { throw pdo::PDOException(); } @@ -734,7 +738,7 @@ int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, throw pdo::PDOException(); } - sqlsrv_php_type.typeinfo.encoding = Z_LVAL_P( bind_data->driver_params ); + sqlsrv_php_type.typeinfo.encoding = Z_LVAL( bind_data->driver_params ); switch( sqlsrv_php_type.typeinfo.encoding ) { case SQLSRV_ENCODING_SYSTEM: @@ -749,10 +753,11 @@ int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, } SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID; - core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, reinterpret_cast( ptr ), + core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast(ptr)), reinterpret_cast( len ), true, &sqlsrv_phptype_out TSRMLS_CC ); - zval** zval_ptr = reinterpret_cast( sqlsrv_malloc( sizeof( zval* ))); - *zval_ptr = reinterpret_cast( convert_to_zval( sqlsrv_phptype_out, reinterpret_cast( ptr ), *len )); + + zval* zval_ptr = ( zval* )( sqlsrv_malloc( sizeof( zval ))); + *zval_ptr = convert_to_zval( sqlsrv_phptype_out, reinterpret_cast( ptr ), *len ); *ptr = reinterpret_cast( zval_ptr ); *len = sizeof( zval ); @@ -776,12 +781,13 @@ int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, // val - Attribute value. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC) +int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; PDO_LOG_STMT_ENTRY; + pdo_sqlsrv_stmt* pdo_stmt = static_cast( stmt->driver_data ); sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); try { @@ -812,6 +818,10 @@ int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC) core_sqlsrv_set_buffered_query_limit( driver_stmt, val TSRMLS_CC ); break; + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + pdo_stmt->fetch_numeric = ( zend_is_true( val )) ? true : false; + break; + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; @@ -837,7 +847,7 @@ int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC) // return_value - Attribute value. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_get_attr( pdo_stmt_t *stmt, long attr, zval *return_value TSRMLS_DC ) +int pdo_sqlsrv_stmt_get_attr( pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC ) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -887,6 +897,12 @@ int pdo_sqlsrv_stmt_get_attr( pdo_stmt_t *stmt, long attr, zval *return_value TS break; } + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + { + ZVAL_BOOL( return_value, driver_stmt->fetch_numeric ); + break; + } + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; @@ -915,7 +931,7 @@ int pdo_sqlsrv_stmt_get_attr( pdo_stmt_t *stmt, long attr, zval *return_value TS // return_value - zval* consisting of the metadata. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, long colno, zval *return_value TSRMLS_DC) +int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -943,7 +959,7 @@ int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, long colno, zval *return_valu SQLLEN not_used; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name, sizeof( field_type_name ), &out_buff_len, ¬_used TSRMLS_CC ); - add_assoc_string( return_value, "sqlsrv:decl_type", field_type_name, 1 ); + add_assoc_string( return_value, "sqlsrv:decl_type", field_type_name ); // get the PHP type of the column. The types returned here mirror the types returned by debug_zval_dump when // given a variable of the same type. However, debug_zval_dump also gives the length of a string, and we only @@ -951,7 +967,7 @@ int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, long colno, zval *return_valu long pdo_type = sql_type_to_pdo_type( core_meta_data->field_type ); switch( pdo_type ) { case PDO_PARAM_STR: - add_assoc_string( return_value, "native_type", "string", 1 ); + add_assoc_string( return_value, "native_type", "string" ); break; default: DIE( "pdo_sqlsrv_stmt_get_col_data: Unknown PDO type returned" ); @@ -963,7 +979,7 @@ int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, long colno, zval *return_valu 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, 1 ); + add_assoc_string( return_value, "table", table_name ); if( stmt->columns[ colno ].param_type == PDO_PARAM_ZVAL ) { add_assoc_long( return_value, "pdo_type", pdo_type ); @@ -1061,6 +1077,11 @@ int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, // since the param isn't reliable, we don't do anything here case PDO_PARAM_EVT_ALLOC: + // if emulate prepare is on, set the bind_param_encoding so it can be used in PDO::quote when binding parameters on the client side + if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( stmt->dbh->driver_data ); + driver_dbh->bind_param_encoding = static_cast( Z_LVAL( param->driver_params )); + } break; case PDO_PARAM_EVT_FREE: break; @@ -1122,10 +1143,10 @@ int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, // since the core layer keys off the zval type, we substitute a null for what they gave us case PDO_PARAM_NULL: { - zval* null_zval; + zval null_zval; php_out_type = SQLSRV_PHPTYPE_NULL; - MAKE_STD_ZVAL( null_zval ); - ZVAL_NULL( null_zval ); + + ZVAL_NULL( &null_zval ); zval_ptr_dtor( ¶m->parameter ); param->parameter = null_zval; break; @@ -1171,7 +1192,7 @@ int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, // that succeeds since the core layer implements DateTime object handling for the sqlsrv // 2.0 driver. To be consistent and avoid surprises of one object type working and others // not, we block all objects here. - CHECK_CUSTOM_ERROR( direction != SQL_PARAM_OUTPUT && Z_TYPE_P( param->parameter ) == IS_OBJECT, + CHECK_CUSTOM_ERROR( direction != SQL_PARAM_OUTPUT && Z_TYPE( param->parameter ) == IS_OBJECT, driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) { throw pdo::PDOException(); } @@ -1182,8 +1203,8 @@ int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, encoding = driver_stmt->conn->encoding(); } // if the user provided an encoding, use it instead - if( param->driver_params != NULL ) { - CHECK_CUSTOM_ERROR( Z_TYPE_P( param->driver_params ) != IS_LONG, driver_stmt, + if( !Z_ISUNDEF(param->driver_params) ) { + CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) { throw pdo::PDOException(); } @@ -1191,7 +1212,7 @@ int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, param->paramno + 1 ) { throw pdo::PDOException(); } - encoding = static_cast( Z_LVAL_P( param->driver_params )); + encoding = static_cast( Z_LVAL( param->driver_params )); switch( encoding ) { case SQLSRV_ENCODING_SYSTEM: @@ -1205,7 +1226,7 @@ int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, } } // and bind the parameter - core_sqlsrv_bind_param( driver_stmt, param->paramno, direction, param->parameter, php_out_type, encoding, + core_sqlsrv_bind_param( driver_stmt, static_cast( param->paramno ), direction, &(param->parameter) , php_out_type, encoding, sql_type, column_size, decimal_digits TSRMLS_CC ); } break; @@ -1219,9 +1240,9 @@ int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, if( !param->is_param ) { break; } - + core_sqlsrv_post_param( reinterpret_cast( stmt->driver_data ), param->paramno, - param->parameter TSRMLS_CC ); + &(param->parameter) TSRMLS_CC ); } break; case PDO_PARAM_EVT_FETCH_PRE: @@ -1249,7 +1270,7 @@ int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, // Returns a sqlsrv_phptype for a given SQL Server data type. -sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_over_stream ) +sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_over_stream, bool prefer_number_to_string ) { sqlsrv_phptype sqlsrv_phptype; int local_encoding = this->encoding(); @@ -1261,15 +1282,31 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUI } switch( sql_type ) { - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + if ( prefer_number_to_string ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + } + break; + case SQL_FLOAT: + case SQL_REAL: + if ( prefer_number_to_string ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + } + break; case SQL_BIGINT: case SQL_CHAR: case SQL_DECIMAL: - case SQL_FLOAT: - case SQL_REAL: case SQL_GUID: case SQL_NUMERIC: case SQL_WCHAR: diff --git a/pdo_sqlsrv/pdo_util.cpp b/pdo_sqlsrv/pdo_util.cpp index 754b6cff..fe25efbb 100644 --- a/pdo_sqlsrv/pdo_util.cpp +++ b/pdo_sqlsrv/pdo_util.cpp @@ -3,7 +3,7 @@ // // Contents: Utility functions used by both connection or statement functions // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -367,15 +367,14 @@ pdo_error PDO_ERRORS[] = { SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -71, true } }, - - { -1, {} } + { UINT_MAX, {} } }; // Returns a sqlsrv_error for a given error code. -sqlsrv_error_const* get_error_message( unsigned int sqlsrv_error_code ) { - - sqlsrv_error_const *error_message = NULL; - int zr = zend_hash_index_find( g_pdo_errors_ht, sqlsrv_error_code, reinterpret_cast( &error_message )); +sqlsrv_error_const* get_error_message(unsigned int sqlsrv_error_code) { + + sqlsrv_error_const *error_message = NULL; + int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr(g_pdo_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; if( zr == FAILURE ) { DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); } @@ -446,8 +445,8 @@ bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error SQLSRV_ASSERT( err == true, "No ODBC error was found" ); } - SQLSRV_STATIC_ASSERT( sizeof( error->sqlstate ) <= sizeof( dbh->error_code )); - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), reinterpret_cast( error->sqlstate )); + SQLSRV_ASSERT(strlen(reinterpret_cast(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow"); + strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast(error->sqlstate)); switch( dbh->error_mode ) { case PDO_ERRMODE_EXCEPTION: @@ -459,11 +458,10 @@ bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error break; case PDO_ERRMODE_WARNING: if( !warning ) { - unsigned int msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + MAX_DIGITS + 1; - sqlsrv_malloc_auto_ptr msg; - msg = static_cast( sqlsrv_malloc( msg_len )); - core_sqlsrv_format_message( msg, msg_len, WARNING_TEMPLATE, error->sqlstate, error->native_code, + char* 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 ); sqlsrv_free( msg ); @@ -499,7 +497,7 @@ bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_erro SQLSRV_ASSERT( err == true, "No ODBC error was found" ); } - SQLSRV_STATIC_ASSERT( sizeof( error->sqlstate ) <= sizeof( pdo_stmt->error_code )); + SQLSRV_ASSERT( strlen( reinterpret_cast( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow"); strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast( error->sqlstate )); switch( pdo_stmt->dbh->error_mode ) { @@ -512,11 +510,10 @@ bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_erro break; case PDO_ERRMODE_WARNING: if( !warning ) { - unsigned int msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + MAX_DIGITS + 1; - sqlsrv_malloc_auto_ptr msg; - msg = static_cast( sqlsrv_malloc( msg_len )); - core_sqlsrv_format_message( msg, msg_len, WARNING_TEMPLATE, error->sqlstate, error->native_code, + char* msg = static_cast( sqlsrv_malloc(SQL_MAX_MESSAGE_LENGTH+1)); + core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, error->native_message ); php_error( E_WARNING, msg ); sqlsrv_free( msg ); @@ -547,13 +544,8 @@ void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pd // SQLSTATE is already present in the zval. add_next_index_long( pdo_zval, last_error->native_code ); - add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message ), 1 /*dup*/ ); + add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message )); } - else { - add_next_index_null( pdo_zval ); /* native code */ - add_next_index_null( pdo_zval ); /* native message */ - } - } // Formats the error message and writes to the php error log. @@ -577,11 +569,11 @@ namespace { void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ) { - zval_auto_ptr ex_obj; - MAKE_STD_ZVAL( ex_obj ); + zval ex_obj; + ZVAL_UNDEF( &ex_obj ); zend_class_entry* ex_class = pdo_get_exception_class(); - int zr = object_init_ex( ex_obj, ex_class ); + int zr = object_init_ex( &ex_obj, ex_class ); SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" ); sqlsrv_malloc_auto_ptr ex_msg; @@ -589,23 +581,27 @@ void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ) 12 + 1; // 12 = "SQLSTATE[]: " ex_msg = reinterpret_cast( sqlsrv_malloc( ex_msg_len )); snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message ); - zend_update_property_string( ex_class, ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, + zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, ex_msg TSRMLS_CC ); - zend_update_property_string( ex_class, ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, + zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, reinterpret_cast( error->sqlstate ) TSRMLS_CC ); - zval_auto_ptr ex_error_info; - MAKE_STD_ZVAL( ex_error_info ); - array_init( ex_error_info ); - add_next_index_string( ex_error_info, reinterpret_cast( error->sqlstate ), 1 /* dup */ ); - add_next_index_long( ex_error_info, error->native_code ); - add_next_index_string( ex_error_info, reinterpret_cast( error->native_message ), 1 /* dup */ ); - zend_update_property( ex_class, ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, - ex_error_info TSRMLS_CC ); + zval ex_error_info; + ZVAL_UNDEF( &ex_error_info ); + array_init( &ex_error_info ); + add_next_index_string( &ex_error_info, reinterpret_cast( error->sqlstate )); + add_next_index_long( &ex_error_info, error->native_code ); + add_next_index_string( &ex_error_info, reinterpret_cast( error->native_message )); + //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) + //and the refcount of the zend_array is incremented by 1 + zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, + &ex_error_info TSRMLS_CC ); - zend_throw_exception_object( ex_obj TSRMLS_CC ); - ex_msg.transferred(); - ex_obj.transferred(); + //DELREF ex_error_info here to decrement the refcount of the zend_array is 1 + //the global hashtable EG(exception) then points to the zend_object in ex_obj in zend_throw_exception_object; + //this ensure when EG(exception) cleans itself at php shutdown, the zend_array allocated is properly destroyed + Z_DELREF( ex_error_info ); + zend_throw_exception_object( &ex_obj TSRMLS_CC ); } } diff --git a/pdo_sqlsrv/template.rc b/pdo_sqlsrv/template.rc index 6dd12c2e..c6a4f82a 100644 --- a/pdo_sqlsrv/template.rc +++ b/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.0 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -59,7 +59,7 @@ BEGIN BEGIN BLOCK "040904b0" BEGIN - VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. 1997-2009 The PHP Group. All rights reserved.\0" + VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright 2001-2016 The PHP Group. All rights reserved.\0" VALUE "CompanyName", "Microsoft Corp.\0" VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0" VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION) diff --git a/pdo_sqlsrv/version.h b/pdo_sqlsrv/version.h index bbe58413..93bb6d47 100644 --- a/pdo_sqlsrv/version.h +++ b/pdo_sqlsrv/version.h @@ -2,7 +2,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -16,10 +16,10 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#define VER_FILEVERSION_STR "3.2.0.0" -#define _FILEVERSION 3,2,0,0 -#define SQLVERSION_MAJOR 3 -#define SQLVERSION_MINOR 2 +#define VER_FILEVERSION_STR "4.1.0.0" +#define _FILEVERSION 4,1,0,0 +#define SQLVERSION_MAJOR 4 +#define SQLVERSION_MINOR 1 #define SQLVERSION_MMDD 0 #define SQLVERSION_REVISION 0 diff --git a/sample/pdo_sqlsrv_sample.php b/sample/pdo_sqlsrv_sample.php new file mode 100644 index 00000000..3c92efa3 --- /dev/null +++ b/sample/pdo_sqlsrv_sample.php @@ -0,0 +1,74 @@ +query( $tsql ); + + //Error handling + FormatErrors ($conn->errorInfo()); + + $productCount = 0; + $ctr = 0; + ?> + +

    First 10 results are :

    + + fetch(PDO::FETCH_ASSOC)) + { + if($ctr>9) + break; + $ctr++; + echo($row['CompanyName']); + echo("
    "); + $productCount++; + } + $getProducts = NULL; + + $tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.* VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())"; + + //Insert query + $insertReview = $conn->query( $tsql ); + FormatErrors ($conn->errorInfo()); + ?> + +

    Product Key inserted is :

    + + fetch(PDO::FETCH_ASSOC)) + { + echo($row['ProductID']."
    "); + } + $insertReview = NULL; + + //Delete Query + //We are deleting the same record + $tsql = "DELETE FROM [SalesLT].[Product] WHERE Name=?"; + $param = "SQL New 1"; + + $deleteReview = $conn->prepare($tsql); + $deleteReview->bindParam(1, $param); + + $deleteReview->execute(); + FormatErrors ($deleteReview->errorInfo()); + + function FormatErrors( $error ) + { + /* Display error. */ + echo "Error information:
    "; + + echo "SQLSTATE: ".$error[0]."
    "; + echo "Code: ".$error[1]."
    "; + echo "Message: ".$error[2]."
    "; + } +?> \ No newline at end of file diff --git a/sample/sqlsrv_sample.php b/sample/sqlsrv_sample.php new file mode 100644 index 00000000..886e06da --- /dev/null +++ b/sample/sqlsrv_sample.php @@ -0,0 +1,69 @@ +"yourdatabase", "Uid"=>"yourusername", "PWD"=>"yourpassword"); + + //Establishes the connection + $conn = sqlsrv_connect($serverName, $connectionOptions); + //Select Query + $tsql = "SELECT [CompanyName] FROM SalesLT.Customer"; + //Executes the query + $getProducts = sqlsrv_query($conn, $tsql); + //Error handling + if ($getProducts == FALSE) + die(FormatErrors(sqlsrv_errors())); + $productCount = 0; + $ctr = 0; + ?> +

    First 10 results are :

    + 9) + break; + $ctr++; + echo($row['CompanyName']); + echo("
    "); + $productCount++; + } + sqlsrv_free_stmt($getProducts); + + $tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.ProductID VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())"; + //Insert query + $insertReview = sqlsrv_query($conn, $tsql); + if($insertReview == FALSE) + die(FormatErrors( sqlsrv_errors())); + ?> +

    Product Key inserted is :

    + "; + + foreach ( $errors as $error ) + { + echo "SQLSTATE: ".$error['SQLSTATE']."
    "; + echo "Code: ".$error['code']."
    "; + echo "Message: ".$error['message']."
    "; + } + } + +?> \ No newline at end of file diff --git a/sqlsrv/CREDITS b/sqlsrv/CREDITS index 3f05e8d7..dc85ce17 100644 --- a/sqlsrv/CREDITS +++ b/sqlsrv/CREDITS @@ -1 +1 @@ -Microsoft Drivers 3.2.0 for PHP for SQL Server (SQLSRV driver) +Microsoft Drivers 4.1 for PHP for SQL Server (PDO driver) diff --git a/sqlsrv/config.w32 b/sqlsrv/config.w32 index 5a3970c5..1fb9fa5b 100644 --- a/sqlsrv/config.w32 +++ b/sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.0 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -21,16 +21,17 @@ ARG_ENABLE("sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (SQLSRV dr if( PHP_SQLSRV != "no" ) { + sqlsrv_src = "conn.cpp init.cpp stmt.cpp util.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp"; + if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") && CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")) { - EXTENSION("sqlsrv", "conn.cpp init.cpp stmt.cpp util.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp" ) + EXTENSION("sqlsrv", sqlsrv_src, PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1") CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_SQLSRV_ODBC'); CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_SQLSRV_ODBC'); ADD_FLAG( 'LDFLAGS_SQLSRV', '/NXCOMPAT /DYNAMICBASE /debug' ); ADD_FLAG( 'CFLAGS_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' ); - ADD_FLAG( 'CFLAGS_SQLSRV', '/D _HAS_CPP0X=0' ); ADD_FLAG( 'CFLAGS_SQLSRV', '/EHsc' ); ADD_FLAG( 'CFLAGS_SQLSRV', '/GS' ); ADD_FLAG( 'CFLAGS_SQLSRV', '/Zi' ); diff --git a/sqlsrv/conn.cpp b/sqlsrv/conn.cpp index 08698fed..2e7f1b2b 100644 --- a/sqlsrv/conn.cpp +++ b/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -55,25 +55,25 @@ struct conn_char_set_func { { convert_to_string( value ); const char* encoding = Z_STRVAL_P( value ); - unsigned int encoding_len = Z_STRLEN_P( value ); + size_t encoding_len = Z_STRLEN_P( value ); - for( zend_hash_internal_pointer_reset( g_ss_encodings_ht ); - zend_hash_has_more_elements( g_ss_encodings_ht ) == SUCCESS; - zend_hash_move_forward( g_ss_encodings_ht )) { + zend_ulong index = -1; + zend_string* key = NULL; + void* ss_encoding_temp = NULL; - sqlsrv_encoding* ss_encoding; - core::sqlsrv_zend_hash_get_current_data( *conn, g_ss_encodings_ht, (void**) &ss_encoding TSRMLS_CC ); + ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, ss_encoding_temp ) { + sqlsrv_encoding* ss_encoding = reinterpret_cast( ss_encoding_temp ); + ss_encoding_temp = NULL; + if (!strnicmp( encoding, ss_encoding->iana, encoding_len )) { - if( !strnicmp( encoding, ss_encoding->iana, encoding_len ) ) { + if ( ss_encoding->not_for_connection ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); + } - if( ss_encoding->not_for_connection ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); - } - - conn->set_encoding( static_cast( ss_encoding->code_page )); - return; - } - } + conn->set_encoding( static_cast(ss_encoding->code_page )); + return; + } + } ZEND_HASH_FOREACH_END(); THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); } @@ -105,8 +105,7 @@ struct int_conn_attr_func { { try { - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_LVAL_P( value )), - SQL_IS_UINTEGER TSRMLS_CC ); + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_LVAL_P( value )), SQL_IS_UINTEGER TSRMLS_CC ); } catch( core::CoreException& ) { throw; @@ -120,9 +119,9 @@ struct bool_conn_attr_func { static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) { try { + core::SQLSetConnectAttr(conn, Attr, reinterpret_cast((zend_long)zend_is_true(value)), + SQL_IS_UINTEGER TSRMLS_CC); - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( zend_is_true( value )), - SQL_IS_UINTEGER TSRMLS_CC ); } catch( core::CoreException& ) { throw; @@ -134,14 +133,14 @@ struct bool_conn_attr_func { //// *** internal functions *** void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ); -void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, __out char** uid, __out char** pwd, - __inout HashTable* ss_conn_options_ht TSRMLS_DC ); -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* ss_stmt_options_ht TSRMLS_DC ); -void add_conn_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, - HashTable* options_ht, zval** data TSRMLS_DC ); -void add_stmt_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, HashTable* options_ht, zval** data TSRMLS_DC ); -int get_conn_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, zval const* value_z TSRMLS_DC ); -int get_stmt_option_key( char* key, unsigned int key_len TSRMLS_DC ); +void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ char** uid, _Out_ char** pwd, + _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ); +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ); +void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, + HashTable* options_ht, zval* data TSRMLS_DC ); +void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, HashTable* options_ht, zval* data TSRMLS_DC ); +int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC ); +int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ); } @@ -202,27 +201,27 @@ const stmt_option SS_STMT_OPTS[] = { SSStmtOptionNames::QUERY_TIMEOUT, sizeof( SSStmtOptionNames::QUERY_TIMEOUT ), SQLSRV_STMT_OPTION_QUERY_TIMEOUT, - new stmt_option_query_timeout + std::unique_ptr( new stmt_option_query_timeout ) }, { SSStmtOptionNames::SEND_STREAMS_AT_EXEC, sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ), SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, - new stmt_option_send_at_exec + std::unique_ptr( new stmt_option_send_at_exec ) }, { SSStmtOptionNames::SCROLLABLE, sizeof( SSStmtOptionNames::SCROLLABLE ), SQLSRV_STMT_OPTION_SCROLLABLE, - new stmt_option_scrollable + std::unique_ptr( new stmt_option_scrollable ) }, { SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE, sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ), SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, - new stmt_option_buffered_query_limit + std::unique_ptr( new stmt_option_buffered_query_limit ) }, - { NULL, 0, SQLSRV_STMT_OPTION_INVALID, NULL }, + { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -415,9 +414,6 @@ const connection_option SS_CONN_OPTS[] = { PHP_FUNCTION ( sqlsrv_connect ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); LOG_FUNCTION( "sqlsrv_connect" ); SET_FUNCTION_NAME( *g_henv_cp ); @@ -429,8 +425,9 @@ PHP_FUNCTION ( sqlsrv_connect ) zval* options_z = NULL; char* uid = NULL; char* pwd = NULL; - unsigned int server_len = 0; - + size_t server_len = 0; + zval conn_z; + ZVAL_UNDEF(&conn_z); // get the server name and connection options int result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &server, &server_len, &options_z ); @@ -447,7 +444,7 @@ PHP_FUNCTION ( sqlsrv_connect ) // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( ss_conn_options_ht ); - core::sqlsrv_zend_hash_init( *g_henv_cp, ss_conn_options_ht, 10 /* # of buckets */, NULL /*hashfn*/, + core::sqlsrv_zend_hash_init( *g_henv_cp, ss_conn_options_ht, 10 /* # of buckets */, ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. @@ -463,13 +460,15 @@ PHP_FUNCTION ( sqlsrv_connect ) // create a bunch of statements ALLOC_HASHTABLE( stmts ); - core::sqlsrv_zend_hash_init( *g_henv_cp, stmts, 5, NULL /* hashfn */, NULL /* dtor */, 0 /* persistent */ TSRMLS_CC ); + core::sqlsrv_zend_hash_init( *g_henv_cp, stmts, 5, NULL /* dtor */, 0 /* persistent */ TSRMLS_CC ); - // register the connection with the PHP runtime - ss::zend_register_resource( return_value, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC ); + // register the connection with the PHP runtime + + ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC); conn->stmts = stmts; stmts.transferred(); + RETURN_RES( Z_RES(conn_z) ); } catch( core::CoreException& ) { @@ -515,9 +514,6 @@ PHP_FUNCTION( sqlsrv_begin_transaction ) { LOG_FUNCTION( "sqlsrv_begin_transaction" ); - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); ss_sqlsrv_conn* conn = NULL; PROCESS_PARAMS( conn, "r", _FN_, 0 ); @@ -562,71 +558,64 @@ PHP_FUNCTION( sqlsrv_begin_transaction ) PHP_FUNCTION( sqlsrv_close ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); - LOG_FUNCTION( "sqlsrv_close" ); zval* conn_r = NULL; ss_sqlsrv_conn* conn = NULL; sqlsrv_context_auto_ptr error_ctx; - full_mem_check(MEMCHECK_SILENT); reset_errors( TSRMLS_C ); try { - - // dummy context to pass to the error handler + + // dummy context to pass to the error handler error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); SET_FUNCTION_NAME( *error_ctx ); - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r ) == FAILURE ) { + if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r) == FAILURE ) { // Check if it was a zval int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &conn_r ); CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - throw ss::SSException(); } - - // if sqlsrv_close was called on a non-existent connection than we just return success. + + // if sqlsrv_close was called on a non-existent connection then we just return success. if( Z_TYPE_P( conn_r ) == IS_NULL ) { - RETURN_TRUE; } - else { - - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + else { + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); } } - conn = static_cast( zend_fetch_resource( &conn_r TSRMLS_CC, -1, ss_sqlsrv_conn::resource_name, NULL, 1, - ss_sqlsrv_conn::descriptor )); - - CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + conn = static_cast( zend_fetch_resource( Z_RES_P( conn_r ) TSRMLS_CC, ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor )); + // if sqlsrv_close was called on an already closed connection then we just return success. + if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) { + RETURN_TRUE; + } + + CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + throw ss::SSException(); } SET_FUNCTION_NAME( *conn ); - + // cause any variables still holding a reference to this to be invalid so they cause // an error when passed to a sqlsrv function. There's nothing we can do if the // removal fails, so we just log it and move on. - int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( conn_r )); - - if( zr == FAILURE ) { - - LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RESVAL_P( conn_r )); + if( zend_list_close( Z_RES_P( conn_r ) ) == FAILURE ) { + LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P( conn_r )); } - - ZVAL_NULL( conn_r ); + + ZVAL_NULL( conn_r ); RETURN_TRUE; } catch( core::CoreException& ) { - + RETURN_FALSE; } catch( ... ) { @@ -635,7 +624,7 @@ PHP_FUNCTION( sqlsrv_close ) } } -void __cdecl sqlsrv_conn_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC ) +void __cdecl sqlsrv_conn_dtor( zend_resource *rsrc TSRMLS_DC ) { LOG_FUNCTION( "sqlsrv_conn_dtor" ); @@ -680,10 +669,6 @@ PHP_FUNCTION( sqlsrv_commit ) { LOG_FUNCTION( "sqlsrv_commit" ); - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); - ss_sqlsrv_conn* conn = NULL; PROCESS_PARAMS( conn, "r", _FN_, 0 ); @@ -737,9 +722,6 @@ PHP_FUNCTION( sqlsrv_rollback ) { LOG_FUNCTION( "sqlsrv_rollback" ); - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); ss_sqlsrv_conn* conn = NULL; @@ -773,9 +755,6 @@ PHP_FUNCTION( sqlsrv_rollback ) PHP_FUNCTION( sqlsrv_client_info ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); LOG_FUNCTION( "sqlsrv_client_info" ); ss_sqlsrv_conn* conn = NULL; @@ -818,10 +797,6 @@ PHP_FUNCTION( sqlsrv_server_info ) { try { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); - LOG_FUNCTION( "sqlsrv_server_info" ); ss_sqlsrv_conn* conn = NULL; PROCESS_PARAMS( conn, "r", _FN_, 0 ); @@ -873,21 +848,18 @@ PHP_FUNCTION( sqlsrv_server_info ) PHP_FUNCTION( sqlsrv_prepare ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); LOG_FUNCTION( "sqlsrv_prepare" ); sqlsrv_malloc_auto_ptr stmt; ss_sqlsrv_conn* conn = NULL; char *sql = NULL; - unsigned int sql_len = 0; + zend_long sql_len = 0; zval* params_z = NULL; zval* options_z = NULL; hash_auto_ptr ss_stmt_options_ht; - zval_auto_ptr stmt_z; - ALLOC_INIT_ZVAL( stmt_z ); + zval stmt_z; + ZVAL_UNDEF(&stmt_z); PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z ); @@ -897,7 +869,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 */, NULL /*hashfn*/, + core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); @@ -914,7 +886,7 @@ PHP_FUNCTION( sqlsrv_prepare ) if( sql == NULL ) { - DIE( "sqlsrv_query: sql string was null." ); + DIE( "sqlsrv_prepare: sql string was null." ); } stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, @@ -923,7 +895,11 @@ PHP_FUNCTION( sqlsrv_prepare ) core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC ); - mark_params_by_reference( stmt, params_z TSRMLS_CC ); + //mark_params_by_reference( stmt, params_z TSRMLS_CC ); + if (params_z) { + stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); + ZVAL_COPY(stmt->params_z, params_z); + } stmt->prepared = true; @@ -932,19 +908,16 @@ PHP_FUNCTION( sqlsrv_prepare ) // store the resource id with the connection so the connection // can release this statement when it closes. - int next_index = zend_hash_next_free_element( conn->stmts ); - long rsrc_idx = Z_RESVAL_P( stmt_z ); - - core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &rsrc_idx, sizeof( long ) TSRMLS_CC ); + zend_long next_index = zend_hash_next_free_element( conn->stmts ); + + core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); stmt->conn_index = next_index; // the statement is now registered with EG( regular_list ) stmt.transferred(); - - zval_ptr_dtor( &return_value ); - *return_value_ptr = stmt_z; - stmt_z.transferred(); + + RETURN_RES(Z_RES(stmt_z)); } catch( core::CoreException& ) { @@ -954,8 +927,8 @@ PHP_FUNCTION( sqlsrv_prepare ) stmt->conn = NULL; stmt->~ss_sqlsrv_stmt(); } - if( Z_TYPE_P( stmt_z ) != IS_NULL ) { - free_stmt_resource( stmt_z TSRMLS_CC ); + if (!Z_ISUNDEF(stmt_z)) { + free_stmt_resource(&stmt_z TSRMLS_CC); } RETURN_FALSE; @@ -998,9 +971,6 @@ PHP_FUNCTION( sqlsrv_prepare ) PHP_FUNCTION( sqlsrv_query ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); LOG_FUNCTION( "sqlsrv_query" ); @@ -1010,9 +980,10 @@ PHP_FUNCTION( sqlsrv_query ) hash_auto_ptr ss_stmt_options_ht; int sql_len = 0; zval* options_z = NULL; - zval* params_z = NULL; - zval_auto_ptr stmt_z; - + zval* params_z = NULL; + zval stmt_z; + ZVAL_UNDEF(&stmt_z); + PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z ); try { @@ -1022,7 +993,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 */, NULL /*hashfn*/, ZVAL_PTR_DTOR, + core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); @@ -1045,9 +1016,9 @@ PHP_FUNCTION( sqlsrv_query ) ss_stmt_options_ht, SS_STMT_OPTS, ss_error_handler, NULL TSRMLS_CC ) ); - stmt->params_z = params_z; if( params_z ) { - zval_add_ref( ¶ms_z ); + stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); + ZVAL_COPY(stmt->params_z, params_z); } stmt->set_func( "sqlsrv_query" ); @@ -1056,22 +1027,18 @@ PHP_FUNCTION( sqlsrv_query ) // execute the statement core_sqlsrv_execute( stmt TSRMLS_CC, sql, sql_len ); - - // register the statement with the PHP runtime - ALLOC_INIT_ZVAL( stmt_z ); - ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC ); - + + // register the statement with the PHP runtime + ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC); // store the resource id with the connection so the connection // can release this statement when it closes. - int next_index = zend_hash_next_free_element( conn->stmts ); - long rsrc_idx = Z_RESVAL_P( stmt_z ); - core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &rsrc_idx, sizeof( long ) TSRMLS_CC ); + zend_ulong next_index = zend_hash_next_free_element( conn->stmts ); + + core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); stmt->conn_index = next_index; stmt.transferred(); - zval_ptr_dtor( &return_value ); - *return_value_ptr = stmt_z; - stmt_z.transferred(); + RETURN_RES(Z_RES(stmt_z)); } catch( core::CoreException& ) { @@ -1081,9 +1048,8 @@ PHP_FUNCTION( sqlsrv_query ) stmt->conn = NULL; // tell the statement that it isn't part of the connection so it doesn't try to remove itself stmt->~ss_sqlsrv_stmt(); } - if( stmt_z ) { - - free_stmt_resource( stmt_z TSRMLS_CC ); + if (!Z_ISUNDEF(stmt_z)) { + free_stmt_resource(&stmt_z TSRMLS_CC); } RETURN_FALSE; @@ -1096,13 +1062,11 @@ PHP_FUNCTION( sqlsrv_query ) void free_stmt_resource( zval* stmt_z TSRMLS_DC ) { - int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( stmt_z )); - if( zr == FAILURE ) { - LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RESVAL_P( stmt_z )); + if( FAILURE == zend_list_close( Z_RES_P( stmt_z ))) { + LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z)); } - ZVAL_NULL( stmt_z ); - zval_ptr_dtor( &stmt_z ); + zval_ptr_dtor(stmt_z); } // internal connection functions @@ -1121,58 +1085,52 @@ void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ) // loop through the stmts hash table and destroy each stmt resource so we can close the // ODBC connection - for( zend_hash_internal_pointer_reset( conn->stmts ); - zend_hash_has_more_elements( conn->stmts ) == SUCCESS; - zend_hash_move_forward( conn->stmts )) { - long* rsrc_idx_ptr = NULL; - - try { - // get the resource id for the next statement created with this connection - core::sqlsrv_zend_hash_get_current_data( *conn, conn->stmts, reinterpret_cast( &rsrc_idx_ptr ) TSRMLS_CC ); - } - catch( core::CoreException& ) { + zval* rsrc_ptr = NULL; + ZEND_HASH_FOREACH_VAL( conn->stmts, rsrc_ptr ) { + try { + int zr = ( rsrc_ptr ) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, *conn, SQLSRV_ERROR_ZEND_HASH ) { + throw core::CoreException(); + } + } + catch( core::CoreException& ) { + DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" ); + } + // see if the statement is still valid, and if not skip to the next one + // presumably this should never happen because if it's in the list, it should still be valid + // by virtue that a statement resource should remove itself from its connection when it is + // destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource + // and move to the next one. + ss_sqlsrv_stmt* stmt = NULL; + stmt = static_cast( Z_RES_VAL_P( rsrc_ptr )); + if( stmt == NULL || Z_RES_TYPE_P( rsrc_ptr ) != ss_sqlsrv_stmt::descriptor ) { + LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves" + " from the connection so this shouldn't be out of sync." ); + continue; + } + // delete the statement by deleting it from Zend's resource list, which will force its destruction + stmt->conn = NULL; - DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" ); - } - - // see if the statement is still valid, and if not skip to the next one - // presumably this should never happen because if it's in the list, it should still be valid - // by virtue that a statement resource should remove itself from its connection when it is - // destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource - // and move to the next one. - ss_sqlsrv_stmt* stmt = NULL; - int type = -1; - stmt = static_cast( zend_list_find( *rsrc_idx_ptr, &type )); - if( stmt == NULL || type != ss_sqlsrv_stmt::descriptor ) { - LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves" - " from the connection so this shouldn't be out of sync." ); - continue; - } - - // delete the statement by deleting it from Zend's resource list, which will force its destruction - stmt->conn = NULL; - - try { - - // this would call the destructor on the statement. - core::sqlsrv_zend_hash_index_del( *conn, &EG( regular_list ), *rsrc_idx_ptr TSRMLS_CC ); - } - catch( core::CoreException& ) { - LOG( SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", *rsrc_idx_ptr ); - } - } + try { + // this would call the destructor on the statement. + int zr = zend_list_close(Z_RES_P(rsrc_ptr)); + } + catch( core::CoreException& ) { + LOG(SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", Z_RES_HANDLE_P(rsrc_ptr)); + } + } ZEND_HASH_FOREACH_END(); zend_hash_destroy( conn->stmts ); FREE_HASHTABLE( conn->stmts ); conn->stmts = NULL; } -int get_conn_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, zval const* value_z TSRMLS_DC ) +int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC ) { 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( 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 ) { @@ -1201,7 +1159,7 @@ int get_conn_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, z } char* value = Z_STRVAL_P( value_z ); - int value_len = Z_STRLEN_P( value_z ); + size_t value_len = Z_STRLEN_P( value_z ); bool escaped = core_is_conn_opt_value_escaped( value, value_len ); CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[ i ].sqlsrv_name ) { @@ -1218,80 +1176,73 @@ int get_conn_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, z return SQLSRV_CONN_OPTION_INVALID; } -int get_stmt_option_key( char* key, unsigned int key_len TSRMLS_DC ) +int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ) { for( int i = 0; SS_STMT_OPTS[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { - if( key_len == SS_STMT_OPTS[ i ].name_len && !stricmp( key, SS_STMT_OPTS[ i ].name )) { - + if( key_len == SS_STMT_OPTS[ i ].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[ i ].name )) { return SS_STMT_OPTS[ i ].key; } } return SQLSRV_STMT_OPTION_INVALID; } -void add_stmt_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, - HashTable* options_ht, zval** data TSRMLS_DC ) +void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, + HashTable* options_ht, zval* data TSRMLS_DC ) { int option_key = ::get_stmt_option_key( key, key_len TSRMLS_CC ); - CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, key ) { + CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, ZSTR_VAL( key ) ) { throw ss::SSException(); } - zval_add_ref( data ); // inc the ref count since this is going into the options_ht too. - core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, (void**)data, sizeof(zval*) TSRMLS_CC ); + Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. + core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); } -void add_conn_option_key( sqlsrv_context& ctx, char* key, unsigned int key_len, - HashTable* options_ht, zval** data TSRMLS_DC ) +void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, + HashTable* options_ht, zval* data TSRMLS_DC ) { - int option_key = ::get_conn_option_key( ctx, key, key_len, *data TSRMLS_CC ); + int option_key = ::get_conn_option_key( ctx, key, key_len, data TSRMLS_CC ); CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key ) { throw ss::SSException(); } - zval_add_ref( data ); // inc the ref count since this is going into the options_ht too. - core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, (void**)data, sizeof(zval*) TSRMLS_CC ); + Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. + core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); } // Iterates through the list of statement options provided by the user and validates them // against the list of supported statement options by this driver. After validation // creates a Hashtable of statement options to be sent to the core layer for processing. -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* ss_stmt_options_ht TSRMLS_DC ) +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ) { try { - if( stmt_options ) { HashTable* options_ht = Z_ARRVAL_P( stmt_options ); - - for( zend_hash_internal_pointer_reset( options_ht ); zend_hash_has_more_elements( options_ht ) == SUCCESS; - zend_hash_move_forward( options_ht )) { + zend_ulong int_key = -1; + zend_string *key = NULL; + zval* data = NULL; + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + size_t key_len = 0; + zval* conn_opt = NULL; + int result = 0; - int type = HASH_KEY_NON_EXISTANT; - char *key = NULL; - unsigned int key_len = 0; - unsigned long int_key = -1; - zval** data; - zval* conn_opt = NULL; - int result = 0; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - type = zend_hash_get_current_key_ex( options_ht, &key, &key_len, &int_key, 0, NULL ); - - if( type != HASH_KEY_IS_STRING ) { - std::ostringstream itoa; - itoa << int_key; - CHECK_CUSTOM_ERROR( true , ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, itoa.str() ) { - throw core::CoreException(); - } - } - core::sqlsrv_zend_hash_get_current_data( ctx, options_ht, (void**) &data TSRMLS_CC ); - add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC ); - } + if (type != HASH_KEY_IS_STRING) { + CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) { + throw core::CoreException(); + } + } + key_len = ZSTR_LEN(key) + 1; + add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); } } catch( core::CoreException& ) { @@ -1304,45 +1255,44 @@ void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout Has // against the predefined list of supported connection options by this driver. After validation // creates a Hashtable of connection options to be sent to the core layer for processing. -void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, __out char** uid, __out char** pwd, __inout HashTable* ss_conn_options_ht TSRMLS_DC ) +void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ char** uid, _Out_ char** pwd, _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ) { try { if( user_options_z ) { HashTable* options_ht = Z_ARRVAL_P( user_options_z ); - - for( zend_hash_internal_pointer_reset( options_ht ); zend_hash_has_more_elements( options_ht ) == SUCCESS; - zend_hash_move_forward( options_ht )) { + zend_ulong int_key = -1; + zend_string *key = NULL; + zval* data = NULL; + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - int type = HASH_KEY_NON_EXISTANT; - char *key = NULL; - unsigned int key_len = 0; - unsigned long int_key = -1; - zval** data = NULL; + CHECK_CUSTOM_ERROR(( Z_TYPE_P( data ) == IS_NULL || Z_TYPE_P( data ) == IS_UNDEF ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key) { + throw ss::SSException(); + } - type = zend_hash_get_current_key_ex( options_ht, &key, &key_len, &int_key, 0, NULL ); - - CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) { - throw ss::SSException(); - } - - core::sqlsrv_zend_hash_get_current_data( ctx, options_ht, (void**) &data TSRMLS_CC ); - - if( key_len == sizeof( SSConnOptionNames::UID ) && !stricmp( key, SSConnOptionNames::UID )) { + CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) { + throw ss::SSException(); + } - *uid = Z_STRVAL_PP( data ); - } + // Length of the key string does not include the null terminator in PHP7, +1 has to be added + size_t key_len = ZSTR_LEN(key) + 1; + if( key_len == sizeof(SSConnOptionNames::UID) && !stricmp(ZSTR_VAL(key), SSConnOptionNames::UID )) { - else if( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( key, SSConnOptionNames::PWD )) { - - *pwd = Z_STRVAL_PP( data ); - } - else { + *uid = Z_STRVAL_P( data ); + } - ::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC ); - } - } + else if( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::PWD )) { + + *pwd = Z_STRVAL_P( data ); + } + else { + + ::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC ); + } + } ZEND_HASH_FOREACH_END(); } } catch( core::CoreException& ) { diff --git a/sqlsrv/core_conn.cpp b/sqlsrv/core_conn.cpp index 02a05d77..97566d3c 100644 --- a/sqlsrv/core_conn.cpp +++ b/sqlsrv/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -42,7 +42,7 @@ const int INFO_BUFFER_LEN = 256; const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" }; // ODBC driver name. -const char CONNECTION_STRING_DRIVER_NAME[] = "Driver={ODBC Driver 11 for SQL Server};"; +const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"}; // default options if only the server is specified const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}"; @@ -57,12 +57,12 @@ const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};"; void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, HashTable* options_ht, const connection_option valid_conn_opts[], - void* driver,__inout std::string& connection_string TSRMLS_DC ); + void* driver,_Inout_ std::string& connection_string TSRMLS_DC ); void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ); const char* get_processor_arch( void ); void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ); -connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, unsigned int key_len TSRMLS_DC ); -void common_conn_str_append_func( const char* odbc_name, const char* val, int val_len, std::string& conn_str TSRMLS_DC ); +connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, SQLULEN key_len TSRMLS_DC ); +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ); } @@ -104,14 +104,14 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - zval** option_zz = NULL; + zval* option_z = NULL; int zr = SUCCESS; - zr = zend_hash_index_find( options_ht, SQLSRV_CONN_OPTION_CONN_POOLING, reinterpret_cast( &option_zz )); - if( zr != FAILURE ) { + option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING); + if (option_z) { // if the option was found and it's not true, then use the non pooled environment handle - if(( Z_TYPE_PP( option_zz ) == IS_STRING && !core_str_zval_is_true( *option_zz )) || !zend_is_true( *option_zz ) ) { + if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) { henv = &henv_ncp; } @@ -124,38 +124,49 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); conn->set_func( driver_func ); + for ( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) { + conn_str = CONNECTION_STRING_DRIVER_NAME[i]; + build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, + conn_str TSRMLS_CC ); - build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, - conn_str TSRMLS_CC ); - - // We only support UTF-8 encoding for connection string. - // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW - wconn_len = (conn_str.length() + 1) * sizeof( wchar_t ); - wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), conn_str.length(), &wconn_len ); - CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message() ) - { - throw core::CoreException(); - } - - SQLSMALLINT output_conn_size; - r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), - static_cast( wconn_len ), NULL, - 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); - // clear the connection string from memory to remove sensitive data (such as a password). - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn_str.clear(); + // We only support UTF-8 encoding for connection string. + // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW + wconn_len = static_cast( conn_str.length() + 1 ) * sizeof( wchar_t ); - if( !SQL_SUCCEEDED( r )) { - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); - // if it's a IM002, meaning that the correct ODBC driver is not installed - CHECK_CUSTOM_ERROR( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && - state[4] == '2', conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch() ) { - throw core::CoreException(); - } - } + wconn_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast(conn_str.length()), &wconn_len); + CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) + { + throw core::CoreException(); + } + + SQLSMALLINT output_conn_size; + r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get()), + static_cast( wconn_len ), NULL, + 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); + // clear the connection string from memory to remove sensitive data (such as a password). + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes + conn_str.clear(); + + if( !SQL_SUCCEEDED( r )) { + SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; + SQLSMALLINT len; + SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); + bool missing_driver_error = ( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && + state[4] == '2' ); + // if it's a IM002, meaning that the correct ODBC driver is not installed + CHECK_CUSTOM_ERROR( missing_driver_error && ( i == DRIVER_VERSION::MAX ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { + throw core::CoreException(); + } + if ( !missing_driver_error ) { + break; + } + } else { + conn->driver_version = static_cast( i ); + break; + } + + } CHECK_SQL_ERROR( r, conn ) { throw core::CoreException(); } @@ -167,7 +178,7 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ // determine the version of the server we're connected to. The server version is left in the // connection upon return. determine_server_version( conn TSRMLS_CC ); - + } catch( std::bad_alloc& ) { memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); @@ -318,7 +329,7 @@ void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ) // sql - T-SQL command to prepare // sql_len - length of the T-SQL string -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRMLS_DC ) +void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ) { try { @@ -333,10 +344,17 @@ void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRML wsql_len = 0; } else { + + if (sql_len > INT_MAX) + { + LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding() ); wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - sql_len, &wsql_len ); + static_cast( sql_len ), &wsql_len ); CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) { throw core::CoreException(); @@ -359,7 +377,7 @@ void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRML // conn - The connection resource by which the client and server are connected. // *server_version - zval for returning results. -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC ) +void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ) { try { @@ -367,7 +385,10 @@ void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_versi SQLSMALLINT buffer_len = 0; get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - ZVAL_STRINGL( server_version, buffer, buffer_len, 0 ); + core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); + if (NULL != buffer) { + sqlsrv_free( buffer ); + } buffer.transferred(); } @@ -383,7 +404,7 @@ void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_versi // conn - The connection resource by which the client and server are connected. // *server_info - zval for returning results. -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval *server_info TSRMLS_DC ) +void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval *server_info TSRMLS_DC ) { try { @@ -422,7 +443,7 @@ void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval *server_info TSR // conn - The connection resource by which the client and server are connected. // *client_info - zval for returning the results. -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC ) +void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ) { try { @@ -463,7 +484,7 @@ void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSR // Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that // the value will be surrounded by { and } by the caller after it has been validated -bool core_is_conn_opt_value_escaped( const char* value, int value_len ) +bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ) { // 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. @@ -472,7 +493,7 @@ bool core_is_conn_opt_value_escaped( const char* value, int value_len ) value_len -= 2; } // check to make sure that all right braces are escaped - int i = 0; + size_t i = 0; while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) { // skip both braces if( value[i] == '}' ) @@ -491,7 +512,7 @@ bool core_is_conn_opt_value_escaped( const char* value, int value_len ) namespace { -connection_option const* get_connection_option( sqlsrv_conn* conn, unsigned long key, +connection_option const* get_connection_option( sqlsrv_conn* conn, SQLULEN key, 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 ) { @@ -513,7 +534,7 @@ connection_option const* get_connection_option( sqlsrv_conn* conn, unsigned long void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, HashTable* options, const connection_option valid_conn_opts[], - void* driver,__inout std::string& connection_string TSRMLS_DC ) + void* driver,_Inout_ std::string& connection_string TSRMLS_DC ) { bool credentials_mentioned = false; bool mars_mentioned = false; @@ -521,9 +542,7 @@ void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* s int zr = SUCCESS; try { - - connection_string = CONNECTION_STRING_DRIVER_NAME; - + // Add the server name common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC ); @@ -566,40 +585,34 @@ void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* s // flag is set to false. if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) { - zval** trace_value = NULL; - int zr = zend_hash_index_find( options, SQLSRV_CONN_OPTION_TRACE_ON, (void**)&trace_value ); + zval* trace_value = NULL; + trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); - if( zr == FAILURE || !zend_is_true( *trace_value )) { + if (trace_value == NULL || !zend_is_true(trace_value)) { zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE ); } } - for( zend_hash_internal_pointer_reset( options ); - zend_hash_has_more_elements( options ) == SUCCESS; - zend_hash_move_forward( options )) { - - int type = HASH_KEY_NON_EXISTANT; - char *key = NULL; - unsigned int key_len = -1; - unsigned long index = -1; - zval** data = NULL; + zend_string *key = NULL; + zend_ulong index = -1; + zval* data = NULL; - type = zend_hash_get_current_key_ex( options, &key, &key_len, &index, 0, NULL ); - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); - - core::sqlsrv_zend_hash_get_current_data( *conn, options, (void**) &data TSRMLS_CC ); + ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); - - if( index == SQLSRV_CONN_OPTION_MARS ) { - mars_mentioned = true; - } - - conn_opt->func( conn_opt, *data, conn, connection_string TSRMLS_CC ); - } + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); + + conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); + + if( index == SQLSRV_CONN_OPTION_MARS ) { + mars_mentioned = true; + } + + conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); // MARS on if not explicitly turned off if( !mars_mentioned ) { @@ -676,7 +689,7 @@ void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) errno = 0; char version_major_str[ 3 ]; SERVER_VERSION version_major; - memcpy( version_major_str, p, 2 ); + memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); version_major_str[ 2 ] = '\0'; version_major = static_cast( atoi( version_major_str )); @@ -690,7 +703,7 @@ void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) conn->server_version = version_major; } -void common_conn_str_append_func( const char* odbc_name, const char* val, int val_len, std::string& conn_str TSRMLS_DC ) +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) { // wrap a connection option in a quote. It is presumed that any character that need to be escaped will // be escaped, such as a closing }. @@ -713,7 +726,7 @@ void conn_str_append_func::func( connection_option const* option, zval* value, s TSRMLS_DC ) { const char* val_str = Z_STRVAL_P( value ); - int val_len = Z_STRLEN_P( value ); + size_t val_len = Z_STRLEN_P( value ); common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC ); } @@ -729,15 +742,15 @@ void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, // Values = ("true" or "1") are treated as true values. Everything else is treated as false. // Returns 1 for true and 0 for false. -int core_str_zval_is_true( zval* value_z ) +size_t core_str_zval_is_true( zval* value_z ) { SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); char* value_in = Z_STRVAL_P( value_z ); - int val_len = Z_STRLEN_P( value_z ); + size_t val_len = Z_STRLEN_P( value_z ); // strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8) - int last_char = val_len - 1; + size_t last_char = val_len - 1; while( isspace( value_in[ last_char ] )) { value_in[ last_char ] = '\0'; val_len = last_char; @@ -745,7 +758,7 @@ int core_str_zval_is_true( zval* value_z ) } // save adjustments to the value made by stripping whitespace at the end - ZVAL_STRINGL( value_z, value_in, val_len, 0 ); + Z_STRLEN_P( value_z ) = val_len; const char VALID_TRUE_VALUE_1[] = "true"; const char VALID_TRUE_VALUE_2[] = "1"; diff --git a/sqlsrv/core_init.cpp b/sqlsrv/core_init.cpp index 8ba175f8..1b1e8e57 100644 --- a/sqlsrv/core_init.cpp +++ b/sqlsrv/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -37,8 +37,8 @@ OSVERSIONINFO g_osversion; // err - Driver specific error handler which handles any errors during initialization. void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ) { - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( long )); - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( long )); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL @@ -146,11 +146,13 @@ void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ) henv_ncp.invalidate(); } + delete &henv_ncp; if( henv_cp != SQL_NULL_HANDLE ) { henv_cp.invalidate(); } + delete &henv_cp; return; } diff --git a/sqlsrv/core_results.cpp b/sqlsrv/core_results.cpp index d4eed5d3..8b2657ed 100644 --- a/sqlsrv/core_results.cpp +++ b/sqlsrv/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -76,19 +76,37 @@ bool get_bit( void* ptr, unsigned int bit ) // read in LOB field during buffered result creation SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - unsigned long mem_used TSRMLS_DC ); + zend_long mem_used TSRMLS_DC ); // dtor for each row in the cache -void cache_row_dtor( void* data ); +void cache_row_dtor(zval* data); // convert a number to a string using locales // There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of // subclassing a new streambuf just to avoid the copy, it's easier to do the copy template -SQLRETURN number_to_string( Number* number_data, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, +SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) { + // get to display size by removing the null terminator from buffer length + size_t display_size = ( buffer_length - sizeof( Char )) / sizeof( Char ); + std::basic_ostringstream os; + // use the display size to determine the sql type. And if it is a double, set the precision accordingly + // the display sizes are set by the ODBC driver based on the precision of the sql type + // otherwise we can just use the default precision as long will not be truncated + size_t real_display_size = 14; + size_t float_display_size = 24; + size_t real_precision = 7; + size_t float_precision = 15; + // this is the case of sql type float(24) or real + if ( display_size == real_display_size ) { + os.precision( real_precision ); + } + // this is the case of sql type float(53) + else if ( display_size == float_display_size ) { + os.precision( float_precision ); + } std::locale loc; os.imbue( loc ); std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf() ), os, ' ', *number_data ); @@ -100,21 +118,21 @@ SQLRETURN number_to_string( Number* number_data, __out void* buffer, SQLLEN buff return SQL_ERROR; } - if( str_num.size() * sizeof(Char) + sizeof(Char) > (size_t) buffer_length ) { + if( str_num.size() * sizeof(Char) > (size_t) buffer_length ) { last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); return SQL_ERROR; } - *out_buffer_length = str_num.size() * sizeof(Char) + sizeof(Char); // include NULL terminator - memcpy( buffer, str_num.c_str(), *out_buffer_length ); + *out_buffer_length = str_num.size() * sizeof( Char ); // str_num.size() already include the NULL terminator + memcpy_s( buffer, buffer_length, str_num.c_str(), *out_buffer_length ); return SQL_SUCCESS; } template -SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) +SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) { Number* number_data = reinterpret_cast( buffer ); std::locale loc; // default locale should match system @@ -170,13 +188,13 @@ sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number ) // convert the error into the encoding of the context sqlsrv_malloc_auto_ptr sql_state; - SQLINTEGER sql_state_len = 0; + SQLLEN sql_state_len = 0; if (!convert_string_from_utf16( enc, wsql_state, sizeof(wsql_state), (char**)&sql_state, sql_state_len )) { return NULL; } sqlsrv_malloc_auto_ptr native_message; - SQLINTEGER native_message_len = 0; + SQLLEN native_message_len = 0; if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) { return NULL; } @@ -214,7 +232,7 @@ SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset } SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ) { SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); @@ -222,8 +240,8 @@ SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLIN } SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) { SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length, @@ -257,9 +275,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS { // 10 is an arbitrary number for now for the initial size of the cache ALLOC_HASHTABLE( cache ); - - core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, NULL /* hashfn */, cache_row_dtor /*dtor*/, 0 /*persistent*/ - TSRMLS_CC ); + core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); // there is no result set to buffer if( col_count == 0 ) { @@ -441,7 +457,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS // read the data into the cache // (offset from the above loop has the size of the row buffer necessary) - unsigned long mem_used = 0; + zend_long mem_used = 0; unsigned long row_count = 0; while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { @@ -478,7 +494,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS } else { - mem_used += meta[i].length; + mem_used += meta[i].length; CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { @@ -523,7 +539,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS // add it to the cache row_dtor_closure cl( this, row ); - sqlsrv_zend_hash_next_index_insert( *stmt, cache, &cl, sizeof( cl ) TSRMLS_CC ); + sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); } } @@ -598,7 +614,7 @@ SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN off } SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ) { last_error = NULL; @@ -634,8 +650,8 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMA } SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) { SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE, @@ -650,7 +666,7 @@ SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSRV_ASSERT( last_error->sqlstate != NULL, "Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" ); - memcpy( diag_info_buffer, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE )); + memcpy_s( diag_info_buffer, buffer_length, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE )); return SQL_SUCCESS; } @@ -658,8 +674,8 @@ SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, unsigned char* sqlsrv_buffered_result_set::get_row( void ) { row_dtor_closure* cl_ptr; - int zr = zend_hash_index_find( cache, current - 1, (void**) &cl_ptr ); - SQLSRV_ASSERT( zr == SUCCESS, "Failed to find row %1!d! in the cache", current ); + cl_ptr = reinterpret_cast(zend_hash_index_find_ptr(cache, static_cast(current - 1))); + SQLSRV_ASSERT(cl_ptr != NULL, "Failed to find row %1!d! in the cache", current); return cl_ptr->row_data; } @@ -686,8 +702,8 @@ SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D ) // private functions template -SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, __out void* buffer, - SQLLEN buffer_length, __out SQLLEN* out_buffer_length, +SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, _Out_ void* buffer, + SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& out_error ) { // hex characters for the conversion loop below @@ -747,8 +763,8 @@ SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, __out voi return r; } -SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLCHAR* row = get_row(); SQLCHAR* field_data = NULL; @@ -765,8 +781,8 @@ SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); } -SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLCHAR* row = get_row(); SQLCHAR* field_data = NULL; @@ -784,11 +800,11 @@ SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_i } -SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof(LONG), "Buffer length must be able to find a long in " + 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(); @@ -813,8 +829,8 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, _ return SQL_SUCCESS; } -SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -825,8 +841,8 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); } -SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -837,8 +853,8 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_i return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); } -SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -852,8 +868,8 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, _ return SQL_SUCCESS; } -SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -864,8 +880,8 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_i return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); } -SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -876,8 +892,8 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_ind return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); } -SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -888,8 +904,8 @@ SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, 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( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -900,8 +916,8 @@ SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index 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( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -912,8 +928,8 @@ SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, _ 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( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { 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" ); @@ -924,8 +940,8 @@ SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, 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( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" ); SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" ); @@ -974,8 +990,13 @@ SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_i bool tried_again = false; do { - int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, to_copy, - (LPWSTR) buffer, to_copy ); + if (to_copy > INT_MAX ) { + LOG(SEV_ERROR, "MultiByteToWideChar: Buffer length exceeded."); + throw core::CoreException(); + } + + int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast(to_copy), + static_cast(buffer), static_cast(to_copy)); if( ch_space == 0 ) { switch( GetLastError() ) { @@ -1012,8 +1033,8 @@ SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_i return r; } -SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::to_same_string" ); @@ -1071,19 +1092,19 @@ SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, _ SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" ); if( to_copy > 0 ) { - memcpy( buffer, field_data + read_so_far, to_copy ); + memcpy_s( buffer, buffer_length, field_data + read_so_far, to_copy ); read_so_far += to_copy; } if( extra ) { OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); - memcpy( reinterpret_cast( buffer ) + to_copy, L"\0", extra ); + memcpy_s( reinterpret_cast( buffer ) + to_copy, buffer_length, L"\0", extra ); } return r; } -SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" ); @@ -1091,7 +1112,7 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_i unsigned char* row = get_row(); SQLCHAR* field_data = NULL; - SQLULEN field_len = NULL; + SQLLEN field_len = NULL; // if this is the first time called for this field, just convert the entire string to system first then // use that to read from instead of converting chunk by chunk. This is because it's impossible to know @@ -1116,9 +1137,9 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_i char default_char = '?'; // allocate enough to handle WC -> DBCS conversion if it happens - temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof( char ), sizeof(char))); - temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, field_len / sizeof(WCHAR), - (LPSTR) temp_string.get(), field_len, &default_char, &default_char_used ); + temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof(char), sizeof(char))); + temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), + (LPSTR) temp_string.get(), static_cast(field_len), &default_char, &default_char_used ); if( temp_length == 0 ) { @@ -1156,7 +1177,7 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_i if( to_copy > 0 ) { - memcpy( buffer, temp_string.get() + read_so_far, to_copy ); + memcpy_s( buffer, buffer_length, temp_string.get() + read_so_far, to_copy ); } SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); @@ -1167,14 +1188,14 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_i } -SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { return to_same_string( field_index, buffer, buffer_length, out_buffer_length ); } -SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invlid conversion to long" ); SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this @@ -1182,14 +1203,14 @@ SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, __out vo unsigned char* row = get_row(); LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - memcpy( buffer, long_data, sizeof( LONG )); + memcpy_s( buffer, buffer_length, long_data, sizeof( LONG )); *out_buffer_length = sizeof( LONG ); return SQL_SUCCESS; } -SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ) +SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) { SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invlid conversion to double" ); SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this @@ -1197,7 +1218,7 @@ SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, __out unsigned char* row = get_row(); double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - memcpy( buffer, double_data, sizeof( double )); + memcpy_s( buffer, buffer_length, double_data, sizeof( double )); *out_buffer_length = sizeof( double ); return SQL_SUCCESS; @@ -1206,9 +1227,9 @@ SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, __out namespace { // called for each row in the cache when the cache is destroyed in the destructor -void cache_row_dtor( void* data ) +void cache_row_dtor( zval* data ) { - row_dtor_closure* cl = reinterpret_cast( data ); + row_dtor_closure* cl = reinterpret_cast( Z_PTR_P( data ) ); BYTE* row = cl->row_data; // don't release this here, since this is called from the destructor of the result_set sqlsrv_buffered_result_set* result_set = cl->results; @@ -1223,13 +1244,14 @@ void cache_row_dtor( void* data ) } sqlsrv_free( row ); + sqlsrv_free( cl ); } SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - unsigned long mem_used TSRMLS_DC ) + zend_long mem_used TSRMLS_DC ) { SQLSMALLINT extra = 0; - SQLLEN* output_buffer_len = NULL; + SQLULEN* output_buffer_len = NULL; // Set the amount of space necessary for null characters at the end of the data. switch( meta.c_type ) { @@ -1259,7 +1281,7 @@ SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_b do { - output_buffer_len = reinterpret_cast( buffer.get() ); + output_buffer_len = reinterpret_cast( buffer.get() ); r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ), to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC ); @@ -1303,7 +1325,7 @@ SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_b already_read += to_read - already_read; to_read = last_field_len; buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); + output_buffer_len = reinterpret_cast( buffer.get() ); // record the size of the field since we have it available *output_buffer_len = last_field_len; full_length_returned = true; @@ -1318,7 +1340,7 @@ SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_b throw core::CoreException(); } buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); + output_buffer_len = reinterpret_cast( buffer.get() ); } } while( true ); diff --git a/sqlsrv/core_sqlsrv.h b/sqlsrv/core_sqlsrv.h index 3867cdc9..c511b057 100644 --- a/sqlsrv/core_sqlsrv.h +++ b/sqlsrv/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -107,7 +107,7 @@ OACR_WARNING_POP #include #include #include - +#include // included for SQL Server specific constants #include "msodbcsql.h" @@ -182,7 +182,7 @@ union sqlsrv_sqltype { int scale:8; } typeinfo; - long value; + zend_long value; }; @@ -196,7 +196,7 @@ union sqlsrv_phptype { unsigned encoding:16; } typeinfo; - long value; + zend_long value; }; // static assert for enforcing compile time conditions @@ -500,6 +500,16 @@ public: return _ptr[ index ]; } + + #ifdef __WIN64 + // 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[](std::size_t index) const + { + return _ptr[index]; + } + #endif + // 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[]( unsigned short index ) const @@ -656,7 +666,7 @@ public: void reset( zval* ptr = NULL ) { if( _ptr ) - zval_ptr_dtor( &_ptr ); + zval_ptr_dtor(_ptr ); _ptr = ptr; } @@ -664,12 +674,7 @@ public: { return sqlsrv_auto_ptr::operator=( ptr ); } -#if PHP_MAJOR_VERSION > 5 || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 3) - operator zval_gc_info*( void ) - { - return reinterpret_cast(_ptr); - } -#endif + private: @@ -884,6 +889,8 @@ class sqlsrv_context { { if( handle_ != SQL_NULL_HANDLE ) { ::SQLFreeHandle( handle_type_, handle_ ); + + last_error_.reset(); } handle_ = SQL_NULL_HANDLE; } @@ -919,7 +926,7 @@ const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista struct sqlsrv_encoding { const char* iana; - unsigned int iana_len; + size_t iana_len; unsigned int code_page; bool not_for_connection; @@ -965,6 +972,14 @@ enum SERVER_VERSION { SERVER_VERSION_2008, // use this for anything 2008 or later }; +// supported driver versions. +enum DRIVER_VERSION : size_t { + MIN = 0, + ODBC_DRIVER_13 = MIN, + ODBC_DRIVER_11 = 1, + MAX = ODBC_DRIVER_11, +}; + // forward decl struct sqlsrv_stmt; struct stmt_option; @@ -976,6 +991,8 @@ struct sqlsrv_conn : public sqlsrv_context { // instance variables SERVER_VERSION server_version; // version of the server that we're connected to + DRIVER_VERSION driver_version; + // initialize with default values sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) : sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) @@ -1010,7 +1027,7 @@ const char ConnectionPooling[] = "ConnectionPooling"; const char Database[] = "Database"; const char Encrypt[] = "Encrypt"; const char Failover_Partner[] = "Failover_Partner"; -const char LoginTimeout[] = "LoginTimggeout"; +const char LoginTimeout[] = "LoginTimeout"; const char MARS_ODBC[] = "MARS_Connection"; const char MultiSubnetFailover[] = "MultiSubnetFailover"; const char QuotedId[] = "QuotedId"; @@ -1087,9 +1104,8 @@ struct str_conn_attr_func { static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) { try { - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), - Z_STRLEN_P( value ) TSRMLS_CC ); + static_cast(Z_STRLEN_P( value )) TSRMLS_CC ); } catch( core::CoreException& ) { throw; @@ -1118,15 +1134,15 @@ sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[], void* driver, const char* driver_func TSRMLS_DC ); void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRMLS_DC ); +void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ); void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ); void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ); void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval* server_info TSRMLS_DC ); -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC ); -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC ); -bool core_is_conn_opt_value_escaped( const char* value, int value_len ); -int core_str_zval_is_true( zval* str_zval ); +void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC ); +void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ); +void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); +bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); +size_t core_str_zval_is_true( zval* str_zval ); //********************************************************************************************************************************* // Statement @@ -1158,7 +1174,7 @@ struct stmt_option { const char * name; // name of the statement option unsigned int name_len; // name length unsigned int key; - stmt_option_functor* func; // callback that actually handles the work of the option + std::unique_ptr func; // callback that actually handles the work of the option }; @@ -1170,7 +1186,7 @@ struct sqlsrv_stream { SQLUSMALLINT field_index; SQLSMALLINT sql_type; sqlsrv_stmt* stmt; - int stmt_index; + std::size_t stmt_index; sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) : stream_z( str_z ), encoding( enc ) @@ -1183,7 +1199,7 @@ struct sqlsrv_stream { }; // close any active stream -void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC ); +void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); extern php_stream_wrapper g_sqlsrv_stream_wrapper; @@ -1197,7 +1213,7 @@ struct sqlsrv_output_param { zval* param_z; SQLSRV_ENCODING encoding; - int param_num; // used to index into the ind_or_len of the statement + SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer bool is_bool; @@ -1239,28 +1255,28 @@ struct sqlsrv_stmt : public sqlsrv_context { bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the // last results unsigned long query_timeout; // maximum allowed statement execution time - unsigned long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) + zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) // 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 // memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold std::deque param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter - zval* param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP - zval* output_params; // hold all the output parameters - zval* param_streams; // track which streams to send data to the server - zval* param_datetime_buffers; // datetime strings to be converted back to DateTime objects + zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP + zval output_params; // hold all the output parameters + zval param_streams; // track which streams to send data to the server + zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects bool send_streams_at_exec; // send all stream data right after execution before returning sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string // to the server) - zval* field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals - zval* active_stream; // the currently active stream reading data from the database + zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals + zval active_stream; // the currently active stream reading data from the database sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); virtual ~sqlsrv_stmt( void ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) = 0; + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string = false ) = 0; }; @@ -1302,25 +1318,25 @@ typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, err // *** statement functions *** sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ); -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int direction, zval* param_z, +void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, SQLSMALLINT decimal_digits TSRMLS_DC ); void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 ); field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ); -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); -void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, - __out void** field_value, __out SQLLEN* field_length, bool cache_field, - __out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC ); +bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ); +void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, + _Out_ void*& field_value, _Out_ SQLLEN* field_length, bool cache_field, + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC); bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ); void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true ); -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z TSRMLS_DC ); -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned int cursor_type TSRMLS_DC ); +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC ); +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ); void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ); void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, long limit TSRMLS_DC ); +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); //********************************************************************************************************************************* @@ -1344,11 +1360,11 @@ struct sqlsrv_result_set { virtual bool cached( int field_index ) = 0; virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0; virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC )= 0; virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0; virtual SQLLEN row_count( TSRMLS_D ) = 0; }; @@ -1356,16 +1372,16 @@ struct sqlsrv_result_set { struct sqlsrv_odbc_result_set : public sqlsrv_result_set { explicit sqlsrv_odbc_result_set( sqlsrv_stmt* ); - virtual ~sqlsrv_odbc_result_set( void ); + virtual ~sqlsrv_odbc_result_set( void ); virtual bool cached( int field_index ) { return false; } virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ); virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ); + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); virtual SQLLEN row_count( TSRMLS_D ); @@ -1390,8 +1406,8 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { // default maximum amount of memory that a buffered query can consume #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings - static const unsigned long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB - static const long BUFFERED_QUERY_LIMIT_INVALID = 0; + static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB + static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC ); virtual ~sqlsrv_buffered_result_set( void ); @@ -1399,11 +1415,11 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { virtual bool cached( int field_index ) { return true; } virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ); virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ); + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); virtual SQLLEN row_count( TSRMLS_D ); @@ -1434,55 +1450,55 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use SQLLEN temp_length; // number of bytes in the temp conversion buffer - typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); + typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; // two dimentional sparse matrix that holds the [from][to] functions that do conversions static conv_matrix_t conv_matrix; // string conversion functions - SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN system_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); + SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); // long conversion functions - SQLRETURN to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length ); - SQLRETURN long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); + SQLRETURN to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); // double conversion functions - SQLRETURN to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length ); - SQLRETURN double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); + SQLRETURN to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); // string to number conversion functions // Future: See if these can be converted directly to template member functions - SQLRETURN string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN string_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length, - __out SQLLEN* out_buffer_length ); + SQLRETURN string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); // utility functions for conversions unsigned char* get_row( void ); @@ -1500,11 +1516,12 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { #define MEMCHECK_SILENT 1 // utility functions shared by multiple callers across files -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLINTEGER& len); -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLINTEGER& cchOutLen ); +bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len); +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len); +bool validate_string(char* string, SQLLEN& len); +bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ); wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, - unsigned int mbcs_len, __out unsigned int* utf16_len ); - + unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); //********************************************************************************************************************************* // Error handling routines and Predefined Errors @@ -1559,8 +1576,8 @@ enum SQLSRV_ERROR_CODES { }; // the message returned by ODBC Driver 11 for SQL Server -const char CONNECTION_BUSY_ODBC_ERROR[] = "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for " - "another command"; +static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command", + "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" }; // SQLSTATE for all internal errors extern SQLCHAR IMSSP[]; @@ -1582,7 +1599,7 @@ enum error_handling_flags { // 2/code) driver specific error code // 3/message) driver specific error message // The fetch type determines if the indices are numeric, associative, or both. -bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, __out sqlsrv_error_auto_ptr& error, +bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, _Out_ sqlsrv_error_auto_ptr& error, logging_severity severity TSRMLS_DC ); // format and return a driver specfic error @@ -1595,11 +1612,11 @@ void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const co const char* get_last_error_message( DWORD last_error = 0 ); // a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer -DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ); +DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ); // convenience functions that overload either a reference or a pointer so we can use // either in the CHECK_* functions. -inline bool call_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_code TSRMLS_DC, bool warning, ... ) +inline bool call_error_handler( sqlsrv_context& ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) { va_list print_params; va_start( print_params, warning ); @@ -1608,7 +1625,7 @@ inline bool call_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_c return ignored; } -inline bool call_error_handler( sqlsrv_context* ctx, unsigned int sqlsrv_error_code TSRMLS_DC, bool warning, ... ) +inline bool call_error_handler( sqlsrv_context* ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) { va_list print_params; va_start( print_params, warning ); @@ -1737,9 +1754,8 @@ namespace core { throw CoreException(); } - - if(( len == sizeof( CONNECTION_BUSY_ODBC_ERROR ) - 1 ) && - !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR )) { + std::size_t driver_version = stmt->conn->driver_version; + if( !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) { THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); } @@ -1755,8 +1771,8 @@ namespace core { // the context to hold the error, they are not passed as const. inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length TSRMLS_DC ) + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) { SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, diag_info_buffer, buffer_length, out_buffer_length ); @@ -1769,7 +1785,7 @@ namespace core { } inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle, - __out_ecount(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) + _Out_writes_(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) { SQLRETURN r; r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); @@ -1785,9 +1801,9 @@ namespace core { SQLSMALLINT ParameterType, SQLULEN ColumnSize, SQLSMALLINT DecimalDigits, - __inout SQLPOINTER ParameterValuePtr, + _Inout_ SQLPOINTER ParameterValuePtr, SQLLEN BufferLength, - __inout SQLLEN * StrLen_Or_IndPtr + _Inout_ SQLLEN * StrLen_Or_IndPtr TSRMLS_DC ) { SQLRETURN r; @@ -1801,8 +1817,8 @@ namespace core { inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, - __out SQLPOINTER field_type_char, SQLSMALLINT buffer_length, - __out SQLSMALLINT* out_buffer_length, __out SQLLEN* field_type_num TSRMLS_DC ) + _Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC ) { SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, buffer_length, out_buffer_length, field_type_num ); @@ -1813,9 +1829,9 @@ namespace core { } - inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, __out_z SQLCHAR* col_name, SQLSMALLINT col_name_length, - __out SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, __out SQLULEN* col_size, - __out SQLSMALLINT* decimal_digits, __out SQLSMALLINT* nullable TSRMLS_DC ) + inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLCHAR* col_name, SQLSMALLINT col_name_length, + _Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size, + _Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC ) { SQLRETURN r; r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, @@ -1898,7 +1914,7 @@ namespace core { } inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type, - __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ) { SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); @@ -1920,8 +1936,8 @@ namespace core { } - inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, __out SQLPOINTER info_value, SQLSMALLINT buffer_len, - __out SQLSMALLINT* str_len TSRMLS_DC ) + inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, _Out_ SQLPOINTER info_value, SQLSMALLINT buffer_len, + _Out_ SQLSMALLINT* str_len TSRMLS_DC ) { SQLRETURN r; r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); @@ -1970,7 +1986,7 @@ namespace core { // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more // parameters or when the parameters are all processed. - inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, __out SQLPOINTER* value_ptr_ptr TSRMLS_DC ) + inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, _Out_ SQLPOINTER* value_ptr_ptr TSRMLS_DC ) { SQLRETURN r; r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); @@ -2055,6 +2071,25 @@ namespace core { // *** zend wrappers *** + + //zend_resource_dtor sets the type of destroyed resources to -1 + #define RSRC_INVALID_TYPE -1 + + // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string + // so allocated memory inside of value_z should be released before assigning it to the new string + inline void sqlsrv_zval_stringl(zval* value_z, const char* str, const std::size_t str_len) + { + if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { + zend_string* temp_zstr = zend_string_init(str, str_len, 0); + zend_string_release(Z_STR_P(value_z)); + ZVAL_NEW_STR(value_z, temp_zstr); + } + else { + ZVAL_STRINGL(value_z, str, str_len); + } + } + + // exception thrown when a zend function wrapped here fails. // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw @@ -2063,7 +2098,7 @@ namespace core { // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error // that can be thrown from it. - inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, unsigned int index, zval* value TSRMLS_DC) + inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, zend_ulong index, zval* value TSRMLS_DC) { int zr = ::add_index_zval( array, index, value ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2079,7 +2114,7 @@ namespace core { } } - inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, char* key TSRMLS_DC ) + inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, const char* key TSRMLS_DC ) { int zr = ::add_assoc_null( array_z, key ); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2087,7 +2122,7 @@ namespace core { } } - inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, char* key, long val TSRMLS_DC ) + inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, const char* key, zend_long val TSRMLS_DC ) { int zr = ::add_assoc_long( array_z, key, val ); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2095,23 +2130,26 @@ namespace core { } } - inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, char* key, char* val, bool duplicate TSRMLS_DC ) + inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, const char* key, char* val, bool duplicate TSRMLS_DC ) { - int zr = ::add_assoc_string( array_z, key, val, duplicate ); + int zr = ::add_assoc_string(array_z, key, val); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } + if (duplicate == 0) { + sqlsrv_free(val); + } } - inline void sqlsrv_array_init( sqlsrv_context& ctx, __out zval* new_array TSRMLS_DC) + inline void sqlsrv_array_init( sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) { - int zr = ::array_init( new_array ); + int zr = ::array_init(new_array); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } - inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval** stream_z TSRMLS_DC ) + inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval* stream_z TSRMLS_DC ) { // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment php_stream_from_zval_no_verify( stream, stream_z ); @@ -2120,53 +2158,89 @@ namespace core { } } - inline void sqlsrv_zend_hash_get_current_data( sqlsrv_context& ctx, HashTable* ht, __out void** output_data TSRMLS_DC ) + inline void sqlsrv_zend_hash_get_current_data(sqlsrv_context& ctx, HashTable* ht, _Out_ zval*& output_data TSRMLS_DC) + { + int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_get_current_data_ptr(sqlsrv_context& ctx, HashTable* ht, _Out_ void*& output_data TSRMLS_DC) { - int zr = ::zend_hash_get_current_data( ht, output_data ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); - } + } } - inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, int index TSRMLS_DC ) + inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, zend_ulong index TSRMLS_DC ) { int zr = ::zend_hash_index_del( ht, index ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } - - inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, unsigned long index, void* data, - uint data_size TSRMLS_DC ) + + inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, zend_ulong index, zval* data_z TSRMLS_DC ) { - int zr = ::zend_hash_index_update( ht, index, data, data_size, NULL ); + int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } - inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, void* data, - uint data_size TSRMLS_DC ) + inline void sqlsrv_zend_hash_index_update_ptr(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData TSRMLS_DC) { - int zr = ::zend_hash_next_index_insert( ht, data, data_size, NULL ); + int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + + inline void sqlsrv_zend_hash_index_update_mem(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData, std::size_t size TSRMLS_DC) + { + int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, zval* data TSRMLS_DC ) + { + int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } } - inline void sqlsrv_zend_hash_init( sqlsrv_context& ctx, HashTable* ht, unsigned int initial_size, hash_func_t hash_fn, - dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) + inline void sqlsrv_zend_hash_next_index_insert_mem(sqlsrv_context& ctx, HashTable* ht, void* data, uint data_size TSRMLS_DC) + { + int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert_ptr(sqlsrv_context& ctx, HashTable* ht, void* data TSRMLS_DC) { - int zr = ::zend_hash_init( ht, initial_size, hash_fn, dtor_fn, persistent ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); } } - - inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, char* key, unsigned int key_len, void** data, - unsigned int data_size, void **pDest TSRMLS_DC ) + + inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, HashTable* ht, uint32_t initial_size, + dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) { - int zr = ::zend_hash_add( ht, key, key_len, data, data_size, pDest ); + ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); + } + + inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, zend_string* key, unsigned int key_len, zval* data, + unsigned int data_size, zval* pDest TSRMLS_DC ) + { + int zr = (pDest = ::zend_hash_add(ht, key, data)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } diff --git a/sqlsrv/core_stmt.cpp b/sqlsrv/core_stmt.cpp index 96559d00..a4ef0d9d 100644 --- a/sqlsrv/core_stmt.cpp +++ b/sqlsrv/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -24,7 +24,7 @@ namespace { // certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the // results of every field request, and if it is out of order, we cache those for preceding fields. struct field_cache { - + void* value; SQLLEN len; sqlsrv_phptype type; @@ -35,7 +35,7 @@ struct field_cache { // if the value is NULL, then just record a NULL pointer if( field_value != NULL ) { value = sqlsrv_malloc( field_len ); - memcpy( value, field_value, field_len ); + memcpy_s( value, field_len, field_value, field_len ); len = field_len; } else { @@ -76,36 +76,36 @@ const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT ); // *** internal functions *** // Only declarations are put here. Functions contain the documentation they need at their definition sites. -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, __out SQLLEN& size TSRMLS_DC ); +void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ); size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ); bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC ); bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z ); -void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ); +void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype + sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC); // returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); +SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - __out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC ); + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); // given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) -void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - __out SQLSMALLINT& sql_type TSRMLS_DC ); -void field_cache_dtor( void* data ); +void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); +void field_cache_dtor( zval* data_z ); void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ); -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ); -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, unsigned long key, const stmt_option stmt_opts[] TSRMLS_DC ); +void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, + _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ); +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ); bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); // assure there is enough space for the output parameter string -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsigned int paramno, SQLSRV_ENCODING encoding, +void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, SQLLEN& buffer_len TSRMLS_DC ); void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ); // send all the stream data void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ); // called when a bound output string parameter is to be destroyed -void sqlsrv_output_param_dtor( void* data ); +void sqlsrv_output_param_dtor( zval* data ); // called when a bound stream parameter is to be destroyed. -void sqlsrv_stream_dtor( void* data ); +void sqlsrv_stream_dtor( zval* data ); bool is_streamable_type( SQLINTEGER sql_type ); } @@ -127,43 +127,32 @@ sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, vo current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), current_stream_read( 0 ), query_timeout( QUERY_TIMEOUT_INVALID ), - buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ), - active_stream( NULL ) + buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) { + ZVAL_UNDEF( &active_stream ); // initialize the input string parameters array (which holds zvals) - MAKE_STD_ZVAL( param_input_strings ); - core::sqlsrv_array_init( *conn, param_input_strings TSRMLS_CC ); - + core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); + // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) - MAKE_STD_ZVAL( param_streams ); - Z_TYPE_P( param_streams ) = IS_ARRAY; - ALLOC_HASHTABLE( Z_ARRVAL_P( param_streams )); - core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( param_streams ), 5 /* # of buckets */, NULL /*hashfn*/, - sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC ); + ZVAL_NEW_ARR( ¶m_streams ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC); // initialize the (input only) datetime parameters of converted date time objects to strings - MAKE_STD_ZVAL( param_datetime_buffers ); - array_init( param_datetime_buffers ); + array_init( ¶m_datetime_buffers ); // initialize the output string parameters (which holds sqlsrv_output_param structures) - MAKE_STD_ZVAL( output_params ); - Z_TYPE_P( output_params ) = IS_ARRAY; - ALLOC_HASHTABLE( Z_ARRVAL_P( output_params )); - core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( output_params ), 5 /* # of buckets */, NULL /*hashfn*/, - sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC ); - + ZVAL_NEW_ARR( &output_params ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC); + // initialize the field cache - MAKE_STD_ZVAL( field_cache ); - Z_TYPE_P( field_cache ) = IS_ARRAY; - ALLOC_HASHTABLE( Z_ARRVAL_P( field_cache )); - core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL_P( field_cache ), 5 /* # of buckets */, NULL /*hashfn*/, - field_cache_dtor, 0 /*persistent*/ TSRMLS_CC ); + ZVAL_NEW_ARR( &field_cache ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC); } // desctructor for sqlsrv statement. sqlsrv_stmt::~sqlsrv_stmt( void ) { - if( active_stream ) { + if( Z_TYPE( active_stream ) != IS_UNDEF ) { TSRMLS_FETCH(); close_active_stream( this TSRMLS_CC ); } @@ -174,8 +163,8 @@ sqlsrv_stmt::~sqlsrv_stmt( void ) efree( current_results ); current_results = NULL; } - - invalidate(); + + invalidate(); zval_ptr_dtor( ¶m_input_strings ); zval_ptr_dtor( &output_params ); zval_ptr_dtor( ¶m_streams ); @@ -189,13 +178,13 @@ sqlsrv_stmt::~sqlsrv_stmt( void ) // execution phase. void sqlsrv_stmt::free_param_data( TSRMLS_D ) { - SQLSRV_ASSERT( Z_TYPE_P( param_input_strings ) == IS_ARRAY && Z_TYPE_P( param_streams ) == IS_ARRAY, + SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY, "sqlsrv_stmt::free_param_data: Param zvals aren't arrays." ); - zend_hash_clean( Z_ARRVAL_P( param_input_strings )); - zend_hash_clean( Z_ARRVAL_P( output_params )); - zend_hash_clean( Z_ARRVAL_P( param_streams )); - zend_hash_clean( Z_ARRVAL_P( param_datetime_buffers )); - zend_hash_clean( Z_ARRVAL_P( field_cache )); + zend_hash_clean( Z_ARRVAL( param_input_strings )); + zend_hash_clean( Z_ARRVAL( output_params )); + zend_hash_clean( Z_ARRVAL( param_streams )); + zend_hash_clean( Z_ARRVAL( param_datetime_buffers )); + zend_hash_clean( Z_ARRVAL( field_cache )); } @@ -242,7 +231,7 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ) { - sqlsrv_malloc_auto_ptr stmt; + sqlsrv_malloc_auto_ptr stmt; SQLHANDLE stmt_h = SQL_NULL_HANDLE; try { @@ -259,34 +248,26 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stm // process the options array given to core_sqlsrv_prepare. if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { + zend_ulong index = -1; + zend_string *key = NULL; + zval* value_z = NULL; - for( zend_hash_internal_pointer_reset( options_ht ); - zend_hash_has_more_elements( options_ht ) == SUCCESS; - zend_hash_move_forward( options_ht )) { + ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) { - char *key = NULL; - unsigned int key_len = 0; - unsigned long index = -1; - zval** value_z = NULL; + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - int type = zend_hash_get_current_key_ex( options_ht, &key, &key_len, &index, 0, NULL ); - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); - - core::sqlsrv_zend_hash_get_current_data( *(stmt->conn), options_ht, (void**) &value_z TSRMLS_CC ); - - const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); - - // if the key didn't match, then return the error to the script. - // The driver layer should ensure that the key is valid. - DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); - - // perform the actions the statement option needs done. - (*stmt_opt->func)( stmt, stmt_opt, *value_z TSRMLS_CC ); - } + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); - zend_hash_internal_pointer_end( options_ht ); + const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); + + // if the key didn't match, then return the error to the script. + // The driver layer should ensure that the key is valid. + DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); + + // perform the actions the statement option needs done. + (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); } sqlsrv_stmt* return_stmt = stmt; @@ -329,9 +310,9 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stm // Return: // Nothing, though an exception is thrown if an error occurs // The php type of the parameter is taken from the zval. -// The sql type is given as a hint if the driver provides it. +// The sql type is given as a hint if the driver provides it. -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int direction, zval* param_z, +void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, SQLSMALLINT decimal_digits TSRMLS_DC ) { @@ -352,13 +333,17 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire } // resize the statements array of int_ptrs if the parameter isn't already set. - if( stmt->param_ind_ptrs.size() < param_num + 1 ) { + 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 ]; - bool zval_was_null = (Z_TYPE_P( param_z ) == IS_NULL); - bool zval_was_bool = (Z_TYPE_P( param_z ) == IS_BOOL); + zval* param_ref = param_z; + if ( Z_ISREF_P( param_z ) ) { + ZVAL_DEREF( param_z ); + } + bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL ); + bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE ); // if the user asks for for a specific type for input and output, make sure the data type we send matches the data we // type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so // we always let that match if they want a string back. @@ -426,12 +411,12 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire // if the sql type is unknown, then set the default based on the PHP type passed in if( sql_type == SQL_UNKNOWN_TYPE ) { - default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); + default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); } // if the size is unknown, then set the default based on the PHP type passed in if( column_size == SQLSRV_UNKNOWN_SIZE ) { - default_sql_size_and_scale( stmt, param_num, param_z, encoding, column_size, decimal_digits TSRMLS_CC ); + default_sql_size_and_scale( stmt, static_cast( param_num ), param_z, encoding, column_size, decimal_digits TSRMLS_CC ); } // determine the ODBC C type @@ -448,15 +433,18 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire buffer_len = 0; } break; - case IS_BOOL: + case IS_TRUE: + case IS_FALSE: case IS_LONG: - { + { + // if it is boolean, set the lval to 0 or 1 + convert_to_long( param_z ); buffer = ¶m_z->value; - buffer_len = sizeof( param_z->value.lval ); + buffer_len = sizeof( Z_LVAL_P( param_z )); ind_ptr = buffer_len; if( direction != SQL_PARAM_INPUT ) { // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_z, param_num, zval_was_bool ); + sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool ); save_output_param_for_later( stmt, output_param TSRMLS_CC ); } } @@ -464,11 +452,11 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire case IS_DOUBLE: { buffer = ¶m_z->value; - buffer_len = sizeof( param_z->value.dval ); + buffer_len = sizeof( Z_DVAL_P( param_z )); ind_ptr = buffer_len; if( direction != SQL_PARAM_INPUT ) { // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_z, param_num, false ); + sqlsrv_output_param output_param( param_ref, static_cast( param_num ), false ); save_output_param_for_later( stmt, output_param TSRMLS_CC ); } } @@ -479,48 +467,43 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) { - zval_auto_ptr wbuffer_z; - ALLOC_INIT_ZVAL( wbuffer_z ); + zval wbuffer_z; + ZVAL_NULL( &wbuffer_z ); - bool converted = convert_input_param_to_utf16( param_z, wbuffer_z ); + bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z ); CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, param_num + 1, get_last_error_message() ) { throw core::CoreException(); } - buffer = Z_STRVAL_P( wbuffer_z ); - buffer_len = Z_STRLEN_P( wbuffer_z ); - core::sqlsrv_add_index_zval( *stmt, stmt->param_input_strings, param_num, wbuffer_z TSRMLS_CC ); - wbuffer_z.transferred(); + buffer = Z_STRVAL_P( &wbuffer_z ); + buffer_len = Z_STRLEN_P( &wbuffer_z ); + core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC); } ind_ptr = buffer_len; if( direction != SQL_PARAM_INPUT ) { - -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4 - // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, - // we reallocate the string if it's interned - if( IS_INTERNED( buffer )) { - ZVAL_STRINGL( param_z, static_cast(buffer), buffer_len, 1 ); - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - } -#endif - + // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, + // we reallocate the string if it's interned + if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) { + core::sqlsrv_zval_stringl( param_z, static_cast(buffer), buffer_len ); + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + } + // if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR) // or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type, // convert it to wchar first if( direction == SQL_PARAM_INPUT_OUTPUT && - (c_type == SQL_C_WCHAR || - (c_type == SQL_C_BINARY && - (sql_type == SQL_WCHAR || - sql_type == SQL_WVARCHAR || - sql_type == SQL_WLONGVARCHAR )))) { + ( c_type == SQL_C_WCHAR || + ( c_type == SQL_C_BINARY && + ( sql_type == SQL_WCHAR || + sql_type == SQL_WVARCHAR || + sql_type == SQL_WLONGVARCHAR )))) { bool converted = convert_input_param_to_utf16( param_z, param_z ); CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, param_num + 1, get_last_error_message() ) { throw core::CoreException(); } - sqlsrv_free( buffer ); buffer = Z_STRVAL_P( param_z ); buffer_len = Z_STRLEN_P( param_z ); ind_ptr = buffer_len; @@ -532,11 +515,12 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire buffer, buffer_len TSRMLS_CC ); // save the parameter to be adjusted and/or converted after the results are processed - sqlsrv_output_param output_param( param_z, encoding, param_num, buffer_len ); + sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len )); + save_output_param_for_later( stmt, output_param TSRMLS_CC ); // For output parameters, if we set the column_size to be same as the buffer_len, - // than if there is a truncation due to the data coming from the server being + // then if there is a truncation due to the data coming from the server being // greater than the column_size, we don't get any truncation error. In order to // avoid this silent truncation, we set the column_size to be "MAX" size for // string types. This will guarantee that there is no silent truncation for @@ -561,11 +545,10 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire { SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); sqlsrv_stream stream_encoding( param_z, encoding ); - HashTable* streams_ht = Z_ARRVAL_P( stmt->param_streams ); - core::sqlsrv_zend_hash_index_update( *stmt, streams_ht, param_num, &stream_encoding, sizeof( stream_encoding ) - TSRMLS_CC ); + HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC ); buffer = reinterpret_cast( param_num ); - zval_add_ref( ¶m_z ); // so that it doesn't go away while we're using it + Z_TRY_ADDREF_P( param_z ); // so that it doesn't go away while we're using it buffer_len = 0; ind_ptr = SQL_DATA_AT_EXEC; } @@ -573,18 +556,23 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire case IS_OBJECT: { SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - zval_auto_ptr function_z; - zval_auto_ptr buffer_z; - zval_auto_ptr format_z; - zval* params[1]; - bool valid_class_name_found = false; + zval function_z; + zval buffer_z; + zval format_z; + zval params[1]; + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( &buffer_z ); + ZVAL_UNDEF( &format_z ); + ZVAL_UNDEF( params ); - zend_class_entry *class_entry = zend_get_class_entry( param_z TSRMLS_CC ); + bool valid_class_name_found = false; + + zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC ); while( class_entry != NULL ) { - if( class_entry->name_length == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && - stricmp( class_entry->name, DateTime::DATETIME_CLASS_NAME ) == 0 ) { + if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && + stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) { valid_class_name_found = true; break; } @@ -599,40 +587,37 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { throw core::CoreException(); } - - ALLOC_INIT_ZVAL( buffer_z ); - ALLOC_INIT_ZVAL( function_z ); - ALLOC_INIT_ZVAL( format_z ); + // if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error' // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' // sql type, it lacks the timezone. if( sql_type == SQL_SS_TIMESTAMPOFFSET ) { - ZVAL_STRINGL( format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), - DateTime::DATETIMEOFFSET_FORMAT_LEN, 1 /* dup */ ); + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), + DateTime::DATETIMEOFFSET_FORMAT_LEN ); } else if( sql_type == SQL_TYPE_DATE ) { - ZVAL_STRINGL( format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN, 1 /* dup */ ); + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); } else { - ZVAL_STRINGL( format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN, - 1 /* dup */); + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); } // call the DateTime::format member function to convert the object to a string that SQL Server understands - ZVAL_STRINGL( function_z, "format", sizeof( "format" ) - 1, 1 ); + core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); params[0] = format_z; // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the // DateTime object and $format_z is the format string. - int zr = call_user_function( EG( function_table ), ¶m_z, function_z, buffer_z, 1, params TSRMLS_CC ); + int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); + zend_string_release( Z_STR( format_z )); + zend_string_release( Z_STR( function_z )); CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { throw core::CoreException(); } - buffer = Z_STRVAL_P( buffer_z ); - zr = add_next_index_zval( stmt->param_datetime_buffers, buffer_z ); + buffer = Z_STRVAL( buffer_z ); + zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z ); CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { throw core::CoreException(); } - buffer_len = Z_STRLEN_P( buffer_z ); - buffer_z.transferred(); + buffer_len = Z_STRLEN( buffer_z ) - 1; ind_ptr = buffer_len; break; } @@ -649,9 +634,8 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire ind_ptr = SQL_NULL_DATA; } - core::SQLBindParameter( stmt, param_num + 1, direction, c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, - &ind_ptr TSRMLS_CC ); - + core::SQLBindParameter( stmt, param_num + 1, direction, + c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); } catch( core::CoreException& e ) { stmt->free_param_data( TSRMLS_C ); @@ -670,13 +654,13 @@ void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, unsigned int param_num, int dire void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len ) { + SQLRETURN r; + try { // close the stream to release the resource close_active_stream( stmt TSRMLS_CC ); - SQLRETURN r; - if( sql ) { sqlsrv_malloc_auto_ptr wsql_string; @@ -712,17 +696,24 @@ void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_ stmt->executed = true; // if all the data has been sent and no data was returned then finalize the output parameters - if( stmt->send_streams_at_exec && (r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { + if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { finalize_output_parameters( stmt TSRMLS_CC ); } - + // stream parameters are sent, clean the Hashtable + if ( stmt->send_streams_at_exec ) { + zend_hash_clean( Z_ARRVAL( stmt->param_streams )); + } } catch( core::CoreException& e ) { // if the statement executed but failed in a subsequent operation before returning, - // we need to cancel the statement - if( stmt->executed ) { + // we need to cancel the statement and deref the output and stream parameters + if ( stmt->send_streams_at_exec ) { + zend_hash_clean( Z_ARRVAL( stmt->output_params )); + zend_hash_clean( Z_ARRVAL( stmt->param_streams )); + } + if( stmt->executed ) { SQLCancel( stmt->handle() ); // stmt->executed = false; should this be reset if something fails? } @@ -742,7 +733,7 @@ void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_ // Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the // user scrolls past a non-scrollable result set -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) +bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ) { // pre-condition check SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE, @@ -751,7 +742,7 @@ bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN try { // clear the field cache of the previous fetch - zend_hash_clean( Z_ARRVAL_P( stmt->field_cache )); + zend_hash_clean( Z_ARRVAL( stmt->field_cache )); CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { throw core::CoreException(); @@ -879,101 +870,100 @@ field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT coln // Nothing, excpetion thrown if an error occurs void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string, - __out void** field_value, __out SQLLEN* field_len, bool cache_field, - __out SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC ) + _Out_ void*& field_value, _Out_ SQLLEN* field_len, bool cache_field, + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) { - try { - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - // if the field has been retrieved before, return the previous result - field_cache* cached = NULL; - if( zend_hash_index_find( Z_ARRVAL_P( stmt->field_cache ), field_index, (void**) &cached ) == SUCCESS ) { - // the field value is NULL - if( cached->value == NULL ) { - *field_value = NULL; - *field_len = 0; - if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } - } - else { - - *field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); - memcpy( *field_value, 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'; - } - *field_len = cached->len; - if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = static_cast( cached->type.typeinfo.type ); } - } - return; - } + try { - sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; + // close the stream to release the resource + close_active_stream(stmt TSRMLS_CC); - SQLLEN sql_field_type = 0; - SQLLEN sql_field_len = 0; + // if the field has been retrieved before, return the previous result + field_cache* cached = NULL; + if (NULL != ( cached = static_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast( field_index ))))) { + // the field value is NULL + if( cached->value == NULL ) { + field_value = NULL; + *field_len = 0; + if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } + } + else { - // Make sure that the statement was executed and not just prepared. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } + field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); + 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'; + } + *field_len = cached->len; + if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } + } + return; + } - // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they - // may also be retrieved. - if( cache_field && ( field_index - stmt->last_field_index ) >= 2 ) { - sqlsrv_phptype invalid; - invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { - SQLSRV_ASSERT( zend_hash_index_find( Z_ARRVAL_P( stmt->field_cache ), i, (void**) &cached ) == FAILURE, - "Field already cached." ); - core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, - sqlsrv_php_type_out TSRMLS_CC ); - // delete the value returned since we only want it cached, not the actual value - if( *field_value ) { - efree( *field_value ); - *field_value = NULL; - *field_len = 0; - } - } - } + sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; - // If the php type was not specified set the php type to be the default type. - if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { - - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Get the length of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); + SQLLEN sql_field_type = 0; + SQLLEN sql_field_len = 0; - // Get the corresponding php type from the sql type. - sqlsrv_php_type = stmt->sql_type_to_php_type( sql_field_type, sql_field_len, prefer_string ); - } + // Make sure that the statement was executed and not just prepared. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } - // Verify that we have an acceptable type to convert. - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { - throw core::CoreException(); - } - - if( sqlsrv_php_type_out != NULL ) - *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); + // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they + // may also be retrieved. + if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { + sqlsrv_phptype invalid; + invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { + SQLSRV_ASSERT((cached = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->field_cache), i))) == NULL, + "Field already cached." ); + core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, + sqlsrv_php_type_out TSRMLS_CC ); + // delete the value returned since we only want it cached, not the actual value + if( field_value ) { + efree( field_value ); + field_value = NULL; + *field_len = 0; + } + } + } - // Retrieve the data - core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + // If the php type was not specified set the php type to be the default type. + if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { - // if the user wants us to cache the field, we'll do it - if( cache_field ) { - field_cache cache( *field_value, *field_len, sqlsrv_php_type ); - core::sqlsrv_zend_hash_index_update( *stmt, Z_ARRVAL_P( stmt->field_cache ), field_index, &cache, - sizeof( field_cache ) TSRMLS_CC ); - } - } + // Get the SQL type of the field. + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - catch( core::CoreException& e) { - throw e; - } + // Get the length of the field. + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); + + // Get the corresponding php type from the sql type. + sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast( sql_field_type ), static_cast( sql_field_len ), prefer_string ); + } + + // Verify that we have an acceptable type to convert. + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { + throw core::CoreException(); + } + + if( sqlsrv_php_type_out != NULL ) + *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); + + // Retrieve the data + core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + + // if the user wants us to cache the field, we'll do it + if( cache_field ) { + field_cache cache( field_value, *field_len, sqlsrv_php_type ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); + } + } + + catch( core::CoreException& e ) { + throw e; + } } // core_sqlsrv_has_any_result @@ -1025,7 +1015,7 @@ void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_ if( r == SQL_NO_DATA ) { - if( stmt->output_params && finalize_output_params ) { + if( &(stmt->output_params) && finalize_output_params ) { // if we're finished processing result sets, handle the output parameters finalize_output_parameters( stmt TSRMLS_CC ); } @@ -1054,28 +1044,28 @@ void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_ // Returns: // Nothing, exception thrown if problem occurs -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, unsigned int param_num, zval* param_z TSRMLS_DC ) +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* param_z TSRMLS_DC ) { - SQLSRV_ASSERT( Z_TYPE_P( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); - SQLSRV_ASSERT( Z_TYPE_P( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); + SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); + SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); // if the parameter was an input string, delete it from the array holding input parameter strings - if( zend_hash_index_exists( Z_ARRVAL_P( stmt->param_input_strings ), param_num )) { - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL_P( stmt->param_input_strings ), param_num TSRMLS_CC ); + if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) { + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC ); } // if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams // PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it // with sqlsrv_send_stream_data. - if( zend_hash_index_exists( Z_ARRVAL_P( stmt->param_streams ), param_num )) { - sqlsrv_stream* stream_encoding; - zend_hash_index_find( Z_ARRVAL_P( stmt->param_streams ), param_num, (void**) &stream_encoding ); - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL_P( stmt->param_streams ), param_num TSRMLS_CC ); + if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) { + sqlsrv_stream* stream_encoding = NULL; + stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->param_streams), param_num)); + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC ); } } //Calls SQLSetStmtAttr to set a cursor. -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned int cursor_type TSRMLS_DC ) +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ) { try { @@ -1129,7 +1119,7 @@ void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRM core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); } -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, long limit TSRMLS_DC ) +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ) { if( limit <= 0 ) { @@ -1154,7 +1144,7 @@ void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) ); } - core_sqlsrv_set_query_timeout( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); + core_sqlsrv_set_query_timeout( stmt, static_cast( Z_LVAL_P( value_z )) TSRMLS_CC ); } catch( core::CoreException& ) { throw; @@ -1169,7 +1159,7 @@ void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ) DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." ); // set the statement attribute - core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( timeout ), SQL_IS_UINTEGER TSRMLS_CC ); + core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC ); // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which // is represented by -1. @@ -1232,7 +1222,7 @@ bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) // get the stream from the zval we bound php_stream* param_stream = NULL; - core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, &stmt->current_stream.stream_z TSRMLS_CC ); + core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); // if we're at the end, then release our current parameter if( php_stream_eof( param_stream )) { @@ -1248,9 +1238,16 @@ bool core_sqlsrv_send_stream_packet( 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 ]; - size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character - size_t read = php_stream_read( param_stream, buffer, buffer_size ); - stmt->current_stream_read += read; + 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 ); + + if (read > UINT_MAX) + { + LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); + throw core::CoreException(); + } + + stmt->current_stream_read += static_cast( read ); if( read > 0 ) { // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it @@ -1264,7 +1261,7 @@ bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, read, wbuffer, sizeof( wbuffer ) / sizeof( wchar_t )); + buffer, static_cast( read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) { // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more @@ -1280,7 +1277,7 @@ bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) } // try the conversion again with the complete character wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, read + new_read, wbuffer, sizeof( wbuffer ) / sizeof( wchar_t )); + buffer, static_cast( read + new_read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); // something else must be wrong if it failed CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { @@ -1308,7 +1305,6 @@ bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) return true; } - void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) { TSRMLS_C; @@ -1335,23 +1331,23 @@ void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_opti // internal function to release the active stream. Called by each main API function // that will alter the statement and cancel any retrieval of data from a stream. -void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC ) +void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { // if there is no active stream, return - if( stmt->active_stream == NULL ) { + if( Z_TYPE( stmt->active_stream ) == IS_UNDEF ) { return; } php_stream* stream = NULL; // we use no verify since verify would return immediately and we want to assert, not return. - php_stream_from_zval_no_verify( stream, &stmt->active_stream ); + php_stream_from_zval_no_verify( stream, &( stmt->active_stream )); SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." ); php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here. - SQLSRV_ASSERT( stmt->active_stream == NULL, "close_active_stream: Active stream not closed." ); + SQLSRV_ASSERT( Z_TYPE( stmt->active_stream ) == IS_UNDEF, "close_active_stream: Active stream not closed." ); } @@ -1359,7 +1355,7 @@ void close_active_stream( __inout sqlsrv_stmt* stmt TSRMLS_DC ) namespace { -bool is_streamable_type( SQLINTEGER sql_type ) +bool is_streamable_type( SQLLEN sql_type ) { switch( sql_type ) { case SQL_CHAR: @@ -1378,7 +1374,7 @@ bool is_streamable_type( SQLINTEGER sql_type ) return false; } -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, __out SQLLEN& size TSRMLS_DC ) +void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ) { try { @@ -1474,200 +1470,204 @@ size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_e // The memory allocation has to happen in the core layer because otherwise // the driver layer would have to calculate size of the field_value // to decide the amount of memory allocation. - -void core_get_field_common( __inout sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ) +void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype + sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) { - try { + try { - close_active_stream( stmt TSRMLS_CC ); + close_active_stream( stmt TSRMLS_CC ); - // make sure that fetch is called before trying to retrieve. - CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { - throw core::CoreException(); - } + // make sure that fetch is called before trying to retrieve. + CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { + throw core::CoreException(); + } - // make sure that fields are not retrieved incorrectly. - CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, - stmt->last_field_index ) { - throw core::CoreException(); - } + // make sure that fields are not retrieved incorrectly. + CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, + stmt->last_field_index ) { + throw core::CoreException(); + } - switch( sqlsrv_php_type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_INT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); + switch( sqlsrv_php_type.typeinfo.type ) { - SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } + case SQLSRV_PHPTYPE_INT: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), + field_len, true /*handle_warning*/ TSRMLS_CC ); - if( *field_len == SQL_NULL_DATA ) { - *field_value = NULL; - break; - } - - *field_value = field_value_temp; - field_value_temp.transferred(); - break; - } + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } - case SQLSRV_PHPTYPE_FLOAT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } + if( *field_len == SQL_NULL_DATA ) { + field_value = NULL; + break; + } - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException (); - } + field_value = field_value_temp; + field_value_temp.transferred(); + break; + } - if( *field_len == SQL_NULL_DATA ) { - *field_value = NULL; - break; - } - - *field_value = field_value_temp; - field_value_temp.transferred(); - break; - } - - case SQLSRV_PHPTYPE_STRING: - { - get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - break; - } - - // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and - // convert it to a DateTime object and return the created object - case SQLSRV_PHPTYPE_DATETIME: - { - char field_value_temp[ MAX_DATETIME_STRING_LEN ]; - zval_auto_ptr field_value_temp_z; - zval_auto_ptr return_value_z; - zval_auto_ptr function_z; - zval* params[1]; - ALLOC_INIT_ZVAL( field_value_temp_z ); - ALLOC_INIT_ZVAL( function_z ); - ALLOC_INIT_ZVAL( return_value_z ); + case SQLSRV_PHPTYPE_FLOAT: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), + field_len, true /*handle_warning*/ TSRMLS_CC ); - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, - MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException (); - } + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } - if( *field_len == SQL_NULL_DATA ) { - ZVAL_NULL( return_value_z ); - *field_value = reinterpret_cast( return_value_z.get() ); - return_value_z.transferred(); - break; - } - - // Convert the string date to a DateTime object - ZVAL_STRINGL( field_value_temp_z, field_value_temp, *field_len, 1 ); - ZVAL_STRINGL( function_z, "date_create", sizeof("date_create") -1, 1 ); - params[0] = field_value_temp_z; + if( *field_len == SQL_NULL_DATA ) { + field_value = NULL; + break; + } - if( call_user_function( EG( function_table ), NULL, function_z, return_value_z, 1, - params TSRMLS_CC ) == FAILURE ) { - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED ); - } + field_value = field_value_temp; + field_value_temp.transferred(); + break; + } - *field_value = reinterpret_cast( return_value_z.get() ); - return_value_z.transferred(); - break; - } - - // create a stream wrapper around the field and return that object to the PHP script. calls to fread - // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file - // for how these fields are used. - case SQLSRV_PHPTYPE_STREAM: - { - zval_auto_ptr return_value_z; - php_stream* stream = NULL; - sqlsrv_stream* ss = NULL; - ALLOC_INIT_ZVAL( return_value_z ); - SQLINTEGER sql_type; + case SQLSRV_PHPTYPE_STRING: + { + get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + break; + } - SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { - throw core::CoreException(); - } - - stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); + // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and + // convert it to a DateTime object and return the created object + case SQLSRV_PHPTYPE_DATETIME: + { + char field_value_temp[ MAX_DATETIME_STRING_LEN ]; + zval params[1]; + zval field_value_temp_z; + zval function_z; - CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { - throw core::CoreException(); - } + ZVAL_UNDEF( &field_value_temp_z ); + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( params ); - ss = static_cast( stream->abstract ); - ss->stmt = stmt; - ss->field_index = field_index; - ss->sql_type = static_cast( sql_type ); - ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, + MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); - // turn our stream into a zval to be returned - php_stream_to_zval( stream, return_value_z ); + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } - // mark this as our active stream - stmt->active_stream = return_value_z; - *field_value = reinterpret_cast( return_value_z.get() ); - return_value_z.transferred(); + zval_auto_ptr return_value_z; + return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); + ZVAL_UNDEF( return_value_z ); - break; - } + if( *field_len == SQL_NULL_DATA ) { + ZVAL_NULL( return_value_z ); + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + break; + } - case SQLSRV_PHPTYPE_NULL: - *field_value = NULL; - *field_len = 0; - break; + // Convert the string date to a DateTime object + core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); + core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 ); + params[0] = field_value_temp_z; - default: - DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); - break; - } + if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, + params TSRMLS_CC ) == FAILURE) { + THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); + } - // sucessfully retrieved the field, so update our last retrieved field - if( stmt->last_field_index < field_index ) { - stmt->last_field_index = field_index; - } - } - catch( core::CoreException& e ) { - throw e; - } + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + zend_string_free( Z_STR( field_value_temp_z )); + zend_string_free( Z_STR( function_z )); + break; + } + + // create a stream wrapper around the field and return that object to the PHP script. calls to fread + // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file + // for how these fields are used. + case SQLSRV_PHPTYPE_STREAM: + { + + php_stream* stream = NULL; + sqlsrv_stream* ss = NULL; + SQLLEN sql_type; + + SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { + throw core::CoreException(); + } + + stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); + + CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { + throw core::CoreException(); + } + + ss = static_cast( stream->abstract ); + ss->stmt = stmt; + ss->field_index = field_index; + ss->sql_type = static_cast( sql_type ); + ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); + + zval_auto_ptr return_value_z; + return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); + ZVAL_UNDEF( return_value_z ); + + // turn our stream into a zval to be returned + php_stream_to_zval( stream, return_value_z ); + + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + break; + } + + case SQLSRV_PHPTYPE_NULL: + field_value = NULL; + *field_len = 0; + break; + + default: + DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); + break; + } + + // sucessfully retrieved the field, so update our last retrieved field + if( stmt->last_field_index < field_index ) { + stmt->last_field_index = field_index; + } + } + catch( core::CoreException& e ) { + throw e; + } } // check_for_next_stream_parameter // see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise // returns false - -bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt TSRMLS_DC ) +bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { int stream_index = 0; SQLRETURN r = SQL_SUCCESS; - sqlsrv_stream* stream_encoding; + sqlsrv_stream* stream_encoding = NULL; zval* param_z = NULL; // get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param @@ -1679,11 +1679,11 @@ bool check_for_next_stream_parameter( __inout sqlsrv_stmt* stmt TSRMLS_DC ) return false; } - HashTable* streams_ht = Z_ARRVAL_P( stmt->param_streams ); + HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); // pull out the sqlsrv_encoding struct - int zr = zend_hash_index_find( streams_ht, stream_index, (void**) &stream_encoding ); - SQLSRV_ASSERT( zr == SUCCESS, "Stream parameter does not exist" ); // if the index isn't in the hash, that's a serious error + stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(streams_ht, stream_index)); + SQLSRV_ASSERT(stream_encoding != NULL, "Stream parameter does not exist"); // if the index isn't in the hash, that's a serious error param_z = stream_encoding->stream_z; @@ -1704,18 +1704,26 @@ bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z "convert_input_param_z called with invalid parameter states" ); const char* buffer = Z_STRVAL_P( input_param_z ); - int buffer_len = Z_STRLEN_P( input_param_z ); + std::size_t buffer_len = Z_STRLEN_P( input_param_z ); int wchar_size; + if (buffer_len > INT_MAX) + { + LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } + // if the string is empty, then just return that the conversion succeeded as // MultiByteToWideChar will "fail" on an empty string. if( buffer_len == 0 ) { - ZVAL_STRINGL( converted_param_z, "", 0, 1 ); + core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); return true; } // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string - wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), buffer_len, NULL, 0 ); + wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, + reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); + // if there was a problem determining the size of the string, return false if( wchar_size == 0 ) { return false; @@ -1724,7 +1732,7 @@ bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) )); // convert the utf-8 string to a wchar string in the new buffer int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), - buffer_len, wbuffer, wchar_size ); + static_cast( buffer_len ), wbuffer, wchar_size ); // if there was a problem converting the string, then free the memory and return false if( r == 0 ) { return false; @@ -1732,8 +1740,9 @@ bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z // null terminate the string, set the size within the zval, and return success wbuffer[ wchar_size ] = L'\0'; - ZVAL_STRINGL( converted_param_z, reinterpret_cast( wbuffer.get() ), - wchar_size * sizeof( wchar_t ), 0 ); + core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), + wchar_size * sizeof( wchar_t ) ); + sqlsrv_free(wbuffer); wbuffer.transferred(); return true; @@ -1741,7 +1750,7 @@ bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z // returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) +SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) { SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; int php_type = Z_TYPE_P( param_z ); @@ -1762,9 +1771,18 @@ SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* break; } break; - case IS_BOOL: + case IS_TRUE: + case IS_FALSE: case IS_LONG: - sql_c_type = SQL_C_LONG; + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) + { + sql_c_type = SQL_C_SBIGINT; + } + else + { + sql_c_type = SQL_C_SLONG; + } break; case IS_DOUBLE: sql_c_type = SQL_C_DOUBLE; @@ -1803,12 +1821,11 @@ SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, unsigned int paramno, zval const* // given a zval and encoding, determine the appropriate sql type -void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - __out SQLSMALLINT& sql_type TSRMLS_DC ) +void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLSMALLINT& sql_type TSRMLS_DC ) { sql_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P( param_z ); - + int php_type = Z_TYPE_P(param_z); switch( php_type ) { case IS_NULL: @@ -1825,9 +1842,19 @@ void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, S break; } break; - case IS_BOOL: + case IS_TRUE: + case IS_FALSE: case IS_LONG: - sql_type = SQL_INTEGER; + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) + { + sql_type = SQL_BIGINT; + } + else + { + sql_type = SQL_INTEGER; + } + break; case IS_DOUBLE: sql_type = SQL_FLOAT; @@ -1874,7 +1901,7 @@ void default_sql_type( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, S // given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate) void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - __out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC ) + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) { int php_type = Z_TYPE_P( param_z ); column_size = 0; @@ -1886,19 +1913,21 @@ void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* column_size = 1; break; // size is not necessary for these types, they are inferred by ODBC - case IS_BOOL: + case IS_TRUE: + case IS_FALSE: case IS_LONG: case IS_DOUBLE: case IS_RESOURCE: break; case IS_STRING: { - SQLULEN byte_len = Z_STRLEN_P( param_z ) * ((encoding == SQLSRV_ENCODING_UTF8) ? sizeof( wchar_t ) : sizeof( char )); + size_t char_size = ( encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( wchar_t ) : sizeof( char ); + SQLULEN byte_len = Z_STRLEN_P( param_z ) * char_size; if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { column_size = SQL_SERVER_MAX_TYPE_SIZE; } else { - column_size = Z_STRLEN_P( param_z ); + column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size; } break; } @@ -1924,13 +1953,14 @@ void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* } } -void field_cache_dtor( void* data ) +void field_cache_dtor( zval* data_z ) { - field_cache* cache = reinterpret_cast( data ); + field_cache* cache = static_cast( Z_PTR_P( data_z )); if( cache->value ) { sqlsrv_free( cache->value ); } + sqlsrv_free( cache ); } @@ -1942,325 +1972,329 @@ void field_cache_dtor( void* data ) void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) { - if( stmt->output_params == NULL ) + if( Z_ISUNDEF(stmt->output_params) ) return; bool converted = true; - HashTable* params_ht = Z_ARRVAL_P( stmt->output_params ); + HashTable* params_ht = Z_ARRVAL( stmt->output_params ); + zend_ulong index = -1; + zend_string* key = NULL; + void* output_param_temp = NULL; - for( zend_hash_internal_pointer_reset( params_ht ); - zend_hash_has_more_elements( params_ht ) == SUCCESS; - zend_hash_move_forward( params_ht ) ) { + ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { + sqlsrv_output_param* output_param = static_cast( output_param_temp ); + zval* value_z = Z_REFVAL_P( output_param->param_z ); + switch( Z_TYPE_P( value_z )) { + case IS_STRING: + { + // 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 ]; + if( str_len == SQL_NULL_DATA ) { + zend_string_release( Z_STR_P( value_z )); + ZVAL_NULL( value_z ); + continue; + } - sqlsrv_output_param *output_param; - core::sqlsrv_zend_hash_get_current_data( *stmt, params_ht, (void**) &output_param TSRMLS_CC ); - - switch( Z_TYPE_P( output_param->param_z )) { - case IS_STRING: - { - // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter - char* str = Z_STRVAL_P( output_param->param_z ); - SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ]; - if( str_len == SQL_NULL_DATA ) { - ZVAL_NULL( output_param->param_z ); - continue; - } - - // if there was more to output than buffer size to hold it, then throw a truncation error - int null_size = 0; - switch( output_param->encoding ) { - case SQLSRV_ENCODING_UTF8: - null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16 - break; - case SQLSRV_ENCODING_SYSTEM: - null_size = 1; - break; - case SQLSRV_ENCODING_BINARY: - null_size = 0; - break; - default: - SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); - break; - } - CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { - throw core::CoreException(); - } - - // if it's not in the 8 bit encodings, then it's in UTF-16 - if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { - - bool converted = convert_string_from_utf16_inplace( output_param->encoding, &str, str_len ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { - // 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'; - } - // set the string length - ZVAL_STRINGL( output_param->param_z, str, str_len, 0 ); - } + // if there was more to output than buffer size to hold it, then throw a truncation error + int null_size = 0; + switch( output_param->encoding ) { + case SQLSRV_ENCODING_UTF8: + null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16 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 ) { - ZVAL_NULL( output_param->param_z ); - } - else if( output_param->is_bool ) { - convert_to_boolean( output_param->param_z ); - } + case SQLSRV_ENCODING_SYSTEM: + null_size = 1; break; - case IS_DOUBLE: - // 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 ) { - ZVAL_NULL( output_param->param_z ); - } + case SQLSRV_ENCODING_BINARY: + null_size = 0; break; default: - DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); + SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); break; + } + CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { + throw core::CoreException(); + } + + // if it's not in the 8 bit encodings, then it's in UTF-16 + if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { + bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { + // 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'; + core::sqlsrv_zval_stringl(value_z, str, str_len); + } + else { + core::sqlsrv_zval_stringl(value_z, str, str_len); + } } - } + 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 ) { + ZVAL_NULL( value_z ); + } + else if( output_param->is_bool ) { + convert_to_boolean( value_z ); + } + else + { + ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); + } + break; + case IS_DOUBLE: + // 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 ) { + ZVAL_NULL( value_z ); + } + break; + default: + DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); + break; + } + value_z = NULL; + } ZEND_HASH_FOREACH_END(); // empty the hash table since it's been processed - zend_hash_clean( Z_ARRVAL_P( stmt->output_params )); - + zend_hash_clean( Z_ARRVAL( stmt->output_params )); return; } -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - __out void** field_value, __out SQLLEN* field_len TSRMLS_DC ) +void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, + _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) { - SQLRETURN r; - SQLSMALLINT c_type; - SQLLEN sql_field_type = 0; - SQLSMALLINT extra = 0; - SQLLEN field_len_temp; - SQLLEN sql_display_size = 0; - char* field_value_temp = NULL; + SQLRETURN r; + SQLSMALLINT c_type; + SQLLEN sql_field_type = 0; + SQLSMALLINT extra = 0; + SQLLEN field_len_temp; + SQLLEN sql_display_size = 0; + char* field_value_temp = NULL; - try { + try { - DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, - "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); - - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); - } + DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, + "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); - // Set the C type and account for null characters at the end of the data. - switch( sqlsrv_php_type.typeinfo.encoding ) { - case CP_UTF8: - c_type = SQL_C_WCHAR; - extra = sizeof( SQLWCHAR ); - break; - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - extra = 0; - break; - default: - c_type = SQL_C_CHAR; - extra = sizeof( SQLCHAR ); - break; - } + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); + } - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + // Set the C type and account for null characters at the end of the data. + switch( sqlsrv_php_type.typeinfo.encoding ) { + case CP_UTF8: + c_type = SQL_C_WCHAR; + extra = sizeof( SQLWCHAR ); + break; + case SQLSRV_ENCODING_BINARY: + c_type = SQL_C_BINARY; + extra = 0; + break; + default: + c_type = SQL_C_CHAR; + extra = sizeof( SQLCHAR ); + break; + } - // Calculate the field size. - calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); + // Get the SQL type of the field. + core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - // if this is a large type, then read the first few bytes to get the actual length from SQLGetData - if( sql_display_size == 0 || sql_display_size == LONG_MAX || - sql_display_size == LONG_MAX >> 1 || sql_display_size == ULONG_MAX - 1 ) { + // Calculate the field size. + calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); - field_len_temp = INITIAL_FIELD_STRING_LEN; - - field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); - - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), - &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); - - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException (); - } + // if this is a large type, then read the first few bytes to get the actual length from SQLGetData + if( sql_display_size == 0 || sql_display_size == INT_MAX || + sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { - if( field_len_temp == SQL_NULL_DATA ) { - *field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } + field_len_temp = INITIAL_FIELD_STRING_LEN; - if( r == SQL_SUCCESS_WITH_INFO ) { + field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - - stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - - if( is_truncated_warning( state ) ) { - - SQLINTEGER dummy_field_len; + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), + &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); - // for XML (and possibly other conditions) the field length returned is not the real field length, so - // in every pass, we double the allocation size to retrieve all the contents. - if( field_len_temp == SQL_NO_TOTAL ) { - - // reset the field_len_temp - field_len_temp = INITIAL_FIELD_STRING_LEN; + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } - do { + if( field_len_temp == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } - SQLINTEGER initial_field_len = field_len_temp; - // Double the size. - field_len_temp *= 2; - - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - field_len_temp -= initial_field_len; + if( r == SQL_SUCCESS_WITH_INFO ) { - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, - field_len_temp + extra, &dummy_field_len, - false /*handle_warning*/ TSRMLS_CC ); + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; - // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL - // so we calculate the actual length of the string with that. - if( dummy_field_len != SQL_NO_TOTAL ) - field_len_temp += dummy_field_len; - else - field_len_temp += initial_field_len; + stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - if( r == SQL_SUCCESS_WITH_INFO ) { - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - } + if( is_truncated_warning( state )) { - } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); - } - else { + SQLLEN dummy_field_len; - // We got the field_len_temp from SQLGetData call. - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - // We have already recieved INITIAL_FIELD_STRING_LEN size data. - field_len_temp -= INITIAL_FIELD_STRING_LEN; - - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, - field_len_temp + extra, &dummy_field_len, - true /*handle_warning*/ TSRMLS_CC ); + // for XML (and possibly other conditions) the field length returned is not the real field length, so + // in every pass, we double the allocation size to retrieve all the contents. + if( field_len_temp == SQL_NO_TOTAL ) { - if( dummy_field_len == SQL_NULL_DATA ) { - *field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException (); - } - - field_len_temp += INITIAL_FIELD_STRING_LEN; - } + // reset the field_len_temp + field_len_temp = INITIAL_FIELD_STRING_LEN; - } // if( is_truncation_warning ( state ) ) - else { - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - } - } // if( r == SQL_SUCCESS_WITH_INFO ) + do { + SQLLEN initial_field_len = field_len_temp; + // Double the size. + field_len_temp *= 2; - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { - throw core::CoreException (); - } - } - } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) - - else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { + field_len_temp -= initial_field_len; - // only allow binary retrievals for char and binary types. All others get a string converted - // to the encoding type they asked for. - - // null terminator - if( c_type == SQL_C_CHAR ) { - sql_display_size += sizeof( SQLCHAR ); - } + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, + field_len_temp + extra, &dummy_field_len, + false /*handle_warning*/ TSRMLS_CC ); - // For WCHAR multiply by sizeof(WCHAR) and include the null terminator - else if( c_type == SQL_C_WCHAR ) { - sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); - } + // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL + // so we calculate the actual length of the string with that. + if( dummy_field_len != SQL_NO_TOTAL ) + field_len_temp += dummy_field_len; + else + field_len_temp += initial_field_len; - field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); - - // get the data - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, - &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); - CHECK_SQL_ERROR( r, stmt ) { - throw core::CoreException(); - } - CHECK_CUSTOM_ERROR( (r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException (); - } + if( r == SQL_SUCCESS_WITH_INFO ) { + core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len + TSRMLS_CC ); + } - if( field_len_temp == SQL_NULL_DATA ) { - *field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { + } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); + } + else { - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { - throw core::CoreException (); - } - } - } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) + // We got the field_len_temp from SQLGetData call. + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - else { - - DIE( "Invalid sql_display_size" ); - return; // to eliminate a warning - } - - *field_value = field_value_temp; - *field_len = field_len_temp; - - // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP - // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. - // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary - // operator to set add 1 to fill the null terminator - field_value_temp[field_len_temp] = '\0'; - } + // We have already recieved INITIAL_FIELD_STRING_LEN size data. + field_len_temp -= INITIAL_FIELD_STRING_LEN; - catch( core::CoreException& ) { + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, + field_len_temp + extra, &dummy_field_len, + true /*handle_warning*/ TSRMLS_CC ); - *field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - catch ( ... ) { + if( dummy_field_len == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } - *field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + field_len_temp += INITIAL_FIELD_STRING_LEN; + } + + } // if( is_truncation_warning ( state ) ) + else { + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + } + } // if( r == SQL_SUCCESS_WITH_INFO ) + + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { + + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) + + else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { + + // only allow binary retrievals for char and binary types. All others get a string converted + // to the encoding type they asked for. + + // null terminator + if( c_type == SQL_C_CHAR ) { + sql_display_size += sizeof( SQLCHAR ); + } + + // For WCHAR multiply by sizeof(WCHAR) and include the null terminator + else if( c_type == SQL_C_WCHAR ) { + sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); + } + + field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); + + // get the data + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, + &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); + CHECK_SQL_ERROR( r, stmt ) { + throw core::CoreException(); + } + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( field_len_temp == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { + + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) + + else { + + DIE( "Invalid sql_display_size" ); + return; // to eliminate a warning + } + + field_value = field_value_temp; + *field_len = field_len_temp; + + // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP + // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. + // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary + // operator to set add 1 to fill the null terminator + field_value_temp[field_len_temp] = '\0'; + } + + catch( core::CoreException& ) { + + field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } + catch ( ... ) { + + field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } } @@ -2268,7 +2302,7 @@ void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_ph // return the option from the stmt_opts array that matches the key. If no option found, // NULL is returned. -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, unsigned long key, const stmt_option stmt_opts[] TSRMLS_DC ) +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ) { for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { @@ -2334,13 +2368,12 @@ bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) // string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and // stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsigned int paramno, SQLSRV_ENCODING encoding, +void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, SQLLEN& buffer_len TSRMLS_DC ) { SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); buffer_len = Z_STRLEN_P( param_z ); - buffer = Z_STRVAL_P( param_z ); SQLLEN expected_len; SQLLEN buffer_null_extra; SQLLEN elem_size; @@ -2371,20 +2404,24 @@ void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsign // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about // not having a NULL terminator on a string. - buffer = static_cast( sqlsrv_realloc( buffer, expected_len )); - buffer_len = expected_len; // set the buffer_len to the new allocation size (includes the null terminator taken out below) + zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); // A zval string len doesn't include the null. This calculates the length it should be // regardless of whether the ODBC type contains the NULL or not. - ZVAL_STRINGL( param_z, reinterpret_cast( buffer ), without_null_len, 0 ); - + // null terminate the string to avoid a warning in debug PHP builds - (static_cast(buffer))[ without_null_len ] = '\0'; - } + ZSTR_VAL(param_z_string)[without_null_len] = '\0'; + ZVAL_NEW_STR(param_z, param_z_string); - // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the - // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY - buffer_len -= buffer_null_extra; + // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the + // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY + buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; + + // Zend string length doesn't include the null terminator + ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; + } + + buffer = Z_STRVAL_P(param_z); // 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, @@ -2394,18 +2431,16 @@ void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, unsign } } - // output parameters have their reference count incremented so that they do not disappear // while the query is executed and processed. They are saved in the statement so that // their reference count may be decremented later (after results are processed) void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ) { - HashTable* param_ht = Z_ARRVAL_P( stmt->output_params ); - int paramno = param.param_num; - core::sqlsrv_zend_hash_index_update( *stmt, param_ht, paramno, ¶m, sizeof( param ) - TSRMLS_CC ); - zval_add_ref( ¶m.param_z ); // we have a reference to the param + HashTable* param_ht = Z_ARRVAL( stmt->output_params ); + zend_ulong paramno = static_cast( param.param_num ); + core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, ¶m, sizeof( sqlsrv_output_param )); + Z_TRY_ADDREF_P( param.param_z ); // we have a reference to the param } @@ -2418,19 +2453,19 @@ void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ) // called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed - -void sqlsrv_output_param_dtor( void* data ) +void sqlsrv_output_param_dtor( zval* data ) { - sqlsrv_output_param *output_param = reinterpret_cast( data ); - zval_ptr_dtor( &output_param->param_z ); // undo the reference to the string we will no longer hold + sqlsrv_output_param *output_param = static_cast( Z_PTR_P( data )); + zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold + sqlsrv_free( output_param ); } // called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed - -void sqlsrv_stream_dtor( void* data ) +void sqlsrv_stream_dtor( zval* data ) { - sqlsrv_stream* stream_encoding = reinterpret_cast( data ); - zval_ptr_dtor( &stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold + sqlsrv_stream* stream_encoding = static_cast( Z_PTR_P( data )); + zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold + sqlsrv_free( stream_encoding ); } } diff --git a/sqlsrv/core_stream.cpp b/sqlsrv/core_stream.cpp index 4cd5eab4..b85c63ce 100644 --- a/sqlsrv/core_stream.cpp +++ b/sqlsrv/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -32,11 +32,8 @@ int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) // free the stream resources in the Zend engine php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM ); - // NULL out the stream zval and delete our reference count to it. - ZVAL_NULL( ss->stmt->active_stream ); - - // there is no active stream - ss->stmt->active_stream = NULL; + // UNDEF the stream zval and delete our reference count to it. + ZVAL_UNDEF( &( ss->stmt->active_stream ) ); sqlsrv_free( ss ); stream->abstract = NULL; @@ -48,10 +45,9 @@ int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) // read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are // set when sqlsrv_get_field is called by the user specifying which field type they want. -size_t sqlsrv_stream_read( php_stream* stream, __out_bcount(count) char* buf, size_t count TSRMLS_DC ) +size_t sqlsrv_stream_read( php_stream* stream, _Out_writes_bytes_(count) char* buf, size_t count TSRMLS_DC ) { - - SQLINTEGER read = 0; + SQLLEN read = 0; SQLSMALLINT c_type = SQL_C_CHAR; char* get_data_buffer = buf; sqlsrv_malloc_auto_ptr temp_buf; @@ -145,21 +141,28 @@ size_t sqlsrv_stream_read( php_stream* stream, __out_bcount(count) char* buf, si } // if the encoding is UTF-8 - if( c_type == SQL_C_WCHAR ) { - - count *= 2; // undo the shift to use the full buffer + if (c_type == SQL_C_WCHAR) { - // flags set to 0 by default, which means that any invalid characters are dropped rather than causing - // an error. This happens only on XP. - DWORD flags = 0; + count *= 2; // undo the shift to use the full buffer + + // flags set to 0 by default, which means that any invalid characters are dropped rather than causing + // an error. This happens only on XP. + DWORD flags = 0; + + // convert to UTF-8 + if (g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER) { + // Vista (and later) will detect invalid UTF-16 characters and raise an error. + flags = WC_ERR_INVALID_CHARS; + } + + if ( count > INT_MAX || (read >> 1) > INT_MAX ) + { + LOG(SEV_ERROR, "UTF-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } - // convert to UTF-8 - if( g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { - // Vista (and later) will detect invalid UTF-16 characters and raise an error. - flags = WC_ERR_INVALID_CHARS; - } int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast( temp_buf.get() ), - read >> 1, buf, count, NULL, NULL ); + static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); if( enc_len == 0 ) { @@ -200,13 +203,8 @@ php_stream_ops sqlsrv_stream_ops = { // open a stream and return the sqlsrv_stream_ops function table as part of the // return value. There is only one valid way to open a stream, using sqlsrv_get_field on // certain field types. A sqlsrv stream may only be opened in read mode. -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 6 -static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, __in const char*, __in const char* mode, - int options, __in char **, php_stream_context* STREAMS_DC TSRMLS_DC ) -#else -static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, __in char*, __in char* mode, - int options, __in char **, php_stream_context* STREAMS_DC TSRMLS_DC ) -#endif +static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, _In_ const char*, _In_ const char* mode, + int options, _In_ zend_string **, php_stream_context* STREAMS_DC TSRMLS_DC ) { #if ZEND_DEBUG diff --git a/sqlsrv/core_util.cpp b/sqlsrv/core_util.cpp index 8d7ae4a5..8bc410f3 100644 --- a/sqlsrv/core_util.cpp +++ b/sqlsrv/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -33,9 +33,9 @@ SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage fa char last_err_msg[ 2048 ]; // 2k to hold the error messages // routine used by utf16_string_from_mbcs_string -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, __in_bcount(mbcs_len) char const* mbcs_in_string, +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, unsigned int mbcs_len, - __out_ecount(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, + _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, unsigned int utf16_len ); } @@ -70,18 +70,19 @@ void core_sqlsrv_register_logger( log_callback driver_logger ) // utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed // and false is returned. -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLINTEGER& len) +bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len) { - SQLSRV_ASSERT( string != NULL && *string != NULL, "String must be specified" ); + SQLSRV_ASSERT( string != NULL, "String must be specified" ); - // for the empty string, we simply returned we converted it - if( len == 0 && *string[0] == '\0' ) { - return true; - } + if (validate_string(*string, len)) { + return true; + } char* outString = NULL; - SQLINTEGER outLen = 0; - bool result = convert_string_from_utf16( encoding, reinterpret_cast(*string), len / sizeof(wchar_t), &outString, outLen); + SQLLEN outLen = 0; + + bool result = convert_string_from_utf16( encoding, + reinterpret_cast(*string), int(len / sizeof(wchar_t)), &outString, outLen); if (result) { @@ -93,7 +94,49 @@ bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, return result; } -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLINTEGER& cchOutLen ) +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len) +{ + char* string = Z_STRVAL_P(value_z); + + if (validate_string(string, len)) { + return true; + } + + char* outString = NULL; + SQLLEN outLen = 0; + + bool result = convert_string_from_utf16(encoding, + reinterpret_cast(string), int(len / sizeof(wchar_t)), &outString, outLen); + + if (result) + { + core::sqlsrv_zval_stringl(value_z, outString, outLen); + sqlsrv_free(outString); + len = outLen; + } + + return result; +} + +bool validate_string(char* string, SQLLEN& len) +{ + SQLSRV_ASSERT(string != NULL, "String must be specified"); + + //for the empty string, we simply returned we converted it + if (len == 0 && string[0] == '\0') { + return true; + } + + if ((len / sizeof(wchar_t)) > INT_MAX) + { + LOG(SEV_ERROR, "UTP-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } + + return false; +} + +bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ) { SQLSRV_ASSERT( inString != NULL, "Input string must be specified" ); SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" ); @@ -126,7 +169,7 @@ bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inStrin char* newString = reinterpret_cast( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ )); int rc = WideCharToMultiByte( encoding, flags, inString, cchInLen, - newString, cchOutLen, NULL, NULL ); + newString, static_cast(cchOutLen), NULL, NULL ); if( rc == 0 ) { cchOutLen = 0; sqlsrv_free( newString ); @@ -139,7 +182,6 @@ bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inStrin return true; } - // thin wrapper around convert_string_from_default_encoding that handles // allocation of the destination string. An empty string passed in returns // failure since it's a failure case for convert_string_from_default_encoding. @@ -219,10 +261,10 @@ bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_ return false; } - SQLINTEGER sqlstate_len = 0; + SQLLEN sqlstate_len = 0; convert_string_from_utf16(enc, wsqlstate, sizeof(wsqlstate), (char**)&error->sqlstate, sqlstate_len); - SQLINTEGER message_len = 0; + SQLLEN message_len = 0; convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); break; } @@ -264,12 +306,12 @@ void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const co LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); } -DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ) +DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ) { va_list format_args; va_start( format_args, format ); - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_buffer, output_len, &format_args ); + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast(output_buffer), SQL_MAX_MESSAGE_LENGTH, &format_args ); va_end( format_args ); @@ -328,8 +370,8 @@ namespace { // returned in utf16_out_string. An empty string passed in will result as // a failure since MBTWC returns 0 for both an empty string and failure // to convert. -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, __in_bcount(mbcs_len) char const* mbcs_in_string, - unsigned int mbcs_len, __out_ecount(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, + unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, unsigned int utf16_len ) { unsigned int win_encoding = CP_ACP; diff --git a/sqlsrv/init.cpp b/sqlsrv/init.cpp index facf6d52..e9c0473b 100644 --- a/sqlsrv/init.cpp +++ b/sqlsrv/init.cpp @@ -2,7 +2,7 @@ // File: init.cpp // Contents: initialization routines for the extension // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -17,6 +17,10 @@ //--------------------------------------------------------------------------------------------------------------------------------- #include "php_sqlsrv.h" + +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE(); +#endif ZEND_GET_MODULE(g_sqlsrv) extern "C" { @@ -32,6 +36,10 @@ HashTable* g_ss_warnings_to_ignore_ht = NULL; // encodings we understand HashTable* g_ss_encodings_ht = NULL; +// Destructors called by Zend for each element in the hashtable +void sqlsrv_error_const_dtor( zval* element ); +void sqlsrv_encoding_dtor( zval* element ); + // henv context for creating connections sqlsrv_context* g_henv_cp; sqlsrv_context* g_henv_ncp; @@ -256,20 +264,21 @@ zend_module_entry g_sqlsrv_module_entry = PHP_MINIT_FUNCTION(sqlsrv) { SQLSRV_UNUSED( type ); - - core_sqlsrv_register_logger( ss_sqlsrv_log ); + core_sqlsrv_register_logger( ss_sqlsrv_log ); + // our global variables are initialized in the RINIT function -#if defined(ZTS) +#if defined(ZTS) if( ts_allocate_id( &sqlsrv_globals_id, sizeof( zend_sqlsrv_globals ), (ts_allocate_ctor) NULL, (ts_allocate_dtor) NULL ) == 0 ) return FAILURE; + ZEND_TSRMLS_CACHE_UPDATE(); #endif - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( long )); - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( long )); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long )); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); REGISTER_INI_ENTRIES(); @@ -401,12 +410,9 @@ PHP_MINIT_FUNCTION(sqlsrv) try { - // initialize list of warnings to ignore + // initialize list of warnings to ignore g_ss_warnings_to_ignore_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - int zr = ::zend_hash_init( g_ss_warnings_to_ignore_ht, 6, NULL, NULL, 1 ); - if( zr == FAILURE ) { - throw ss::SSException(); - } + zend_hash_init( g_ss_warnings_to_ignore_ht, 6, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); sqlsrv_error_const error_to_ignore; @@ -415,8 +421,7 @@ PHP_MINIT_FUNCTION(sqlsrv) error_to_ignore.native_message = NULL; error_to_ignore.native_code = 5701; error_to_ignore.format = false; - zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL ); - if( zr == FAILURE ) { + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { throw ss::SSException(); } @@ -425,8 +430,7 @@ PHP_MINIT_FUNCTION(sqlsrv) error_to_ignore.native_message = NULL; error_to_ignore.native_code = 5703; error_to_ignore.format = false; - zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL ); - if( zr == FAILURE ) { + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { throw ss::SSException(); } @@ -435,8 +439,7 @@ PHP_MINIT_FUNCTION(sqlsrv) error_to_ignore.native_message = NULL; error_to_ignore.native_code = -1; error_to_ignore.format = false; - zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL ); - if( zr == FAILURE ) { + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { throw ss::SSException(); } @@ -445,8 +448,7 @@ PHP_MINIT_FUNCTION(sqlsrv) error_to_ignore.native_message = NULL; error_to_ignore.native_code = -1; error_to_ignore.format = false; - zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL ); - if( zr == FAILURE ) { + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { throw ss::SSException(); } @@ -455,8 +457,7 @@ PHP_MINIT_FUNCTION(sqlsrv) error_to_ignore.native_message = NULL; error_to_ignore.native_code = -1; error_to_ignore.format = false; - zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL ); - if( zr == FAILURE ) { + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { throw ss::SSException(); } @@ -465,8 +466,7 @@ PHP_MINIT_FUNCTION(sqlsrv) error_to_ignore.native_message = NULL; error_to_ignore.native_code = 40608; error_to_ignore.format = false; - zr = zend_hash_next_index_insert( g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof( sqlsrv_error_const ), NULL ); - if( zr == FAILURE ) { + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { throw ss::SSException(); } @@ -475,8 +475,7 @@ PHP_MINIT_FUNCTION(sqlsrv) error_to_ignore.native_message = NULL; error_to_ignore.native_code = 9927; error_to_ignore.format = false; - zr = zend_hash_next_index_insert(g_ss_warnings_to_ignore_ht, &error_to_ignore, sizeof(sqlsrv_error_const), NULL); - if (zr == FAILURE) { + if (NULL == zend_hash_next_index_insert_mem(g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { throw ss::SSException(); } @@ -491,26 +490,20 @@ PHP_MINIT_FUNCTION(sqlsrv) // supported encodings g_ss_encodings_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - int zr = zend_hash_init( g_ss_encodings_ht, 3, NULL /*use standard hash function*/, NULL /*no resource destructor*/, 1 /*persistent*/ ); - if( zr == FAILURE ) { - throw ss::SSException(); - } + zend_hash_init( g_ss_encodings_ht, 3, NULL /*use standard hash function*/, sqlsrv_encoding_dtor /*resource destructor*/, 1 /*persistent*/ ); sqlsrv_encoding sql_enc_char( "char", SQLSRV_ENCODING_CHAR ); - zr = zend_hash_next_index_insert( g_ss_encodings_ht, &sql_enc_char, sizeof( sqlsrv_encoding ), NULL /*no pointer to the new value necessasry*/ ); - if( zr == FAILURE ) { + if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_char, sizeof( sqlsrv_encoding ))) { throw ss::SSException(); } sqlsrv_encoding sql_enc_bin( "binary", SQLSRV_ENCODING_BINARY, true ); - zr = zend_hash_next_index_insert( g_ss_encodings_ht, &sql_enc_bin, sizeof( sqlsrv_encoding ), NULL /*no pointer to the new value necessasry*/ ); - if( zr == FAILURE ) { + if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_bin, sizeof( sqlsrv_encoding ))) { throw ss::SSException(); } sqlsrv_encoding sql_enc_utf8( "utf-8", CP_UTF8 ); - zr = zend_hash_next_index_insert( g_ss_encodings_ht, &sql_enc_utf8, sizeof( sqlsrv_encoding ), NULL /*no pointer to the new value necessasry*/ ); - if( zr == FAILURE ) { + if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_utf8, sizeof( sqlsrv_encoding ))) { throw ss::SSException(); } } @@ -522,18 +515,11 @@ PHP_MINIT_FUNCTION(sqlsrv) // initialize list of sqlsrv errors g_ss_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - int zr = ::zend_hash_init( g_ss_errors_ht, 50, NULL, NULL, 1 ); - if( zr == FAILURE ) { - LOG( SEV_ERROR, "%1!s!: Failed to initialize the sqlsrv errors hashtable.", _FN_ ); - return FAILURE; - } + ::zend_hash_init( g_ss_errors_ht, 50, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); - for( int i = 0; SS_ERRORS[ i ].error_code != -1; ++i ) { - - zr = ::zend_hash_index_update( g_ss_errors_ht, SS_ERRORS[ i ].error_code, - &( SS_ERRORS[ i ].sqlsrv_error ), sizeof( SS_ERRORS[ i ].sqlsrv_error ), NULL ); - if( zr == FAILURE ) { - + 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; } @@ -545,7 +531,6 @@ PHP_MINIT_FUNCTION(sqlsrv) } try { - // retrieve the handles for the environments core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, ss_error_handler, "PHP_MINIT_FUNCTION for sqlsrv" TSRMLS_CC ); } @@ -563,6 +548,18 @@ PHP_MINIT_FUNCTION(sqlsrv) return SUCCESS; } +// called by Zend for each parameter in the g_ss_warnings_to_ignore_ht and g_ss_errors_ht hash table when it is destroyed +void sqlsrv_error_const_dtor( zval* elem ) { + sqlsrv_error_const* error_to_ignore = static_cast( Z_PTR_P(elem) ); + pefree(error_to_ignore, 1); +} + +// called by Zend for each parameter in the g_ss_encodings_ht hash table when it is destroyed +void sqlsrv_encoding_dtor( zval* elem ) { + sqlsrv_encoding* sql_enc = static_cast( Z_PTR_P(elem) ); + pefree(sql_enc, 1); +} + // Module shutdown function // Free the environment handles allocated in MINIT and unregister our stream wrapper. @@ -572,7 +569,7 @@ PHP_MINIT_FUNCTION(sqlsrv) PHP_MSHUTDOWN_FUNCTION(sqlsrv) { SQLSRV_UNUSED( type ); - + UNREGISTER_INI_ENTRIES(); // clean up the list of sqlsrv errors @@ -607,12 +604,14 @@ PHP_RINIT_FUNCTION(sqlsrv) { SQLSRV_UNUSED( module_number ); SQLSRV_UNUSED( type ); + +#if defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif SQLSRV_G( warnings_return_as_errors ) = true; - ALLOC_INIT_ZVAL( SQLSRV_G( errors )); - Z_SET_ISREF_P( SQLSRV_G( errors )); - ALLOC_INIT_ZVAL( SQLSRV_G( warnings )); - Z_SET_ISREF_P( SQLSRV_G( warnings )); + ZVAL_NULL( &SQLSRV_G( errors )); + ZVAL_NULL( &SQLSRV_G( warnings )); LOG_FUNCTION( "PHP_RINIT for php_sqlsrv" ); @@ -627,8 +626,6 @@ PHP_RINIT_FUNCTION(sqlsrv) LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit )); - // verify memory at the end of the request (in debug mode only) - full_mem_check(MEMCHECK_SILENT); return SUCCESS; } @@ -645,24 +642,19 @@ PHP_RSHUTDOWN_FUNCTION(sqlsrv) LOG_FUNCTION( "PHP_RSHUTDOWN for php_sqlsrv" ); reset_errors( TSRMLS_C ); + // TODO - destruction zval_ptr_dtor( &SQLSRV_G( errors )); zval_ptr_dtor( &SQLSRV_G( warnings )); - // verify memory at the end of the request (in debug mode only) - full_mem_check(MEMCHECK_SILENT); - return SUCCESS; } // Called for php_info(); Displays the INI settings registered and their current values PHP_MINFO_FUNCTION(sqlsrv) { -#if defined(ZTS) - SQLSRV_UNUSED( tsrm_ls ); -#endif - php_info_print_table_start(); php_info_print_table_header(2, "sqlsrv support", "enabled"); + php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); php_info_print_table_end(); DISPLAY_INI_ENTRIES(); } diff --git a/sqlsrv/msodbcsql.h b/sqlsrv/msodbcsql.h index a16bf59a..8bcf83d0 100644 --- a/sqlsrv/msodbcsql.h +++ b/sqlsrv/msodbcsql.h @@ -888,7 +888,7 @@ RETCODE SQL_API bcp_moretext (HDBC, DBINT, LPCBYTE); RETCODE SQL_API bcp_readfmtA (HDBC, LPCSTR); RETCODE SQL_API bcp_readfmtW (HDBC, LPCWSTR); RETCODE SQL_API bcp_sendrow (HDBC); -RETCODE SQL_API bcp_setbulkmode (HDBC, INT, __in_bcount(cbField) void*, INT cbField, __in_bcount(cbRow) void *, INT cbRow); +RETCODE SQL_API bcp_setbulkmode (HDBC, INT, _In_reads_bytes_(cbField) void*, INT cbField, _In_reads_bytes_(cbRow) void *, INT cbRow); RETCODE SQL_API bcp_setcolfmt (HDBC, INT, INT, void *, INT); RETCODE SQL_API bcp_writefmtA (HDBC, LPCSTR); RETCODE SQL_API bcp_writefmtW (HDBC, LPCWSTR); @@ -958,7 +958,7 @@ HANDLE __stdcall OpenSqlFilestream ( LPCWSTR FilestreamPath, SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess, ULONG OpenOptions, - __in_bcount(FilestreamTransactionContextLength) + _In_reads_bytes_(FilestreamTransactionContextLength) LPBYTE FilestreamTransactionContext, SSIZE_T FilestreamTransactionContextLength, PLARGE_INTEGER AllocationSize); @@ -995,11 +995,11 @@ extern "C" { // type definition for LocalDBCreateInstance function typedef HRESULT __cdecl FnLocalDBCreateInstance ( // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - __in_z PCWSTR wszVersion, + _In_z_ PCWSTR wszVersion, // I the instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags + _In_ DWORD dwFlags ); // type definition for pointer to LocalDBCreateInstance function @@ -1008,14 +1008,14 @@ typedef FnLocalDBCreateInstance* PFnLocalDBCreateInstance; // type definition for LocalDBStartInstance function typedef HRESULT __cdecl FnLocalDBStartInstance ( // I the LocalDB instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags, + _In_ DWORD dwFlags, // O the buffer to store the connection string to the LocalDB instance - __out_ecount_z_opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, + _Out_writes_opt_z_(*lpcchSqlConnection) LPWSTR wszSqlConnection, // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is // too small, has the buffer size required, in characters, including trailing null. - __inout_opt LPDWORD lpcchSqlConnection + _Inout_opt_ LPDWORD lpcchSqlConnection ); // type definition for pointer to LocalDBStartInstance function @@ -1027,19 +1027,19 @@ typedef FnLocalDBStartInstance* PFnLocalDBStartInstance; // type definition for LocalDBFormatMessage function typedef HRESULT __cdecl FnLocalDBFormatMessage( // I the LocalDB error code - __in HRESULT hrLocalDB, + _In_ HRESULT hrLocalDB, // I Available flags: // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, // the error message will be truncated to fit into the buffer - __in DWORD dwFlags, + _In_ DWORD dwFlags, // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - __in DWORD dwLanguageId, + _In_ DWORD dwLanguageId, // O the buffer to store the LocalDB error message - __out_ecount_z(*lpcchMessage) LPWSTR wszMessage, + _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is // too small, has the buffer size required, in characters, including trailing null. If the function succeeds // contains the number of characters in the message, excluding the trailing null - __inout LPDWORD lpcchMessage + _Inout_ LPDWORD lpcchMessage ); // type definition for function pointer to LocalDBFormatMessage function @@ -1112,14 +1112,14 @@ FnLocalDBStartInstance LocalDBStartInstance; // type definition for LocalDBStopInstance function typedef HRESULT __cdecl FnLocalDBStopInstance ( // I the LocalDB instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I Available flags: // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - __in DWORD dwFlags, + _In_ DWORD dwFlags, // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately // without waiting for LocalDB instance to stop - __in ULONG ulTimeout + _In_ ULONG ulTimeout ); // type definition for pointer to LocalDBStopInstance function @@ -1149,9 +1149,9 @@ FnLocalDBStopInstance LocalDBStopInstance; // type definition for LocalDBDeleteInstance function typedef HRESULT __cdecl FnLocalDBDeleteInstance ( // I the LocalDB instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags + _In_ DWORD dwFlags ); // type definition for pointer to LocalDBDeleteInstance function @@ -1202,10 +1202,10 @@ typedef TLocalDBInstanceName* PTLocalDBInstanceName; // type definition for LocalDBGetInstances function typedef HRESULT __cdecl FnLocalDBGetInstances( // O buffer for a LocalDB instance names - __out PTLocalDBInstanceName pInstanceNames, + _Out_ PTLocalDBInstanceName pInstanceNames, // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, // has the number of existing LocalDB instances - __inout LPDWORD lpdwNumberOfInstances + _Inout_ LPDWORD lpdwNumberOfInstances ); // type definition for pointer to LocalDBGetInstances function @@ -1267,11 +1267,11 @@ typedef LocalDBInstanceInfo* PLocalDBInstanceInfo; // type definition for LocalDBGetInstanceInfo function typedef HRESULT __cdecl FnLocalDBGetInstanceInfo( // I the LocalDB instance name - __in_z PCWSTR wszInstanceName, + _In_z_ PCWSTR wszInstanceName, // O instance information - __out PLocalDBInstanceInfo pInfo, + _Out_ PLocalDBInstanceInfo pInfo, // I Size of LocalDBInstanceInfo structure in bytes - __in DWORD cbInfo); + _In_ DWORD cbInfo); // type definition for pointer to LocalDBGetInstances function typedef FnLocalDBGetInstanceInfo* PFnLocalDBGetInstanceInfo; @@ -1298,10 +1298,10 @@ typedef TLocalDBVersion* PTLocalDBVersion; // type definition for LocalDBGetVersions function typedef HRESULT __cdecl FnLocalDBGetVersions( // O buffer for installed LocalDB versions - __out PTLocalDBVersion pVersions, + _Out_ PTLocalDBVersion pVersions, // I/O on input has the number slots for versions in the pVersions buffer. On output, // has the number of existing LocalDB versions - __inout LPDWORD lpdwNumberOfVersions + _Inout_ LPDWORD lpdwNumberOfVersions ); // type definition for pointer to LocalDBGetVersions function @@ -1352,11 +1352,11 @@ typedef LocalDBVersionInfo* PLocalDBVersionInfo; // type definition for LocalDBGetVersionInfo function typedef HRESULT __cdecl FnLocalDBGetVersionInfo( // I LocalDB version string - __in_z PCWSTR wszVersion, + _In_z_ PCWSTR wszVersion, // O version information - __out PLocalDBVersionInfo pVersionInfo, + _Out_ PLocalDBVersionInfo pVersionInfo, // I Size of LocalDBVersionInfo structure in bytes - __in DWORD cbVersionInfo + _In_ DWORD cbVersionInfo ); // type definition for pointer to LocalDBGetVersionInfo function @@ -1402,13 +1402,13 @@ FnLocalDBStopTracing LocalDBStopTracing; // type definition for LocalDBShareInstance function typedef HRESULT __cdecl FnLocalDBShareInstance( // I the SID of the LocalDB instance owner - __in_opt PSID pOwnerSID, + _In_opt_ PSID pOwnerSID, // I the private name of LocalDB instance which should be shared - __in_z PCWSTR wszPrivateLocalDBInstanceName, + _In_z_ PCWSTR wszPrivateLocalDBInstanceName, // I the public shared name - __in_z PCWSTR wszSharedName, + _In_z_ PCWSTR wszSharedName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags); + _In_ DWORD dwFlags); // type definition for pointer to LocalDBShareInstance function typedef FnLocalDBShareInstance* PFnLocalDBShareInstance; @@ -1426,9 +1426,9 @@ FnLocalDBShareInstance LocalDBShareInstance; // type definition for LocalDBUnshareInstance function typedef HRESULT __cdecl FnLocalDBUnshareInstance( // I the LocalDB instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags); + _In_ DWORD dwFlags); // type definition for pointer to LocalDBUnshareInstance function typedef FnLocalDBUnshareInstance* PFnLocalDBUnshareInstance; @@ -1653,11 +1653,11 @@ Cleanup: HRESULT __cdecl LocalDBCreateInstance ( // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - __in_z PCWSTR wszVersion, + _In_z_ PCWSTR wszVersion, // I the instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags + _In_ DWORD dwFlags ) { LOCALDB_PROXY(LocalDBCreateInstance)(wszVersion, pInstanceName, dwFlags); @@ -1666,14 +1666,14 @@ LocalDBCreateInstance ( HRESULT __cdecl LocalDBStartInstance( // I the instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags, + _In_ DWORD dwFlags, // O the buffer to store the connection string to the LocalDB instance - __out_ecount_z_opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, + _Out_writes_z__opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is // too small, has the buffer size required, in characters, including trailing null. - __inout_opt LPDWORD lpcchSqlConnection + _Inout_opt_ LPDWORD lpcchSqlConnection ) { LOCALDB_PROXY(LocalDBStartInstance)(pInstanceName, dwFlags, wszSqlConnection, lpcchSqlConnection); @@ -1682,14 +1682,14 @@ LocalDBStartInstance( HRESULT __cdecl LocalDBStopInstance ( // I the instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I Available flags: // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - __in DWORD dwFlags, + _In_ DWORD dwFlags, // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately // without waiting for LocalDB instance to stop - __in ULONG ulTimeout + _In_ ULONG ulTimeout ) { LOCALDB_PROXY(LocalDBStopInstance)(pInstanceName, dwFlags, ulTimeout); @@ -1698,9 +1698,9 @@ LocalDBStopInstance ( HRESULT __cdecl LocalDBDeleteInstance ( // I the instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags + _In_ DWORD dwFlags ) { LOCALDB_PROXY(LocalDBDeleteInstance)(pInstanceName, dwFlags); @@ -1709,19 +1709,19 @@ LocalDBDeleteInstance ( HRESULT __cdecl LocalDBFormatMessage( // I the LocalDB error code - __in HRESULT hrLocalDB, + _In_ HRESULT hrLocalDB, // I Available flags: // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, // the error message will be truncated to fit into the buffer - __in DWORD dwFlags, + _In_ DWORD dwFlags, // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - __in DWORD dwLanguageId, + _In_ DWORD dwLanguageId, // O the buffer to store the LocalDB error message - __out_ecount_z(*lpcchMessage) LPWSTR wszMessage, + _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is // too small, has the buffer size required, in characters, including trailing null. If the function succeeds // contains the number of characters in the message, excluding the trailing null - __inout LPDWORD lpcchMessage + _Inout_ LPDWORD lpcchMessage ) { LOCALDB_PROXY(LocalDBFormatMessage)(hrLocalDB, dwFlags, dwLanguageId, wszMessage, lpcchMessage); @@ -1730,10 +1730,10 @@ LocalDBFormatMessage( HRESULT __cdecl LocalDBGetInstances( // O buffer with instance names - __out PTLocalDBInstanceName pInstanceNames, + _Out_ PTLocalDBInstanceName pInstanceNames, // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, // has the number of existing LocalDB instances - __inout LPDWORD lpdwNumberOfInstances + _Inout_ LPDWORD lpdwNumberOfInstances ) { LOCALDB_PROXY(LocalDBGetInstances)(pInstanceNames, lpdwNumberOfInstances); @@ -1742,11 +1742,11 @@ LocalDBGetInstances( HRESULT __cdecl LocalDBGetInstanceInfo( // I the instance name - __in_z PCWSTR wszInstanceName, + _In_z_ PCWSTR wszInstanceName, // O instance information - __out PLocalDBInstanceInfo pInfo, + _Out_ PLocalDBInstanceInfo pInfo, // I Size of LocalDBInstanceInfo structure in bytes - __in DWORD cbInfo + _In_ DWORD cbInfo ) { LOCALDB_PROXY(LocalDBGetInstanceInfo)(wszInstanceName, pInfo, cbInfo); @@ -1767,13 +1767,13 @@ LocalDBStopTracing() HRESULT __cdecl LocalDBShareInstance( // I the SID of the LocalDB instance owner - __in_opt PSID pOwnerSID, + _In_opt_ PSID pOwnerSID, // I the private name of LocalDB instance which should be shared - __in_z PCWSTR wszLocalDBInstancePrivateName, + _In_z_ PCWSTR wszLocalDBInstancePrivateName, // I the public shared name - __in_z PCWSTR wszSharedName, + _In_z_ PCWSTR wszSharedName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags) + _In_ DWORD dwFlags) { LOCALDB_PROXY(LocalDBShareInstance)(pOwnerSID, wszLocalDBInstancePrivateName, wszSharedName, dwFlags); } @@ -1781,10 +1781,10 @@ LocalDBShareInstance( HRESULT __cdecl LocalDBGetVersions( // O buffer for installed LocalDB versions - __out PTLocalDBVersion pVersions, + _Out_ PTLocalDBVersion pVersions, // I/O on input has the number slots for versions in the pVersions buffer. On output, // has the number of existing LocalDB versions - __inout LPDWORD lpdwNumberOfVersions + _Inout_ LPDWORD lpdwNumberOfVersions ) { LOCALDB_PROXY(LocalDBGetVersions)(pVersions, lpdwNumberOfVersions); @@ -1793,9 +1793,9 @@ LocalDBGetVersions( HRESULT __cdecl LocalDBUnshareInstance( // I the LocalDB instance name - __in_z PCWSTR pInstanceName, + _In_z_ PCWSTR pInstanceName, // I reserved for the future use. Currently should be set to 0. - __in DWORD dwFlags) + _In_ DWORD dwFlags) { LOCALDB_PROXY(LocalDBUnshareInstance)(pInstanceName, dwFlags); } @@ -1803,11 +1803,11 @@ LocalDBUnshareInstance( HRESULT __cdecl LocalDBGetVersionInfo( // I LocalDB version string - __in_z PCWSTR wszVersion, + _In_z_ PCWSTR wszVersion, // O version information - __out PLocalDBVersionInfo pVersionInfo, + _Out_ PLocalDBVersionInfo pVersionInfo, // I Size of LocalDBVersionInfo structure in bytes - __in DWORD cbVersionInfo) + _In_ DWORD cbVersionInfo) { LOCALDB_PROXY(LocalDBGetVersionInfo)(wszVersion, pVersionInfo, cbVersionInfo); } diff --git a/sqlsrv/php_sqlsrv.h b/sqlsrv/php_sqlsrv.h index c7aee2de..2f5be3cc 100644 --- a/sqlsrv/php_sqlsrv.h +++ b/sqlsrv/php_sqlsrv.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -73,7 +73,7 @@ typedef int socklen_t; #pragma warning(pop) -#if PHP_MAJOR_VERSION > 5 || PHP_MAJOR_VERSION < 5 || ( PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3 ) +#if PHP_MAJOR_VERSION < 7 #error Trying to compile "Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)" with an unsupported version of PHP #endif @@ -148,7 +148,7 @@ struct ss_sqlsrv_conn : sqlsrv_conn }; // resource destructor -void __cdecl sqlsrv_conn_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC ); +void __cdecl sqlsrv_conn_dtor( zend_resource *rsrc TSRMLS_DC ); //********************************************************************************************************************************* // Statement @@ -175,10 +175,10 @@ struct ss_sqlsrv_stmt : public sqlsrv_stmt { void new_result_set( TSRMLS_D ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ); + sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ); bool prepared; // whether the statement has been prepared yet (used for error messages) - int conn_index; // index into the connection hash that contains this statement structure + zend_ulong conn_index; // index into the connection hash that contains this statement structure zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys int fetch_fields_count; @@ -223,7 +223,7 @@ PHP_FUNCTION(sqlsrv_rows_affected); PHP_FUNCTION(sqlsrv_send_stream_data); // resource destructor -void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC ); +void __cdecl sqlsrv_stmt_dtor( zend_resource *rsrc TSRMLS_DC ); // "internal" statement functions shared by functions in conn.cpp and stmt.cpp void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC ); @@ -265,15 +265,15 @@ extern "C" { ZEND_BEGIN_MODULE_GLOBALS(sqlsrv) // global objects for errors and warnings. These are returned by sqlsrv_errors. -zval* errors; -zval* warnings; +zval errors; +zval warnings; // flags for error handling and logging (set via sqlsrv_configure or php.ini) -long log_severity; -long log_subsystems; -long current_subsystem; +zend_long log_severity; +zend_long log_subsystems; +zend_long current_subsystem; zend_bool warnings_return_as_errors; -long buffered_query_limit; +zend_long buffered_query_limit; ZEND_END_MODULE_GLOBALS(sqlsrv) @@ -281,11 +281,11 @@ ZEND_EXTERN_MODULE_GLOBALS(sqlsrv); } -// macros used to access the global variables. Use these to make global variable access agnostic to threads -#ifdef ZTS -#define SQLSRV_G(v) TSRMG(sqlsrv_globals_id, zend_sqlsrv_globals *, v) -#else -#define SQLSRV_G(v) sqlsrv_globals.v +// macro used to access the global variables. Use it to make global variable access agnostic to threads +#define SQLSRV_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(sqlsrv, v) + +#if defined(ZTS) +ZEND_TSRMLS_CACHE_EXTERN(); #endif // INI settings and constants @@ -353,6 +353,7 @@ enum SS_ERROR_CODES { SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF }; extern ss_error SS_ERRORS[]; @@ -367,42 +368,42 @@ PHP_FUNCTION(sqlsrv_errors); // bytes. The return is the number of UTF-16 characters in the string // returned in utf16_out_string. unsigned int convert_string_from_default_encoding( unsigned int php_encoding, char const* mbcs_in_string, - unsigned int mbcs_len, __out wchar_t* utf16_out_string, + unsigned int mbcs_len, _Out_ wchar_t* utf16_out_string, unsigned int utf16_len ); // create a wide char string from the passed in mbcs string. NULL is returned if the string // could not be created. No error is posted by this function. utf16_len is the number of // wchar_t characters, not the number of bytes. wchar_t* utf16_string_from_mbcs_string( unsigned int php_encoding, const char* mbcs_string, - unsigned int mbcs_len, __out unsigned int* utf16_len ); + unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); // *** internal error macros and functions *** bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, sqlsrv_error const* ssphp TSRMLS_DC, ... ); void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function, sqlsrv_error const* ssphp TSRMLS_DC, ... ); -void __cdecl sqlsrv_error_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC ); +void __cdecl sqlsrv_error_dtor( zend_resource *rsrc TSRMLS_DC ); // release current error lists and set to NULL inline void reset_errors( TSRMLS_D ) { - if( Z_TYPE_P( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE_P( SQLSRV_G( errors )) != IS_NULL ) { + if( Z_TYPE( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE( SQLSRV_G( errors )) != IS_NULL ) { DIE( "sqlsrv_errors contains an invalid type" ); } - if( Z_TYPE_P( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE_P( SQLSRV_G( warnings )) != IS_NULL ) { + if( Z_TYPE( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE( SQLSRV_G( warnings )) != IS_NULL ) { DIE( "sqlsrv_warnings contains an invalid type" ); } - if( Z_TYPE_P( SQLSRV_G( errors )) == IS_ARRAY ) { - zend_hash_destroy( Z_ARRVAL_P( SQLSRV_G( errors ))); - FREE_HASHTABLE( Z_ARRVAL_P( SQLSRV_G( errors ))); + if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( SQLSRV_G( errors ))); + FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( errors ))); } - if( Z_TYPE_P( SQLSRV_G( warnings )) == IS_ARRAY ) { - zend_hash_destroy( Z_ARRVAL_P( SQLSRV_G( warnings ))); - FREE_HASHTABLE( Z_ARRVAL_P( SQLSRV_G( warnings ))); + if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( SQLSRV_G( warnings ))); + FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( warnings ))); } - ZVAL_NULL( SQLSRV_G( errors )); - ZVAL_NULL( SQLSRV_G( warnings )); + ZVAL_NULL( &SQLSRV_G( errors )); + ZVAL_NULL( &SQLSRV_G( warnings )); } #define THROW_SS_ERROR( ctx, error_code, ... ) \ @@ -455,8 +456,7 @@ public: #define LOG_FUNCTION( function_name ) \ const char* _FN_ = function_name; \ SQLSRV_G( current_subsystem ) = current_log_subsystem; \ - LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); \ - CheckMemory _check_memory_; + LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); #define SET_FUNCTION_NAME( context ) \ { \ @@ -475,21 +475,6 @@ enum logging_subsystems { LOG_ALL = -1, }; -struct CheckMemory { - - CheckMemory( void ) - { - // test the integrity of the Zend heap. - full_mem_check(MEMCHECK_SILENT); - } - - ~CheckMemory( void ) - { - // test the integrity of the Zend heap. - full_mem_check(MEMCHECK_SILENT); - } -}; - //********************************************************************************************************************************* // Utility Functions @@ -498,11 +483,8 @@ struct CheckMemory { // generic function used to validate parameters to a PHP function. // Register an invalid parameter error and returns NULL when parameters don't match the spec given. template -inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, char const* param_spec, const char* calling_func, int param_count, ... ) +inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, char const* param_spec, const char* calling_func, size_t param_count, ... ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); SQLSRV_UNUSED( return_value ); zval* rsrc; @@ -527,7 +509,7 @@ inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, char const* param_spec, va_list vaList; va_start(vaList, param_count); //set the pointer to first argument - for(int i=0; i< param_count; ++i) { + for(size_t i = 0; i < param_count; ++i) { arr[i] = va_arg(vaList, void*); } @@ -588,7 +570,7 @@ inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, char const* param_spec, } // get the resource registered - h = static_cast( zend_fetch_resource( &rsrc TSRMLS_CC, -1, H::resource_name, NULL, 1, H::descriptor )); + h = static_cast( zend_fetch_resource(Z_RES_P(rsrc) TSRMLS_CC, H::resource_name, H::descriptor )); CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { @@ -622,13 +604,14 @@ namespace ss { } }; - inline void zend_register_resource( __out zval* rsrc_result, void* rsrc_pointer, int rsrc_type, char* rsrc_name TSRMLS_DC ) + inline void zend_register_resource(_Out_ zval& rsrc_result, void* rsrc_pointer, int rsrc_type, char* rsrc_name TSRMLS_DC) { - int zr = ZEND_REGISTER_RESOURCE( rsrc_result, rsrc_pointer, rsrc_type ); + int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE, - rsrc_name ) { + rsrc_name ) { throw ss::SSException(); } + Z_TYPE_INFO(rsrc_result) = IS_RESOURCE_EX; } } // namespace ss diff --git a/sqlsrv/stmt.cpp b/sqlsrv/stmt.cpp index 5c2433e6..40eb589f 100644 --- a/sqlsrv/stmt.cpp +++ b/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -53,15 +53,18 @@ const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1; // map a Zend PHP type constant to our constant type enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = { + SQLSRV_PHPTYPE_INVALID, SQLSRV_PHPTYPE_NULL, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_INVALID, SQLSRV_PHPTYPE_INT, SQLSRV_PHPTYPE_FLOAT, - SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_STRING, SQLSRV_PHPTYPE_INVALID, SQLSRV_PHPTYPE_DATETIME, - SQLSRV_PHPTYPE_STRING, SQLSRV_PHPTYPE_STREAM, SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_INVALID, SQLSRV_PHPTYPE_INVALID }; @@ -85,22 +88,23 @@ const char SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF[] = "Variable parameter %d not pa /* internal functions */ -zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len ); -void fetch_fields_common( __inout ss_sqlsrv_stmt* stmt, int fetch_type, __out zval* return_value, bool allow_empty_field_names - TSRMLS_DC ); -bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, __out SQLUINTEGER* column_size, - __out SQLSMALLINT* decimal_digits ); +void convert_to_zval( sqlsrv_stmt* stmt, SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len, zval& out_zval ); + +void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, zend_long fetch_type, _Out_ zval& fields, bool allow_empty_field_names + TSRMLS_DC ); +bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, _Out_ SQLULEN* column_size, + _Out_ SQLSMALLINT* decimal_digits ); sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ); void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC ); bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype type ); -void parse_param_array( ss_sqlsrv_stmt* stmt, __inout zval* param_array, unsigned long index, __out int& direction, - __out SQLSRV_PHPTYPE& php_out_type, __out SQLSRV_ENCODING& encoding, __out SQLSMALLINT& sql_type, - __out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC ); +void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, + _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ); void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); -bool verify_and_set_encoding( const char* encoding_string, __out sqlsrv_phptype& phptype_encoding TSRMLS_DC ); +bool verify_and_set_encoding( const char* encoding_string, _Out_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ); } @@ -135,10 +139,9 @@ ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) } sqlsrv_free( fetch_field_names ); } - if( params_z ) { - zval_ptr_dtor( ¶ms_z ); - params_z = NULL; + zval_ptr_dtor( params_z ); + sqlsrv_free(params_z); } } @@ -161,7 +164,7 @@ void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) } // Returns a php type for a given sql type. Also sets the encoding wherever applicable. -sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) +sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ) { sqlsrv_phptype ss_phptype; ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; @@ -292,7 +295,7 @@ PHP_FUNCTION( sqlsrv_execute ) bind_params( stmt TSRMLS_CC ); core_sqlsrv_execute( stmt TSRMLS_CC ); - + RETURN_TRUE; } catch( core::CoreException& ) { @@ -326,8 +329,9 @@ PHP_FUNCTION( sqlsrv_fetch ) LOG_FUNCTION( "sqlsrv_fetch" ); ss_sqlsrv_stmt* stmt = NULL; + // NOTE: zend_parse_parameter expect zend_long when the type spec is 'l',and core_sqlsrv_fetch expect short int int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied - int fetch_offset = 0; // default value for parameter if one isn't supplied + zend_long fetch_offset = 0; // default value for parameter if one isn't supplied // take only the statement resource PROCESS_PARAMS( stmt, "r|ll", _FN_, 2, &fetch_style, &fetch_offset ); @@ -378,9 +382,9 @@ PHP_FUNCTION( sqlsrv_fetch_array ) LOG_FUNCTION( "sqlsrv_fetch_array" ); ss_sqlsrv_stmt* stmt = NULL; - int fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied - int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied - int fetch_offset = 0; // default value for parameter if one isn't supplied + zend_long fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied + int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied + zend_long fetch_offset = 0; // default value for parameter if one isn't supplied // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset @@ -402,8 +406,10 @@ PHP_FUNCTION( sqlsrv_fetch_array ) if( !result ) { RETURN_NULL(); } - - fetch_fields_common( stmt, fetch_type, return_value, true /*allow_empty_field_names*/ TSRMLS_CC ); + zval fields; + ZVAL_UNDEF( &fields ); + fetch_fields_common( stmt, fetch_type, fields, true /*allow_empty_field_names*/ TSRMLS_CC ); + RETURN_ARR( Z_ARRVAL( fields )); } catch( core::CoreException& ) { @@ -450,9 +456,9 @@ PHP_FUNCTION( sqlsrv_field_metadata ) // get the number of fields in the resultset num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - zval_auto_ptr result_meta_data; - ALLOC_INIT_ZVAL( result_meta_data ); - core::sqlsrv_array_init( *stmt, result_meta_data TSRMLS_CC ); + zval result_meta_data; + ZVAL_UNDEF( &result_meta_data ); + core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC ); for( SQLSMALLINT f = 0; f < num_cols; ++f ) { @@ -460,16 +466,16 @@ PHP_FUNCTION( sqlsrv_field_metadata ) core_meta_data = core_sqlsrv_field_metadata( stmt, f TSRMLS_CC ); // initialize the array - zval_auto_ptr field_array; - ALLOC_INIT_ZVAL( field_array ); - core::sqlsrv_array_init( *stmt, field_array TSRMLS_CC ); + zval field_array; + ZVAL_UNDEF( &field_array ); + core::sqlsrv_array_init( *stmt, &field_array TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *stmt, field_array, FieldMetaData::NAME, + core::sqlsrv_add_assoc_string( *stmt, &field_array, FieldMetaData::NAME, reinterpret_cast( core_meta_data->field_name.get() ), 0 TSRMLS_CC ); core_meta_data->field_name.transferred(); - core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC ); switch( core_meta_data->field_type ) { case SQL_DECIMAL: @@ -478,9 +484,9 @@ PHP_FUNCTION( sqlsrv_field_metadata ) case SQL_TYPE_DATE: case SQL_SS_TIME2: case SQL_SS_TIMESTAMPOFFSET: - core::sqlsrv_add_assoc_null( *stmt, field_array, FieldMetaData::SIZE TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::SCALE, core_meta_data->field_scale TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SCALE, core_meta_data->field_scale TSRMLS_CC ); break; case SQL_BIT: case SQL_TINYINT: @@ -490,33 +496,30 @@ PHP_FUNCTION( sqlsrv_field_metadata ) case SQL_REAL: case SQL_FLOAT: case SQL_DOUBLE: - core::sqlsrv_add_assoc_null( *stmt, field_array, FieldMetaData::SIZE TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, field_array, FieldMetaData::SCALE TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); break; default: - core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::SIZE, core_meta_data->field_size TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, field_array, FieldMetaData::PREC TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, field_array, FieldMetaData::SCALE TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SIZE, core_meta_data->field_size TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::PREC TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); break; } // add the nullability to the array - core::sqlsrv_add_assoc_long( *stmt, field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable TSRMLS_CC ); // add this field's meta data to the result set meta data - core::sqlsrv_add_next_index_zval( *stmt, result_meta_data, field_array TSRMLS_CC ); - field_array.transferred(); + core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC ); // always good to call destructor for allocations done through placement new operator. core_meta_data->~field_meta_data(); } // return our built collection and transfer ownership - zval_ptr_dtor( &return_value ); - *return_value_ptr = result_meta_data; - result_meta_data.transferred(); + RETURN_ZVAL(&result_meta_data, 1, 1); } catch( core::CoreException& ) { @@ -596,7 +599,7 @@ PHP_FUNCTION( sqlsrv_rows_affected ) LOG_FUNCTION( "sqlsrv_rows_affected" ); SQLRETURN r = SQL_SUCCESS; ss_sqlsrv_stmt* stmt = NULL; - SQLINTEGER rows = -1; + SQLLEN rows = -1; PROCESS_PARAMS( stmt, "r", _FN_, 0 ); @@ -644,7 +647,7 @@ PHP_FUNCTION( sqlsrv_num_rows ) LOG_FUNCTION( "sqlsrv_num_rows" ); ss_sqlsrv_stmt* stmt = NULL; - SQLINTEGER rows = -1; + SQLLEN rows = -1; PROCESS_PARAMS( stmt, "r", _FN_, 0 ); @@ -759,15 +762,17 @@ PHP_FUNCTION( sqlsrv_fetch_object ) LOG_FUNCTION( "sqlsrv_fetch_object" ); ss_sqlsrv_stmt* stmt = NULL; - zval* class_name_z = NULL; - zval* ctor_params_z = NULL; + zval* class_name_z = NULL; + zval* ctor_params_z = NULL; int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied - int fetch_offset = 0; // default value for parameter if one isn't supplied + zend_long fetch_offset = 0; // default value for parameter if one isn't supplied // stdClass is the name of the system's default base class in PHP char* class_name = const_cast( STDCLASS_NAME ); - unsigned int class_name_len = STDCLASS_NAME_LEN; + std::size_t class_name_len = STDCLASS_NAME_LEN; HashTable* properties_ht = NULL; + zval retval_z; + ZVAL_UNDEF( &retval_z ); // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset @@ -787,8 +792,8 @@ PHP_FUNCTION( sqlsrv_fetch_object ) CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { throw ss::SSException(); } - class_name = Z_STRVAL_P( class_name_z ); - class_name_len = Z_STRLEN_P( class_name_z ); + class_name = Z_STRVAL( *class_name_z ); + class_name_len = Z_STRLEN( *class_name_z ); zend_str_tolower( class_name, class_name_len ); } @@ -802,19 +807,21 @@ PHP_FUNCTION( sqlsrv_fetch_object ) RETURN_NULL(); } - fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, return_value, false /*allow_empty_field_names*/ TSRMLS_CC ); - properties_ht = Z_ARRVAL_P( return_value ); + fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ TSRMLS_CC ); + properties_ht = Z_ARRVAL( retval_z ); // find the zend_class_entry of the class the user requested (stdClass by default) for use below - zend_class_entry** class_entry; - int zr = zend_lookup_class( class_name, class_name_len, &class_entry TSRMLS_CC ); + zend_class_entry* class_entry = NULL; + zend_string* class_name_str_z = zend_string_init( class_name, class_name_len, 0 ); + int zr = ( NULL != ( class_entry = zend_lookup_class( class_name_str_z TSRMLS_CC ))) ? SUCCESS : FAILURE; + zend_string_release( class_name_str_z ); CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_BAD_CLASS, class_name ) { throw ss::SSException(); } // create an instance of the object with its default properties // we pass NULL for the properties so that the object will be populated by its default properties - zr = object_and_properties_init( return_value, *class_entry, NULL /*properties*/ ); + zr = object_and_properties_init( &retval_z, class_entry, NULL /*properties*/ ); CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { throw ss::SSException(); } @@ -824,7 +831,9 @@ PHP_FUNCTION( sqlsrv_fetch_object ) // causes duplicate properties when the visibilities are different and also references the // default parameters directly in the object, meaning the default property value is changed when // the object's property is changed. - zend_merge_properties( return_value, properties_ht, 1 /*destroy_ht*/ TSRMLS_CC ); + zend_merge_properties( &retval_z, properties_ht TSRMLS_CC ); + zend_hash_destroy( properties_ht ); + FREE_HASHTABLE( properties_ht ); // find and call the object's constructor @@ -839,30 +848,30 @@ PHP_FUNCTION( sqlsrv_fetch_object ) // calling zend_fcall_info_init with a test callable. // if there is a constructor (e.g., stdClass doesn't have one) - if( (*class_entry)->constructor ) { + if( class_entry->constructor ) { // take the parameters given as our last argument and put them into a sequential array - sqlsrv_malloc_auto_ptr params_m; - zval_auto_ptr ctor_retval_z; - ALLOC_INIT_ZVAL( ctor_retval_z ); + sqlsrv_malloc_auto_ptr params_m; + zval ctor_retval_z; + ZVAL_UNDEF( &ctor_retval_z ); int num_params = 0; - if( ctor_params_z != NULL ) { - HashTable* ctor_params_ht = Z_ARRVAL_P( ctor_params_z ); + if ( ctor_params_z ) { + HashTable* ctor_params_ht = Z_ARRVAL( *ctor_params_z ); num_params = zend_hash_num_elements( ctor_params_ht ); - params_m = reinterpret_cast( sqlsrv_malloc( num_params * sizeof( zval**) )); + params_m = reinterpret_cast( sqlsrv_malloc( num_params * sizeof( zval ) )); - int i; - for( i = 0, zend_hash_internal_pointer_reset( ctor_params_ht ); - zend_hash_has_more_elements( ctor_params_ht ) == SUCCESS; - zend_hash_move_forward( ctor_params_ht ), ++i ) { - - zr = zend_hash_get_current_data_ex( ctor_params_ht, reinterpret_cast(&(params_m[ i ])), NULL ); - CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { - throw ss::SSException(); - } - } //for - } //if( ctor_params_z != NULL ) + int i = 0; + zval* value_z = NULL; + ZEND_HASH_FOREACH_VAL( ctor_params_ht, value_z ) { + ZVAL_COPY_VALUE( ¶ms_m[i], value_z ); + zr = ( NULL != ¶ms_m[i] ) ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { + throw ss::SSException(); + } + i++; + } ZEND_HASH_FOREACH_END(); + } //if( !Z_ISUNDEF( ctor_params_z )) // call the constructor function itself. zend_fcall_info fci; @@ -870,32 +879,28 @@ PHP_FUNCTION( sqlsrv_fetch_object ) memset( &fci, 0, sizeof( fci )); fci.size = sizeof( fci ); - fci.function_table = &(*class_entry)->function_table; - fci.function_name = NULL; - fci.retval_ptr_ptr = &ctor_retval_z; + fci.function_table = &( class_entry )->function_table; + ZVAL_UNDEF( &( fci.function_name ) ); + fci.retval = &ctor_retval_z; fci.param_count = num_params; fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred. -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 2 - fci.object_pp = &return_value; -#else - fci.object_ptr = return_value; -#endif + fci.object = reinterpret_cast( &retval_z ); + memset( &fcic, 0, sizeof( fcic )); fcic.initialized = 1; - fcic.function_handler = (*class_entry)->constructor; - fcic.calling_scope = *class_entry; -#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 2 - fcic.object_pp = &return_value; -#else - fcic.object_ptr = return_value; -#endif + fcic.function_handler = class_entry->constructor; + fcic.calling_scope = class_entry; + + fci.object = reinterpret_cast( &retval_z ); + zr = zend_call_function( &fci, &fcic TSRMLS_CC ); CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { throw ss::SSException(); } - } //if( (*class_entry)->constructor ) + } //if( class_entry->constructor ) + RETURN_ZVAL( &retval_z, 1, 1 ); } catch( core::CoreException& ) { @@ -905,6 +910,10 @@ PHP_FUNCTION( sqlsrv_fetch_object ) zend_hash_destroy( properties_ht ); FREE_HASHTABLE( properties_ht ); } + else if ( Z_TYPE( retval_z ) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( retval_z )); + FREE_HASHTABLE( Z_ARRVAL( retval_z )); + } RETURN_FALSE; } @@ -1059,6 +1068,8 @@ PHP_FUNCTION( sqlsrv_get_field ) void* field_value = NULL; int field_index = -1; SQLLEN field_len = -1; + zval retval_z; + ZVAL_UNDEF(&retval_z); // get the statement, the field index and the optional type PROCESS_PARAMS( stmt, "rl|l", _FN_, 2, &field_index, &sqlsrv_php_type ); @@ -1066,15 +1077,17 @@ PHP_FUNCTION( sqlsrv_get_field ) try { // validate that the field index is within range - SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + int num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + if( field_index < 0 || field_index >= num_cols ) { THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); } - core_sqlsrv_get_field( stmt, field_index, sqlsrv_php_type, false, &field_value, &field_len, false/*cache_field*/, + core_sqlsrv_get_field( stmt, field_index, sqlsrv_php_type, false, field_value, &field_len, false/*cache_field*/, &sqlsrv_php_type_out TSRMLS_CC ); - zval_ptr_dtor( &return_value ); - *return_value_ptr = convert_to_zval( sqlsrv_php_type_out, &field_value, field_len ); + convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z ); + sqlsrv_free( field_value ); + RETURN_ZVAL( &retval_z, 1, 1 ); } catch( core::CoreException& ) { @@ -1167,25 +1180,20 @@ void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC ) } HashTable* params_ht = Z_ARRVAL_P( params_z ); - - for( zend_hash_internal_pointer_reset( params_ht ); - zend_hash_has_more_elements( params_ht ) == SUCCESS; - zend_hash_move_forward( params_ht )) { - - char *key = NULL; - unsigned int key_len = 0; - unsigned long index = -1; - zval** value_z = NULL; - - // make sure it's an integer index - int type = zend_hash_get_current_key_ex( params_ht, &key, &key_len, &index, 0, NULL ); - - CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { - throw ss::SSException(); - } - - core::sqlsrv_zend_hash_get_current_data( *stmt, params_ht, (void**) &value_z TSRMLS_CC ); + zend_ulong index; + zend_string* key = NULL; + zval* value_z = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, value_z ) { + + // make sure it's an integer index + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { + throw ss::SSException(); + } + // This code turns parameters into references. Since the function declaration cannot // pass array elements as references (without requiring & in front of each variable), // we have to set the reference in each of the zvals ourselves. In the event of a @@ -1193,32 +1201,21 @@ void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC ) // parameter array's first element. // if it's a sole variable - if( Z_TYPE_PP( value_z ) != IS_ARRAY ) { - // print a warning if they didn't send it as a reference - if( !PZVAL_IS_REF( *value_z ) && Z_REFCOUNT_P( *value_z ) > 1 ) { - php_error( E_WARNING, SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF, index + 1 ); - } - Z_SET_ISREF_PP( value_z ); // mark it as a reference - } - // else mark [0] as a reference - else { - - zval** var = NULL; - int zr = zend_hash_index_find( Z_ARRVAL_PP( value_z ), 0, reinterpret_cast( &var )); - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { - throw ss::SSException(); - } - // print a warning if they didn't send it as a reference - if( !PZVAL_IS_REF( *var ) && Z_REFCOUNT_P( *var ) > 1 ) { - php_error( E_WARNING, SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF, index + 1 ); - } - - Z_SET_ISREF_PP( var ); // mark it as a reference - } - } + if ( Z_TYPE_P( value_z ) != IS_ARRAY ) { + ZVAL_MAKE_REF( value_z ); + } + else { + zval* var = NULL; + int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( value_z ), 0 ))) ? SUCCESS : FAILURE; + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { + throw ss::SSException(); + } + ZVAL_MAKE_REF( var ); + } + } ZEND_HASH_FOREACH_END(); // save our parameters for later. - zval_add_ref( ¶ms_z ); + Z_TRY_ADDREF_P( params_z ); stmt->params_z = params_z; } @@ -1236,66 +1233,58 @@ void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC ) stmt->executed = false; zval* params_z = stmt->params_z; - + HashTable* params_ht = Z_ARRVAL_P( params_z ); + + zend_ulong index = -1; + zend_string *key = NULL; + zval* param_z = NULL; - for( zend_hash_internal_pointer_reset( params_ht ); - zend_hash_has_more_elements( params_ht ) == SUCCESS; - zend_hash_move_forward( params_ht )) { - - char *key = NULL; - unsigned int key_len = 0; - unsigned long index = -1; - zval** param_z = NULL; - zval* value_z = NULL; - int direction = SQL_PARAM_INPUT; - SQLSRV_ENCODING encoding = stmt->encoding(); - if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) { - encoding = stmt->conn->encoding(); - } - SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE; - SQLULEN column_size = SQLSRV_UNKNOWN_SIZE; - SQLSMALLINT decimal_digits = 0; - SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; - - // make sure it's an integer index - int type = zend_hash_get_current_key_ex( params_ht, &key, &key_len, &index, 0, NULL ); - - CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { - throw ss::SSException(); - } - - core::sqlsrv_zend_hash_get_current_data( *stmt, params_ht, (void**) ¶m_z TSRMLS_CC ); + ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) { + zval* value_z = NULL; + SQLSMALLINT direction = SQL_PARAM_INPUT; + SQLSRV_ENCODING encoding = stmt->encoding(); + if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) { + encoding = stmt->conn->encoding(); + } + SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE; + SQLULEN column_size = SQLSRV_UNKNOWN_SIZE; + SQLSMALLINT decimal_digits = 0; + SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; + // make sure it's an integer index + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { + throw ss::SSException(); + } + // if it's a parameter array - if( Z_TYPE_PP( param_z ) == IS_ARRAY ) { + if( Z_TYPE_P( param_z ) == IS_ARRAY ) { - zval** var = NULL; - int zr = zend_hash_index_find( Z_ARRVAL_PP( param_z ), 0, reinterpret_cast( &var )); + zval* var = NULL; + int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( param_z ), 0 ))) ? SUCCESS : FAILURE; CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { - zval_ptr_dtor( ¶ms_z ); throw ss::SSException(); } - + // parse the parameter array that the user gave - parse_param_array( stmt, *param_z, index, direction, php_out_type, encoding, sql_type, column_size, - decimal_digits TSRMLS_CC ); - value_z = *var; + parse_param_array( stmt, param_z, index, direction, php_out_type, encoding, sql_type, column_size, + decimal_digits TSRMLS_CC ); + value_z = var; } else { - value_z = *param_z; + value_z = param_z; } - // bind the parameter - core_sqlsrv_bind_param( stmt, index, direction, value_z, php_out_type, encoding, sql_type, column_size, decimal_digits - TSRMLS_CC ); + core_sqlsrv_bind_param( stmt, index, direction, value_z, php_out_type, encoding, sql_type, column_size, + decimal_digits TSRMLS_CC ); - } + } ZEND_HASH_FOREACH_END(); } catch( core::CoreException& ) { - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - zval_ptr_dtor( &stmt->params_z ); + zval_ptr_dtor( stmt->params_z ); + sqlsrv_free( stmt->params_z ); stmt->params_z = NULL; throw; } @@ -1344,13 +1333,12 @@ PHP_FUNCTION( sqlsrv_cancel ) } } -void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC ) +void __cdecl sqlsrv_stmt_dtor(zend_resource *rsrc TSRMLS_DC) { LOG_FUNCTION( "sqlsrv_stmt_dtor" ); // get the structure ss_sqlsrv_stmt *stmt = static_cast( rsrc->ptr ); - if( stmt->conn ) { int zr = zend_hash_index_del( static_cast( stmt->conn )->stmts, stmt->conn_index ); if( zr == FAILURE ) { @@ -1385,9 +1373,6 @@ void __cdecl sqlsrv_stmt_dtor( zend_rsrc_list_entry *rsrc TSRMLS_DC ) PHP_FUNCTION( sqlsrv_free_stmt ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); LOG_FUNCTION( "sqlsrv_free_stmt" ); @@ -1395,8 +1380,6 @@ PHP_FUNCTION( sqlsrv_free_stmt ) ss_sqlsrv_stmt* stmt = NULL; sqlsrv_context_auto_ptr error_ctx; - // we do this manually instead of with PROCESS_PARAMS because we return TRUE even if there is a parameter error. - full_mem_check(MEMCHECK_SILENT); reset_errors( TSRMLS_C ); try { @@ -1426,22 +1409,26 @@ PHP_FUNCTION( sqlsrv_free_stmt ) } // verify the resource so we know we're deleting a statement - stmt = static_cast( zend_fetch_resource( &stmt_r TSRMLS_CC, -1, ss_sqlsrv_stmt::resource_name, NULL, - 1, ss_sqlsrv_stmt::descriptor )); + stmt = static_cast(zend_fetch_resource_ex(stmt_r TSRMLS_CC, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor)); + // if sqlsrv_free_stmt was called on an already closed statment then we just return success. + // zend_list_close sets the type of the closed statment to -1. + if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) { + RETURN_TRUE; + } + if( stmt == NULL ) { THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); } - + // delete the resource from Zend's master list, which will trigger the statement's destructor - int zr = zend_hash_index_del( &EG( regular_list ), Z_RESVAL_P( stmt_r )); - if( zr == FAILURE ) { - LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RESVAL_P( stmt_r )); + if( zend_list_close( Z_RES_P(stmt_r) ) == FAILURE ) { + LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P( stmt_r )->handle); } ZVAL_NULL( stmt_r ); - + RETURN_TRUE; } @@ -1463,7 +1450,7 @@ void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* } const char* scroll_type = Z_STRVAL_P( value_z ); - unsigned int cursor_type = -1; + unsigned long cursor_type = -1; // find which cursor type they would like and set the ODBC statement attribute as such if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) { @@ -1501,75 +1488,64 @@ void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* } namespace { - -zval* convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len ) + +void convert_to_zval(sqlsrv_stmt* stmt, SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len, zval& out_zval) { - zval* out_zval = NULL; + if ( in_val == NULL ) { + ZVAL_NULL( &out_zval); + return; + } - switch( sqlsrv_php_type ) { - - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - { - ALLOC_INIT_ZVAL( out_zval ); - if( *in_val == NULL ) { - ZVAL_NULL( out_zval ); - } - else { + switch (sqlsrv_php_type) { - if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) { - ZVAL_LONG( out_zval, **( reinterpret_cast( in_val ))); - } - else { - ZVAL_DOUBLE( out_zval, **( reinterpret_cast( in_val ))); - } - } + case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_FLOAT: + { + if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) { + ZVAL_LONG( &out_zval, *(static_cast( in_val ))); + } + else { + ZVAL_DOUBLE( &out_zval, *(static_cast( in_val ))); + } + break; + } - if( *in_val ) { - sqlsrv_free( *in_val ); - } + case SQLSRV_PHPTYPE_STRING: + { + ZVAL_STRINGL( &out_zval, static_cast( in_val ), field_len); + break; + } - break; - } + case SQLSRV_PHPTYPE_STREAM: + { + out_zval = *( static_cast( in_val )); + stmt->active_stream = out_zval; + //addref here because deleting out_zval later will decrement the refcount + Z_TRY_ADDREF( out_zval ); + break; + } + case SQLSRV_PHPTYPE_DATETIME: + { + out_zval = *( static_cast( in_val )); + break; + } - case SQLSRV_PHPTYPE_STRING: - { - ALLOC_INIT_ZVAL( out_zval ); + case SQLSRV_PHPTYPE_NULL: + ZVAL_NULL(&out_zval); + break; - if( *in_val == NULL ) { - - ZVAL_NULL( out_zval ); - } - else { - - ZVAL_STRINGL( out_zval, reinterpret_cast( *in_val ), field_len, 0 /*duplicate*/ ); - } - break; - } - - case SQLSRV_PHPTYPE_STREAM: - case SQLSRV_PHPTYPE_DATETIME: - - out_zval = ( reinterpret_cast( *in_val )); - break; - - case SQLSRV_PHPTYPE_NULL: - ALLOC_INIT_ZVAL( out_zval ); - ZVAL_NULL( out_zval ); - break; - - default: - DIE( "Unknown php type" ); - break; - } - - return out_zval; + default: + DIE("Unknown php type"); + break; + } + return; } + // put in the column size and scale/decimal digits of the sql server type // these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx -bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, __out SQLUINTEGER* column_size, - __out SQLSMALLINT* decimal_digits ) +bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, _Out_ SQLULEN* column_size, + _Out_ SQLSMALLINT* decimal_digits ) { *decimal_digits = 0; @@ -1817,105 +1793,101 @@ void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC ) } } -void fetch_fields_common( __inout ss_sqlsrv_stmt* stmt, int fetch_type, __out zval* return_value, bool allow_empty_field_names - TSRMLS_DC ) +void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, zend_long fetch_type, _Out_ zval& fields, bool allow_empty_field_names + TSRMLS_DC ) { - void* field_value = NULL; - sqlsrv_phptype sqlsrv_php_type; - sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID; - zval_auto_ptr fields; + void* field_value = NULL; + sqlsrv_phptype sqlsrv_php_type; + sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID; - // make sure that the fetch type is legal - CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, stmt->func() ) { - throw ss::SSException(); - } + // make sure that the fetch type is legal + CHECK_CUSTOM_ERROR((fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, stmt->func()) { + throw ss::SSException(); + } - // get the numer of columns in the result set - SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - - // if this is the first fetch in a new result set, then get the field names and - // store them off for successive fetches. - if(( fetch_type & SQLSRV_FETCH_ASSOC ) && stmt->fetch_field_names == NULL ) { + // get the numer of columns in the result set + SQLSMALLINT num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); - SQLSMALLINT field_name_len; - char field_name_temp[ SS_MAXCOLNAMELEN+1 ]; - sqlsrv_malloc_auto_ptr field_names; - field_names = static_cast( sqlsrv_malloc( num_cols * sizeof( sqlsrv_fetch_field_name ))); + // if this is the first fetch in a new result set, then get the field names and + // store them off for successive fetches. + if(( fetch_type & SQLSRV_FETCH_ASSOC ) && stmt->fetch_field_names == NULL) { - for( int i = 0; i < num_cols; ++i ) { + SQLSMALLINT field_name_len; + char field_name_temp[SS_MAXCOLNAMELEN + 1]; + sqlsrv_malloc_auto_ptr field_names; + field_names = static_cast( sqlsrv_malloc( num_cols * sizeof(sqlsrv_fetch_field_name))); - core::SQLColAttribute( stmt, i + 1, SQL_DESC_NAME, field_name_temp, SS_MAXCOLNAMELEN+1, &field_name_len, NULL - TSRMLS_CC ); - field_names[ i ].name = static_cast( sqlsrv_malloc( field_name_len, sizeof( char ), 1 )); - memcpy( (void*) field_names[ i ].name, field_name_temp, field_name_len ); - field_names[ i ].name[ field_name_len ] = '\0'; // null terminate the field name since SQLColAttribute doesn't. - field_names[ i ].len = field_name_len + 1; - } + for(int i = 0; i < num_cols; ++i) { - stmt->fetch_field_names = field_names; - stmt->fetch_fields_count = num_cols; - field_names.transferred(); - } + core::SQLColAttribute(stmt, i + 1, SQL_DESC_NAME, field_name_temp, SS_MAXCOLNAMELEN + 1, &field_name_len, NULL + TSRMLS_CC); + field_names[i].name = static_cast( sqlsrv_malloc( field_name_len, sizeof(char), 1 )); + memcpy_s(( void* )field_names[i].name, ( field_name_len * sizeof( char )) ,field_name_temp, field_name_len); + field_names[i].name[field_name_len] = '\0'; // null terminate the field name since SQLColAttribute doesn't. + field_names[i].len = field_name_len + 1; + } - MAKE_STD_ZVAL( fields ); - int zr = array_init( fields ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } + stmt->fetch_field_names = field_names; + stmt->fetch_fields_count = num_cols; + field_names.transferred(); + } - // get the fields - for( int i=0; i< num_cols; ++i ) { - - // this zval_auto_ptr is never transferred because we rely on its destructor to decrement the reference count - // we increment its reference count within each fetch type (below) - zval_auto_ptr field; - SQLLEN field_len = -1; + int zr = array_init( &fields ); + CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { + throw ss::SSException(); + } - core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/, - &field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out TSRMLS_CC ); - field = convert_to_zval( sqlsrv_php_type_out, &field_value, field_len ); - - if( fetch_type & SQLSRV_FETCH_NUMERIC ) { - - zr = add_next_index_zval( fields, field ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } - zval_add_ref( &field ); - } + for( int i = 0; i < num_cols; ++i ) { + SQLLEN field_len = -1; - if( fetch_type & SQLSRV_FETCH_ASSOC ) { - - CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[ i ].len == 1 && !allow_empty_field_names ), stmt, - SS_SQLSRV_WARNING_FIELD_NAME_EMPTY ) { - throw ss::SSException(); - } + core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/, + field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out TSRMLS_CC ); - 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 ) { - throw ss::SSException(); - } - zval_add_ref( &field ); - } - } - } //for loop + zval field; + ZVAL_UNDEF( &field ); + convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, field ); + sqlsrv_free( field_value ); + if( fetch_type & SQLSRV_FETCH_NUMERIC ) { + + zr = add_next_index_zval( &fields, &field ); + CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { + throw ss::SSException(); + } + } + + if( fetch_type & SQLSRV_FETCH_ASSOC ) { + + CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 1 && !allow_empty_field_names ), stmt, + SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) { + throw ss::SSException(); + } + + 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 ) { + throw ss::SSException(); + } + } + } + //only addref when the fetch_type is BOTH because this is the only case when fields(hashtable) + //has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because + //fields now only has 1 element pointing to field and we want the ref count to be only 1 + if (fetch_type == SQLSRV_FETCH_BOTH) { + Z_TRY_ADDREF(field); + } + } //for loop - *return_value = *fields; - ZVAL_NULL( fields ); - zval_ptr_dtor( &fields ); - fields.transferred(); } -void parse_param_array( ss_sqlsrv_stmt* stmt, __inout zval* param_array, unsigned long index, __out int& direction, - __out SQLSRV_PHPTYPE& php_out_type, __out SQLSRV_ENCODING& encoding, __out SQLSMALLINT& sql_type, - __out SQLULEN& column_size, __out SQLSMALLINT& decimal_digits TSRMLS_DC ) +void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, + _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) { - zval** var_or_val; - zval** temp; + zval* var_or_val = NULL; + zval* temp = NULL; HashTable* param_ht = Z_ARRVAL_P( param_array ); sqlsrv_sqltype sqlsrv_sql_type; @@ -1930,42 +1902,47 @@ void parse_param_array( ss_sqlsrv_stmt* stmt, __inout zval* param_array, unsigne // handle the array parameters that contain the value/var, direction, php_type, sql_type zend_hash_internal_pointer_reset( param_ht ); if( zend_hash_has_more_elements( param_ht ) == FAILURE || - zend_hash_get_current_data( param_ht, (void**) &var_or_val ) == FAILURE ) { + (var_or_val = zend_hash_get_current_data(param_ht)) == NULL) { THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ); } // if the direction is included, then use what they gave, otherwise INPUT is assumed - if( zend_hash_move_forward( param_ht ) == SUCCESS && zend_hash_get_current_data( param_ht, (void**) &temp ) == SUCCESS && - Z_TYPE_PP( temp ) != IS_NULL ) { + if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && + Z_TYPE_P( temp ) != IS_NULL ) { - CHECK_CUSTOM_ERROR( Z_TYPE_PP( temp ) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { + CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { throw ss::SSException(); } - direction = Z_LVAL_PP( temp ); + direction = static_cast(Z_LVAL_P( temp )); CHECK_CUSTOM_ERROR( direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { throw ss::SSException(); } + + CHECK_CUSTOM_ERROR(!Z_ISREF_P(var_or_val) && (direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1) { + throw ss::SSException(); + } + } else { direction = SQL_PARAM_INPUT; } // extract the php type and encoding from the 3rd parameter - if( zend_hash_move_forward( param_ht ) == SUCCESS && zend_hash_get_current_data( param_ht, (void**) &temp ) == SUCCESS && - Z_TYPE_PP( temp ) != IS_NULL ) { + if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && + Z_TYPE_P( temp ) != IS_NULL ) { php_type_param_was_null = false; sqlsrv_phptype sqlsrv_phptype; - CHECK_CUSTOM_ERROR( Z_TYPE_PP( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1 ) { + CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1 ) { throw ss::SSException(); } - sqlsrv_phptype.value = Z_LVAL_PP( temp ); + sqlsrv_phptype.value = Z_LVAL_P( temp ); CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_phptype ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1 ) { @@ -1985,7 +1962,13 @@ void parse_param_array( ss_sqlsrv_stmt* stmt, __inout zval* param_array, unsigne else { php_type_param_was_null = true; - php_out_type = zend_to_sqlsrv_phptype[ Z_TYPE_PP( var_or_val ) ]; + + if (Z_ISREF_P(var_or_val)){ + php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(Z_REFVAL_P(var_or_val))]; + } + else{ + php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(var_or_val)]; + } encoding = stmt->encoding(); if( encoding == SQLSRV_ENCODING_DEFAULT ) { encoding = stmt->conn->encoding(); @@ -1993,17 +1976,17 @@ void parse_param_array( ss_sqlsrv_stmt* stmt, __inout zval* param_array, unsigne } // get the server type, column size/precision and the decimal digits if provided - if( zend_hash_move_forward( param_ht ) == SUCCESS && zend_hash_get_current_data( param_ht, (void**) &temp ) == SUCCESS && - Z_TYPE_PP( temp ) != IS_NULL ) { + if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && + Z_TYPE_P( temp ) != IS_NULL ) { sql_type_param_was_null = false; - CHECK_CUSTOM_ERROR( Z_TYPE_PP( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1 ) { + CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1 ) { throw ss::SSException(); } - sqlsrv_sql_type.value = Z_LVAL_PP( temp ); + sqlsrv_sql_type.value = Z_LVAL_P( temp ); // since the user supplied this type, make sure it's valid CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_sqltype( sqlsrv_sql_type ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, @@ -2012,7 +1995,7 @@ void parse_param_array( ss_sqlsrv_stmt* stmt, __inout zval* param_array, unsigne throw ss::SSException(); } - bool size_okay = determine_column_size_or_precision( stmt, sqlsrv_sql_type, &column_size, &decimal_digits ); + bool size_okay = determine_column_size_or_precision(stmt, sqlsrv_sql_type, &column_size, &decimal_digits); CHECK_CUSTOM_ERROR( !size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1 ) { @@ -2037,7 +2020,9 @@ void parse_param_array( ss_sqlsrv_stmt* stmt, __inout zval* param_array, unsigne int encoding; sqlsrv_phptype sqlsrv_phptype; - sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, column_size, true ); + + sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, (SQLUINTEGER)column_size, true ); + // we DIE here since everything should have been validated already and to return the user an error // for our own logic error would be confusing/misleading. SQLSRV_ASSERT( sqlsrv_phptype.typeinfo.type != PHPTYPE_INVALID, "An invalid php type was returned with (supposed) " @@ -2132,23 +2117,22 @@ bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype sql_type ) // verify an encoding given to type_and_encoding by looking through the list // of standard encodings created at module initialization time -bool verify_and_set_encoding( const char* encoding_string, __out sqlsrv_phptype& phptype_encoding TSRMLS_DC ) +bool verify_and_set_encoding( const char* encoding_string, _Out_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ) { - for( zend_hash_internal_pointer_reset( g_ss_encodings_ht ); - zend_hash_has_more_elements( g_ss_encodings_ht ) == SUCCESS; - zend_hash_move_forward( g_ss_encodings_ht ) ) { - - sqlsrv_encoding* encoding; - int zr = zend_hash_get_current_data( g_ss_encodings_ht, (void**) &encoding ); - if( zr == FAILURE ) { - DIE( "Fatal: Error retrieving encoding from encoding hash table." ); - } - - if( !stricmp( encoding_string, encoding->iana )) { - phptype_encoding.typeinfo.encoding = encoding->code_page; - return true; - } - } + void* encoding_temp = NULL; + zend_ulong index = -1; + zend_string* key = NULL; + ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, encoding_temp ) { + if ( !encoding_temp ) { + DIE( "Fatal: Error retrieving encoding from encoding hash table." ); + } + sqlsrv_encoding* encoding = reinterpret_cast( encoding_temp ); + encoding_temp = NULL; + if( !stricmp( encoding_string, encoding->iana )) { + phptype_encoding.typeinfo.encoding = encoding->code_page; + return true; + } + } ZEND_HASH_FOREACH_END(); return false; } @@ -2157,12 +2141,8 @@ bool verify_and_set_encoding( const char* encoding_string, __out sqlsrv_phptype& // into a sqlsrv_sqltype bit fields (see php_sqlsrv.h). void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); - char* size_p = NULL; - int size_len = 0; + size_t size_len = 0; long size = 0; if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &size_p, &size_len ) == FAILURE ) { @@ -2205,12 +2185,8 @@ void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) // field. encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h) void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); - - long prec = SQLSRV_INVALID_PRECISION; - long scale = SQLSRV_INVALID_SCALE; + zend_long prec = SQLSRV_INVALID_PRECISION; + zend_long scale = SQLSRV_INVALID_SCALE; if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &prec, &scale ) == FAILURE ) { @@ -2244,14 +2220,11 @@ void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) // encodes the type and encoding into a sqlsrv_phptype structure (see php_sqlsrv.h) void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." ); char* encoding_param; - int encoding_param_len = 0; + size_t encoding_param_len = 0; // set the default encoding values to invalid so that // if the encoding isn't validated, it will return the invalid setting. diff --git a/sqlsrv/template.rc b/sqlsrv/template.rc index 91ff6c1d..c6a4f82a 100644 --- a/sqlsrv/template.rc +++ b/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.0 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -59,9 +59,9 @@ BEGIN BEGIN BLOCK "040904b0" BEGIN - VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. 1997-2009 The PHP Group. All rights reserved.\0" + VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright 2001-2016 The PHP Group. All rights reserved.\0" VALUE "CompanyName", "Microsoft Corp.\0" - VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)\0" + VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0" VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION) VALUE "InternalName", FILE_NAME "\0" VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" diff --git a/sqlsrv/util.cpp b/sqlsrv/util.cpp index 5602c0c6..43cc265f 100644 --- a/sqlsrv/util.cpp +++ b/sqlsrv/util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -36,14 +36,14 @@ char log_msg[ LOG_MSG_SIZE ]; SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; // *** internal functions *** -void copy_error_to_zval( zval** error_z, sqlsrv_error_const* error, zval** reported_chain, zval** ignored_chain, +void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reported_chain, zval* ignored_chain, bool warning TSRMLS_DC ); bool ignore_warning( char* sql_state, int native_code TSRMLS_DC ); -bool handle_errors_and_warnings( sqlsrv_context& ctx, zval** reported_chain, zval** ignored_chain, logging_severity log_severity, +bool handle_errors_and_warnings( sqlsrv_context& ctx, zval* reported_chain, zval* ignored_chain, logging_severity log_severity, unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC ); -int sqlsrv_merge_zend_hash_dtor( void* dest TSRMLS_DC ); -bool sqlsrv_merge_zend_hash( __inout zval* dest_z, zval const* src_z TSRMLS_DC ); +int sqlsrv_merge_zend_hash_dtor( zval* dest TSRMLS_DC ); +bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ); } @@ -300,8 +300,8 @@ ss_error SS_ERRORS[] = { { SQLSRV_ERROR_DRIVER_NOT_INSTALLED, - { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 for SQL Server. " - "Access the following URL to download the ODBC Driver 11 for SQL Server for %1!s!: " + { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 or 13 for SQL Server. " + "Access the following URL to download the ODBC Driver 11 or 13 for SQL Server for %1!s!: " "http://go.microsoft.com/fwlink/?LinkId=163712", -49, true } }, @@ -360,6 +360,12 @@ ss_error SS_ERRORS[] = { SQLSRV_ERROR_INVALID_BUFFER_LIMIT, { IMSSP, (SQLCHAR*) "Setting for " INI_BUFFERED_QUERY_LIMIT " was non-int or non-positive.", -60, false } }, + { + SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, + { IMSSP, (SQLCHAR*)"Variable parameter %1!d! not passed by reference (prefaced with an &). " + "Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value." + , -61, true } + }, // internal warning definitions { @@ -368,13 +374,14 @@ ss_error SS_ERRORS[] = { }, // terminate the list of errors/warnings - { -1, {} } + { UINT_MAX, {} } }; sqlsrv_error_const* get_error_message( unsigned int sqlsrv_error_code ) { sqlsrv_error_const *error_message = NULL; - int zr = zend_hash_index_find( g_ss_errors_ht, sqlsrv_error_code, reinterpret_cast( &error_message )); + + int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr( g_ss_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; if( zr == FAILURE ) { DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); } @@ -451,65 +458,41 @@ bool ss_error_handler(sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool PHP_FUNCTION( sqlsrv_errors ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); + SQLSRV_UNUSED( execute_data ); - long flags = SQLSRV_ERR_ALL; - full_mem_check(MEMCHECK_SILENT); + zend_long flags = SQLSRV_ERR_ALL; LOG_FUNCTION( "sqlsrv_errors" ); - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) { - - LOG(SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ ); - RETURN_FALSE; - } - - if( flags == SQLSRV_ERR_ALL ) { - - int result; - zval_auto_ptr both_z; - - MAKE_STD_ZVAL( both_z ); - result = array_init( both_z ); - if( result == FAILURE ) { - - RETURN_FALSE; - } - - if( Z_TYPE_P( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( both_z, SQLSRV_G( errors ) TSRMLS_CC )) { - - RETURN_FALSE; - } - - if( Z_TYPE_P( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( both_z, SQLSRV_G( warnings ) TSRMLS_CC )) { - - RETURN_FALSE; - } - - if( zend_hash_num_elements( Z_ARRVAL_P( both_z )) == 0 ) { - - RETURN_NULL(); - } - - zval_ptr_dtor( &return_value ); - *return_value_ptr = both_z; - both_z.transferred(); - } - - else if( flags == SQLSRV_ERR_WARNINGS ) { - - zval_ptr_dtor( &return_value ); - *return_value_ptr = SQLSRV_G( warnings ); - zval_add_ref( &SQLSRV_G( warnings )); - } - else { - - zval_ptr_dtor( &return_value ); - *return_value_ptr = SQLSRV_G( errors ); - zval_add_ref( &SQLSRV_G( 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( 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); + RETURN_FALSE; + } + } + if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_WARNINGS ) { + if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( warnings ) TSRMLS_CC )) { + zval_ptr_dtor(&err_z); + RETURN_FALSE; + } + } + if( zend_hash_num_elements( Z_ARRVAL_P( &err_z )) == 0 ) { + zval_ptr_dtor(&err_z); + RETURN_NULL(); + } + RETURN_ZVAL( &err_z, 1, 1 ); } // sqlsrv_configure( string $setting, mixed $value ) @@ -529,14 +512,12 @@ PHP_FUNCTION( sqlsrv_errors ) PHP_FUNCTION( sqlsrv_configure ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); + SQLSRV_UNUSED( execute_data ); LOG_FUNCTION( "sqlsrv_configure" ); char* option; - int option_len; + size_t option_len; zval* value_z; sqlsrv_context_auto_ptr error_ctx; @@ -572,7 +553,7 @@ PHP_FUNCTION( sqlsrv_configure ) throw ss::SSException(); } - long severity_mask = Z_LVAL_P( value_z ); + zend_long severity_mask = Z_LVAL_P( value_z ); // make sure they can't use 0 to shut off the masking in the severity if( severity_mask < SEV_ALL || severity_mask == 0 || severity_mask > (SEV_NOTICE + SEV_ERROR + SEV_WARNING) ) { RETURN_FALSE; @@ -591,7 +572,7 @@ PHP_FUNCTION( sqlsrv_configure ) throw ss::SSException(); } - long subsystem_mask = Z_LVAL_P( value_z ); + zend_long subsystem_mask = Z_LVAL_P( value_z ); if( subsystem_mask < LOG_ALL || subsystem_mask > (LOG_INIT + LOG_CONN + LOG_STMT + LOG_UTIL) ) { RETURN_FALSE; @@ -609,7 +590,7 @@ PHP_FUNCTION( sqlsrv_configure ) throw ss::SSException(); } - long buffered_query_limit = Z_LVAL_P( value_z ); + zend_long buffered_query_limit = Z_LVAL_P( value_z ); CHECK_CUSTOM_ERROR( buffered_query_limit < 0, error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) { @@ -653,12 +634,10 @@ PHP_FUNCTION( sqlsrv_configure ) PHP_FUNCTION( sqlsrv_get_config ) { - SQLSRV_UNUSED( return_value_used ); - SQLSRV_UNUSED( this_ptr ); - SQLSRV_UNUSED( return_value_ptr ); + SQLSRV_UNUSED( execute_data ); char* option = NULL; - int option_len; + size_t option_len; sqlsrv_context_auto_ptr error_ctx; LOG_FUNCTION( "sqlsrv_get_config" ); @@ -715,50 +694,49 @@ PHP_FUNCTION( sqlsrv_get_config ) namespace { -void copy_error_to_zval( zval** error_z, sqlsrv_error_const* error, zval** reported_chain, zval** ignored_chain, +void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reported_chain, zval* ignored_chain, bool warning TSRMLS_DC ) { - MAKE_STD_ZVAL( *error_z ); - - if( array_init( *error_z ) == FAILURE ) { + + if( array_init( error_z ) == FAILURE ) { DIE( "Fatal error during error processing" ); } // sqlstate - zval_auto_ptr temp; - MAKE_STD_ZVAL( temp ); - ZVAL_STRINGL( temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE, 1 ); - zval_add_ref( &temp ); - if( add_next_index_zval( *error_z, temp ) == FAILURE ) { + zval temp; + ZVAL_UNDEF(&temp); + core::sqlsrv_zval_stringl( &temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE ); + //TODO: reference? + Z_TRY_ADDREF_P( &temp ); + if( add_next_index_zval( error_z, &temp ) == FAILURE ) { DIE( "Fatal error during error processing" ); } - if( add_assoc_zval( *error_z, "SQLSTATE", temp ) == FAILURE ) { + if( add_assoc_zval( error_z, "SQLSTATE", &temp ) == FAILURE ) { DIE( "Fatal error during error processing" ); } - temp.transferred(); // native_code - if( add_next_index_long( *error_z, error->native_code ) == FAILURE ) { + if( add_next_index_long( error_z, error->native_code ) == FAILURE ) { DIE( "Fatal error during error processing" ); } - if( add_assoc_long( *error_z, "code", error->native_code ) == FAILURE ) { + if( add_assoc_long( error_z, "code", error->native_code ) == FAILURE ) { DIE( "Fatal error during error processing" ); } // native_message - MAKE_STD_ZVAL( temp ); - ZVAL_STRING( temp, reinterpret_cast( error->native_message), 1 ); - zval_add_ref( &temp ); - if( add_next_index_zval( *error_z, temp ) == FAILURE ) { + ZVAL_UNDEF(&temp); + ZVAL_STRING( &temp, reinterpret_cast( error->native_message ) ); + //TODO: reference? + Z_TRY_ADDREF_P(&temp); + if( add_next_index_zval( error_z, &temp ) == FAILURE ) { DIE( "Fatal error during error processing" ); } - if( add_assoc_zval( *error_z, "message", temp ) == FAILURE ) { + if( add_assoc_zval( error_z, "message", &temp ) == FAILURE ) { DIE( "Fatal error during error processing" ); } - temp.transferred(); // If it is an error or if warning_return_as_errors is true than // add the error or warning to the reported_chain. @@ -766,17 +744,17 @@ void copy_error_to_zval( zval** error_z, sqlsrv_error_const* error, zval** repor { // if the warning is part of the ignored warning list than // add to the ignored chain if the ignored chain is not null. - if( warning && ignore_warning( reinterpret_cast( error->sqlstate ), error->native_code TSRMLS_CC ) && + if( warning && ignore_warning( reinterpret_cast(error->sqlstate), error->native_code TSRMLS_CC ) && ignored_chain != NULL ) { - if( add_next_index_zval( *ignored_chain, *error_z ) == FAILURE ) { + if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { DIE( "Fatal error during error processing" ); } } else { // It is either an error or a warning which should not be ignored. - if( add_next_index_zval( *reported_chain, *error_z ) == FAILURE ) { + if( add_next_index_zval( reported_chain, error_z ) == FAILURE ) { DIE( "Fatal error during error processing" ); } } @@ -786,45 +764,46 @@ void copy_error_to_zval( zval** error_z, sqlsrv_error_const* error, zval** repor // It is a warning with warning_return_as_errors as false, so simply add it to the ignored_chain list if( ignored_chain != NULL ) { - if( add_next_index_zval( *ignored_chain, *error_z ) == FAILURE ) { + if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { DIE( "Fatal error during error processing" ); } } } } -bool handle_errors_and_warnings( sqlsrv_context& ctx, zval** reported_chain, zval** ignored_chain, logging_severity log_severity, +bool handle_errors_and_warnings( sqlsrv_context& ctx, zval* reported_chain, zval* ignored_chain, logging_severity log_severity, unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC ) { bool result = true; bool errors_ignored = false; - int prev_reported_cnt = 0; + size_t prev_reported_cnt = 0; bool reported_chain_was_null = false; bool ignored_chain_was_null = false; int zr = SUCCESS; - zval* error_z = NULL; + zval error_z; + ZVAL_UNDEF(&error_z); sqlsrv_error_auto_ptr error; // array of reported errors - if( Z_TYPE_P( *reported_chain ) == IS_NULL ) { + if( Z_TYPE_P( reported_chain ) == IS_NULL ) { reported_chain_was_null = true; - zr = array_init( *reported_chain ); + zr = array_init( reported_chain ); if( zr == FAILURE ) { DIE( "Fatal error in handle_errors_and_warnings" ); } } else { - prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_PP( reported_chain )); + prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_P( reported_chain )); } // array of ignored errors if( ignored_chain != NULL ) { - if( Z_TYPE_P( *ignored_chain ) == IS_NULL ) { + if( Z_TYPE_P( ignored_chain ) == IS_NULL ) { ignored_chain_was_null = true; - zr = array_init( *ignored_chain ); + zr = array_init( ignored_chain ); if( zr == FAILURE ) { DIE( "Fatal error in handle_errors_and_warnings" ); } @@ -854,7 +833,7 @@ bool handle_errors_and_warnings( sqlsrv_context& ctx, zval** reported_chain, zva if( SQLSRV_G( warnings_return_as_errors ) ) { - if( zend_hash_num_elements( Z_ARRVAL_PP( reported_chain )) > prev_reported_cnt ) { + if( zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) > prev_reported_cnt ) { // We actually added some errors errors_ignored = false; @@ -863,15 +842,15 @@ bool handle_errors_and_warnings( sqlsrv_context& ctx, zval** reported_chain, zva } // if the error array came in as NULL and didn't have anything added to it, return it as NULL - if( reported_chain_was_null && zend_hash_num_elements( Z_ARRVAL_PP( reported_chain )) == 0 ) { - zend_hash_destroy( Z_ARRVAL_PP( reported_chain )); - FREE_HASHTABLE( Z_ARRVAL_PP( reported_chain )); - ZVAL_NULL( *reported_chain ); + if( reported_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) == 0 ) { + zend_hash_destroy( Z_ARRVAL_P( reported_chain )); + FREE_HASHTABLE( Z_ARRVAL_P( reported_chain )); + ZVAL_NULL( reported_chain ); } - if( ignored_chain != NULL && ignored_chain_was_null && zend_hash_num_elements( Z_ARRVAL_PP( ignored_chain )) == 0 ) { - zend_hash_destroy( Z_ARRVAL_PP( ignored_chain )); - FREE_HASHTABLE( Z_ARRVAL_PP( ignored_chain )); - ZVAL_NULL( *ignored_chain ); + if( ignored_chain != NULL && ignored_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( ignored_chain )) == 0 ) { + zend_hash_destroy( Z_ARRVAL_P( ignored_chain )); + FREE_HASHTABLE( Z_ARRVAL_P( ignored_chain )); + ZVAL_NULL( ignored_chain ); } // If it was an error instead of a warning than we always return errors_ignored = false. @@ -882,44 +861,35 @@ bool handle_errors_and_warnings( sqlsrv_context& ctx, zval** reported_chain, zva // see RINIT in init.cpp for information about which errors are ignored. bool ignore_warning( char* sql_state, int native_code TSRMLS_DC ) { - for( zend_hash_internal_pointer_reset( g_ss_warnings_to_ignore_ht ); - zend_hash_has_more_elements( g_ss_warnings_to_ignore_ht ) == SUCCESS; - zend_hash_move_forward( g_ss_warnings_to_ignore_ht ) ) { + zend_ulong index = -1; + zend_string* key = NULL; + void* error_temp = NULL; - void* error_v = NULL; - - if( zend_hash_get_current_data( g_ss_warnings_to_ignore_ht, (void**) &error_v ) == FAILURE ) { - return false; - } - - sqlsrv_error* error = static_cast( error_v ); - if( !strncmp( reinterpret_cast( error->sqlstate ), sql_state, SQL_SQLSTATE_SIZE ) && - ( error->native_code == native_code || error->native_code == -1 )) { - return true; - } - } + ZEND_HASH_FOREACH_KEY_PTR( g_ss_warnings_to_ignore_ht, index, key, error_temp ) { + sqlsrv_error* error = static_cast( error_temp ); + if (NULL == error) { + return false; + } + + if( !strncmp( reinterpret_cast( error->sqlstate ), sql_state, SQL_SQLSTATE_SIZE ) && + ( error->native_code == native_code || error->native_code == -1 )) { + return true; + } + } ZEND_HASH_FOREACH_END(); return false; } -int sqlsrv_merge_zend_hash_dtor( void* dest TSRMLS_DC ) -{ -#if defined(ZTS) - SQLSRV_UNUSED( tsrm_ls ); -#endif - zval_ptr_dtor( reinterpret_cast( &dest )); - +int sqlsrv_merge_zend_hash_dtor( zval* dest TSRMLS_DC ) +{ + zval_ptr_dtor( dest ); return ZEND_HASH_APPLY_REMOVE; } // sqlsrv_merge_zend_hash // merge a source hash into a dest hash table and return any errors. -bool sqlsrv_merge_zend_hash( __inout zval* dest_z, zval const* src_z TSRMLS_DC ) +bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ) { -#if defined(ZTS) - SQLSRV_UNUSED( tsrm_ls ); -#endif - if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" ); if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" ); @@ -928,29 +898,24 @@ bool sqlsrv_merge_zend_hash( __inout zval* dest_z, zval const* src_z TSRMLS_DC ) } HashTable* src_ht = Z_ARRVAL_P( src_z ); - int result = SUCCESS; + zend_ulong index = -1; + zend_string* key = NULL; + zval* value_z = NULL; - for( zend_hash_internal_pointer_reset( src_ht ); - zend_hash_has_more_elements( src_ht ) == SUCCESS; - zend_hash_move_forward( src_ht ) ) { - - void* value_v; - zval* value_z; + ZEND_HASH_FOREACH_KEY_VAL( src_ht, index, key, value_z ) { + if ( !value_z ) { + zend_hash_apply( Z_ARRVAL_P(dest_z), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); + return false; + } - result = zend_hash_get_current_data( src_ht, (void**) &value_v ); - if( result == FAILURE ) { - zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); - return false; - } - value_z = *(static_cast( value_v )); - result = add_next_index_zval( dest_z, value_z ); + int result = add_next_index_zval( dest_z, value_z ); - if( result == FAILURE ) { - zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); - return false; - } - zval_add_ref( &value_z ); - } + if( result == FAILURE ) { + zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); + return false; + } + Z_TRY_ADDREF_P( value_z ); + } ZEND_HASH_FOREACH_END(); return true; } diff --git a/sqlsrv/version.h b/sqlsrv/version.h index 0ea079ed..93bb6d47 100644 --- a/sqlsrv/version.h +++ b/sqlsrv/version.h @@ -2,7 +2,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 3.2 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -16,11 +16,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#define VER_FILEVERSION_STR "3.2.1.0" -#define _FILEVERSION 3,2,1,0 -#define SQLVERSION_MAJOR 3 -#define SQLVERSION_MINOR 2 -#define SQLVERSION_MMDD 1 +#define VER_FILEVERSION_STR "4.1.0.0" +#define _FILEVERSION 4,1,0,0 +#define SQLVERSION_MAJOR 4 +#define SQLVERSION_MINOR 1 +#define SQLVERSION_MMDD 0 #define SQLVERSION_REVISION 0 From e310687f07106d3c0b585c9d40d73b478faf2b4b Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Thu, 1 Sep 2016 17:14:27 -0700 Subject: [PATCH 06/43] Updated ReadMe --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 639a03bc..173b3b88 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ **Welcome to the Microsoft Drivers for PHP for SQL Server PHP 7** +**Note:** For the PHP 5 project, see the PHP 5 branch. + The Microsoft Drivers for PHP for SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PDO for accessing data in all editions of SQL Server 2005 and later (including Azure SQL DB). These drivers rely on the Microsoft ODBC Driver for SQL Server to handle the low-level communication with SQL Server. This 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 release(s) will contain more functionality, bug fixes, and more (see Plans below for more details). @@ -10,13 +12,13 @@ The Microsoft Drivers for PHP for SQL Server Team ##Announcements -August 22, 2016 (4.1.1): Updated Windows drivers built and compiled with PHP 7.0.9 are available and include a couple of bug fixes: +**August 22, 2016** (4.1.1): Updated Windows drivers built and compiled with PHP 7.0.9 are available and include a couple of bug fixes: - Fixed issue with storing integers in varchar field. - Fixed issue with invalid connection handler if one connection fails. - Fixed crash when emulate prepare is on. -July 28, 2016 (4.1.0): Thanks to the community's input, this release expands drivers functionalities and also includes some bug fixes: +**July 28, 2016** (4.1.0): Thanks to the community's input, this release expands drivers functionalities and also includes some bug fixes: - `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag is added to PDO_SQLSRV driver to handle numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` @@ -29,6 +31,11 @@ July 28, 2016 (4.1.0): Thanks to the community's input, this release expands dri - Fixed string truncation in bind output parameters when the size is not set and the length of initialized variable is less than the output. - Fixed bind string parameters as bidirectional parameters (`PDO::PARAM_INPUT_OUTPUT `) in PDO_SQLSRV driver. Note for output or bidirectional parameters, `PDOStatement::closeCursor` should be called to get the output value. +**July 06, 2016**: PHP Driver 4.0 for SQL Server with PHP 7 support is now GA. You can get the binaries [HERE](https://github.com/Azure/msphpsql/releases) or download the exe from the [Microsoft Download Center](https://www.microsoft.com/en-us/download/details.aspx?id=20098). + + Please visit the [blog][blog] for more announcements. + + ## Build Note: if you prefer, you can use the pre-compiled binary found [HERE](https://github.com/Azure/msphpsql/releases) @@ -119,7 +126,7 @@ The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. ## Resources -**Documentation**: [MSDN Online Documentation][phpdoc]. Please note that this documentation is not yet updated for PHP 7. +**Documentation**: [MSDN Online Documentation][phpdoc]. **Team Blog**: Browse our blog for comments and announcements from the team in the [team blog][blog]. From 7965686985a7bd3d32139c4693521d404b9da532 Mon Sep 17 00:00:00 2001 From: Hadis Fard Date: Thu, 1 Sep 2016 17:52:36 -0700 Subject: [PATCH 07/43] Added windows to the README file title --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 173b3b88..8387f5a4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Microsoft Drivers for PHP for SQL Server -**Welcome to the Microsoft Drivers for PHP for SQL Server PHP 7** +**Welcome to the Microsoft Drivers for PHP for SQL Server PHP 7 on Windows** -**Note:** For the PHP 5 project, see the PHP 5 branch. +**Note:** For the PHP 5 project, see the PHP 5 branch, and for Linux project, see PHP 7.0-Linux branch. The Microsoft Drivers for PHP for SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PDO for accessing data in all editions of SQL Server 2005 and later (including Azure SQL DB). These drivers rely on the Microsoft ODBC Driver for SQL Server to handle the low-level communication with SQL Server. @@ -150,4 +150,4 @@ The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. [phpazure]: https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-php-simple-windows/ -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. \ No newline at end of file +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From 72ddc16e5928527c964fef4d5e110d6d367ac50e Mon Sep 17 00:00:00 2001 From: Hadis Fard Date: Tue, 8 Nov 2016 14:27:50 -0800 Subject: [PATCH 08/43] Moved code of conduct to a separate section --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8387f5a4..e2e78113 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,10 @@ Thank you! The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details. +## Code of conduct + +This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments. + ## Resources **Documentation**: [MSDN Online Documentation][phpdoc]. @@ -150,4 +154,3 @@ The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. [phpazure]: https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-php-simple-windows/ -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From 71fe4ca6548e2e5677d1252b270e04f93593ed17 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Wed, 16 Nov 2016 15:37:54 -0500 Subject: [PATCH 09/43] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index e2e78113..2b5eaa0c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7 with improveme The Microsoft Drivers for PHP for SQL Server Team +##Get Started +* [**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) +* [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php-windows) + ##Announcements **August 22, 2016** (4.1.1): Updated Windows drivers built and compiled with PHP 7.0.9 are available and include a couple of bug fixes: From a02691e277d5804c3d8b28fcdfee31e6fd2df8ab Mon Sep 17 00:00:00 2001 From: Hadis Kakanejadi Fard Date: Fri, 3 Feb 2017 16:20:29 -0800 Subject: [PATCH 10/43] merged dev into master --- .travis.yml | 34 + CHANGELOG.md | 212 + Dockerfile-msphpsql | 72 + LICENSE | 2 +- README.md | 324 +- appveyor.yml | 180 + binaries/x64/php_pdo_sqlsrv_7_nts.dll | Bin 391896 -> 0 bytes binaries/x64/php_pdo_sqlsrv_7_ts.dll | Bin 393432 -> 0 bytes binaries/x64/php_sqlsrv_7_nts.dll | Bin 406744 -> 0 bytes binaries/x64/php_sqlsrv_7_ts.dll | Bin 410840 -> 0 bytes binaries/x86/php_pdo_sqlsrv_7_nts.dll | Bin 323800 -> 0 bytes binaries/x86/php_pdo_sqlsrv_7_ts.dll | Bin 325336 -> 0 bytes binaries/x86/php_sqlsrv_7_nts.dll | Bin 331480 -> 0 bytes binaries/x86/php_sqlsrv_7_ts.dll | Bin 335064 -> 0 bytes pdo_sqlsrv/CREDITS | 1 - pdo_sqlsrv/core_sqlsrv.h | 2263 ----- pdo_sqlsrv/core_stmt.cpp | 2471 ------ pdo_sqlsrv/core_util.cpp | 400 - pdo_sqlsrv/msodbcsql.h | 2343 ----- source/packagize.sh | 11 + source/pdo_sqlsrv/CREDITS | 1 + source/pdo_sqlsrv/config.m4 | 68 + {pdo_sqlsrv => source/pdo_sqlsrv}/config.w32 | 84 +- {pdo_sqlsrv => source/pdo_sqlsrv}/pdo_dbh.cpp | 2970 +++---- .../pdo_sqlsrv}/pdo_init.cpp | 710 +- .../pdo_sqlsrv}/pdo_parser.cpp | 721 +- .../pdo_sqlsrv}/pdo_stmt.cpp | 2698 +++--- .../pdo_sqlsrv}/pdo_util.cpp | 1209 ++- .../pdo_sqlsrv/php_pdo_sqlsrv.h | 798 +- {pdo_sqlsrv => source/pdo_sqlsrv}/template.rc | 166 +- source/shared/FormattedPrint.cpp | 2805 ++++++ source/shared/FormattedPrint.h | 231 + source/shared/StringFunctions.cpp | 145 + .../shared/StringFunctions.h | 66 +- {pdo_sqlsrv => source/shared}/core_conn.cpp | 1607 ++-- {pdo_sqlsrv => source/shared}/core_init.cpp | 353 +- .../shared}/core_results.cpp | 2942 ++++--- {sqlsrv => source/shared}/core_sqlsrv.h | 4604 +++++----- {sqlsrv => source/shared}/core_stmt.cpp | 4971 +++++------ {pdo_sqlsrv => source/shared}/core_stream.cpp | 530 +- {sqlsrv => source/shared}/core_util.cpp | 790 +- source/shared/globalization.h | 517 ++ source/shared/interlockedatomic.h | 62 + source/shared/interlockedatomic_gcc.h | 63 + source/shared/interlockedslist.h | 142 + source/shared/localization.hpp | 878 ++ source/shared/localizationimpl.cpp | 1043 +++ source/shared/msodbcsql.h | 426 + source/shared/sal_def.h | 796 ++ source/shared/typedefs_for_linux.h | 103 + {sqlsrv => source/shared}/version.h | 64 +- source/shared/xplat.h | 490 ++ source/shared/xplat_intsafe.h | 7635 +++++++++++++++++ source/shared/xplat_winerror.h | 132 + source/shared/xplat_winnls.h | 144 + source/sqlsrv/CREDITS | 1 + source/sqlsrv/config.m4 | 42 + {sqlsrv => source/sqlsrv}/config.w32 | 81 +- {sqlsrv => source/sqlsrv}/conn.cpp | 2602 +++--- {sqlsrv => source/sqlsrv}/init.cpp | 1340 +-- {sqlsrv => source/sqlsrv}/php_sqlsrv.h | 1238 +-- {sqlsrv => source/sqlsrv}/stmt.cpp | 4512 +++++----- {sqlsrv => source/sqlsrv}/template.rc | 166 +- {sqlsrv => source/sqlsrv}/util.cpp | 1844 ++-- sqlsrv/CREDITS | 1 - sqlsrv/core_conn.cpp | 774 -- sqlsrv/core_init.cpp | 175 - sqlsrv/core_results.cpp | 1361 --- sqlsrv/core_stream.cpp | 261 - sqlsrv/msodbcsql.h | 2343 ----- test/output.py | 133 + test/pdo_sqlsrv/autonomous_setup.php | 16 + test/pdo_sqlsrv/pdo_002_connect_app.phpt | 34 + test/pdo_sqlsrv/pdo_011_quote.phpt | 55 + test/pdo_sqlsrv/pdo_012_bind_param.phpt | 30 + test/pdo_sqlsrv/pdo_013_row_count.phpt | 60 + .../pdo_014_integer_custom_formats.phpt | 56 + ...do_015_integer_custom_formats_pooling.phpt | 61 + test/pdo_sqlsrv/pdo_016.phpt | 52 + test/pdo_sqlsrv/pdo_017.phpt | 60 + test/pdo_sqlsrv/pdo_018_next_result_set.phpt | 60 + .../pdo_019_next_result_set_pooling.phpt | 65 + .../pdo_sqlsrv/pdo_020_bind_params_array.phpt | 58 + test/pdo_sqlsrv/pdo_021_extended_ascii.phpt | 52 + test/pdo_sqlsrv/pdo_022_xml_bind_value.phpt | 79 + test/pdo_sqlsrv/pdo_023.phpt | 90 + .../pdo_sqlsrv/pdo_040_error_information.phpt | 45 + ...prepare_execute_fetch_pooling_default.phpt | 73 + ...prepare_execute_fetch_pooling_enabled.phpt | 73 + ...repare_execute_fetch_pooling_disabled.phpt | 73 + ...28_setAttribute_clientbuffermaxkbsize.phpt | 58 + test/sqlsrv/autonomous_setup.php | 16 + test/sqlsrv/srv_001.phpt | 21 + test/sqlsrv/srv_002.phpt | 21 + test/sqlsrv/srv_011_temporary_table.phpt | 32 + test/sqlsrv/srv_012_sqlsrv_fetch_array.phpt | 52 + test/sqlsrv/srv_013_sqlsrv_get_field.phpt | 48 + test/sqlsrv/srv_014_sqlsrv_get_field.phpt | 53 + .../srv_028_data_conversion_nvarchar.phpt | 55 + ..._029_data_conversion_varchar_datetime.phpt | 47 + .../sqlsrv/srv_031_sqlsrv_field_metadata.phpt | 97 + .../srv_034_field_metadata_unicode.phpt | 68 + test/sqlsrv/srv_036_transaction_commit.phpt | 83 + test/sqlsrv/srv_037_sqlsrv_has_rows.phpt | 107 + test/sqlsrv/srv_037_transaction_rollback.phpt | 82 + test/sqlsrv/srv_047_stream_nvarchar.phpt | 55 + test/sqlsrv/srv_048_stream_nvarchar.phpt | 42 + test/sqlsrv/srv_049_stream_nvarchar_utf8.phpt | 43 + .../srv_050_error_conversion_varchar_int.phpt | 46 + .../srv_051_error_conversion_nchar.phpt | 47 + test/sqlsrv/srv_052_mars.phpt | 40 + .../srv_053_mars_disabled_error_checks.phpt | 56 + test/sqlsrv/srv_073_database.phpt | 41 + .../srv_074_create_existing_database.phpt | 62 + test/sqlsrv/srv_074_database.phpt | 62 + test/sqlsrv/srv_074_database_wide_string.phpt | 61 + test/sqlsrv/srv_075_database.phpt | 44 + test/sqlsrv/srv_075_database_wide_string.phpt | 49 + .../srv_228_sqlsrv_clientbuffermaxkbsize.phpt | 81 + 119 files changed, 38041 insertions(+), 30771 deletions(-) create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 Dockerfile-msphpsql create mode 100644 appveyor.yml delete mode 100644 binaries/x64/php_pdo_sqlsrv_7_nts.dll delete mode 100644 binaries/x64/php_pdo_sqlsrv_7_ts.dll delete mode 100644 binaries/x64/php_sqlsrv_7_nts.dll delete mode 100644 binaries/x64/php_sqlsrv_7_ts.dll delete mode 100644 binaries/x86/php_pdo_sqlsrv_7_nts.dll delete mode 100644 binaries/x86/php_pdo_sqlsrv_7_ts.dll delete mode 100644 binaries/x86/php_sqlsrv_7_nts.dll delete mode 100644 binaries/x86/php_sqlsrv_7_ts.dll delete mode 100644 pdo_sqlsrv/CREDITS delete mode 100644 pdo_sqlsrv/core_sqlsrv.h delete mode 100644 pdo_sqlsrv/core_stmt.cpp delete mode 100644 pdo_sqlsrv/core_util.cpp delete mode 100644 pdo_sqlsrv/msodbcsql.h create mode 100644 source/packagize.sh create mode 100644 source/pdo_sqlsrv/CREDITS create mode 100644 source/pdo_sqlsrv/config.m4 rename {pdo_sqlsrv => source/pdo_sqlsrv}/config.w32 (57%) rename {pdo_sqlsrv => source/pdo_sqlsrv}/pdo_dbh.cpp (85%) rename {pdo_sqlsrv => source/pdo_sqlsrv}/pdo_init.cpp (63%) rename {pdo_sqlsrv => source/pdo_sqlsrv}/pdo_parser.cpp (96%) rename {pdo_sqlsrv => source/pdo_sqlsrv}/pdo_stmt.cpp (93%) rename {pdo_sqlsrv => source/pdo_sqlsrv}/pdo_util.cpp (90%) rename pdo_sqlsrv/pdo_sqlsrv.h => source/pdo_sqlsrv/php_pdo_sqlsrv.h (90%) rename {pdo_sqlsrv => source/pdo_sqlsrv}/template.rc (87%) create mode 100644 source/shared/FormattedPrint.cpp create mode 100644 source/shared/FormattedPrint.h create mode 100644 source/shared/StringFunctions.cpp rename pdo_sqlsrv/version.h => source/shared/StringFunctions.h (64%) rename {pdo_sqlsrv => source/shared}/core_conn.cpp (77%) rename {pdo_sqlsrv => source/shared}/core_init.cpp (97%) rename {pdo_sqlsrv => source/shared}/core_results.cpp (81%) rename {sqlsrv => source/shared}/core_sqlsrv.h (94%) rename {sqlsrv => source/shared}/core_stmt.cpp (91%) rename {pdo_sqlsrv => source/shared}/core_stream.cpp (84%) rename {sqlsrv => source/shared}/core_util.cpp (82%) create mode 100644 source/shared/globalization.h create mode 100644 source/shared/interlockedatomic.h create mode 100644 source/shared/interlockedatomic_gcc.h create mode 100644 source/shared/interlockedslist.h create mode 100644 source/shared/localization.hpp create mode 100644 source/shared/localizationimpl.cpp create mode 100644 source/shared/msodbcsql.h create mode 100644 source/shared/sal_def.h create mode 100644 source/shared/typedefs_for_linux.h rename {sqlsrv => source/shared}/version.h (70%) create mode 100644 source/shared/xplat.h create mode 100644 source/shared/xplat_intsafe.h create mode 100644 source/shared/xplat_winerror.h create mode 100644 source/shared/xplat_winnls.h create mode 100644 source/sqlsrv/CREDITS create mode 100644 source/sqlsrv/config.m4 rename {sqlsrv => source/sqlsrv}/config.w32 (59%) rename {sqlsrv => source/sqlsrv}/conn.cpp (95%) rename {sqlsrv => source/sqlsrv}/init.cpp (93%) rename {sqlsrv => source/sqlsrv}/php_sqlsrv.h (95%) rename {sqlsrv => source/sqlsrv}/stmt.cpp (93%) rename {sqlsrv => source/sqlsrv}/template.rc (86%) rename {sqlsrv => source/sqlsrv}/util.cpp (96%) delete mode 100644 sqlsrv/CREDITS delete mode 100644 sqlsrv/core_conn.cpp delete mode 100644 sqlsrv/core_init.cpp delete mode 100644 sqlsrv/core_results.cpp delete mode 100644 sqlsrv/core_stream.cpp delete mode 100644 sqlsrv/msodbcsql.h create mode 100644 test/output.py create mode 100644 test/pdo_sqlsrv/autonomous_setup.php create mode 100644 test/pdo_sqlsrv/pdo_002_connect_app.phpt create mode 100644 test/pdo_sqlsrv/pdo_011_quote.phpt create mode 100644 test/pdo_sqlsrv/pdo_012_bind_param.phpt create mode 100644 test/pdo_sqlsrv/pdo_013_row_count.phpt create mode 100644 test/pdo_sqlsrv/pdo_014_integer_custom_formats.phpt create mode 100644 test/pdo_sqlsrv/pdo_015_integer_custom_formats_pooling.phpt create mode 100644 test/pdo_sqlsrv/pdo_016.phpt create mode 100644 test/pdo_sqlsrv/pdo_017.phpt create mode 100644 test/pdo_sqlsrv/pdo_018_next_result_set.phpt create mode 100644 test/pdo_sqlsrv/pdo_019_next_result_set_pooling.phpt create mode 100644 test/pdo_sqlsrv/pdo_020_bind_params_array.phpt create mode 100644 test/pdo_sqlsrv/pdo_021_extended_ascii.phpt create mode 100644 test/pdo_sqlsrv/pdo_022_xml_bind_value.phpt create mode 100644 test/pdo_sqlsrv/pdo_023.phpt create mode 100644 test/pdo_sqlsrv/pdo_040_error_information.phpt create mode 100644 test/pdo_sqlsrv/pdo_060_prepare_execute_fetch_pooling_default.phpt create mode 100644 test/pdo_sqlsrv/pdo_061_prepare_execute_fetch_pooling_enabled.phpt create mode 100644 test/pdo_sqlsrv/pdo_062_prepare_execute_fetch_pooling_disabled.phpt create mode 100644 test/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt create mode 100644 test/sqlsrv/autonomous_setup.php create mode 100644 test/sqlsrv/srv_001.phpt create mode 100644 test/sqlsrv/srv_002.phpt create mode 100644 test/sqlsrv/srv_011_temporary_table.phpt create mode 100644 test/sqlsrv/srv_012_sqlsrv_fetch_array.phpt create mode 100644 test/sqlsrv/srv_013_sqlsrv_get_field.phpt create mode 100644 test/sqlsrv/srv_014_sqlsrv_get_field.phpt create mode 100644 test/sqlsrv/srv_028_data_conversion_nvarchar.phpt create mode 100644 test/sqlsrv/srv_029_data_conversion_varchar_datetime.phpt create mode 100644 test/sqlsrv/srv_031_sqlsrv_field_metadata.phpt create mode 100644 test/sqlsrv/srv_034_field_metadata_unicode.phpt create mode 100644 test/sqlsrv/srv_036_transaction_commit.phpt create mode 100644 test/sqlsrv/srv_037_sqlsrv_has_rows.phpt create mode 100644 test/sqlsrv/srv_037_transaction_rollback.phpt create mode 100644 test/sqlsrv/srv_047_stream_nvarchar.phpt create mode 100644 test/sqlsrv/srv_048_stream_nvarchar.phpt create mode 100644 test/sqlsrv/srv_049_stream_nvarchar_utf8.phpt create mode 100644 test/sqlsrv/srv_050_error_conversion_varchar_int.phpt create mode 100644 test/sqlsrv/srv_051_error_conversion_nchar.phpt create mode 100644 test/sqlsrv/srv_052_mars.phpt create mode 100644 test/sqlsrv/srv_053_mars_disabled_error_checks.phpt create mode 100644 test/sqlsrv/srv_073_database.phpt create mode 100644 test/sqlsrv/srv_074_create_existing_database.phpt create mode 100644 test/sqlsrv/srv_074_database.phpt create mode 100644 test/sqlsrv/srv_074_database_wide_string.phpt create mode 100644 test/sqlsrv/srv_075_database.phpt create mode 100644 test/sqlsrv/srv_075_database_wide_string.phpt create mode 100644 test/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..de98b18d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,34 @@ +sudo: required + +os: linux +dist: trusty + +services: + - docker + +env: + global: + - REPORT_EXIT_STATUS=1 + - ACCEPT_EULA=Y + - PHPSQLDIR=/REPO/msphpsql-PHP-7.0-Linux + - SQLSERVERHOSTNAME=sql + +before_install: + - docker pull microsoft/mssql-server-linux + +install: + - docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql . + - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=' -p 1433:1433 --name=$SQLSERVERHOSTNAME -d microsoft/mssql-server-linux + +script: + - docker run -e TRAVIS_JOB_ID -t -d -w $PHPSQLDIR --link $SQLSERVERHOSTNAME --name=client msphpsql-dev + - docker ps -a + - docker exec client php ./source/pdo_sqlsrv/run-tests.php ./test/pdo_sqlsrv/*.phpt + - docker exec client php ./source/sqlsrv/run-tests.php ./test/sqlsrv/*.phpt + - docker exec client coveralls -e ./source/shared/ --gcov-options '\-lp' + - docker stop client + - docker ps -a + + +notifications: + email: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..3b78e599 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,212 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) + +## Windows/Linux 4.1.6 - 2017-02-03 +Updated PECL release packages. Here is the list of updates: +### Added +- Merged Linux and Windows code. +- Enabled connection pooling with unixODBC. To enable pooling: + - in odbcinst.ini, add `Pooling=Yes` to the `[ODBC]` section and a positive `CPTimeout` value to `[ODBC Driver 13 for SQL Server]` section. See http://www.unixodbc.org/doc/conn_pool.html for detailed instructions. + +###Fixed +- Fixed issues with sqlsrv_has_rows() to prevent it from moving statement cursor ([issue #37](https://github.com/Microsoft/msphpsql/issues/37)). +- Fixed sqlsrv client buffer size to only allow positive integers ([issue #228](https://github.com/Microsoft/msphpsql/issues/228)). +- Fixed PECL installation errors when PHP was installed from source ([issue #213](https://github.com/Microsoft/msphpsql/issues/213)). +- Fixed segmentation fault with PDOStatement::getColumnMeta() when the supplied column index is out of range ([issue #224](https://github.com/Microsoft/msphpsql/issues/224)). +- Fixed the assertion error (Linux) when fetching data from a binary column using the binary encoding ([issue #226](https://github.com/Microsoft/msphpsql/issues/226)). + +## Windows 4.1.5 - 2017-01-19 +Updated Windows drivers (4.1.5) compiled with PHP 7.0.14 and 7.1 are available. Here is the list of updates: + +### Added +- Added Unicode Column name support([issue #138](https://github.com/Microsoft/msphpsql/issues/138)). + +###Fixed +- Fixed issue output parameters bound to empty string ([issue #182](https://github.com/Microsoft/msphpsql/issues/182)). +- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)). + + +### Changed +- Code structure is updated to facilitate the development; shared codes between both drivers are moved to "shared" folder to avoid code duplication issues in development. To build the driver from source: + - if you are building the driver from source using PHP source, copy the "shared" folder as a subfolder to both the sqlsrv and pdo_sqlsrv folders. + +## Linux 4.0.8 - 2016-12-19 +Production release of Linux drivers is available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. Here is the list of updates: + +### Added +- Added `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` attribute support in PDO_SQLSRV driver.`SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag handles numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, + `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` + If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. + Note for exceptions: + - When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string. + - When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off. +- Added Unicode Column name support([issue #138](https://github.com/Microsoft/msphpsql/issues/138)). + +###Fixed +- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)). +- Fixed precision issues when double data type returned as strings using buffered queries in PDO_SQLSRV driver. +- Fixed issue with buffered cursor in PDO_SQLSRV driver when CharacterSet is UTF-8 ([issue #192](https://github.com/Microsoft/msphpsql/issues/192)). +- Fixed segmentation fault in error cases when error message is returned with emulate prepare attribute is set to true in PDO_SQLSRV driver. +- Fixed issue with empty output parameters on stored procedure([issue #182](https://github.com/Microsoft/msphpsql/issues/182)). +- Fixed memory leaks in buffered queries. + + +## Linux 4.0.7 - 2016-11-23 +Linux drivers compiled with PHP 7.0.13 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. + +### Added +- Ported buffered cursor to Linux. + +### Changed +- Code structure is updated to facilitate the development; shared codes between both drivers are moved to "shared" folder to avoid code duplication issues in development. To build the driver from source, use "packagize" script as follows: + - if you are using the phpize, clone or download the source, run the script within the source directory and then run phpize. + - if you are building the driver from source using PHP source, give the path to the PHP source to the script. + +### Fixed + - Fixed string truncation error when inserting long strings. + - Fixed querying from large column name. + - Fixed issue with trailing garbled characters in string retrieval. + - Fixed issue with detecting invalid UTF-16 strings coming from server. + - Fixed issues with binding input text, ntext, and image parameters. + +## Linux 4.0.6 - 2016-10-25 +Linux drivers compiled with PHP 7.0.12 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. + +### Changed + - Drivers versioning has been redesigned as Major#.Minor#.Release#.Build#. Build number is specific to binaries and it doesn't match with the number on the source. + - Compiler C++ 11 is enabled in config file. + +### Fixed + - Fixed the issue with duplicate warning messages in PDO_SQLSRV drivers when error mode is set to PDO::ERRMODE_WARNING. + - Fixed the issue with invalid UTF-8 strings, those are detected before executing any queries and proper error message is returned. + - Fixed segmentation fault in sqlsrv_fetch_object and sqlsrv_fetch_array function. + +## Windows 4.1.4 - 2016-10-25 +Windows drivers compiled with PHP 7.0.12 and 7.1 are available. Here is the list of updates: + +### Changed + - Drivers versioning has been redesigned as Major#.Minor#.Release#.Build#. Build number is specific to binaries and it doesn't match with the number on the source. + +### Fixed + - Fixed the issue with duplicate warning messages in PDO_SQLSRV drivers when error mode is set to PDO::ERRMODE_WARNING. + +## Linux 4.0.5 - 2016-10-04 +Linux drivers compiled with PHP 7.0.11 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. + +### Fixed + - Fixed segmentation fault when calling PDOStatement::getColumnMeta on RedHat 7.2. + - Fixed segmentation fault when fetch mode is set to ATTR_EMULATE_PREPARES on RedHat 7.2. + - Fixed [issue #139](https://github.com/Microsoft/msphpsql/issues/139) : sqlsrv_fetch_object calls custom class constructor in static context and outputs an error. + +## Windows 4.1.3 - 2016-10-04 +Updated Windows drivers (4.1.3) compiled with PHP 7.0.11 and 7.1.0RC3 are available. Here is the list of updates: + +### Fixed +- Fixed [issue #139](https://github.com/Microsoft/msphpsql/issues/139) : sqlsrv_fetch_object calls custom class constructor in static context and outputs an error. + +##Linux 4.0.4 - 2016-09-09 +Linux drivers compiled with PHP 7.0.10 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. + +### Added +- Added Support for EMULATE_PREPARE feature. +- Added following integer SQL Types constants for cases which function-like SQL types constants cannot be used e.g. type comparison: + + SQLSRV constant | Typical SQL Server data type | SQL type identifier + ------------ | ----------------------- | ---------------------- + SQLSRV_SQLTYPE_DECIMAL | decimal | SQL_DECIMAL + SQLSRV_SQLTYPE_NUMERIC | numeric | SQL_NUMERIC + SQLSRV_SQLTYPE_CHAR | char | SQL_CHAR + SQLSRV_SQLTYPE_NCHAR | nchar | SQL_WCHAR + SQLSRV_SQLTYPE_VARCHAR | varchar | SQL_VARCHAR + SQLSRV_SQLTYPE_NVARCHAR | nvarchar | SQL_WVARCHAR + SQLSRV_SQLTYPE_BINARY | binary | SQL_BINARY + SQLSRV_SQLTYPE_VARBINARY | varbinary | SQL_VARBINARY + + Note: These constants should be used in type comparison operations (refer to issue [#87](https://github.com/Microsoft/msphpsql/issues/87) and [#99](https://github.com/Microsoft/msphpsql/issues/99) ), and don't replace the function like constants with similar syntax. For binding parameters you should use the function-like constants, otherwise you'll get an error. + +### Fixed + - Fixed undefined symbols at SQL* error when loading the drivers. + - Fixed undefined symbol issues at LocalAlloc and LocalFree on RedHat7.2. + - Fixed [issue #144](https://github.com/Microsoft/msphpsql/issues/144) (floating point exception). + - Fixed [issue #119](https://github.com/Microsoft/msphpsql/issues/119) (modifying class name in sqlsrv_fetch_object). + +## Windows 4.1.2 - 2016-09-09 +Updated Windows drivers (4.1.2) compiled with PHP 7.0.10 are available. Here is the list of updates: + +### Added +- Added following integer SQL Types constants for cases which function-like SQL types constants cannot be used e.g. type comparison: + + SQLSRV constant | Typical SQL Server data type | SQL type identifier + ------------ | ----------------------- | ---------------------- + SQLSRV_SQLTYPE_DECIMAL | decimal | SQL_DECIMAL + SQLSRV_SQLTYPE_NUMERIC | numeric | SQL_NUMERIC + SQLSRV_SQLTYPE_CHAR | char | SQL_CHAR + SQLSRV_SQLTYPE_NCHAR | nchar | SQL_WCHAR + SQLSRV_SQLTYPE_VARCHAR | varchar | SQL_VARCHAR + SQLSRV_SQLTYPE_NVARCHAR | nvarchar | SQL_WVARCHAR + SQLSRV_SQLTYPE_BINARY | binary | SQL_BINARY + SQLSRV_SQLTYPE_VARBINARY | varbinary | SQL_VARBINARY + + Note: These constants should be used in type comparison operations (refer to issue [#87](https://github.com/Microsoft/msphpsql/issues/87) and [#99](https://github.com/Microsoft/msphpsql/issues/99) ), and don't replace the function like constants with similar syntax. For binding parameters you should use the function-like constants, otherwise you'll get an error. + +### Fixed + - Fixed [issue #119](https://github.com/Microsoft/msphpsql/issues/119) (modifying class name in sqlsrv_fetch_object). + + +## Linux 4.0.3 - 2016-08-23 +Linux drivers compiled with PHP 7.0.9 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. + +### Fixed + - Fixed data corruption in binding integer parameters. + - Fixed invalid sql_display_size error. + - Fixed issue with invalid statement options. + - Fixed binding bit parameters. + +## Windows 4.1.1 - 2016-08-22 +Updated Windows drivers(4.1.1) compiled with PHP 7.0.9 are available and include a couple of bug fixes: + +### Fixed +- Fixed issue with storing integers in varchar field. +- Fixed issue with invalid connection handler if one connection fails. +- Fixed crash when emulate prepare is on. + + +## Linux 4.0.2 - 2016-07-29 + +### Fixed + - The PDO_SQLSRV driver no longer requires PDO to be built as a shared extension. + - Fixed an issue with format specifiers in error messages. + - Fixed a segmentation fault when using buffered cursors. + - Fixed an issue whereby calling sqlsrv_rows_affected on an empty result set would return a null result instead of 0. + - Fixed an issue with error messages when there is an error in sizes in SQLSRV_SQLTYPE_*. + +## Windows 4.1.0 - 2016-07-28 + +### Fixed + - `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag is added to PDO_SQLSRV driver to handle numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, + `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` + If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. + Note for exceptions: + - When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string. + - When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off. + - Fixed float truncation when using buffered query. + - Fixed handling of Unicode strings and binary when emulate prepare is on in `PDOStatement::bindParam`. To bind a unicode string, `PDO::SQLSRV_ENCODING_UTF8` should be set using `$driverOption`, and to bind a string to column of Sql type binary, `PDO::SQLSRV_ENCODING_BINARY` should be set. + - Fixed string truncation in bind output parameters when the size is not set and the length of initialized variable is less than the output. + - Fixed bind string parameters as bidirectional parameters (`PDO::PARAM_INPUT_OUTPUT `) in PDO_SQLSRV driver. Note for output or bidirectional parameters, `PDOStatement::closeCursor` should be called to get the output value. + + +## Linux 4.0.1 - 2016-07-09 + +### Added +- Added support for PDO_SQLSRV driver on RedHat 7. + +###Changed +- Improved handling varchar(MAX). +- Improved handling basic stream operations. + +## Linux 4.0.0 - 2016-06-11 + +### Added +- The early technical preview (ETP) for SQLSRV and PDO_SQLSRV drivers for Linux with basic functionalities is now available. The SQLSRV driver has been built and tested on Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2, and PDO_SQLSRV driver has been built and tested on Ubuntu 15.04, Ubuntu 16.04. diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql new file mode 100644 index 00000000..4c65931f --- /dev/null +++ b/Dockerfile-msphpsql @@ -0,0 +1,72 @@ +#Download base image ubuntu 16.04 + +FROM ubuntu:16.04 + +# Update Ubuntu Software repository +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \ + apt-get -y install \ + apt-transport-https \ + apt-utils \ + autoconf \ + curl \ + g++ \ + gcc \ + git \ + lcov \ + libxml2-dev \ + locales \ + make \ + php7.0 \ + php7.0-dev \ + python-pip \ + re2c \ + unixodbc-dev \ + unzip && apt-get clean + +ARG PHPSQLDIR=/REPO/msphpsql-PHP-7.0-Linux + +# set locale to utf-8 +RUN locale-gen en_US.UTF-8 +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' + +#install ODBC driver +RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - +RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list + +#RUN echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/mssql-ubuntu-xenial-release/ xenial main" > /etc/apt/sources.list.d/mssqlpreview.list +#RUN apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893 +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql mssql-tools + +#install coveralls +RUN pip install --upgrade pip && pip install cpp-coveralls + +#Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html ) +#One option is to get source from zip file of repository. + +#another option is to copy source to build directory on image +RUN mkdir -p $PHPSQLDIR +COPY . $PHPSQLDIR + +WORKDIR $PHPSQLDIR/source/ + +RUN chmod +x ./packagize.sh +RUN /bin/bash -c "./packagize.sh" + +RUN echo "extension = pdo_sqlsrv.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` +RUN echo "extension = sqlsrv.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` + +WORKDIR $PHPSQLDIR/source/sqlsrv +RUN phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install + +WORKDIR $PHPSQLDIR/source/pdo_sqlsrv +RUN phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install + +# set name of sql server host to use +WORKDIR $PHPSQLDIR/test/pdo_sqlsrv +RUN sed -i -e 's/localhost/sql/g' autonomous_setup.php + +WORKDIR $PHPSQLDIR/test/sqlsrv +RUN sed -i -e 's/localhost/sql/g' autonomous_setup.php + +ENV REPORT_EXIT_STATUS 1 +ENV TEST_PHP_EXECUTABLE /usr/bin/php diff --git a/LICENSE b/LICENSE index d332c60c..b52d9d43 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright(c) 2016 Microsoft Corporation +Copyright(c) 2017 Microsoft Corporation All rights reserved. MIT License diff --git a/README.md b/README.md index 2b5eaa0c..b87ad74c 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,39 @@ # Microsoft Drivers for PHP for SQL Server -**Welcome to the Microsoft Drivers for PHP for SQL Server PHP 7 on Windows** - -**Note:** For the PHP 5 project, see the PHP 5 branch, and for Linux project, see PHP 7.0-Linux branch. +**Welcome to the Microsoft Drivers for PHP for SQL Server PHP 7** The Microsoft Drivers for PHP for SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PDO for accessing data in all editions of SQL Server 2005 and later (including Azure SQL DB). These drivers rely on the Microsoft ODBC Driver for SQL Server to handle the low-level communication with SQL Server. This 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 release(s) will contain more functionality, bug fixes, and more (see Plans below for more details). -The Microsoft Drivers for PHP for SQL Server Team +SQL Server Team +###Status of Most Recent Builds +| AppVeyor (Windows) |Travis CI (Linux) | Coverage Status +|-------------------------|--------------------------| ------------------ +| [![av-image][]][av-site]| [![tv-image][]][tv-site] |[![Coverage Status][]][coveralls-site] + +[av-image]: https://ci.appveyor.com/api/projects/status/github/Microsoft/msphpsql?branch=dev&svg=true +[av-site]: https://ci.appveyor.com/project/Microsoft-PHPSQL/msphpsql +[tv-image]: https://travis-ci.org/Microsoft/msphpsql.svg?branch=dev +[tv-site]: https://travis-ci.org/Microsoft/msphpsql/ +[Coverage Status]: https://coveralls.io/repos/github/Microsoft/msphpsql/badge.svg?branch=dev +[coveralls-site]: https://coveralls.io/github/Microsoft/msphpsql?branch=dev ##Get Started + * [**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) * [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php-windows) + ##Announcements -**August 22, 2016** (4.1.1): Updated Windows drivers built and compiled with PHP 7.0.9 are available and include a couple of bug fixes: +**December 19, 2016**: We are delighted announce that production release for PHP Linux Driver for SQL Server is available. PECL packages (4.0.8) are updated with the latest changes, and Linux binaries (4.0.8) compiled with PHP 7.0.14 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. For complete list of changes please visit [CHANGELOG](https://github.com/Microsoft/msphpsql/blob/dev/CHANGELOG.md) file. -- Fixed issue with storing integers in varchar field. -- Fixed issue with invalid connection handler if one connection fails. -- Fixed crash when emulate prepare is on. - -**July 28, 2016** (4.1.0): Thanks to the community's input, this release expands drivers functionalities and also includes some bug fixes: - - - `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag is added to PDO_SQLSRV driver to handle numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, - `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` - If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. - Note for exceptions: - - When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string. - - When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off. - - Fixed float truncation when using buffered query. - - Fixed handling of Unicode strings and binary when emulate prepare is on in `PDOStatement::bindParam`. To bind a unicode string, `PDO::SQLSRV_ENCODING_UTF8` should be set using `$driverOption`, and to bind a string to column of Sql type binary, `PDO::SQLSRV_ENCODING_BINARY` should be set. - - Fixed string truncation in bind output parameters when the size is not set and the length of initialized variable is less than the output. - - Fixed bind string parameters as bidirectional parameters (`PDO::PARAM_INPUT_OUTPUT `) in PDO_SQLSRV driver. Note for output or bidirectional parameters, `PDOStatement::closeCursor` should be called to get the output value. - -**July 06, 2016**: PHP Driver 4.0 for SQL Server with PHP 7 support is now GA. You can get the binaries [HERE](https://github.com/Azure/msphpsql/releases) or download the exe from the [Microsoft Download Center](https://www.microsoft.com/en-us/download/details.aspx?id=20098). - Please visit the [blog][blog] for more announcements. -## Build +## Build (Windows) Note: if you prefer, you can use the pre-compiled binary found [HERE](https://github.com/Azure/msphpsql/releases) @@ -65,7 +57,7 @@ You must first be able to build PHP 7 without including these extensions. For h This software has been compiled and tested under PHP 7.0.8 using the Visual C++ 2015 compiler. -## Install +## Install (Windows) ####Prerequisites @@ -80,6 +72,243 @@ This software has been compiled and tested under PHP 7.0.8 using the Visual C++ 3. Restart the Web server. +## Install (Linux) +Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apache, and Microsoft PHP drivers on Ubuntu 15, 16 and RedHat 7. To see how to get PHP SQLSRV drivers running on Debian, please visit [Wiki](https://github.com/Microsoft/msphpsql/wiki/Dockerfile-for-getting-pdo_sqlsrv-for-PHP-7.0-on-Debian-in-3-ways). Note that Debian is not officially supported and this instruction hasn't been tested in our test lab. + +### Step 1: Install PHP (unless already installed) + +#### PHP 7.0 + +**Ubuntu 15.04, Ubuntu 15.10** + + sudo su + sh -c 'echo "deb http://packages.dotdeb.org jessie all \ndeb-src http://packages.dotdeb.org jessie all" >> /etc/apt/sources.list' + apt-get update + apt-get install php7.0 php7.0-fpm php-pear php7.0-dev mcrypt php7.0-mcrypt php-mbstring php7.0-xml re2c gcc g++ + + +**Ubuntu 16.04** + + sudo su + apt-get update + apt-get -y install php7.0 mcrypt php7.0-mcrypt php-mbstring php-pear php7.0-dev php7.0-xml re2c gcc g++ + + +**RedHat 7** + + sudo su + wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm + rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm + subscription-manager repos --enable=rhel-7-server-optional-rpms + yum-config-manager --enable remi-php70 + yum update + yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc + + + +#### PHP 7.1 + + +**Ubuntu 16.04** + + sudo su + add-apt-repository ppa:ondrej/php + apt-get update + apt-get -y install php7.1 mcrypt php7.1-mcrypt php-mbstring php-pear php7.1-dev + +**RedHat 7** + + sudo su + wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm + rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm + subscription-manager repos --enable=rhel-7-server-optional-rpms + yum-config-manager --enable remi-php71 + yum update + yum install php php-pdo php-xml php-pear php-devel + + + + +### Step 2: Install pre-requisites + +**Ubuntu 15.04** + + sudo su + sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/mssql-ubuntu-vivid-release/ vivid main" > /etc/apt/sources.list.d/mssqlpreview.list' + sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893 + apt-get update + apt-get install msodbcsql + #for silent install use ACCEPT_EULA=Y apt-get install msodbcsql + sudo apt-get install unixodbc-dev-utf16 + +**Ubuntu 15.10** + + sudo su + curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - + curl https://packages.microsoft.com/config/ubuntu/15.10/prod.list > /etc/apt/sources.list.d/mssql-release.list + exit + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install msodbcsql mssql-tools + sudo apt-get install unixodbc-dev-utf16 + + +**Ubuntu 16.04** + + sudo su + curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - + curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list + exit + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install msodbcsql mssql-tools + sudo apt-get install unixodbc-dev-utf16 + +**RedHat 7** + + sudo su + curl https://packages.microsoft.com/config/rhel/7/prod.repo > /etc/yum.repos.d/mssql-release.repo + exit + sudo yum update + sudo yum remove unixODBC #to avoid conflicts + sudo ACCEPT_EULA=Y yum install msodbcsql mssql-tools + sudo yum install unixODBC-utf16-devel + + + + +*Note: On Ubuntu, you need to make sure you install PHP 7 before you proceed to step 2. The Microsoft PHP Drivers for SQL Server will only work for PHP 7+. + +### Step 3: Install Apache + +####PHP 7.0 + +**Ubuntu** + + sudo apt-get install libapache2-mod-php7.0 + sudo apt-get install apache2 + +**RedHat** + + sudo yum install httpd + +####PHP 7.1 + +**Ubuntu** + + sudo apt-get install libapache2-mod-php7.1 + sudo apt-get install apache2 + +**RedHat** + + sudo yum install httpd + + +### Step 4: Install the Microsoft PHP Drivers for SQL Server + + sudo pecl install sqlsrv + sudo pecl install pdo_sqlsrv + +*Note: it installs the stable version, for specific version you should set the version. For example, `sudo pecl install sqlsrv-4.0.8` + + +### Step 5: Add the Microsoft PHP Drivers for SQL Server to php.ini + + +####PHP 7.0 + +**Ubuntu** + + echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/apache2/php.ini + echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/apache2/php.ini + echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/cli/php.ini + echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/cli/php.ini + + +**RedHat** + + echo "extension= /usr/lib64/php/modules/sqlsrv.so" > /etc/php.d/sqlsrv.ini + echo "extension= /usr/lib64/php/modules/pdo_sqlsrv.so" > /etc/php.d/pdo_sqlsrv.ini + + +####PHP 7.1 + + +**Ubuntu 16.04** + + echo "extension=/usr/lib/php/20160303/sqlsrv.so" >> /etc/php/7.1/apache2/php.ini + echo "extension=/usr/lib/php/20160303/pdo_sqlsrv.so" >> /etc/php/7.1/apache2/php.ini + echo "extension=/usr/lib/php/20160303/sqlsrv.so" >> /etc/php/7.1/cli/php.ini + echo "extension=/usr/lib/php/20160303/pdo_sqlsrv.so" >> /etc/php/7.1/cli/php.ini + + + +**RedHat** + + echo "extension= /usr/lib64/php/modules/sqlsrv.so" > /etc/php.d/sqlsrv.ini + echo "extension= /usr/lib64/php/modules/pdo_sqlsrv.so" > /etc/php.d/pdo_sqlsrv.ini + + + +### Step 6: Restart Apache to load the new php.ini file + +**Ubuntu** + + sudo service apache2 restart + +**RedHat** + + sudo apachectl restart + +### Step 7: Create your sample app +Navigate to `/var/www/html` and create a new file called testsql.php. Copy and paste the following code in tetsql.php and change the servername, username, password and databasename. + + "yourDatabase", + "Uid" => "yourUsername", + "PWD" => "yourPassword" + ); + //Establishes the connection + $conn = sqlsrv_connect($serverName, $connectionOptions); + //Select Query + $tsql= "SELECT @@Version as SQL_VERSION"; + //Executes the query + $getResults= sqlsrv_query($conn, $tsql); + //Error handling + + if ($getResults == FALSE) + die(FormatErrors(sqlsrv_errors())); + ?> +

    Results :

    + "); + } + sqlsrv_free_stmt($getResults); + function FormatErrors( $errors ) + { + /* Display errors. */ + echo "Error information:
    "; + + foreach ( $errors as $error ) + { + echo "SQLSTATE: ".$error['SQLSTATE']."
    "; + echo "Code: ".$error['code']."
    "; + echo "Message: ".$error['message']."
    "; + } + } + ?> + +### Step 8: Run your sample app + +Go to your browser and type in http://localhost/testsql.php +You should be able to connect to your SQL Server/Azure SQL Database. + +The drivers are distributed as shared binary extensions for PHP. They are available in thread safe (*_ts.so) and-non thread safe (*_nts.so) versions. The source code for the drivers is also available, and you can choose whether to compile them as thread safe or non-thread safe versions. The thread safety configuration of your web server will determine which version you need. + ## Sample Code For samples, please see the sample folder. For setup instructions, see [here] [phpazure] @@ -87,14 +316,25 @@ For samples, please see the sample folder. For setup instructions, see [here] [ - This release contains the PHP 7 port of the SQLSRV and PDO_SQLSRV drivers, and does not provide backwards compatibility with PHP 5. - Binding output parameter using emulate prepare is not supported. +- Linux + - ODBC 3.52 is supported but not 3.8. + - Connection using named instances using '\' is not supported. + - Local encodings other than UTF-8 are not supported, and SQLSRV_ENC_CHAR only supports ASCII characters with ASCII code of 0 to 127. ## Known Issues - User defined data types and SQL_VARIANT. +- Binary column binding with emulate prepare ([issue#140](https://github.com/Microsoft/msphpsql/issues/140) ) +- Linux + - The following features are not supported with connection pooling: + - Unicode connection strings + - sqlsrv_server_info and sqlsrv_client_info return false + - In certain scenarios a generic error message maybe returned instead of a specific error when pooling is disabled + - When retrieving data from columns with a data type of XML, varchar(max), nvarchar(max), or varbinary(max) no data maybe returned or the data maybe truncated depending on the length of the data in the source table. ## Future Plans -- Expand SQL 16 Feature Support (example: Always Encrypted) -- Build Verification/Fundamental Tests -- Bug Fixes +- Expand SQL 16 Feature Support (example: Always Encrypted). +- Build Verification/Fundamental Tests. +- Bug Fixes. ## Guidelines for Reporting Issues We appreciate you taking the time to test the driver, provide feedback and report any issues. It would be extremely helpful if you: @@ -102,6 +342,7 @@ We appreciate you taking the time to test the driver, provide feedback and repor - Report each issue as a new issue (but check first if it's already been reported) - Try to be detailed in your report. Useful information for good bug reports include: * What you are seeing and what the expected behaviour is + * Can you connect to SQL Server via `sqlcmd`? * Which driver: SQLSRV or PDO_SQLSRV? * Environment details: e.g. PHP version, thread safe (TS) or non-thread safe (NTS), 32-bit &/or 64-bit? * Table schema (for some issues the data types make a big difference!) @@ -117,7 +358,7 @@ Thank you! **Q:** What's next? -**A:** On Jan 29, 2016 we released an early technical preview for our PHP Driver and several since. We will continue to release frequently to improve the quality of our driver. +**A:** On July 20, 2016 we released the early technical preview for our PHP Driver. We will continue releasing frequent technical previews until we reach production quality. **Q:** Is Microsoft taking pull requests for this project? @@ -135,7 +376,7 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf ## Resources -**Documentation**: [MSDN Online Documentation][phpdoc]. +**Documentation**: [MSDN Online Documentation][phpdoc]. Please note that this documentation is not yet updated for PHP 7. **Team Blog**: Browse our blog for comments and announcements from the team in the [team blog][blog]. @@ -151,11 +392,24 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf [phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild -[phpdoc]: http://msdn.microsoft.com/en-us/library/dd903047%28SQL.11%29.aspx +[phpdoc]: http://msdn.microsoft.com/library/dd903047%28SQL.11%29.aspx -[odbc11]: https://www.microsoft.com/en-us/download/details.aspx?id=36434 +[odbc11]: https://www.microsoft.com/download/details.aspx?id=36434 -[odbc13]: https://www.microsoft.com/en-us/download/details.aspx?id=50420 +[odbc13]: https://www.microsoft.com/download/details.aspx?id=50420 -[phpazure]: https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-php-simple-windows/ +[odbcLinux]: https://msdn.microsoft.com/library/hh568454(v=sql.110).aspx +[phpazure]: https://azure.microsoft.com/documentation/articles/sql-database-develop-php-simple-windows/ + +[PHPMan]: http://php.net/manual/install.unix.php + +[LinuxDM]: https://msdn.microsoft.com/library/hh568449(v=sql.110).aspx + +[httpd_source]: http://httpd.apache.org/ + +[apr_source]: http://apr.apache.org/ + +[httpdconf]: http://php.net/manual/en/install.unix.apache2.php + +[ODBCinstallers]: https://blogs.msdn.microsoft.com/sqlnativeclient/2016/09/06/preview-release-of-the-sql-server-cc-odbc-driver-13-0-0-for-linux diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..f45dc84d --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,180 @@ +version: '{branch}.{build}' + +branches: + # whitelist + #only: + + # blacklist + except: + - PHP-7.0-Linux + - PHP5 + +environment: + # MSSQL credentials from https://www.appveyor.com/docs/services-databases/ + MSSQL_PASSWORD: Password12! + MSSQL_USERNAME: sa + PHP_DEPSVER: 7.0 + PHP_SDK: c:\projects\php + matrix: + - BUILD_PLATFORM: x64 + MSSQL_SERVERNAME: (local)\SQL2012SP1 + SQL_INSTANCE: SQL2012SP1 + PHP_VC: 14 + PHP_MAJOR_VER: 7.0 + PHP_MINOR_VER: latest + PHP_SDK_DIR: c:\projects\php\x64 + PHP_INSTALL_DIR: c:\projects\php\x64\bin + PHP_ZTS: --disable-zts + platform: x64 + - BUILD_PLATFORM: x86 + MSSQL_SERVERNAME: (local)\SQL2014 + SQL_INSTANCE: SQL2014 + PHP_VC: 14 + PHP_MAJOR_VER: 7.0 + PHP_MINOR_VER: latest + PHP_SDK_DIR: c:\projects\php\x86 + PHP_INSTALL_DIR: c:\projects\php\x86\bin + platform: x86 + - BUILD_PLATFORM: x64 + MSSQL_SERVERNAME: (local)\SQL2016 + SQL_INSTANCE: SQL2016 + PHP_VC: 14 + PHP_MAJOR_VER: 7.1 + PHP_MINOR_VER: latest + PHP_SDK_DIR: c:\projects\php\x64 + PHP_INSTALL_DIR: c:\projects\php\x64\bin + platform: x64 + - BUILD_PLATFORM: x86 + MSSQL_SERVERNAME: (local)\SQL2008R2SP2 + SQL_INSTANCE: SQL2008R2SP2 + PHP_VC: 14 + PHP_MAJOR_VER: 7.1 + PHP_MINOR_VER: latest + PHP_SDK_DIR: c:\projects\php\x86 + PHP_INSTALL_DIR: c:\projects\php\x86\bin + PHP_ZTS: --disable-zts + platform: x86 + + +# PHP_MAJOR_VER is PHP major version to build (7.0, 7.1) +# PHP_MINOR_VER is PHP point release number (or latest for latest release) +# PHP_INSTALL_DIR is where the built PHP binaries go +# PHP_SDK_DIR is where PHP source is extracted to (e.g. PHP_SDK_DIR\php-7.0.14-src) +# PHP_SDK is where PHP sdk binary tools are extracted to +# PHP_VC is the Visual C++ version +# PHP_ZTS is defined to disable thread safe build + +# Build worker image (VM template) +image: Visual Studio 2015 + +matrix: + fast_finish: true + +#services: + #- mssql2012sp1 + +# clone directory (or %APPVEYOR_BUILD_FOLDER%) +clone_folder: c:\projects\sqlphp + +build: + parallel: true # enable MSBuild parallel builds + +install: + - echo start SQL Server + # Based on http://www.appveyor.com/docs/services-databases + - ps: >- + [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null ; + [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null ; + + $instanceName = $env:SQL_INSTANCE; + $uri = "ManagedComputer[@Name='$env:COMPUTERNAME']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']"; + $wmi = New-Object ('Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer'); + $tcp = $wmi.GetSmoObject($uri); + $tcp.IsEnabled = $true; + $tcp.Alter(); + Start-Service "MSSQL`$$instanceName"; + + Set-Service SQLBrowser -StartupType Manual; + Start-Service SQLBrowser; + - echo Set PHP version... + - appveyor DownloadFile http://windows.php.net/downloads/releases/sha1sum.txt + # determine latest PHP versions + - ps: >- + If ($env:PHP_MINOR_VER -Match "latest") { + $env:PHP_VERSION=type sha1sum.txt | where { $_ -match "php-($env:PHP_MAJOR_VER\.\d+)-src" } | foreach { $matches[1] } ; + } Else { + $env:PHP_VERSION=$env:PHP_MAJOR_VER + '.' + $env:PHP_MINOR_VER; + } + - echo Downloading PHP-SDK + - appveyor DownloadFile http://windows.php.net/downloads/php-sdk/php-sdk-binary-tools-20110915.zip + - move php-sdk-binary-tools-20110915.zip .. + - echo Downloading PHP source code [%PHP_VERSION%] + - ps: (new-object net.webclient).DownloadFile('http://windows.php.net/downloads/releases/php-' + ${env:PHP_VERSION} + '-src.zip', ${env:APPVEYOR_BUILD_FOLDER} + '\..\php.zip') + #- echo Downloading PHP deps [%PHP_DEPSVER%] + #- ps: (new-object net.webclient).DownloadFile('http://windows.php.net/downloads/php-sdk/deps-' + ${env:PHP_DEPSVER} + '-vc' + ${env:PHP_VC} + '-' + ${env:BUILD_PLATFORM} + '.7z', ${env:APPVEYOR_BUILD_FOLDER} + '\..\deps.7z') + - echo Downloading MSODBCSQL 13 + - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/1/E/7/1E7B1181-3974-4B29-9A47-CC857B271AA2/English/' + ${env:BUILD_PLATFORM} + '/msodbcsql.msi', 'msodbcsql.msi') + - ps: msiexec /i msodbcsql.msi /quiet /qn /norestart + - cd .. + - cd + - 7z x -y php-sdk-binary-tools-20110915.zip -o%PHP_SDK% + - 7z x -y php.zip -o%PHP_SDK_DIR% + - echo update SQL connection string + - ps: (Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\pdo_sqlsrv\autonomous_setup.php) | ForEach-Object { $_ -replace "localhost", ${env:MSSQL_SERVERNAME} -replace "", ${env:MSSQL_PASSWORD} } | Set-Content ${env:APPVEYOR_BUILD_FOLDER}\test\pdo_sqlsrv\autonomous_setup.php + - ps: Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\pdo_sqlsrv\autonomous_setup.php + - ps: (Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\sqlsrv\autonomous_setup.php) | ForEach-Object { $_ -replace "localhost", ${env:MSSQL_SERVERNAME} -replace "", ${env:MSSQL_PASSWORD} } | Set-Content ${env:APPVEYOR_BUILD_FOLDER}\test\sqlsrv\autonomous_setup.php + - ps: Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\sqlsrv\autonomous_setup.php + +build_script: + - '"C:\\Program Files (x86)\\Microsoft Visual Studio %PHP_VC%.0\\VC\\vcvarsall.bat" %BUILD_PLATFORM%' + - Echo copy msphp code to ext folder + - mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv + - mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv\shared + - mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv + - mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv\shared + - copy /Y %APPVEYOR_BUILD_FOLDER%\source\sqlsrv %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv + - copy /Y %APPVEYOR_BUILD_FOLDER%\source\shared %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv\shared + - copy /Y %APPVEYOR_BUILD_FOLDER%\source\shared %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv\shared + - copy /Y %APPVEYOR_BUILD_FOLDER%\source\pdo_sqlsrv %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv + - cd %PHP_SDK_DIR%\php-%PHP_VERSION%-src + - cd + - dir + - '%PHP_SDK%\bin\phpsdk_setvars.bat' + - buildconf.bat + # only build CLI and MSSQL extensions + - configure.bat --disable-all %PHP_ZTS% --enable-cli --enable-sqlsrv=shared --with-pdo-sqlsrv=shared --enable-pdo=shared --with-prefix=%PHP_INSTALL_DIR% + - copy php.ini-development php.ini + - echo extension_dir=%PHP_INSTALL_DIR%\ext >> php.ini + - echo extension=php_sqlsrv.dll >> php.ini + - echo extension=php_pdo_sqlsrv.dll >> php.ini + - nmake + - nmake install + - Echo copy php.ini and run-tests.php from php source to install directory. + - copy php.ini %PHP_INSTALL_DIR% + - copy run-tests.php %PHP_INSTALL_DIR% + - dir %PHP_INSTALL_DIR% + +test_script: + - cd %PHP_INSTALL_DIR% + - php --ini + - php -i + - php run-tests.php -p php.exe %APPVEYOR_BUILD_FOLDER%\test\sqlsrv\*.phpt > %APPVEYOR_BUILD_FOLDER%\test\sqlsrv.log 2>&1 + - type %APPVEYOR_BUILD_FOLDER%\test\sqlsrv.log + - php run-tests.php -p php.exe %APPVEYOR_BUILD_FOLDER%\test\pdo_sqlsrv\*.phpt > %APPVEYOR_BUILD_FOLDER%\test\pdo_sqlsrv.log 2>&1 + - type %APPVEYOR_BUILD_FOLDER%\test\pdo_sqlsrv.log + +after_test: + - cd %APPVEYOR_BUILD_FOLDER%\test\ + - python output.py + - ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\nativeresult1.xml)) + - ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\nativeresult2.xml)) + - ps: >- + [xml]$results = Get-Content nativeresult1.xml ; + [xml]$results2 = Get-Content nativeresult2.xml ; + $failure = $results.SelectSingleNode("//failure"); + $failure2 = $results2.SelectSingleNode("//failure"); + if ($failure -ne $null -Or $failure2 -ne $null) + { + $host.SetShouldExit(1); + Write-Host "Forcing build failure due to phpt unit test failure(s)"; + } \ No newline at end of file diff --git a/binaries/x64/php_pdo_sqlsrv_7_nts.dll b/binaries/x64/php_pdo_sqlsrv_7_nts.dll deleted file mode 100644 index ec4951ea947d0f263fde4d94016ef27c91c32129..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 391896 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

    ps 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�kt!$^gD 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%0D$jbFG4H_l!(j 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

    v<&Av%WI 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℞P*qmts65EYDi!kEy&z-%WGo-(+((`-GfV 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

  • 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%0Ad-l5;Rt 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 N(n_A@9q^mD{|B)3N}K=y diff --git a/binaries/x86/php_pdo_sqlsrv_7_ts.dll b/binaries/x86/php_pdo_sqlsrv_7_ts.dll deleted file mode 100644 index 20d3f1fa6268c446b40678288083655f9878b273..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 325336 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 V!$I<{%{a!l(Z6fOh^KUF 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
  • >2aZ|(eo+>?F+)>s$5WHptkc(9xFPx))w@+#ewN+&eU6obHM0qA=gF9nH_ zCoe`~8r^}!PT0wFsgnxZ8GhrW{ac+c=xSP0zt)~3C~J!BPp;9Q%xHhYQ<*^6|FaY( z@@d@a%0vXY-{rD^P`u-B1#>#eyaq?BAdXg%FK3OF`H-gzX+97`8n70;Zf|*vhGM2G zAKcD=JFCF>kt_?0-`e`x>Nx_fGg$@kpbEi*{wON z9sieD1@WAU7|*Ey&k5fEI33{<488+&;lB!wPX9Et#6?SQCbskk(bDf(MP@c?%9TwK zXHbypJ z$E*Jv!!d|uCz&U(pFKhRz$d+Ugyl#n7+5~Ww~%t+tjHAx z40Og3jje1#;M`0mEjzJ(MJOCk(Nl=NLPvS|!D{%ww zqZ4Hu;xA?`GS8u0OrpZ}CF(+k206#e_J$z;fV@oj>llZ>kqIp=SStt6vAiSYRx^rb z`LSbp!aURx>>|MfTg(Ye+Qpourg`{+e4()huQLUl*zH69Nf!@@-OAXD?8k1tAa+QK z4Uki3ChRy&$Crb9aN;M(wS}+&-ikPDpw}8)>BNL_AT-1jSzcI88>im+vW$Nix*q-e z=L)0WTa&*3Q~rs*uSWN*!49ORL);nu@m1NfreRduLFK31e!}vQJ^5B>EW>neOmgRf z{h8AL(giM@!48BJ7lB1%oW*V^q@}b~7)eVq8F8B?c0+!_E_=x?R^p(Qx7ve|aiBLY zk$<+#l}Vp>gB(nJQwcH_0Lh8FG?C(r^3#@11CX#y2ah63=QsGK(OgWOb?}RpNQJ9= z0lm-L!?y}z$b<&qJ<_2BW{^UY4+_g=B@}>GVzcavg5m@ySvn}9CjiB1;v)b`cmm#K z>hCgiFm05EDk)X14&j5B3p8fZp`VUMGm_b1ZgBQ>0bM;{%Y@|?;EjxeH#Et7rl&NX z^RAALXEL$feq|D|oqRk4cQAb{eQ{%%_adQ?V)u;kOaq1pVT7m#T2FW`_EaY)>ScSe zy;X)^J~+b|QLFu~efX*|rMSB9NnFsVt)x*SPb??f~B7L-lBX#k0X{~#O;Ptx(gq=#@uyYOesU0POX>w-h%FC>C|08@@E zQ_&Hh6s`O-jqRy?3}}>%aRC-|-@sxI=-&QV;(h%4T6RYUn+Vh62vXlWz3k2$mS))r zLuIP{v6T7+ozTRUKn^RG)B8L)K}O7|&R;%xMlp$T#rdo_KO1|mb=91jsuwj413c^v zUiOU)b`^F8S?i^Nth0vQlEX%8wpj)5ElX=)sq?Ab6101nL5dd?bfev+`OD9mQ9|vO z-T(M zyb%GP&GJd>Yk?_)>#Cox*H&bL3f(`cY#Py@5$8o-ucG~gt&nggwb$nId0ZH3B(7=bM?TDilcuCeio zT!F;yvTGfP+5^GJArf8j9w{G@5}3tONntAk2u>3eP>26)9RSE1%@9L^T~>9atzJjR zf=WsZVGiZi?cgR9f z>fN&k@)<}V10q~HK6R>!3+X64;CD7Do~;C4u#BfN6G>I@qu_Vk=k$GzSp12qGmlgt z4bZUSTMp}~;Xl4qn1U$;KOIa`#nwHx5A8XrJj zz-s`g*H!Nyy8u{Gd??m;KZ@xDjrF~GjV~=QvU$e_)SU(&<^ZbaepwTnh4fadsi9d* z^owscqnvTQDek%**T|(trDG#1<85}v;%$&nC6>Vz!Ji3XH}yFUp$eRD++9(KpNmX5 z7X;3z2$j|1CMk$VhC`EG!rAASyN3TLD`0mt-ocPchI(VB73;Qmd{4!On?BB8Y z{vq^la7YoQ&_~P;GG(_}&`yJjT-k1iTJA&`MM$1Gpq8hH%7t7QD#y9%P@3IVZB}LD zD7^=v)Lx8?TJCBHm1mLcAbK4t&qYxao&j&zTS55hE-%Do5nYDL%jh+~>3T$M+`R>t zX03c-=MzDxt1ick_kWL7xnO+(TMz$gYYWcYd7XDe}l*Qf- zV#jQDhC%XzP_&ALp$aRyMmY0h;%OC2jH~s{agWy%W}|P~V>iJrB$lY;a}hYyXhkZ6 z6_xxV=xCveN|XVQww*3W35eCF%U3`sdS+#bUv~_@@#hQnC-4L03kkV<2@C|9YHsfF z>Jy>S5^{5TPQ=PL{yQoU*a+0Ukw8WykRAze@ITXjEMhOS!(fm!(*nKf8khcEYrDPW z$M`9{5n#au zk1X5*9_g7XxGk%4=0y-biLpFmQ$dgax(XJuw6n4flHLA0FXCHGh0oIq*V`h2LtbX7 zg|`%kB@vYDg~%(c>JW1u40fc4gB@=xmZO1}!y(ok{x=rr1x1_(R)D=&I(uj|sg+Ka zO8x+&hP|l4%>jYc(`ezU%`8rE;>#~In|xi={6l98Slb@{kaDh6^~TXu{7k@r^@G1X zM$um)6*irJy*=1J6Nf@J#s+O&|06)!P@(%6H0HiFZ+G5nNb+X(=is;EACKRJHRVA- zGFd_rY+96tWvh}NrO8KZ?!30?pk~%|Q$xXieX#BS(T*{=8tV24ZMGFWc{hA>MILaV z$<=(v4b&@#CZ@0}5x)uN&n#>3O-lz)x&FLe47;WS z3stxqW+HJaF6FU++NUp)Z!o6d^E+cOnWh$@?Qv` zyLH3dLPCwbmC(uDiz>U_H=2S2BUV&JT&=yw&5o9L14_$_aGSwu^$_NF^BSvyp!V=J zE$_;QFUf}^=wVj#(Ny_#37&2Jm#a1L|(f&&(R#x;G%ApcAtvT^tp zQ`x^x5oJ+;RV}9dKxb%Gi>p1gkwB4`zkELNDrc~138ug1_Cu%u9}M{9+pRFf1iQc% z^!p9xQFsoCo~k>`{#dycov_;1e;aEPI^u|+Bko(#Lo-Nr3$^_fz3sVWMA)GeqMg0~ ziW?nfbIaIXeDst8gsN53)yed^z2zpVGEm6eF0IM|NTHiGz+B*Vc)a|@%kh2HbS$a( z5Eb-YNEI3t5mS$=Xf(ZZyHqzon-_9B@fI?KJ3ZviAYi)PnWpeou!MOw4V=tDCUDxCzjM`i8!;R*~DR30ZZt~RfrzR6}z~=->_=^NN)Dtt0MuT!mJu}xz@|gmx?hAcIm@N<42^R);U>Sp;xWT zpkd@sUPi@k#h3taC2%gt-Tv(E;L(r#qk>0|`9}ng+5%%(Rc_u9BAO3VP`WCYF5jlh zM8f@bx|}q1MW8=SoknMMP2}-rpiD*dZiUsO1|0m%YehfV3rEUFDwI+UF%h72&^cn` zB7NH6YQ1{3sUi1EsEepc{t-e6hCjMK-)Mp;bTLH&ue4bMld(WeME-H#W~sqU2TMRd z@ic?Yx57H{GWe50iNx|p0>&_Mc7)%-sRMO6HQ-Jb58nk#o8(SsYE_%KIz2itn>;)S z(HTWC5Kr6&@F@mwWTV5k6)lD*0;BVBo9f#|w`q-IeQmhV+`S(DS7dY{8guW#>we67 z58sbRY|N*(L#%Q;?GBV`G!q4*jAzkGU8+e_#wrVPzXWjcmgr z48isuJfm+h?CfxZeND&4>JdkFrUWubot{KKFGCE%^YLS=Drq!;R|X@by#^l81t^AWodt8H<-6 zhglpWscDO;o_3hzyUEpCiq$AVA*f9XtS(*C1$pVKM5mBBydpE z77{Frp#SQy(7lIe0~W-uee)x;V>W(#_-Voq-P2FzfZ1^jf79@H9DeAYe!B7V_qd&MT zrI3=@T!+x=+=pnA!t>ckhwY6Ber)zHgr^y6L?D$_xL8rT7C2Np2fOFLG7(jg;M{sd z4RA>bRXFau-pcog(N{?RI=hBnD7FdWlEi3)ETUOCEkS0~#QhhpuN+#y!*q zTQ_q5fgR4dj>gA|g&5G8SP#Tpn8&1g6CUg3|Cs~wrd3*DJq^K6zKkg9XSYxf{A0ZQ ziY)pVf=u9j>>7)i1@=W=)=PC)WSRXX_;8W`Y%l+tU6e;kT`zwXTUhXI1FWoLTHDt` zal3FTEewiLzm477jCOPz#}MN2{d0gHh{r4d^U4+!g{U&5fuwm&mI*t2L1RuZDGx7$ zDKir2)v9)J%ehfp?eg&7Ptk3~Qa^eoe*80rle6n=4Xv-;ImYLD2s5Pf4ACoi8{}3c ztSuykIDT$MmPzxnVtLoveXGXEqEau=js3r$L z6rl5e?H{@&L81=g-{%h>j5To#L()r6JiQd2fM1A1db}_kAwXDOQPd&5c!VJG@CY-UIe{N{{ZHTy!a3H^A9k4byl$7!~dRc zMpF8PHe*INKm#WY#Q1)!hrbVxcC^7RzCHGRmxupad|xD*P=c#+O@T|BS4}thvzk|3 zVG5kvU3!Iin>mSygd+PcOfq7oR@uM?8-h?q%q4794h|BNh0=j>h@E5OJ0gLOu9OSQd)taPN8+FO2&tKhY!KmmhF zfNK&2jX-#}3-VQ}467dsMbB61D1z@}rMVzvM0zY8D;P>F(&J0=0ofb`lW0nZTW*$8 z9k~)qM%mvY7(St&Vrwh%-znV?i2wykAh5j31%9%QbS>4~w;T&<^V%$kX%0|~juxBLh77YSr{ve}5(BCX(_W_koh zTWntURvMZM?oER!xL{{fqeLd#Z=lhC26op#(OXPRbj=vi=SaX631k|^Bqsz5?&Z_S z7~qDFLFUQ9`47S(Hw3~dn{83}-sVrz0&`)RBJmQ-CH+q+$HMmn=2EaLLvtV0AN#^E z3FE~UykJ+s(uKDSNm2JUVzF>SCc%;c+nqtEEWL)`c8*xF3|X0`Ay8)b@JFDkM^V?1dkt#~^exF`rm$B=yM3K+Ho-%1j0!Y^XOuXeqa zcR_gucCo?=j?~3}cV@IVV3(F}+< z%fqKbhXCB<2RRC7p-xxugSy-och&n4*uiu zEk;pNe=|M2yGB5Xfkrn6GAY)N>ZzCYkpAoC|G0_DSk)CpNj(t@uOA~y1T6aW-;QXX zWPjGj_h;Dg2^ixetf&f1tf{T-E6*0P<{=?2U}1^vwioHUI}>c6`bZ&bJw|Fb{Ppb) z;<*{syalo<#(~D``lzZ$eydWzDuBLP01baGPP!SwpR0SV=W36*KKOAY(q>x-FWcGB zj$kSO&-m`mGlr|xiXzD#4)&-MPu0Mo;EsntiKf7UTD}1yI))3fS2c6@hTQw<1xzg% zB@;{u`(yq&`#9mqQ}5o6SJT>)u%S_du;z0pJlewqAmr-Ds2Qz_9GO$DwO! ztx6Fn4vpjzV}K9a7K}uepYQ7dl_AUTRl+P_)vDV0BQZ4z3)oNLBQpzN5aQob4g<@& z9a{mJv~9vJZSt8jg@M!K)f;KF3d<)vjC7h+PwQ*;*1zG4$b{**zvgBIC~YMpmQlWz zpNVHQmEjG75Yc1VN7jXY2`ipMuPW$-Tfb$uzraV|^EN?Cq!R<1jiWsLLKreYTvB-6 z&N*EDIzxc@{LL{W7xha(t4x*N-?MOVDMvpF2D`5e$Av^;U^g<7Bhq-|U%I4y& zQ9ZE?r+o7@Xu>G<{aC5`#8UI(0ns6%`3`vEnzm8XBa@nT$0{9_Sg9qkQpxY0k7o$Z z;q-a8L#WOA%#_;mjnJNZ#|iEECHf;`l!-y5LN_Q9tnjPn5gIa*Ccn_3DWDnueIJ5# zg!lTkP>jg;Q>gfmDtEtD)yo^lMF*=F>)L3usU8jAgP+a#F_~b|N%_C?k2GwM82uwt zll+&G@;QTux)bT+`n(sh0Xco6+0|ay92mW^IC8zKy`-?&|Dk<{`TWBGV+4g?1P5|g zT!s<6Z@d`67W`==aI<$39k&t0`X%vpc}b*KOY()bgjTI1a`N8VvA<5_c-)<|5u=0FTySu1m;Wo8KU4w?!zl9X{mv6qBl4F3^X{= zBxx`e5wQqWQ}72ya=J$B*aDrCNH(&~9AhN)@Zxn&YZrfX7>#(b41&^C@R5If^IF7= zjq$Cfc77c>CTLxEj3K(sw(b**%Ub*TAEF!ZIHcG3df)=^^1q5T2OLreppMSDG;wOG z6PFK;eLi`b?e>^eb1pj!{RtZq5(>icKN03OVP7CEv95l>w06j~*he6DmgVin0Y6B_ zk;}g(+chkoD7nonO8Ttd6pP`-*klhpQ&=?rQB=1ob3s43AL(e~#)>XU9{Chpd0jtw zBp{0%i?N<{{XOV(R2-qGpF!ft55c}n>`@prOzasLa7{I^3aU+5=Y_bypN8H6?m>2! zn0f`15ybP$#Vg%pw1xj;vO${eEgs&FU`zHLh*Z#so}Y1JzFO69|1nvBrbm|L!{$^C zbf53x??T0ox)<-`zmC;$ALRdN9jukM_t_uYbv@E<*w+`3o7JT`GEy8*U_lmfA-a)b zRgP>Gp)G-4c2!9umT848N36&kVM{TTYO=1#9a=&cEI zqJUKI?FO#IAl=~M$48*IP5tzl5M46@#AcJ=~nsd3#GPp2rX{Qd9v1*jp#zg{Cc{YC7RlltNM~-NPRP z0`}H=u-kvbC=$q=eP05TgzXLzjTp_4y9;A&@(~?}+$3_C*j41BE5SKLB+{8ghvO|L zP5rt)K4?_@(CsllWxwVTK+|FX(Y=RX1b}o|`3bCpL}eCPw}{C?K6wWti(&Ib zmRYq`R{s9m#3tHdv4chSc)%fNnFF!lJMhZFN2jR%nL85yP{{!R~Isi-XM%{1DW-IE=wg$Btsaf5J z8r0%ZlHql5a+e_}UeI5Z_BfwNmO~14p7ch1@i7m{JG|&r6GT zjP5=BV-#=L^wTLRj^>Fejun$q9JA6>91HQ2Jvk-n^Q3rb$lLbEibWL8ij{B^Wt;^! z5d?uCPYKkwQ4j)#wUe2zLEoBJJFdZlwQEPV9oAp8y=`1rQ9u-xANxCD%JH9d$y*dl zWGELd66eop8!)~>H;ivcRJn{^=O7dc+b}SOBRu>T5ee-eEn}+F=p#u(JRLoZmPA}a zZmi^oQH@8G{Cfo6u1%F#pKgi!S&i#fTC?!8O}_>{oTPu;Nq)?OJR zND=gNwFp*PhijlJ>+BRqeMpGF*ro_1rPk!Vwr;g6i!@?ZWV7GbS!(}H=QTypx|fes zXCU;SinY;w-isf-KOf1rkJMB*Y;cp_J$&5srS%kf5AsXS}2xTN~y>v>XY9 z#y8{~HNMe_=yD>W%tRuZPgF9m?W4FaNiH^Ofhuj(2+$B&D)DM-tBWfnqEjTORA@-r z`!iH}tV%?CKZ&Ykf}%EKeB)_vd#vQ0i6xh!WW%Na2<5wRKRa;!JbtbK`W*P7Yk_an zUJHkMe2$U5i0UUPRv!|%kUkb!V%s|Wgsi+)Y_@IJT-5v6(VhgcqvOcRL2Zdx|S%AQB*bOdT5`&ng2 zUkYM11(7SXWZE`C89;jQNMOdsQriA+E>muN%19;Fb+(k;*@f3Ps3XgmW- zGAq9n)-qaC(`xxoL_G-9v$v8u+6NmsX2EP*(-fL9GcggzdsJ?W>jHR$CvcA%|`jA{!*&yl~Y1B2Tc`ZCIqey+~ z!73b1!wZ_+lv543TO52Y9Yol=imY4LFQE+g!LE67lL{`#&5+3}VFWWclne6D#}G#7 zd2ZtKi|`!!)neB}!TwvJS!K>#*e5*U!RTLoi|vSa5&`E(QXlG+Pw`q*fuQv+RK)%dV&nqgBmp zG{g8>sJbg5+9QlI637=qeass=NO61ezoQyVf`$s6?cNMQ=F%=};~tp1<+!`CFx>!O zTAad$Xi3aU)K2qhTs5x|-;#oWfCv+J#=b?xWASaUX(nvaF7yw>bv_N$HDs5zx0aJ| z>z)ZYztSUY(fnsdtb_m%AE88}DcEV{wH52r-V*&GRr4UP^+7n)Zo3uXNbj6czBNR9+M>BvR$BlW^mwBd+X0ZP&=rj2pZIsE74Sm zWxpE8QQb4;?n_oR6N?44kp*oYn91I2=}@a?;xfmwQ=NOl{_jG-F`5ZjP}uCA3BwnF z{sWq%pkF{-<*9B|NZmIhZV;v90pwm2NpnfoDW&&+p zAN53nr`;unZ+Q5EO<5E+XK(pFrZ)w&=&Ek=I0d1$i*<&^X%8&>QSIjzS$IebX#Eu>8yteFKpefJ8$A^Uy``d+`X~hMw zF~}$2B4ecoE-yG% z57O*S4a?bktE&*A?mPyO#RlQUsUqKNnX3}RIN(1UzmEsTHDFaEiW~5QeIh74?HSO9 zDLmpC;-T6Koak%+&4*N!J!dcbJyN?s&LOcW9Thn(&ml2>w_V#t)Ypw1pNE^q_#8h6 z1TF7K2u71wBw~+U%uJ!7leJpNQ2L%_HlVJs={wHC<&`*wJAMNqYSq<>8}4Jc~StnfGWFFmL<~o+J!6;G(U<^SaB@9455@TbNUwfDQ?Pu%$XXgqV8E zMLrucAmNscM(LXntqd8Z2upk+6kMc;AQ>Xpqup4^Uxxq$v1B6fDUvw_o^XH zmMbJ&DjU?oO;WIMbqac+J-oI?C-W?Unow(he3rEu2WR~kqzQPSyPQXgMk)&sXK#ui zZGzbETgz{g)<&!@?;AVKiCQK7K^~7>Dfjo07!WUx>+PNR{fGxJ6xTe2Un|DJ8Syl> zQ9^Orjz6GeQ0uR8^;`V>6M_{#L--@W#n<=^h8L?Q+1`zTRg(I*IY;#GKKzlPMBl{K zxA61Nh{lXTcDh%m);~!FU;O?817Bd^3k-aLfiE!d1qQyrz!wb%ct^t7c?j4B?1MaR-|UQrpFy-6c-!hy&+z?-P;%qMqb?m`BB7 z#n{Gv2HDQAS6Rgg(Ms?gbKrwS)0u#xdSjW(a&xQ}vKPVHh=UsCGrEtx74Zgi%7PWA znh&JZYSUY(gSF%n#fCpR2MQKTOZN~B>3v4spY?l6Qi~*XNSvA1{{AoP<<*F_!#3`Tnz( z$L7EHybQ?(XE`h#TP*>nR;(a#)r@lMijnZB?d7A#z${&E)s$9vq9W&@EOdb1e^d3=neEDd7#b#MS#gDk&7HAe0V2HtSS!X>=>pd;w1^+11s`7bWDC2$36e` z?VSpLj~}??jt;($FKz5!w0feU64k?H$2_HB{T1BbCmq)44#?jJMG~1rZ;5_E6j5?WzBlk~U@7^1@_n^xb7A!r zevr6jEZW{b`+YLJ#=SlpjBZ5XA^2q(W4?%-s&VrriW>3F$?8K^WQ9)7vEev%wZiJp zl8ZjV02U%5jSg{esA&j?>Uv1zA5d}FcxXXAlI_aiRu+OMlK28WiUJvTQH_i!0=bB) z#dK7WT&&oE17mEip|FklDaMz)vR1_9S|MHFcy4?Um5*%1Mu%w(?aJ@zb2LcRAgB@S zqYT|Bwy}=xVuxv`o{CgJnNS{NmB1`e;$-8WJV1hfB7FWzULsOe*d#BF3N*>(Pj-*` zc=`y$tyA*QEBU)v7=m5NmB!&4<>O-ulUm;cf9T2IV;8W06vF!ba);`mI^vo;rTXGx z9S7BQD)9U>zdr^1)2`_MDLw=trp7IUN&Ox2eNuaa%A5fIgUX&tdvW;vmw%52v0TqE z`^OTJhh<~YE{pO=)ldJ_af5{d>hI~`5Bu+sK%MxUh;qO|+aH@nh(q4bROBNXz3QKFbxoAJGcq zV5duF{;c08);t(q9Pf>xw~C=B7{R&$rv-6Da+Gy}QuWZW(4z!%Q`kt=O2>950ZZ_L zp3HIbBItqdLd4<`R0|^W^@46{dFmdD#<30|pdt#Mh=C4YOsBuWZY%v6_UB4&dQ@zt z5z#|u$elFzcZ*;j5!a9Bk+{7yVcJWBcoY$PP20Twgiu(8pa0im(GXeeGd+8sjHVjg zzvL%?SI3KZ zBLTkGet(+qMV4EEJU%80+U0{m+aa(CxdI24o&a_0-%CVYMIvZLrv{q9Cebs|q&Kp} zs;#v0d;W_sCwEd#l;e4k1ggFPZPHueRR4}B&LuaoN|Hzd5BNH&~K~Nw9N8@4o z`c4`Xsmx&YK?b+Rk484mY?kq>bz1i>8usRzy^jzK%PN6foCSgBM2EXJL zNF8&LJi^-X*A$9S?RXM>Q*#cTK21!9#o86EjT&OFx94Og%(l65u7-)m&~cmP2@JVQ9;5=#5t!3 zny6Rn6m!5je?c80qCti1_k24Rzyw12oNi!#OCUI{IChp2HK7&T65gMTK2LU@bV6mQ zKl%Qo^N+n3T!S4plkHQR3H1ra6M7SP*UQ#eF+^wCrCC67IZAtZ?bms{YW7;1tKtHxJR!FfQQ4W${Jdl+@$#gwU7$QUYQ0J#{eQUg9%g z0To62xd@d-`+2EWd0xS;73dE=uI8^{+ij95RVzL(*xRdCeu_%44_@xe#kBsV#LfwO za@y_BKUw=%f0p*A(rC&4TjV$o>ipu!$5ju_jvuYa~X^7=k2yGyN+Gy$jbvP5pkn94N_g_}e?r~9X=nzQd<71I^U34c? z=lQ{?jXiCUhHvkr9Y&2VFK4nU^u@+!Cl;zip%W6<|KKGUErQ|chaV8BC2WamQQZ6A z4*8zc8~OdWhI}8Z|BjQquO9M!tp3Y~dJlcJn6i`_d~>jVXuOfgNq(;tI}-}D5%Y_q zM{yrl;$-*1Vqv_N{@1wgjG;NHzh?^VusmIV&y^nxnxx789$TC4cEho($Sf*6K5_jt z;5*sh{|ok@)3MJ0UUJpi$klKL_O-*?@G1;6C-OGb$C=v(p(DP&iQ;(28dUjR$iGzO z`)FqVP#z4_Ih3_Q+8dZ*kE(VK8jYEI_=P-7nFJt1BQ?5_%rw@qrok;;oY>NNCvM5eN0lS;QJwhw!dyDP@Gs9` z0lLXGofZe+@&xA>jzFT~=on=17Y!wzkb=Zg7=yJ35|FYIRS!bINghaNNQ|-kv+vvT zZ&Bh;fW&w^lYjUta4<<^Z%jc#GdffeThqukw=dL8>9pvd+`w8Nk`8IwKN!Wb%>S1k^M3~W zZ;EaeAAFvJ+lFIZ>a1$r9@X6y&5ZpLX>O{!M-A*^s}wcxsoJz(z}H3a?fx$TUvSkD zk@yPvEg=Ok zXEXOp570i%OJUSO3QOz+wfidL{%O`8(U_BNqYoLM3lJ?n`rmJ z;sjKP-RZTwSb%~Qx{vVG-$#e#5f3*#0cvfcbL*lB@K`7*nUF{lJR8;{N)qhr1)h-i z^*XU(7deZz@a?dG>)V94Apd`x$<(%R5pwFYs$GyXpj1#o5(vy&eL24r-&*9ivQ%{X z?Wy%Xf!(CWHuuAJFShx*((U6?^+Pw2OvgXT=R+Ohcy$;Kgj3O$7YUzDOa#Vo-xA2~ zkqKLA>LXPxT1E0%_u<6u#T}^|Yy68V!`~e>Wu44loh1K#R(~u8+p3<9e=}KrPl9Jq zeaY)1>EFrcV~1ouF0m*ep5gz^^RTai1k~E|Zct;;2u^bbyew;Ui)< zJ6YDMQPif&Q>c9=M2h)5Xn+_XKF{!(U@^jJvtWK9#RWcrU=^gBGZmwVIWl3J;00Fs zN%TI!KnM6KBE{*Y(}aAV63jCIGoXc5kl}@0edAknO9zQs>$g#|Eo)~DNhwAyRoZb* zH^iopi6ZR)_h7d-jF{Z>+Ofz+td`D}*ZfX4R`NXb(xy`QqC>TB>=gOD;w^}fZAoj$ zn9CNsGHPpBz!dCu)bbHgAVw~z6A4Bkwa2%MoAekE763tRiyXe^PXxVlLz&Bt9-Jq- zik>;WwR8Elcj+A+yDGRbVqwOs1#%~R6i0wZ;3tNj$-j{3p}{0{kIqjGqzS~>I$?sr z>bW9qTD$N$_%a_6y!}E6J!}>V|3SC0FkotX_OJ!3zJ}3QjIa4Ge=eKD zXbTwbqle#>4{>A)GR1b$lNP-gpaM+iHO#OC&c>OcQWOFQnUB;+pk4iRm2Kvm>11>V zva_u_N~zWYc75@EvKgajFa21gj}^j9jTb2q#qzq?m8H5V9O}ww&4LgECeU;S(Q|GM zosWTY*hG0W09eN18;M|zBZCC+gl6JDha{%mdkOa z&*pT&QX*I3HmlL*nahvAe2m`ZV5aAYcPuT~u6XRv>X|QQqRgLAhR)4PgDtd-+RQ~( zPTvQZB(?lU@UgQrxO(Z41JqK>nf^$jN4etBO7RG3N4tw`Ake-Cp)?qCv>Ms5lF>*~ z=84e=hQpM|OWocj8Qx^1#Qx%vX=itDswBj9QZ>p;wlcR2oV%R>f#$dF5% zZZ3*v6BdZWk`a6%7aNrS#ox~Z1GKzb^IjA(Mc!^QvN^LqDS(sMUhy#)#ORIAGmAG& zEHBs>_^=-FCWAH|&>W!rPdJ4t??w9Dk$t=XOP#&qC_W#PeR!iSwPb^}sNfY;5NF3R zU_b5sM3~PpYtUf->u2qc)IZ|lkLCIU|3G}J2K2B8C;6L+-z4ZOrzy`%`_{MO?OR2| zoNwWCY=<9i2)>%-wEk!hS%gDajU-o96*V_))H6=dj*7DCo|)qRk1f1|4+tgK|H9f6`8FM$vn~1h@aCUE1~`S3xpaS+Abb}sO%fK z1fD~8mxurSXQ%=nI*?6py9Y-PZ;wo92b{q!C0adMw3&OmuL!8F@$ugvMYZDs6XN8T z;TRJKmS~SJ_coIwas}Z{Z}in?0CUy$U>CCE9Xf?$hXDf?9*56|Hu%gR;bZq9z!1FA zVO5c#)7`6uto^>|$eR0JZC#ya$G+wgthVXMsT&_fk) z>}sf7XndjabW$LUIZ*DR8oJA~u%Ri3M9Ij6?L>>#2OYo$D_i#|E5gyOxuNpupePi~ zheFXb(YNh)m|H2K@>~-T&yVzoDVDIWWdSU0gZCvMa1Y#X$L6Piu7$!itUS8~H?3qR zv)RjzGmowLY7-)KcZbKa=e0Y`8_mU-O-Hl$f`_J8&w+QU?b&0I#uVSKdo4gC{{`-v zuQR^br&UM4c)r?;_ZwyDJ+ky_+{N=*iQj!O{0fJPcMy)%+YaMsMICF+YpkhIE}F zEy?VoLlAt;cm)m-!B7)?`$E(=kq^|3*OU+Rp2wpM4YumLL4PFL@g`gdnw9u`Cmx@} zpFd>alcFEi0yjvb++<}-D77IS?5o56uOK5ii~+M*xsxq$eAI)01jI1l*v;?I zfgU)E(svjDdwDLx3!>btm%s5?6x3c1f9*dJ=!{O)czG5QjiXyQ6f4-B9{!Q&z|(j= zcpR`l#P_LOMaDx-0R@8lS%Yg76Id}f{S2ufmn%G7#9ml+l}ya&=t?f}aaMJ7KU%PV z`U(6o0c$P)`A-E!Q7bSixE=+O)qYPBS-Ij{jQd6fCO!|nX!;|)z>2mX=!;HjHR<1yQ+Fo zC%BH=L4R@J6=tSwMuP4+4j<9E@kFI8kGz5_JXbjl2TC4(K0?y)%*vrXFn1XiW7) z)xowStHru>D88wLMQXn;*YI(G5*`bWZ$FLo`1{3Jw_VvZ==-`oz(cG){d*+E#xR3_ z(Q2h9=O$?V!ZfkLKVhdnOmZYt=vx z|M5=*%<(f}=6A&-M1%NTkLfePh6}z7ZgbiV+-1eF82UjXd9UdVTdg|;;x@zGOz156Lc3uVyNmqhfl zPOW?Rxo}?U9w}tV&45=8l1k(%kS4??e&68AFF#S`N_^$*{5kjt^~y07p8X-EqW)x& zy_I4J;p@w52ZZ*ro^T_bFjJmEl-Ltbi2`@nw4D(uCsEa;Ryv0u<+h)!6~qRqmfUA_ z`sdZYC4~Rh`vfA|x*@{}7Lez@C0wC$KHJ;AiXy$*ds)(}_xW#1GM{$K^Li${h~k z*ll?9kbLCjtFT}~S{-WUV7|0H*9;5|DN)X&YU*S)ZNH*ow7|;sL_5X)E!Z-sf1_jl z>%vHgrDQ7&$+Ue#^{<~^z4C2Q4fPLVPZo8#OFr`QA4v1EQ|#ZG9}ntZwycrHZ#yt3`u6~#at>VigU9b6z53`|(f(olGM>PrL-LWA7YK!J z*zs$7~Y}?YuPh zDLC4@4}d4lN*{(QbscD!w<9KfN+&5Q_Vw2RFUVZ%044yoaV#&y&JE>R!+gw|92;j_ zn~y3uhzqBSubixw=L}O+o*WF_a0v-iFpSHyf@}G_rwPjlblA>shjyW38QIGxp;t)_ ztU&|0r`5oPwfrL?u%dyj+!kwKtA{^!zZjr|p5_lzJI3$tV*GwCY6DSh<~zP01s4ae z`cNB65slUIpiycQe;`(BlZTg}RNEd<*^q4TwjgkipDieWbtZ+f9)APZ3C}HNi`krl z-=v-iARWd@*Otj z-%B+A4)2c6zZJ&((`Q&ZD9PT^j~)aEoc8r(MWcgOH;5_Lg+IWPoMQ8zj83s7kosd& zZ1Q(8HnX9%IB+Zn6v%bE$yDFbH%hIv4l_g$cWU|MzfgrIEOMdn2n<&Q6bGTlB35kd zy7#3&2`f()e&OlszoW{X!?ce{!^+cDvv?gUmItLIuHsk&WAkkr@Pqj_An_ro>&7Q^ zeDv^zP*-$(fSHW?4)8GxM`uxJR~e2-aHv+OMZk<{7;y4-=g-OtXA7uuPX-kg&PSy< zc^TpI!{azg;Q!gC1Gv#NU!|DDuNf~#T^ZQ(`aT>(9$VHj(HB;%w*R%*;V702RLr$}pFofe zCk!@@@~)*Fi=}5NVUM)RxDdIFJ2N zf?n4^p5|NNvDTG@r#<}p-@>S2BOD#R8?eI$E6$rlxE>BB!SREr!uJ6Z1FBmvlF&dk?;n`{moR?!nZf>g#%-hQn}B zir8<`M@Z977kK0*O{1a=Z? z89`+{s71a)7=l*3O>G%7507PaY?tbWySog#(?X(t1*Jo7LI5W9X5#00)F!|(&1foI zei;iCZd3|j`8(IXqg?{w@h!XsUWN%Rs08kj2^;XYqTu+_R}hb+!D=K+@q7P33v?lP zfua?fw0d*FC(Fn97Jdt;_@Gq`o`~bwKo9;ZACTpPGXDgdT#%Pw7_!+*s~pueI={fi zRIMUKn`Z{Ospf)Cqq>KatGh3;IyLnPNZF=tQ!Y#GE;9dr?0pS3SSxaXH zCha+U^#6Xl>UsjUwN<#3xMQ-}OdeIw{(H@b9sF>x4&16AcB_Zx-~%6+><3FDFp~OU zaBQRj21m!hnE(WKwG;Z58S)_>ewWbo1Q|4^=mnbV;1v=pcQ{h zR&h_mCv4_rv=n=$?_cj#ocF0_FlDg^vooCAc|*c{tF17_efiI|4BoaSTP(jk2VD!F zkp49||3xsy<%KA3?5@!lfN?H~4&M6S<&ks2Jkh~BZ_UVn=+f;bD@9khXH9>l@wo8P z<+v8%`@pH>q~ft0|DY{u4Nkz|M|lD^bxWIn7!y%;!xiC6uu2S%Z0;SNvz;3n5NIQF zpFWA&ci;LnY0zEvG)4aIg<7!NFcv~x>E|=O@@2U0Yo6|6^i@xD`PV)6>2BUGd79!c zMoFsjA%%=KR$-?`CL^p~VP`)Dfj+eX37Zigg_&1W&_*nuFeTyiPNrlWXJx#o0sweE z(QO>VI$F(Ts8GQ8!7dDP41X#y{67%B`(OVq)B6~HPvZ9se$U}&p92P-2PiN=fdL8( zP+))p0~Gk%rhwA-tt_>a6zTVav^9ch-C=Nr^pj0WupCV7EVi5BczEr5fwgOB9q}gY ztgw!AEFq!L5`%)atC${yC zhAKa=|MFv&^^36w%SOmwBt(c97_tSF4ue&FVnF#)+!~v%M*rIH$@YzqXl)+nfnREW zyXJBD|F-d_17ZvRGp~>MJ%_7Uad?aTf9ZF(_BQ}Gx=9`|C#U_g8gB|qtnt`7koUc~hU2}l`#XQ!Up(8pFaPd;YP_b8rbvJAbb8DG zIQAfMF3#dv=*Qp0q4!pNogs`fvpB2?muJ7=(?Y&#f{!XZ0vlqSnOL6W zB*ub<^Rxigl060xaC!=U>6yoV0I@&%DSbVSjz>q|<+TMzFnBLF&V%4|@SaZM-!Wt- z;C6_qp1g~#o_gY@t$OOQo=%xh2k$wpqI%7MoNvz=yYrq-yy0o;PKoBp#N`Q2)@}83 zJ}w5GJlLD0MiHLc`E3_?h|ebV=qSEu%n0nGA`V~15p(XA!coe5e3}9}GERRBRJI(K zbYxVM>bCb~j`s$~dx7J9v*Z1Q1AdR=eL+I{IH#zZpY6LoACJ??Vwi|Kw{nus?Q6Q~ z^ybOOq;!Ao_$WQ64O{CD7+-Cq)NDUHO&sI#pXn!lRX_2w`iZ}^pZLsv;(u{R-}pb% zPyAO?#%ub2+}@7y!>#0W%a)q-f)7z8lq0GPwsMbu&4V=#XIPk7GI}7 zi~iov-hX@Ih#~&`JchqsXq`NhnDM#HBx{+D%q!~b4C@n7pFen&s?!G7ZX>Egj3 zn2XgkY<8LEXWegujWRGSVIzB(%9+CTyRabg8++Eo{l!xfE;n9YC*(+x$>|m@KGP_U z9(YgW-~Vp(aQY8+;_vh~?2P~4_MX_<$>(wP_9Ir1y-(68jr}KlFG@ zviuwA?^r)Y=Z)C*^VqvMlp-dRII9q+k(|s9-47Xc?cmlqaEhY+E;Qe{(iOfSwm#G` zXCur!8$>37YKf2zE3 z#_|H!w}o_qZt{z~$G;(C_n(PXTWnRRCuBAO$z<){hdBOy$czAc`2|{R_c(u2m_QyGvXZ?MB`{SHIf1Hbe z9ZJii3l-F(4}Y8~%T)TS_PGAO`Q&JAgy~Q6_sE@zwQvnmjlY_+x=u{0&Y6)l*ENkgcjHWebvkm{$VI&3+z=X z|6gdUEs)0xk@9P6C&WfBgVc{c|L|z!{(qB6nM1IEE`V!sb@DMk);#m*9FQU0*v30t z@La^S*wXMDZ_kF1HGD0Snqazz*<8`&c<(-j9MLiZhbjHXbIyaM+HttxB%cK|pND6` z32S)f+}0^i@BdY1^LM4J*1LI&e9_d3qD=Q6yt4)Ai(*s$FanPO<1%Ovu-u%}{fp3j zs9e_pT;9jp4*xrk$S8jP zrJbKmhi$NfOycMGrWEWCw}Y5hv+X%ZA4w=CI-HGyaHTbfUCr0n$h>A;?XC%qtd+q8 zrzS^*cB4jM=?igK0Yqr4`!>)^!=dkhe@$Jpy%&|I?MAOF*iXx`Uif>DFDOH&Rn#Lo zDq(avd>F=pDxV>}2!Z`Ds0{v%<@Vs!9HiRzf%-O>7_ZcD z@hm(?!FAfz>=Mxy@GJjJB&pVo@^Py4mjsM}|CFp`j zrEK`;;-TIg45HcaZ}lTNMxne0&&QssHpRm5JS%7)p3e}U`-xA%8R7GKDt!Dle4f~t zfX}yJG94e$F&T&-V6uu+*AP!N@buTBfH>yC*!0Zq7cm&vrChI{zv%Z{6?h^$?1=i@*+OnJSThl{&24(BsZXT|3L{tvKnd3pA!T!Bc1;*xN zcYg^E8xO!sBfTKX_t;w($Fh#%9L%mv2V~EV=k9dRzp?vQ*xA7zwwh1Imf7|jQ~RSR zhPRe%I0xEQG(F}3Ib=ud&a6e2458|sV&20qXiX3b$?lI2&2JCD%WSA3qx$)Ec zg>Z-p7)z?(k1Z`JXR?oC#0(hcLF&Rpih!lYFTX|+tS4G_axIL=U7-(XHQVSmE)qj% zr0Fy!S7BIF2Q)ha#_JWpwd`bTw!-8{)ZI!vT4s6iM6_FGC0nw9fDk8f_yDqOUi4K+ z!9Lal2cJLLx(Kt&O3W@D)Cjro^%HS~x6#wGG5cVFmmVfZb>Dzg%|Gu|38OvSg~@AO z2xUF9EQXesd9;U`JF|`1KvEctf-tod5c*hjh5f{*H|uj$%8 zh^zQ#ww-?h>COwjng1Hhq+7O}Y*}$yK7kG^@0{&5IKiOV{Ig}!zRm9q-tg1dm@khx zfWwVPjAb?N$AJF%cq~R`@Hpg2QmFg?!5;d3wk%GK%>E3s;*@2lv8>YGvYDt?^v-M) zYxTFSGcsm|?@(QAmM;5zRd!+*d%si=LKo8rhtJy#X_~$G=PIsyFR;i!C5Xj$wwYKQ zGhSS2VR0;AY+eB@0OiQ|jzDy9RtM(w#qYbyJz z>FJ2Eiy7p59;*?Je9rdm zh|o3msUn^vmv!Ba2#gm39e8XBhaL2Jt3*fvP_`lHCn_){efF!k?!B?~aN8Ynny!?|^#g zx`4@#827Da>mgrc#CXC}JTMVJyWWJ@k2wMp#L&$ngtfW{g**oU<1PgQ+{-y)$c!zK z*#I?PKt;wYvSyiNoqd-{)~AeRw^?MxxwOn%z+MZ$ix=4ZY7e zxWd8+XWSZY21Xhp940JdUB7_tK;ij_Q4=IGIon@-Pbhb@;Fh!fdA@SCWCP{w_%FOJ z23uw7`s>mjF_y(kdj$1IX<~8s?s62HWCaMG-~ZiA@8G91y?gM>YtQtyOA;=%#O}S-;ZSN%Q&D1l6w|PUzlut3W#b)%Y~AQ}(iiTIgB zWJ8b+W1bT?6~}Sgich4{l*<#o=%6d}wr57T;vvHhy0RF;=>5OKF-3QN-nL)4VlmgG zJRN=;LZcq+#$>v<8w2sM#bahc)R1ky7>I>{-Csaq|AJpVe%tUnh@T5?FEZSJJAb5j zW~K`sN$8IQk6Bqn&h_D92-vnkh{VNt%;w%f8?M5s%G&Q|dc{@@40WK5SjYoc4}3ss z_Sp-Y+da@|9;F;IY`!#e+YmEJemjH=p56=?oe@T5^UGd;fat6ikLW)E<133{jkbFv z3d_PT+x&gPe7B*e&x>x|2p2!Y&EjXW_-WWNF4=L%1aO$r#jOGy`;(9OokXO;1-utlv7h}Sl1HK2^OE&vJ98%e;}8Bh7;#FTR! z*kFCC@DW@znevFwmC>QlY>dJ;2k!Kk-+&lKtXt;!h?q(0iA*|Pu;l65fRP<9`;x@6 zpI>B>OeK5+qXhu5@p~bb+p6;r5?Uma{nF^U7=|z`$1U%oM{bMd@|F0Pw-&;G(x9As zeustylRk0;%iPj2UOK^!WEg=L0reHI<(ML&j9*k3k7hX<3U+$^IOMbqC?8D*lob_K`G|pF!E*XR@ih}eH%$HC%WZ}5>?Oy(Z59WBwR~G zJrn zt#^)fg)iKC=L}b5q+E08=&;;4Bt9IoySww-;brIUPP9%qJ9;;#yXa>PT(NCB7(PPh z7DX4$TO24i?!giXZB%Z2wm^`mB>DyMR=79Tf@2j{Fr2W@F4p!^r3i_q71M&BIY(|j;%|8Fy)s+#*2AfG#{ z!$s{tYk_dTq6IMZo|1-*O@}km zH47AabOcy>#AK`~crey%L5W-F2(bAsY#%=+CM)2Ft&BWJae=QDhuG)h$Ij4(uJ`;)74Ik#tR*}|=MC@!ujC|m^LrUN+u^&&HIJg#oxrOHcqxEGWS0M` z2H0*qev?)IcGMpLO#*f=Apvu~Lci5^D!wOFTqzJtDn4f15r+cbe-{D@s0gzHd^o9x z=oJDz^7(u2@YP%I@^~Uw#oJ|V)?(v4JGec@rEze3u;(=w;O6-7*+@EL#z(_P?OHTy z`*S(l+t@PiVx2`CO}|d`&=D~9-!2qWjr@S|BGwD${sMgcZ6-YUG6asQ(@g)gyXG*KG(3&EvA^$(aJCYXL&qz5t#`Jc4G9G@-3b`#F`i?NyE^t9L(( z8y|b{n~0oz*1!LHDbxEa{Ce=a>{zDvO8oeKC+^?C?+^IxGw%%3GnN{kejOj5#2_iP z2iz5&665p^LEm({Cc{eC3`H2YUF-RtIjkJUgW<(Clp0oc#5N5N`3@rNDV5lCxV#kK z_B01M`dc>str?w#-# z2N803=rLxQsj}oejZYqY0)3-(o?ZNdm*PN4>mpMETy-0~Xi}8r_6E`e>OIGqMz$Q+VK(q4%`23W&t)q@tIc&O^qs7nsGm|eKX5#`}Qr3ACo;nx4nr6tFquMFSOt}vx&#z!MJh*u>7=LB;jX?H#6$RX^JgYWbb$_=YEb3 zGa;D{YMslkwQ^ZBScbtb!B`yh#3=EJgtM5IIoI>#B22zfKR#~9VSC4G8!w37Bb2#^ zq}tz`IsG^^bI-EWTg_4>CVFja_We2ER;Q}gdjipn=n;5*Yw+YeT`(BOM^C_Rir;1w z>3D+=N(ili$j_rI^^KIK#-PmTJrshXZhSx&J0AQX8=TKS)Dw&49|W`Je7imRQhpC| zMCVP>vQEzSZf1d97uYXn`%m$*t6&uM_%GpchB;tPYMq^dX|Z*YC-esyukOWWMl(O5 zpk~G|V_{$pnwS~q&PqTfGkgt)P8*E31;#u(jKjwAI1EfzS%d*+dJ=qGZmuWv4gd#?-vtG^*5Y=<9S4dj>)#3?0Md=R!RaqR z&w3*uenEK0=M(-Bf`9X~KAS09bj1jK=`Z4K_3w-2<0A`3z&Q1$Y*u5Lq$gsmuCVWo z-LGF`$EfXrSJB5q?&s0Oo2C+;yX`9sQsdX*T*~F2Y!%&wir?ecAWoYtcp_lzD1g{O zFRMjh9^KfxAELP3Mjd&-R!x}f7mW4s`+%4l8CbZn`0e=#wy zLh=%w_potIJTF+J^9B>ir}MGjRSx0E;m!DNDmc3e5RnL%e!9wy=c>%gw;Ml&ByLt7 zT(T2z+wMk9Qcrmgkme6uqZ;>fuOk`uqE6!eB!2JUHx^m1!B3BEysP$~Wp96PpY1Q( zf{E>~ie&q{zHhdFr?h_~+J8LOO}-Vt3` zkjm3-_rI|eHedJssoHM%DvNP1h1?%!+`}``cC&zG>b9#TZ91o@wtMuas_hKizl7gG z{9eH?1KG=3_l`M@t@UjuX&($#L(txI6SOJv4vzF_$M_llmS9ixKj=C#`eiS#-x+}` zzWVUGQG?YBCPd8@V+L7!ZU8=A4Ob}-%#h(MLC_IDnq;1YYB++kES1B6H_m@HaEq}h7c zjEo3;y(TLkmKyC>3Rux*&BAzW@FB!l~_L2E|8EHGb|n zL;8FGS|jpU|hBioo-+##f`K+1oIPo);4OJg)N4{z&wEGf1g&qw_Na z1yw@Zzq}!BnoNYyGSYX0%HQ@Plci!5?pzK04Nzc!0s|BnpuhkH1}O07q5wBF?Q<>P z)#s11^-j^!{Mhs&e!=)1tT@htY50g}4t9%XwZPf<%aPyreZsX3i_9Br1=t>?O0?C1 z2uT}%9-?!cW=`HO&3~?p*6E}2e>48Q=8mlWzstnOnMbLwD_>H+dN<(sV)@ZLy>S|Q z+248#mVoZ*zl*%VEz@jl^5THf6sWDv1_4A>Fn|Em>Ya>e3UcyO|;ns<9o5R z85GSP(&Cu~pG;`A-=%A{H06I!_7+Abm!Bs67t^J;nUbmYchU#v-A#TFKauLxdz_|h z3o~B)4mvS%8Vx$*N&gR%zwP_^w3UDsK=J2m<3BL@69*d;sC{$jlN7jyc1G~XTP z!IS(a;rm>G(CXf161qQsE)IBa7r_yU0t$B|Bg&1<Lm1GGw2sKjnf1!_QVa+=vuO1A%xzBIAqK4N^g?&j(Cacb3=*4E zu}-z9f42THocyfTZ@#5A{eD{g7e4!Z0w2tU79QpR-@yXZHN7{%9N^;1WG&Y^S|VFk zoQ!|o!mXn8=Pj`IkM^4DI%s*9Vy~-ZixIoVc%6qc&c#{M=xy%tukL>@)3(0%!-&3R z4!&yePx#_=%k@dCJJ1ha8nLqijj)k_3jiH59{41`g@P{z-!$H4Rb-LxtRKiv7y{sj z!4vI>mb=G4!|j9pO*2Z$q7$EFGmrmK^uYfA${hd9^!<(F$G@NT!v0uh{%@ikFBs#G zPybnK_SBqNKbZb}&aCGb!)CH|(fhF#w^Xp{@%B$JBkKpFB>x$Op7Dil;9w3kq3okP z1iuoWj1Wj-KZP+kjRD8HOQgjT3^eSjH$<9yw9uW<-Z@W$A#C=-=fYn2sXvIl@ME}z zd{iC%!zYA*V#RS__3pih_$T~avi-*|`u%PEaRfF0po&y~z>EgIAL1V>orbm_MzHix zPc&nmDLVIOpReYdW%l{TlWM;u8u2t=&+n1n_gggR!O?2SxXr64m17KkR0VhVsT0Ma zEWM`!)?c7pFyNalBLf)3s9KMX!gMqmo5!y$4fXy8*&C2Ow32{U3m`bu9ntFVS(||q zmqyh>F!c&5y1Or3n}J~LN|qbnSwKX*GpP3W{th`On3(x{VtK8zf9?UuT&e5>w^V(jZM?LIvey>>RFcrN&-9Npzc%l^+b;HDUF>nQ5n!1)h zb__F`x^c@q8+FSSbc3$8(=4XCzyoh;Ny5fi#*HBw==V9k@aX1c-rqV z=3*Zb(7JC%Ksg}85Vmz8N{98vuEBG#xBl+=s$@>u?9CR1Au}5WM~W~ z;qrj-=;hdY#;yg76!&qIE(SaK%y!eQJ@>8L$fjR_k(f=t3<0H9(_5|nLjO z&{PJ5To|mdBMFyCX!sFp7lby#HIO7R{%|o#mn|=`kz-I26dcVb$7qXlXhurP;PHi? zk>K$K$Lq&(*f!UqFGr8VC6$jKx*H)#a=#ONIiUx9dGI5O88(lZ3bW?cK*lalfeZuB zv^HFzyCEtWHDcF>Ri;2pE+T zBUtq5P1hoXBLm(^InWgEblPST6>nqAI6RIGtQ{H(snUuFI2(CD&lxdhqpUU-0nN|m z?f4C5BP_zj;A_E{yo%dIC*%_o<1tp-ut_nWx>e7kw<%=DbZ|~Ij~$ms1@hlUt9Vqe zduXKYb^m}Tv;-4;7<2ppjK6&f#Bx`XLFG(LESLVjV!5SqCDX)m7sz#jZ(YQ=adY>u$N; zC)fMsnkQ)Gx<{^i<;r<7miw4o+vNI`T-)XPtXvPt^{`xz%Jp@*cFOgnTzlkt zO0K&E7xqfdgSVst4pq@vAmDv z_R95?Tu;ii2iHViJ#aXdOB2QX_H8qHZzy%)6VvKEE$V`CJeHd$*C}$HD%TluEs!h6 z`dBUO{~0rEAJkKh3S!p55wW0 zaYZ>aLa^2N3Wg_LjmVd8TR(=So!58k%Iw%kX`#cMsr z+Yo44{HK>=aP~%r6JiVWP@a}~ZrZ9!xpEp-BLu%{xdEZp0=dqUYmr>}3D8*X47pB~ z>lC@>$#sHU$I5k#TzzsKE!RiT=`YqSS~+3 z8q2*`Qg+Jo>vBCV*JE-$iYx1JSnj%JvD`zB_h%jN?T+`S9Pe$8_s1OX`yB6k9q)S_ z?~gd%A9TDw;CR2^@qVA<{eMv%_yTMs_5{sMB9_DAH$UxU@*zW=YnMuqo)s_vC!>(E0X!vfw#JT z28cwZBV#3`CYk$@lPnB&?ikEK@G{P^-F?3BeiAQ>s1`kx58LIRU;NS`vnarji000V z*ZP)^KZLIR(xLo8EZ9N@X=uM(oNeY(@8!{XGqMilJYG1Wb@3?p&w9LY@a;Wgv)-Ei zGCn_CoIU-;aLsV+Rt>q8>g!Iq=15i?#b;d4m?o| zPb90a^F-c~QnpW4-_x;tP@h1?GyS?~v;g37;p z99=dVaCf_>9}Q!yj6UC!^UZUSriVVj=%yh77vt@Z5<+;0apeSQ!#hX1;4s2W1ZDFz zMRd8c+#G0)xmfLD)yM*kZmbFMJjD1a7M|U|v2qW1(A7ZYA|3?{o_4h!~}(Ma<;PVhUT|FL8iVZ-YiJ7btW6s6&}2GOSX#)ouileJtmbg(S=Y3ujpEdtbnSTzL0PnoD3|8=q#KVP$e9n7l*$XuGie-4@tR)qR-~KU>86A z&|8heqWhQM2L*+D%v%Sp7vwb^0@IM-z?!$~iE z9tVI3Fyh(e|229%bbTY>U(dK1Dv^Gg6gB2x>e@E7s%c$k7``S~9Ox>7qh3{kwWC6N zi1t=AS^nECk<-|e!`9sNh&McI{1$j-6BNG%RV8GnvJr5}3pQ)+l~3#xOZQe!zGGREjg7@p&8`LoIAHOZ6zIX4BI`KDBB-Ue4oXP=0$scb>5qdL2x1 zLELc6g%csZT;?$*pwQ*r_{zQ4g+UTA0izzn_VN`ejTjCehB5LXR=E|V#hCN*955?K zQ8@GM1SsQ2FkRm1f8*aXcCPp}o2+@}F=6x}Ylj%mdEbw94B|Vr?8ftu>R?12G+w|i zLFmEj;b3|x6qLpaXAyJ029C4_{P$)M&~R%D(6*^TkuEH!+!3fhdE(Vp%x z(Emm=7RT~J-r?AzwLalDpEZ1T6bF9xW}HWs(LZ|7$eGsfP?6Z6so&5?p?1TU?zzs9 zekr2F5%Uv*E%Q*vm$9r7heRd_mG3w#~iEt4{1{t53Dl{^I6k|q%%E=CdNh{s1 z=1LF77oK9|_tb9Po#nMHpqKzES|q^--fDrNN)aC)0c0^wczL2QJ~(as6s+GdcstTT zw}lmzZ6%ffX}3UPx;P|Nk@)wVjQn9}J!d@1gRC6?gpDyAsVG4&SCaFBA>?e7ZZ8|4 zS=o~~uyxA#zr!)CFFK80 z-Ews=uQN~Ky87OcK18g(clZdoUDP9igHFmV*O1$lCvbbloz0l{M%Xlr6D1ojz!k`Lx!(?Dlq7$JWt! z=b)+Hqq3-JSum&<@B2IPseq{qfim7hd(o!GkjWT8|706|G#QrePhvV2a^iK{$>ZH4n@>(C$$xG3(2D|K>b2(>pTn6~uF1lf z#+rjC;a948IHNN2pDSP5auM^iZaG$x^VR6@Z)q9QvccW5$kVbEwLBIr?!;ne+nY6Y z!TAvCuC5CQYlmTZe*EgW!Mez>L+`x0He=bxpIhtNx<6xi5`ge*fXMB+A(k_<9UhXy zm(5%`IMWp#J+pzgkuw8>vs~eG5s2Fm1ZKG+=S6280y@n<%xIB^?!jijmQ$cBkU0S~ zWHn}Np1gL*vi#>%sjVxHwPdw4xm#}Xw3Gnz=d`?icd9n4u%hw-ocT4xQDswAyTjT zkVj<)H)8-l>3f)=;q#jh`BXU9auSx`F3wq!TI|mh0E@*zD}jos9FQ)@;k%?919&sA zvN~P<+pRLJh7N-S<$;!~kAfP;eb|A+Oxv>KUd+EOtvh-BB2I|KTE4uM*ZvuNd zy87NPJ|ef)`{nlKu-tYu$n9&ja{E_U+{GUF+>Qrui~8LyJMP0nD<2-jLx*6%Aw61f zdTpR($8L$;gWc_6Km-|pONW0C6d(m1{=Jm2Tl|kI)cn@!@5OY+2@WFl@SJZSc<1Yl~X_Phn%vCT#Bj|8^1rC%hsd80Qga&fd^ z#7y6yFMKR~;mk{T8!_`z-iFU~75pQR$NCVGkZ~!hmPE7WToTk$_Jj&9gsErjYT|{h zNpDN-7;h_n<2$t#&Lsco{O<)?f<`RTi^0*9M%je^p6Z90?=6^`^i;P%_=Nyfp*)T( ze#>5qBrL1|)K8u5S8`)$YY9H#LQSIaV-A1Mj*5#m(r_c1gQqYm_PRuWP`>o(g}*9z z5;8tk`5ocmI}V12zH{_pN(bGmDNDf{6dTIln@_FKX`Fh+D{6$jSoOf|d29nSQE^Wz zs)|qk6ravNH2sH>Ax|s<>~8OlBa!Ur{^94%g=>gT@32EHB8|NBLW@WteQq>Z)ifVM z$3c5+aHBYrJQ8A@BIOv;W+1@<<7F(5;f$%-ly0y zKIu8f?VbMnrcq0y*JJ%LH2Op109FyPSDFvHr~kh3KhY*Sp93WT@D2k2Z?+S_XZipT z%iSycPW+T|EcXGqJ}B2mM{#ob0S)X_ z$#sV8)J>IZNJ8B5JVmYr>RH04%Jq<3pOxz)a@`}>y>i_r*T>}ACfBFrdRVUQa^)a| z{XMx-zK-SkYiseSF5fRs<375}^VaED6fVqKXJtok+?s#LSc+3V z(Hl$gRE(##mFKy=T`&9$=0!TuNkmO%)C@*_8&U8|-qnsTcUwsgBS{XEjAD{6ICG!E zVVAa3UPNtlM!m|Yql{YNjQTF4_A+XgS>}S5ZDl@&GQY$mJDKESGs!r6k`qX>kx8nV zy4kJ!8Tfav}@y7P@wo|^Zrp? z|LQXz$8@baX{-A_Ks%4nP7>M|0SzAw>H4Q*jwHXmfV719j&TA{ZHJgRB#Di#^8v55 z_+-~PcDTop^a*C#H5%4yc-hN*k1$_eI+XjQIz0CWVEKseqP%(<2JoH(7C~6lLPGBy z4d^evo|+(5{sYHTU9+NGaTz|yY(U@uS%1g{v=dCW%@^%emBAA z_nV&la2L~W{shuzol*KlOn;L2y!2Y?>K}%&8|c>)Fb=%psDBR%JeL4`Eb#Mb09g5( zQ{_K$9`YAljQmT|=Fe8;&rX&9v&`Sg^3PA3-^4#FRsMy{zncVj@yDqNVBvoNLr4Pu zNd1}LOZ-2dHou8~bE^DDyvV$xs zXThVzqhq-|nBLlYE5f=y3BEKw3Xlg{i(OsDf1ZvRL-}T8@v3=j*D|CBf#&{ zKLx*D7=u~#>%@!DkB9k9`W?j4Gvk%xCVqRmfZua`GY;_T#Lr{l_W=%ow>6M{pFu(= ztx~0cd%vxH zT&U^O+{p8ET~pD%t;J)y3I+k|ZG>lhnpcaC^AsPpLvD%QI1haIZ9KId9R)n6ogto@ zw#FqI-?x692Hyng(n~_L82GBz7krQZQsKKB5R4aICcfY2)rqgy!gnz7o%b=|y9)_5 zE@yH#?jU6L(pl;7P3Y~Z=zAP3L;7}Rfxedrz89v#cV)JwZ#kaYI?1O${?S?DyB`?@ z-_QT*kEQQMoS`AUd4OQtL3qaPygKQ-Zv(EgMt->BEb)ch3O6d~*&6tbocGHG zGUbv5gj{+dARj%JiV~^wf5gh4&HRU$fA?ABe;LQ~m0WZfPi-?kj{JeM$p80N{(G6f zlli?V^JDIo`2%N%F@N-YKp%JeSDI6Zyo`$FDy9OVIE7e*qI`QL!TVUfZ@!d@Jdh7k z!GN96oHkfEcT1-pE`ZW3Ky{>&wZxEcg_Ueuu-Dip_tC zr?z>-tR^v!aR=cWw6+$n7)@V-#D74 zzE*nD^D3sVX8M0mm)`g$ViM`|?qKAa!CYICbMaHO{0ynlbl8+9@xnl3l@8D?%KrDS zfTbw-Frkjl1=Py{m2MIDd%Avl3S%%I=XJgDeLUK;HZnu+7-V?+hqfgym-b67sKrcl zjEP=AqSoThuIZ?=1&3qh6_S|b@GQaaWO=y)zkmFJtc(P)678pW9G+765(* z0YELSe}t#D5ECyq6IVf5VU>FfiND1paCYeJy2e)8jgKMfA1pYnOypyt^X!QpL83-0 zQHY67Uxq{{kJu>Djzq;)qS;JzoQXQ@iO6PS8TA;W_S&PUEEsO(&tjt8OmvSu5%U{v zMAb5?)*f{~qJDs==&lhfWv$!Jbn}?5$exZX!LKoD45P*&sf2N0_W!bK3j`hkrK`w%sk>29pX(@Z9M{Zb^_k3{y4^xxze zy`ii34>nd}zh48vsLlOJ)T;ZiqgJiOhq{adw)77m{k2H1`1DHV+{m2Ybmmk9A?e#( z)VCR3J#PSx=o?-S7+*N#STB;$Kg|TVQ&#}oDu5%dwRT)Nx<1JyN15aXCeb~beDolr z_A+XWJ&OFXgHbyfH55^;P~u!>A~g(^BcHs)HrCLdLY5`SqAEF$nX8%k@b{VdWY;w$ zfIV?86YrX!IB68q7cl(;NN=U%$nt&$qDC`nn=|ScjOrbSsMXG>4n`eg)J^uNdjW7S zqxLZ>&mP6T_jioCmr)}TMYQh%Vr#SM{6z81Q)%=~Wl~8gm|=h@9h)(?Y^PqFq*^txU9siQMVH zABK} z`hM@?a`BV~7jE!}ru?-%H0oxz_X%DC@a6_3wToY=gU#K=k3zL{wG~@#{PrhM=Ff4f zv__}AdCa#L@wRp}KZ0odhN|3b+xK_-`uIvB`k}yBmv`+@`17V>)|mOz(}~W|pE$Y> zx|eT%=k%XfO_hk$z}o-?1}HE`R5jK&)vpe(xMfk%tl7Sz#+voPM&G1--|G5CU&-PHzLH=gLRQSKud54I zg=^~Td^JtJRgtDmz6~|ukgqY=6sZk2N%G3N`fv!jtLoRStE{U|flJ!*CjEKJ+c@;qc%j=sGPdC4A zePwM;wJ(Gws12fl!jZj@qWi^%bjv zYijB$!i|-6O_jn_)9hu`uUZRM^KGbX^3_FZYaNBz;gHj6!e^e@p0}~Swsuuz)$M1R zyEKliPv`ZKT)}GJhETB1S5;fzluWS%?2BreOj@2@POua@Vc1tw*ANN&8Y&wr*9F7q zY2kWbB)odkHPg^ut5+kSHdwa?y^?JathP0}Zl_3NW3Vn{lLa zY%7*$KX;P<^D9+_@f176jRZTPwq$ z;;K!;ZPjWFs;%4M?zPPleuDX6hldqw%zAD(%gdwxCu_{y(2K%BD#WT+Q zw9Ca|Lh)a?vEoxGw4$a7T_6&i<|_?hOkEwRQ{!uWT`js~6?=J;j|qMCs|68X>duGy zstB+{y|1>uZjI1ESImrS0CQTS#v>Rr0yo6Rj5&&p!A=-18f$`R8(lT<9k~K@!i=y^ z28npRSdhI>t*fbHcZ`EGN0y*3Shv2WvA%8{xX&D!ni9bX&go&L7^y(*GlAOqUY57> zBk{Vfs;u@^RW^lL;Cy_;bPc*qJ!-kSwtfR@=`)9@b%MHJ<#H9q2gs(HPX&GJz=Ggj zA4bn0c($_6muIGQxn`WkczhYw<`?0f;-?edhjgx6k?xbg?G}vjAII+^m)Bd0u+?hJ zXWAa5>s;*e1{S%zotGkfoXfj!By<5Ikali7-TXRGuaN^Mdv9rBso%E-lX)En3B_r3 z7~|1#HdHp&ImU3^Uz@^6VQV9etLArh2OEvlpaYP1M1qZ*rcKkMyNpFTSi{hF4QsaW zvz_4+fDTON}K~mRb^yNh~x)D27M4XY#q%k#DZ57#8`t)8r%rZ z29_K-%rr~z(pXc)fu|bdq3U5A6KYmP!a?8qn)+HnLF3JP-nDso*WkX0T@+`? zwQdzyXcCyJjyxfCP@|cX9!gl^2sCZl8Vp0#HI-{BRue~iA+~2J2hCYgG{10eh1ISf zdP%d?>;d`Bx2k3hx>Y?$OvX{tL!6`H{ljYev#C@u`;83!l?gB_)hytvMZc^FHa4Pb z|A{5JT=79@H5dR>_6CY0)t3;m2sM>eAvFUkf@_|^M4-n!5WpHvC6P%dbC%VkK;4Iz-+=k9+wUCCY9t}i!`b0*0c%TVVyav*h+S}QrGi(U*4J5O93}0-UQ(0OKO}p z)KmwPG5QejA>g#w{L%2)dThmhYWpX{?*sc3bX?t7ubY*8shG{)1|~jr5E7f10>f5f zzYo=8+^kH>nXW8btEDV3T|P^0N!`!-#n0J!(v{=D&za2?2-eABM-4q(`lcSbo7C8C zwl>FuYO3ndIc3%6m>TC7my`rhMhPTG%*l$3 zRzj>I&CH|;|IU)$>|j`kr0%{NKP{VZVpV)q!FgR3i0i`O8Vrq)Y-PgLE2XN++Nua@ zgEqmupxO%0YEtuIrnd6iAI=hg;gbGF30)6`UR8a=CKE@qV8nA-pLTjOo04mQCJL`w zqf#-uV!@(?_`Gyqa2!KKAFzrRmCah7uN=$S;)p*_RqH>tjluEQD5BrC-};RmidliKjRc+Xg195c|X1Sf^bNIe!pz zLHKR;W4{k;hTV8>80GRF#k~hV?_jJo#$z2(j33_j;TOQq4r*U7-j~GJ3+%7_i_1xh zwZdAW>Gf#A1fj1Temj0vXe$zro%{I?B}%ZgM${v&YeDcpX*>s~dyC3eowbyxsSTeT2B3-Z?1ndNbojsSXJXdu~$}0l% z0;S6X{)#I&!lRL(Cz0j5JSua7iV1;??|_$)vZeewP7QQf>6|MkU1QGJ z9F8y!f%c+PO>E!vFmwy0&up(Fx~S4ORfIA6g7c_9g}z2DlUMsh7v(eKsFV?{Mi9bO z6C0Vjx%BIC{ZVx z3M-Dvl!hhPSs-~=)w+Mp{Xz^&HR~X3aMU)Ngj)^43aK!yr`-Mad^gnnYqni$jt5zXc*yLBy* z@xa!1tLuZd1x<_dfN=7@vVSxdxl8qU1qOyn6g3 zQPg;;fU->@j{So9C}a&4|Bb$g*{b#-R_*<8YeH=uB<-n~DpHc)zUyYqo&=E45H^%c zspB;vLezE4A0?$r<}aMfI>eiz63b$Tpa^)n-e?slI-44>pHo2w&_$VJlIYXp#c=|r zNbgg;EWJ98sZ(EnRRpSCrMX9yxv;KejT<8k;e=&g9D*vnn!4Z=(w@mds%s*tde?W> z5U)Uwk^AQVkR&r@l3 zYE4((QKN1MdBgk?HDe(NOQ10|{;(IP0Z( zRSogeKmcY{`- zdP6;CKh#C*!jsYLBfU8@qtDwaB{l=o;@bl=eYumh6@eM2^h=_RlGti(V_?Rw!&G0n zCg`(gwZ=OS1|LjsrOAs?Qw`m+v)%F^N zfq<*9p`o@$Y27(I)v+#x;V`;D(W*JH4luXOU9&ACia>omR~ATzHKer>@2{(B+|&@3 z^*Hwrl2B#z#mYGN5N+PT3f?+Q03CguN zQV-E#z6dn1W2g!eF~E`MMRjUVT~%r}>3G^${7&~?fD!UgkhW(Z-FEG!I^%v)5-=f4^~6YX;p zeg8rI>0slQCz0@$~H5OHoiXW-ZKZ|YgcJ%YIN5l-d7Da7$v$F(8u zJiO=N=Xq2;>$t;aJ{C6`am+{f`_@4Jg1Di0W|?`2eSdpVV4{+l*e^|$5h~qQ=eE%oYI|^|h!!u!p5XZV`m{Slp0`IKHM#S-1$IU|= zapUh^#PP1$W+hTkKI({(8LQl@t+;zlAo8^31}$7lW( zY|QkA5cg3$6ZgG{J|;`pp-hR38oTQeuc{Z9*MsD}1l=EaG%^=q)d zfdQ!|tkk&KA3(>Kuk>-%c;0%GdTz(FZ-#oN_-})O6#>TB3Z){hSXa5R;`UVua^tdOpVID>Q7kx8NG}e6P#x?OdUr4Lr9`QqM&hZqd`_AA%gMlUULqF55SRxcWqWb5-`@D7HbSKwQ<8JDjsQ94d87NxhQ-*{#Iws&1s%U5oV(k`G<))5H zrfRx$)vH2RUayc4J*~AGuvIHv-gFePp`O^5p^EBy6Rrbhb$v3*E-o#&m<+DN(wL%^ zmax@!q*Y7`SZ9Ko6E`vM)YW#tl(-Zqri3xgl#mrc9d@C*0NhiTQE2M2xDJU6GPZ1K z@|D4eLQIVMR=g#btISc&0vtGLQ`IB}PLs~gN$D$|4u@$zQ5&VJc$zZLxZ;ld+8kDD zo+d7-$lvGAaoX1Q7~_^}+tq6|Y4dp)(6YJp}hxMVnx>83FVt6{8&XvJvm+gpQy z0`2T0Vxm=3ts))tFyoTYbzv}`HjT0K>zc5>m|v~UpMk_3T$@PoSPNlGwGKP|b{rgK zNOBW3CvFm#TzbOzK$@By20eMGzFMp6Y~>`UNddGD1Mns&`=j8qs%AZ(WTkFE}wc#dt{Lt5Bn%?$1%C*s7#S{kSPtKck z^`yK>lP8pxOemi{X^KRcO2op_(j^ssEEkKQL)5VBaf{3ROO|T|yqUlrUo?LS*69_J zELDCi(@PdDsVJGfWYK~J72HqLsj$ve`)q>4>;?0&ny;8uHfN51NkwttvWlB#Rg}!X z*_J#Z{TzSk?0No@iiKsxSiR0pfNa7I6fP+&uE4%QMUj6_VcCLGJ3KRuuDVVl$T{Om zmY0!op%JGBMvx{^i&dNu5uT zPObRu{VU#4d}0i)a>s29(6waeZe4wK1f5-puvTX<`59VLr6MzXssM?@(6Od)VCL3& zBXv67o*Ou-zEZ)?ZB6WEN7#4<%a@w4 zN{~9fDHI9g$VFWoRjVn&S(_t^^yOK>P_(ZZ5a zF_4<(V{RGx$pM(H^p;+{EdA6UJhe-5<`8ZBXxQN8ITC$bgOuf}OmCqHH#Sb2hTUP4 zP8z4Phk;W8!FoOY37AwRai8 zhjI276EkJw!=Z;k31R}%p?=tFl1(2QWfCg_y@~}-!;bIzVp0U5h31(1-G z8oKW1_Hf-;N_f(v9>=`+&c1WOO`NPfBW$6oeP+etx~#s^#7735+CJByPXH!+LIt!C z5V+5DxoDGHQozuawL>jfSIydYOyD?9>t}*Aknby5!lZG=eN&EL3%_V3MLHVk z_%oqTJnb1bTgtjw%8)d5XZ)Gq!}e8aS=D}3vuSZT8ycr;D_Z2Gqp{{grb$&Vj%A|M z)~0z_T9_LIKA$#7ci4{fg~v47-D57pdc`{S3d@BMtSU8%a$j9rxrPIyn!l%22dk8< z!ANB#;BwW;i3$-xBu~0>i9hM6jC!b=7r937F-Ts0kyR632#cv0o(`^}nTDI(0n7>8 zJk-n?n>mP0(zKOShE1-}sP&a5RnzsIzF)LO&ZzoR%=#t-|Fu({S8*FUYSe0q1x z;*z-S8wGa}6tRM!pvZNMX`*iST~i}lV;WgQR5AAelj@)(nPj6i>I-<<+w`_^j$e+? z&c;zKgiOYP*K>+hP_eR#L1;egX}BdW`T*v=CAVn>!V}~m#*1SD2)9bP3=#K^2yE+O(yyZ;pqTNZIWj-TyoTJAj6j}!P12% z7dORuK!JW6n#2uArSE2(DilSKFIf|$iUcg(@)pZ;rD<7F z`S6nJ9D%P0w4_0hxyRWwLCgo$ynq+G;d|T!IsvS+Tb+ryx(#0$z)5r&Kg1#xo@IVxdV+$kGRINZ=KQ2NK( zhI5?l)cn&w`VP*p!Lqt`Vtb_knbebE=aj@lvO&pHm7s@dgGW#{b9?jjI(kLf?2;=d zOXs$n>m=jp;L$YjE43}MqA2?uZ8E0 zopgOwjXK6Gr)3;9L0dTa&GgS^0XLm2N!vJb%eWM&gI=n#0Sg3mo_rO}MSWwpjFW+V zHI87gACt~XJT`uF&<$nQ`wS21%D_K*hc@$I7NSZ&{H|V2C zQm0o#3t9dc_$A8D{7k^Z&cHkZN?w$sUQntj?3D{ZlG!fg&b9C*%bqT^ioQ17Hj0Y>@!izqTCj$-#e@iUZ+gFhOaM)mD&AP}sIcpAI4)N?MQML3< zCR5RinPRNCP-f9k*Go~{kxVVV6t!gW#+3#(&SVEYVW&P`Q4?}0*g&_U_`A?k!sR8_ z6n4371~PHQG-o!a1EP)IROu)WO{6un%Q*XwiLBaZF0rUwaBf3b^*uW{>Gw4V&<3jy zS{y5t*AzG221}y#e!k9URCtMR4>6Q0y(J z5vicffIiHUBwFhN%>JE>wMACz!M4UPDJ?G5atqW;*0J3<{<6m)mKXXIK;DKKpVfhApk0rOZhxaeB~_WE@~( zE7{=0#XYmLr7l@DhAp4DA8kz!)+i#wgDD0`yDQ5YvB04uU}rd6RBGd#L>_RgU78}R zn!+{Vh_aGK)6zuDHjE~YgTUR9-;^A=wBr0kJ2VguQVVX0qH=Ox`?`sll}u|&c+RCw zBJoit8y(a2Yjn+3B2=0C14^x?Hm}&{&0sx6$ZGn-=;rAPO{#;fX_6jEFRkKA;3r{@ zRH)lVhy{ygiJrn11iJ}cVbV)2J;If@V})TWNilF=i0XOGLN1YO6A-gks_**5hpvRy zu;HJsO2GcCYtc^~oXFU#0li&N^*>w3i__XZCutM0xW$~$8sH!r;hdukdOYk1%Pn)^ zI>yxfnlnRO6ckdJR`q?gk12C<>PqrkrNj=0&Q~cvl0cRUldzk_$)P4rD!^%Ho`<5=uq?+Q@^9I0Br$eAYYBS*@v=usJADZhR50m?WVZGJ5fx zFZ37cww?}i<=uKxu9PM~0&3KcY|YxrMjYuhP2uCC12~g&GfRz#-W9c^zmJ4+b$jVM z`+1@cS6O!0W{OI?G%k7z(ZzBB3Gfc+>~FROh^cXf*^Q~%rtAUdRbV&B9IJvjUO*oq zvRFqH#3M@=BWvOY(;c0ufWgY36xtpRgPekF1y!!c2~V+_7G@wsnW1WeP@kG}vr>b= zZGov!0xFnQDxqc){{r+bBw#HZP~03O5$%%A>n{|uW=hnn8XgOvov(tAO4C!TuM#r~ z_D_Idy3?t4%=;h1bzAjy7kv#hJ5WYoXsQ$^pSpi5O+4fT#xzx#B*lUst`B^^*~%}7 z+2GnKoOBzjfodQ=W4#97v1p{Y`w&)91!M7RDR@Ga^lXa8G!LD~@hA-R9U2?ty1hs3{r$g6Q`hbnquSgu6>HN8?DDD*FVeL%Jz|2&t86CQ+yH@ zqH+`vcF_U+&*>lXu0$n3Wd9ygfA-$qm*%C@c4niaDxO}}(ztg@|L z5AP{5AL&=U110);v_2eRC|*D|OD0O!@^R#g)i)JtYw%?P>TJ}Qa?>pFBtfVA0#%(~ z#fvN`;@LnyT&@)5Bor&FO}cxtWsR%o^#N3PVOQ8%j!iH{g=rHh159FeyG?|7fYp|t zz-fS=I;(48z1jsjR;2M>8$aJHX+-Z^7u=BOp48>3b9+segu9;5qA`Ew=(jN{F|}R?e)3cUO7>B)Wm0>+xsNp`7@I6 zQ<(gbg$-mIpupKupxy2DJmdDBUI*U;cPqc2@U(HZQ2xiKe{d1#gzuEtD@uPGqs_1} z=AG$u8aQi>@wos$XI_T$ZzIyLbin1<6gI=Q;;vycEz9Je9kw&SN&%lCNdHs{*cj6U z$JcGJnKl({XMPQPS-|Zb1;4iXop<6r!3>*<`>p0P)24!L&;JzkEyXVcKg9Ra7qR|BUV(vkJ05m;)kveq zX?vOpfTzLeJ7E==;i<~p3s|I^{$u(8ej8K3Vwv}v;Sb>6j-T7a-44f&lO4`Ul$EL- zPNXP{IQ5$0_Hwe9sCJ^C8hbfo?KDIj|5(-xGrY*W>->yQm*0*j(+Lf6x8ted5D)(8 zG)_E7OUC5^??Vn8$rG=eVZFGIhA-$;Ji)w#!9P3Bc6j$8O)8!!08EuLekH6@Gdx|H zI&~-DZgs$5+uUb{J&60W<}=f#YF~T)f|Atz%y5*!@(Js-8LrD<_)7d(huwfRnm(*; zf23)i8MXlT2K=7FF9iD9`x*15qNyF;DWpk7)B8;LwqgaJJ!W{SGG~;g?q{7TV6n_o zX1E(Zz3u#OhhygjCmhXpTTyNS{es*6NTX6StO57?@JmG_=1oN-JG>J}lZr-djxt$1 zVI4NZ)0N3|{F{pLxYq%Le3#AdMB#4~?s{Hg+Eje!%+GxMn^5NVmYQ%>h~l-78P;Il znNG*sY3H3{_!X9mgjm2HVX4+J+ zo%uDa$B@1{1?-Jx*mm6an9od`3bsA}F{B}E=NNt1Q3lfxR;L+mn0K9@@pihW%Fi^u za<_L3d}Q%i{}Jv~GpxY8Go6mN!*%A>Y33pQW(Qo-e770)0PY$#)25<}Grxwt5$TVo zfPK;o>$SmV+ElQe`8Di)NI!*sy=;HPd!89qio3>}X;Z;==GUNJNBZ3Y`hU)w_h3L4>-+ouzu*7==hJiVymQaJ_uPBWJ@?#m zAMg2;O|;dOT{XX2cEA{|?sPR}LvzS&X^dFqn`o;myJ~*5Yzg$?N;xWy`XoldCHs_3 zwAGbeHNRSp2lOkdDZ5a?ZSyIcXsau`YJRos6`*JEUN!ZeV&Xec!RZmJeL}R=m0dNz zTGk%W$5bhsbh$^tawboOZ_re!7V}TyLTb_>iDi&hFbou z_}|k5uC^Mcm0iQw0dasCfW?4!0DAzJ0Bsx^#sC-xCh$BY=&7jxG%o56A@+ z0m=aSP~ZT|07n2F``{k13=q^;!)ya2hG`gWKMk`M5Yu17G#;Q~wg6g(YnXcg&jY>( z^ckpOmIM3;fd|0zfX@MC0R3PM69vctYyp%4bP*cH1(*W(7;p{HYKVsE2bciJ1#AVB z0ImT#4b?CM0OJ6U1C|5609*qE57RIKks8=m8s=%h2EaFflYsWaHOwGD24Epz4d4^N z4}bDVg+2!40*U~~0l}j+OkaQpFdwiA@Dbo=fPa*R=?3Tna03Ij0_vuH|cy{-h1y3Ue`bJ`AF1^i)cW`~~33_nUFA+PrG4?h(|uQ@v+flW#`4-D}6rMC?<9Y*Ukc zOF*kLY&(_+~ZW&)Tb}-&q4b+6Pba zNxbCT;oaY^@Zp*2r1lUE_4yw38QmAwKtts$QOem_3r&GSQ(6nn4u$4I4K&105YmXB z0MH^TzkGQPQ)t@NLX)V_ur<(7yGj*)!fK!)e)5!ZqHCZb-|%dVHSw2P4UIh37#k!r z0f>oChnUKyOO0tB)0rOOdVR&n-y=AI`-3Q8vvRM4-?jwzJur`a=e-;El<)uMm4COl z&eUdJo~a!(@q4LqPrRslOOYE;y=uKB~YE0$PMg2#0m7*TC4~~H- z&(BZ3r#>vjeSLpFohsv7Kuh^{`tXKZxh#r(ubigd`oo%6v}+W8tL>tWR>w)@joOtT zSHbr*r+4nILQG}9rQExe`*6g>&q%~7k296}h~D=iI-;RCs}35H=>@gW_?An*sobhK z-+Pj6x^9-vB~yEYrUibL*41seSNltisZDgLG1Wu5GdIw?uI@oB6DR@E(byAETj@T> zhepMDd~hI$iWrmE~uegU-|;jQ<)o; zeAMnOHRMzKkmzr=twcw2D6B=zZIt_?ua}Q`h^d~%hzb7+V(RnPmAC*g@n5Lie}EXb z<(FFDhC1pi2=nIq0x{LM7ctd$7%|m%Qi;nDQ+=0}`|F6yE5i@9zMz)1x39R5H{VIb zRNwE2slLAuQ+*AU_UM%MwNmcys-b;(b=0R*`0S3D>N6sy>YRwFzClVHiJ00Kt=!*_ z7`NpYwU54Aqit1t`wIGd%ioBY>iYmO$x;bos_&SBKZ98APbFpsc=Odq46^b|t#@V} z^~NdrlMqwAX^5%bxrnLWJO%#}VybtI5*Hw*{@92Z=<-Xg_d*@@M)vdOk3vlK-jA5- z%|=Z1&Q|aX5mUWOlsF$T)w>EY(B+p}Z))q>`Oo(^#&uRdPN0qM@;pG zD{&-Z$`^&Wyi)uiT=76J?hImT%O8k|A1be?7Jk%nsEph}6?#Y8rsg(K9+^J8f?m7{ zdKTqV-XVyI$9t6eh{wb_@>4#lv&e_XHFe}6`eGj*OKyT*r}Sfp(vN1Jehg?^d!ML0 z>U`>qs3Q;Y=uzrisMPtqPn|RC$gkEJ=~HJ>9eG@oNx4G%c%N0J@gNzbxD5G;FNK7O zba~~D?p2uzZ0EI=7{n?a#gyOoa{1f@55942SCc6pK@7^HI*6B4rH+}k)Zqf1dWDAG z*p@BG>)RGxHEk(X>e*RKJyeHFtq5^;c-?IYZeP(B3BRd*;fQ^TsnlP@rw(fgwV!C` zR-sk%Q5sz?IrIk7(7O34^x*C?diHY(rt}u&VK^+$2X`so%XL+Zo4sEZHF4G?Wet6xPL|a zOL2dOsko>9S#kYFnWS=lh8<7sJ%^a?^Og7=1y5-tQ#4jHJ9)>3)-2kC5Ppl#xYIqs z&T4R{tHCwaSIC%}w`VoDh-z@D)!-IX!4c0(!LwR6-P7+a3LX7caXYKzrL=><`Q|0s zEhvxr>D?if?GPlD%O(Aui*%w>@%C!y)$t>mDZ&1g_JVY;rjxvsYAdJzfqONb@<(2* zyqA0i_o{wbi@v9JwCh;W-;{@NL_>C#tBaS0t$~Kxeo!eVtQMN93Qcq^H0_jrO|6B- zrqJZnLKCCV#ST~Ei->*G z)qMXgvCLa!#TtJVbD%ojsGW3COhA1?5Z%?Q3s{ta+e(-3+#H)&dG0=dd+L80M;FSV zGJ7KSEkLbP#SyP-QIBuApUVgazN5BN8w-5eP4@&v)!<62;HaG0!4-W*`3Se78r(ME)Uwt3j{!$K5YSpicI9}3=Ygm?dk>~dQv_U*HWXH}M!=W*|w{>mej z)8E6rZ~BKhmD9h+y>EKl!%&Q)q%}pds2dKJ%r#5d2WfA%2n-exz#3 zA(`bMtCV-JBC}%<6Fv?xwRxg)Ur&+k+YwXwcOs^?(51$d&!gzcY{W!I7p3R;+{?)S zU_f$8-_RC&R_=e5CE}afQZUlnH=7X?ZX051%h!mhZ%Po8ydOnOeSI1+qVmgk&gJ17 z-BczesU`EBvtZM9rAR_WC8OMJ>dp_G65Cd+^6 zCd!vWD)l#MvC0RV z&nK%RU-IJ>8C)GjuXiu7B_vUpGLb>kQcw;}i)I`Lezk6f4noR8=a4EbHUd6=$ zr;dFjeiJ^&hfbwcX*>!|UNtmCTlJ!L=|_8Q3$?BFKdGD7#=R@}YKPy%S5L&gX}f-2dB`%csIu zeydx5Mu-*qGX}q@UsDnL6eG)vdQWq9@fhzM&Q#(^#HxN>TP3fWPn9`}H{1lxmRe}k zGS!&sR)1F)tK)DTyi+>)HYuk2VGT99aRBw6;ypg;bl;Ah>d&FCE?nh%lq?fdKt(b3=TPz+Ye?S;tuy?F_L z>$=qgw|d}K5BzWK0rj0!%dOh-Jr1o+OK`swkPl$)VwiU+F8`{y_oZvS67i}uda0WC z^XC6syYN=K|A%@YZk|S03~_3KaR z)!}^e5RES0Usn|8FPHCmt)d=Eqf4dF#W{f&QxWD=LrfV5|Ek(T)(^Xxc(|Ze#O&9#s#N57GT|V+$O~5-O?qzg2?HQdu z8uFCD=!#<*U8~Jrzm_Tsa>3OE1xmgDX*sKgwSRMHtuO2;8^CxZZ=`_`8b#)^@3br6@&)-d0LDS+@>MOG;M= z{wMXWI@VM)&6zT+1v$muc}e%=lObH*u8O&a*MQ2nQsAyB`&62rM3=i8^9eZ0OL~-W zVc%+WR}>uSPniz5KNTG55W1diI6q}HM*k;uAPF51MY}|Yp>u6fh$$YqqT#02{;7bjDn+e zgm96-{h;7zO(EP;;0`G`T3-lv0yuhR4aGCG))21X2aWE4f}?eZaAm;lS8%^7IO(uP z_q~EUtKjAWr}~|)DY%`$?NjKkE4a(R?FEkdpiIHJN;SIg6uRFOTsCm3AL+b;y8zs` z3f%<-Cmzx0b}Kk+XJno|!0l4%xTw$-0Y`o-s`qyVmw!~F`$oZCQgG6b8r|0l?y`dO z0QZ%G`$NH91&*F^r1Gw+d5>vyUjhdd(*Z#JR|4D@3Y}iT>5pr4pDVaOmAv`Dk>8EV zyQ1K9KWTKIDY$bAE)Td*fdd)SNhz-wxa|tAvx1BM8SnQ~a6t-g4RD_*xGoA#|BFWV zF>qAxUrHU9f%{0ok#eGTM4rU^?3BEI3N9D84;7rhf-3^<10}Ck!AYkyx~&SXo`Nd? zZi|vPK*6!6HM;i|TsMWk%fM|`aP+|!D{Qngh(5p(i!XQJ8z`uBW@DBka9#p2U7)$3!GE8r@Jnuk8< zQODvsJS~$2FarL37I^^k0C9jY zKzD!+P=>xJ25bcE0S|3fXqiO7bif`!ua(FL_zB=xh4z5Qf9Dm>FgftQ;?Z3uCj!6$ zSOENhIq)Oqz`vLS-)7ES0DO5l@cZTT0Kj*X1HWYs{9-wS0Sf_B0q_IlgaF{z$bnA< zM-NO6e3?1$(dGQB?O2Tdcnh!t@B`oiU;*GdAYiqY2?TTngaal3W&{4o)o{(<%Xb;| zBqERJZ{`dBoAet|&u(fH^1TK40`L>yI-v1ukV8OEKp0>FaMJ-2e*cq;Mc%)2MS#8? zpcx?gZ{{sosAWC{tN}a&$N}6B826->$p$2TCb{`8P(%%tyg$v z18dZ+_3FQ4y=u+Gx+i%Z)Gm=}g**B@xC_6VF|o1oCuqIN%uH?757H*OQ(Z2JVII06 zg+5hFNe|wT;?9O=3%hBF+2~D6$%NB+vRq>z6B{pYZRvBSOgvJ&vB_cXbr$-X6dzt20_gZIdzAso*H~@1q5QR;JhUX78SW%J8B$N47wPiXb~Dj_ zoEODnYS7=~4@(;@3q=G57Vi^Ajo!l`@+_edOqoW`o9IqRjYlqonP4S74WIu_16y*g zbG6cdTSyWhWTdz=nGN2w$!XQ5UStweGCisADEa1jO&()% zLcs-w4v5W)hY-dlV+h?D%G$|ts+o$SN@u_Qu$@H0Nss>r=qXnox zNFp{V?UB0{>_K8OvZmn?ziRQyWMeuqvEDJFcH(2(+04eOGePd~%~i2V7k|OTHzYlAa;iH# zJ}oiT4Z4>IM+BjXiBK<@_#6CwBZl^iP+KwI-x(V%yC&&#(q4b$J)zOjW0@x@<;H_c zBu4fLbB$%5qVyXNK+Dr7F-|8N8l`_c#_3ersit@knbH<|8UY(;9W~ufFCwa;tT}=k2G2_$-$*2+nWPsID*^ z1}R5aI6;U_fl7*pVyyjW)H_lmLkC8NM)rwHicdg6N{{m{U__kyjb$FwIH`vJk-CQW z>E{gno2>G#D1mpep`lUbXSTd6G&DRkbP%|k`(Mi)$@G@0W97ji5RH{`{PsURR*70C z)!-egf3Yrgto{zG-m$7YRADqsOwxZNXEa32`&Gw?q&;>_4N_XiB)z#Q#k^QmYX7zR z)S;?Q1JulL?GuZ6`M;Y-dF(!@I(8)Ua$i;_wKaEzykyh3<#JR32=XI2$^4`TapCV_q_d3~pFwxY$_U0WdGx;cezTM0MG#qU-1#7*|YkkEe7P;KREjx3tXHJ4QfH`SH)uGWdiyJ+&? zygHDF2!%n1V0#xC8Wu}S)23QVs+%#jtk^MfVa#_ZEjfMar1-RH3IBk#*g+7VeYKZS zH&b#c*HFr|w12`xIC%K0#@uyRPx;%_b52f5O`rM?`hc=C?P|_nHvhy3*MO6!1HO1UY1`l$RYhLzeUG;CP^{$3MP zh#l02VUA#yCZfmw2e$Bh6kDzU;5|C#Q}VyK39M4`k3J>;>)WKth5zJJ_P=h%S1vmM z9x9`%zHZ7c!D@(YEX%nfjiJ8#Sq`p;D4v5MNt*UAtbG5lTym)~Lqzf?F zOma_}ls?5xzEikS6Mfv83F#hqTsz%>S58~-cb~C+v$IE$Gqn6!R{RA#=z;E^5kEd&I}QrcQiK*-{|$Ww){Cv zxbw06rB91Zh1J6_QGG{@9y)5^kbdPoWnrlP_|%ldSWi4Pj$H6*>Eu3VtTm^B#LE6zMorf@fVmDPR}}gYyyPG(t&{A5x}+r6B5i zI@EE;R?5@?oZ*BFLyUs($`$1fkNc)1OhgtS@n(hC1n;j2Ooc>9UQP(Xm@9tAy5Y4? z37M1`GBqVF1Z9V0OirVxpF>i)5H7?k=v63i#^Lczu3Ca}VCPFxr34HU4rP1^s+IvS zd~$8+m~1KOz67)CjZX8*X=3?&>Msu!zH3c>{u-0x>y1+9jq2iOCTRv{DmUpF{oV1H z816_o_GwVZBQ4B5ZZb_H754%8|3}gZ&SVcAQ;cE8S3M;d!!RM*;gj7N(<0p&Np#MT zmf-H2j^4&>Y)I6V&lv_LGEIE2&=fdY0+QJ)*Cn5bjKV;LW}=yH`o#>^kmuejjw2&5 z6~pBN#p)llxP`sd1GjqMRuB9?)&uyKDLyzrf&7{WJdc8J5>QY?L0KRIJ-x#L?<7G2 z1w|B;QV>ZmZOEmdRQ^TpYw+OVZ}|yu25&Aw0==8T`;vt=^ow4ZK+lQG?>F;F$B9(&tAOuAe=;$xb^%N9Q;K9S_3|_DV5(*-N z5x^+JEd_XE3a;(|mVz7#@P-u>KmkhugD2S;4+VG)3$oGcoQf%+$K;t@3W_Mm7ZB)0 z1h7}Yy%B-Vgup{VF&+_ON-4mLobZbRy$wMe1$dzg67cxF{0u%5>A49fK3ro zM1j69f}DN`bOR9JyHU8M0B?>$p%mx_BESoakV*kw<&6UHa6aRqfF9Fl@X8q64na^r zLF6z5c=iPaP{51;=qMzt1hXTAD2Mke=J03v+1x3m9D}`{MASk1t=u-rx6fmCw^c1)#h?9S@ejw@K57v4h z@KAu)k)iT<1bFEf%A1P7m5o6E0{wasf#)j(x_TPmC}1gYQ4mQ%90eW_db zCX2~nQsDO-kJ3_@RK|^x(_m}DDavHXG%Wm?Ec|>Fk3+~XL;YSv-oBujgnw?(XEB+; zk3uh`;Fq59pwx6Gk(taSAWbjVkbu%B1Lu}0BH@3IL`o*;l0Y*RW^o46^!S~O-`-qQ zR+5Z&Blk4i#pACK;$=?b!42{2r66AP$eD$I9>#*FLwlo4`qvBh$$(zq(~Z>LH`Li1 zX?pyl7I{z$^~iX+EIsoY@H|@0G8}eE^oxThRxh`e>Q>t^KyLA5xgIJj6uhM(t|*Jz zLM_Y$WS|vp^abs)VCq8h)c*9Kb!p&XC}Qecx`u$C1i5^*x4gV&$hF}+@!-g>eEh=X zI*2|6=~)QVQDRNygvoToO@=%!H1Zif?NaYYV)qmQ{79M7ORvY-KqM0eDbypR`n-7B za2lCLK0HM!eCzEv5ZB_62WOKczyus znW%jV?o0rZ#a>9I{-<42N0b+Vvc}6L-8jbZ=~grDwe>mm5J{-FHQq6dmiZng_riFj zc==Z66y148^Y8Lcrz?Kw72o+(mu&csg?9$s{Fu@h7?}n))LC@|tBxP#>w^;xKV~%a z6z#rACssEbC!u#LOLH&W)BL3?4B!WCN7_K`y$X$lcM;3lY?M3#X&C8LpOK*a4YUPX z8~(DxJ!xF(G=27@)U-@{CmcmtGVwPjlj1W&CZ!}~q-UllWrg4b$`YSBsn-;)lO7)Q zlq4KHDSr)M@5J?DJ9)Dc%@m=|LWE9^hI+l;hEhH8St;XEQd6>Sf`Llp#EL^t#n5HL zpXH>c;8|~+;#6Sel#U@G`Z4%Jqx7kn`Y1T(G=_*mf*~Z{kmL?wSt-%&b_9$`S9g_MlF?*LFd0K6*3E^8ykHJ78^(UbXeUL8ffID~X_^ zA!wB8G<;VX|_e>GKR|TE1SXd^k7Tys)6t)S!3;tp!v4?08!^ELt zqBu#MCO#@I6kiqBiiP6G;!g3XcuKq^`WYG-IvTngj0TrskRi^HVwh}r*zknm6~h|C z2Ezx29fltaKO0UP&Ka&6>Kk>&kBpxhzcd~({$xC5^fT2rH8OQHbv5-cg_`=C2Akqd zkDHz~eQP>wI%m3W>L*1>tE5d*8?)X#!#vOIw8UBFTM8`Qt?^dTHrN(pOR!C_Jz*=f z{bFljkF!s*=h&~<>p50AHabo@{&ci-_H&MK-s|)@7dzKD|8O$J@NS`6Vl$Y1B{39Jwbbudv_j9ytM>=6zK^~IKAe=%BI zC;lW}7P}jw4XK7F4T}s#hFyk(hU1rKjz2BN*U2Xl`T53IS zy=tvzYheqv4YQrJ`Pt3nIWA?eqF{Y6G&i&}M1qHh3@Z(7jaK8m#u>&Jjqe-3GafWv zG+s5fHQjA`!}O);l&O)_LmDiFn}?a_nx8U%X)ZBymPwY6EY~deSQl9@SXtX+wvTKL z?LF)+`+fGs_BHmq9fETxdN0|TNd|No*|iMQk?+QHyon#jPvPhDuk$OUVo7Ue&AFB^ z>u~E>YjfKS+l#jMZAWcqZI^6G_C+Y^l)aUszaz>q%khPy)N#$R!uhGQ#CZ;-`Y{(! zW+V1a_Ab`IPGAGLMcg0UZTv)j6~CU}f^qnTKgahJ%t9YwoRBVL3u}cV;(76^xZLoz zVUyuFB&)emZ@k+$&X{hTYAiH(E6zLb!&ljpY@PcYinrZY$n_N zwnWai%!akYku_m}^*I$Tj2|^NdT3?-@Ta?lhJfFBqGc+M0q)ys3{V z&NRt1#kAdYz;xaeB#oA4N;%SOX|A+D%9GYhMbhWeDXFQsn>pLO)cmQr*j(S@v?N%v zEYmE{TUJ@#wj8pYvoy4ZSYNXKXf3n;VQp^{Y%kf?+q&7qArqPQllH&tjU7!LcR4yc zIL8=AqQm1Ta(wN$;Hc+p=24U(uj4kudyGnU$VvQA@(R+#jx<9WvhM~UO8qmOf<^HJxE&f`>PoF7d;rUly_l4xTG zvP0PzHiuovET)-?iub~?o)0zcYtdov={Ej_&g!35OxT=1zx;Y z%oOK~hee$s$iQJ0=xYc!L>Z=Igq||IVR#>!@i0c}f+5D3Wn5r<+gJpt{m$4(YA?k} zNz#1i_b;S9l4!mUBeT%F$6U|S+S1k1!y;Mimcf>E%S_8`$mT}N`?b4_KeD zuC;E3cHC`kWN#0>pJbnJUuFNozQ-;)?sH6bEOh+fxWm~Qe-&wP_Hhnyj&@FS&T&5J zEN~V%w>ytHf1|a)7h`UvCktyCiYBsDa*G7B0nq!it!u37toPX_ z+ZNhR*sj@{+V8S=wzGDFy{~<|Js!`i}DOpn2?co}Q(ap=bD zCJwxfl9S$V4yV=}|K9NcfRZn34> z+c|bRn0!BG33z#kU&O!4ujM!LKkyfMjnG(VAvlE5LV}PgOc7od-W5I(b_#n1Cwepi z+I+J3q4=Hnqj(x}<1~ymBw*#9Z1~Xdo#98!k&6a{u@Cxnma&zogK3^=vnf!Dl(tBh zrP0t=%`6ivvn(2GUu(Yg2v%#4?MvGbd$C>T80}c;Ao~Uk75K^DW$wvl8kizK_^B|( zx!bs=TuUyIdx(1$>&(wsUEA?iK8v5nzrugOcQqPdk$r1iW!qq5iv6&!WSDu}U;KCC zMC*L(LF)&$(e_lwBaR4XymJ%PQR)Yu1H;T`uX07Oh)xUFh3?`A@ey%}xDqy@Xw*tQ ztz)bS)>qMIA6vh+cCi_4bD&29>|O1{Fk83Vzr;#XhP9Gbkf$A+9lIR$ox`2c&P0s# zH0L~LzVj339_JzFFSM4Fd2IxLwguY-`q9JAW#59`b{lseB$?p@`L>Yc0g&VvJ`=j} zF@8BT3-86CO@f>)JEzk1!Fz=7}m}?>4N#NHNuA1z9uv#SBm)bvsJrL>`;rI$R&oRjJX-64W%FB+c4#uf<1~?l#bJMFo&V`q9Ern=vA9gzX6?>g+$o1m-bJ1KDwDCt+V_HB{jlvvA z=BJ=_1;Rnh+##?_Pm6zww;5>OCK|RI_8NXN#2ep$wQ$;auPM)T8!T$KR3IIZ#+V;6 zFEej4e{C)`Yb`A--7F&@VPp*jSfi|NYp(TuYYAE?+2+CO*=wt34}^y8?QlTeUU0nb zIN)dvZgXJI_{(d38+InUoV~=h;DTY%+{?Yhea7wKO1Ohu3#=Jg!fau#ut3Nanur~t zS=Ni+gHwO#PMcvc=E2j3R}FUAP2)|oU^l-HE6yP;lD?6CllsGQ zn*r-}llhqWEVyoix%!OdTgyqT3l}Y2tdpQ&u3L4sK&-9pY2@bbOb}!uny57IcyG>Bg_%*c))SLbFOm<*7tnp3e2drSTQy@3!UG< zT4nV9@*3Tr-Hvra!_DAV^Y8Jy_*49!e1y7QB_oV~U6Y#M;@@nr}a7cQ_u#%Gd#OY_k(Dd0?_pWD~6W%h)5Vh*e?-r{xp)JFo(zi*v=E z*vSmVjv@(rw$HGl^)R_iv5=uROhwqA1!84$NwLyuXwOzyx!0MGTL;@F+UDEdu#~+feOZTCt+gpZOk}L}>%Pi|*?On07wDy2@kG3v`HBn+cX8qNA z(b~f~#x|g7{h_kVv`)NISxZl+prs4$+l!yHUd_p8+PQgu!Z-tKSNX3=X!EBt{)f2Wper4X6OalYd6L! z)P;BPBl#TcxL0Eh-2v44?UTQdI zI038jGQ0{5yb6KFR>pSN2O5px*n2-@eB8JdTI7f^6Fs)a^qc7gX^pf^+9B>yUvtLQVWIl$b>{D?W%ywCizxy&47F~jPd2MO6^`NXo*a>^0{U&Ctahgb=(TW_;9 zvvr0>oCEvi6|BF9Y#r>qp-*D%Zu>m@O8e*bZ|wn&CXN=cVMbwXo8frI@v39J<1G5E zjkB9mbUNXSc-r|H`t2v@dFOSKmt23GdoWBaJCV(T-Mj%HKYWx{%4i%=$ni=7NSEVmJcaaiGI z$r5(OpfUC^dgslTri-SlrnXXNSV3`83be;3*c&!7_b?AOPc&zmgRQ--UpjsV-%No& z6OOU%&yE0xPq9ncm9QvhV9)w0_N=XW55{f-e}u0u+$k6Ys}OU6thfp@eJj?8 zv*Pb!0K6PQhF%5%9;SY1(HN|3DTXPA8Q2fg+<6YOXFa6i3#@j>4Gkd;Z6FE3Mh+fH zyRolvpmD4*1=i{t#*MJf&lxYlFQPRyFg3w$sRQgo&J<=Ej=447^o*$x(sIOf5^GK; zDO^gHT3R~88cc`f^`vDxdcLdGgq87Q_y%ZY^w?(Ep0q8(4!MKf3U9!J*o9n#U6bU< z#%$S&yBnpyX6ABDEV;5-RmEgX4|egb}(dmL`qU@IJJ9BUl~ z*e4b`K6dPOoOk@?=!(DUiE*YnCp&XV&Win+R*-}4Y#2M7O~Q`%W!Pbx*xhUcu08&& zVJfW4!`wN}0f}|XzM&%i5@xdw`_mVMqnO77v5TKBz95!jOnXD~eSn#3F!sY5Gt>Aa z^xV79b48f7JFr76#;&3SKCDvXG2;o?E@jYvmoaXPNsCbnHVrl1kMWvedeih7M(j^h zODP7sjP2-$dRSLO&4bKt^8s^HjNJzKc7{Of6#=O8G0_9ea+ULM+zoIpQ;zH=n>$l4p1kR_z8up`klg$7t+-=3?DmkKWn^PhgN_ zk*32dvw+Yj><6 zk7Eb&nYBJFEY4=P#oID%Gi~c^AHx3q9{aia_NMk0*wft&ORKMals(b@fc-i9tM)hS z>tPY^#2)<@*u}Rw?sRm7cftX0*%Zfvj>Qi75m-h;TNvgAHlJO^u4UK36Y>GO4Ze_K zb}y{1qiFxH=-WSW_*|cB%r%4e>@F?{J|&hjVvXs;g>yq;eU8OilfS^-W%0VGH8h0Pe4}<>XZYB#+i(D8S%NKVEk&>r{;)K&TC9EG#VxizZkuj@&Hf&C zm`CjA?3b|5ywlOik?tsl{c{4o^*b;h7<|oiCdR8DWZuQaz)LloTg9#AwsWVrZv0?= zJXVJp{4DH{*YSneYi@(Y?t)k4FnZ|>86#Yx3Jsz)bx$1KfH#) z<^ks6=5g2`T{aK0M8abH$nt|F(mL8Y9scrV@DIOd{S@}%HEVC1!4_(Zv`xoq@VM<0 z+YZ}h+jX1P-q5bYOlW8Cgq=q(%mxcQ>;3H!_7V1I`~8>~6YL)Q6nJxH+2`7ywCCAh zu;*j_SZiMgE%bqX8+Kd8&_oB}=Q&~j)qcVLr=4-scQkf1bF`wz#aRuWI>6u9!CuR- zFNFU3#Qu}L0c`H!uvX7GIzd~#$Lw-^CQRsqI*@{(oJ1o@$VrT4Ta&VgSrJ*zS#%7!@d<&iYi?N64QJiW#4^Q)* zIME<$dl9Vd@8C-bHXF^kd z@5?O1OOS@WhCd8rXx(PTe|JHm^(Een zgdC0RFVW)Us4=hOTfwH)^FjPoD`V4Q_Ze*KX=8FV%u!!C|v!=Rxepn(dxEzm*R zu;bbZPxwKu6k6y6cN#6Yz+J{J%v)ZdQX=EfIs!2AI!7SJzMVtw2&%Mujc`Qbq@e`8 zRI{gnd<+t-_ekd`*o~#|6rON0`5M_@gkSI-dHG~mEvsYOv3j;AE5R=uj=qj&6XAEs zf>pnO&4mTH2D-TrdU*%Chb?7~!LNT68o4pFaXak(MNZ~EyTfcOcgvhbdN z7;~IC3+FHk%z5S|=6rLZc^fRZ3+Ag>djp}5S&N7j*k*BAA}om(kL-_J11((uduEGe zkL9%G0_+-EliOKEt7MI^j>7&f#u|t9*#lo|9z5jRtUIB{_h4sx8mBp0TVtCJei7Cd zZi}$R*^*(s&b8&+R=~ScWZQ;4`!U;98v`#7c}s$!tD@~O&`{aXP_yC9S_8YN#9j(r z#K&SB~h4r`->%%eG({{lsp z?vN$$GRYmDVjE&(T49cP+ZP90K1Xih23TVU;ltCyXV=r|LYtCd3oby5w!m9?n%bh% zGJ7DW3>$!!^u*kW#GWh*b7l#<0c|M3Y`My6!F@1hNQBJ&T-YYWdm&~9aZkLT;9{`j z^T5)`;q&->SQrQ8IdcIsCIB{thO6am~G6#IbxwK^~JEZOJQl3LFR)^tSt56rbt+h zao9mGG3A@qKqnSL=69HiO(#qN@U^s(^zezYk_10cIChaaIBCq4mf*Bw4Lpp6kpB`$ zJ%bjv-CIT)wJk~tD*aM|V@tPi=eA`9EY{+||<)Gz+ zrLi>-avp4rgoNinx^uB^Y=dlHg|AO<6Kys~^8$Fx^0D?Fw4H_&2Vi`ovBsuigqL6~ zJ%RP)Dt!0B*q3KJW;+&Ow^NAGJmF}J(X>HEQ(@cZKt^*ho<)$)V)*>ZoRCnxmMO%T z>eyB+9udNbMne~7%VSyu4OlFX>Ivw-08R(V>q9$P5~CrB zB!^2ds%u~q6~XIKB9ASNYfp@9Bt|t08j!|w3!cL$6;23RSV6%eD@KT;;7Q6B^Teg{ z2s6-i5zuup*hw`u+Kge)W{Egw%Yo-;t?W12hP~7=?4LxF4f-p_lmp$h#539tlNdSZQ# zw4`Ev&xHq>bX19=or0`Ap`mQnFj+^XVmCV5+6sGT7xYl1En3z@Gi|eR9<;%>1*3Ni z=LDpKZ1ynBPde=)-Ln(A=PGZexU?UJx4$ z=@Ze8WayJ@XvRE6H}1fm=rkLLU1qYp+LAV$34J(QUUf;g(5kydwla=!wB|B6r_u?n z1U;;7;+R%olDaq{Szdi}piPP(c{^}+Rg9-zysPU8*iSlmGlN74PXtAXC9tDPu`9U% z|4blO-Py|133*s=m!gF$;L%(wue^nZZ8$Y7l}~|bcfSX^hW7Ndo7b5F@$5n{^o2+b2L^~8GXg5-{p5+S|W@P#gb z_Q}V}y8*g~JbkqGmPr@zbV`8iZ|@2H<1&ZI>u<6-6@Ky@=%76F3ha3c&0C;@ie=x) z1z7w6*cY|4=;d`d46+?R&P`_S}CK~@X^%ypye#0L1{j`3wO#Xh_d}R0mi7~NmgbFo zjlZA2e{*`f!V+!tmk)PtQ1G%mE=$t>o@lwJ(6qAHm#4FfaVRy z#AhbsJxE#UX`GI2Lde^i-!{UXh<9uya(A)q2-C3nokP^We$eB?>-Yl!`Hu^@j%)`? z*EDZckxp*`3c+iLCVBLceM8y1THekXSwUbqGd%}87PltWSbobsvi{$~+3jp2;;d0~ z?T}%SBe-sC7w_#|X?;^X$?gn&*vNkRej|rkOriZlL;8ta-w?bhjOV(roxLq;S8a<% zx-+JvB)HjGew}=K*smU=ndNr}1j4VO|13X0X0NHs8m-^2hI<;Gf2+fi?ep90iin?7 z^ikRRd$sO-Q%3i;qaKgyc;evkva~}}Jz<-Ed-C&j%g=m1&G^E(u{oF0CzLdP{=)6w z*Si|~+t_EfzrO+BpE>%Z{&>#XZ(JXqA33FI51qU4lkM-mSFrYzIe`~mvYk4g^zzCx z_q`?lG39FB)kfQLd+yg6#L0OlOImLh=f1LU_)xF8 z=~r*h*xrqedL%Y8>b1p|-$QK^2G8zPx=6}e_wD{`OYd1Iezb9y8lXq&vF*{N?VD&@XamoG-1(K=Te~dWy5{22wkN_f-~Z*=k7OBY-&xy=ZIKh$ zS-kxHi2k02XIxjNTv^|v;1k38JJ?Z_zk_xNJD44qKOn!~oW99fSsqL8-U%70y~_VC zrx*Snp|@vZ3SoPDGVpPXgsjZo<-OJc} z$EH*;b?VgWD!`{qYA!j8ZBG1l(Kcoqszqu7e8mM565`^=IN!04=-A@E3o!#W{r+Cu zi2PY^kKZ?7znHwW$-FtQohS(8GQKG7@L})qj{5bc?zaS4oBPULn_N<8cQf1f^bb?Mow2d!51Y?kTyl17+S+R`?<}7D?@R@fHbg{g3?Sm8BIuAVABqMb5_WJYF8$SNP`S1L16Bf08 z^tWm6B!?6aY8x2;)3dW4-~ZyAKDN7O1&I zw|->G_%tZTaz%z`P2ff~!R2otMP_8p<+to3kN#VDtD?vhjjX9bBiE^zp1GQCeE*2% zz2Lq5cP)?Kyt4n5gcgooe+=0D+37Q%J^xPE;gh%S-+4#$?+%IxYve^eW~x(1D!V)-LY@5?xm$e%7!nW z{ra(`tL=MVJ=?HV_?kc88M<;wlffUB1)gVi6ol`+_qw$2iKZd-Huv=()`?l%<;@2T zX|t2x&|?RDz1?!%J1bSi6o;OQscxcbe6-aQ*Bf#tyMFc4q|csy`n|jb%Ui{pM0Pk$ z?xtGkixvHGK2c?Su&|LR)ob2e;BcbW13n0oSun66oG~drL`*QUA>)kWl0wXh{5UQl zku{hlv62G(vgwI0c5i4A?f1E{+0rSEJKo^ z_hMNoMBqa37k#X(0>`TQ2`X?X9z`0Y-a`dCZcYW(P zV^1ZOH7>dI{p@d<;Z1bAR_tmS{PO}q>QU17Q_cLzr-WxNymoZ_HU9%E+YjyA@4mJJ ze=Hr=qU@ok%H}rxV%D20uB>`3_TqDKJ7#SAaOR6CXFAOK=(n$)%pN@M;=bG4?Hk!_ z@8S_>4O?43neTdbS;LhfEtY=VKP%wWfeR%o2O38;YdTu9enyKc1Fm05zFO2T=BTsT zG-3JKS=)bbwZ84wJGVD{@t%62uN6P{@*^*6?!7wb$2Ps{t&JSed%>U6O77BMu6O0` ziLCHv#+*HJs>LU*KC8dp z`c3@oqZ`AX$a{4TJF6ZHM9m7XzH1mSbd}%lg=hP!SZlGef!46WGhLr}_FT_Izt$}@ zSlqbQWH0wLOqir_z&gW16^~1+~hIalggE?Rr)9{He-qH`eP?o%Q$l!%5w@w@Uz>5QJ+kdUg z;g=@Q{pR(|u!r|Oa^U>dbH){)$Mie+_M7(7;N<7ptX`Rs8TDJsykpl4c^Ub8r^Mbh zwcn$UNiB*q@2$6a{D_4s*QNB{-@4KDXR_`-I;HohLxJo)mv=85ckRp1WBYTF?{#Z_ z%*B3_aknnG(`Tlk_I%!+`_(e3{$nviqh&kQx~yE@Ga%XtkZf6tjZ(E1v{*O`rL5jVi%B=9#k_yn zO%sOZ3rf9FfBE|{_m~|IckTc7sdU#n{JRrQHR+wUX5gh$v6Ih4SVQ*peY4T^FMkQ) zUhTYdX5^Bbj`yv#_m0@Sd`;BSA3g6EZn!+{-GLdG94A8`{^CgEmMLGZT&fTGvr*)> zsINkP9JG6j=jS!IFW0P$DlMEhc+9y)eU|=q{_L+m&gmf93Zq^q8`=4>?ki@sd-iDF zZSBt;9eR1evM)|FU-|UV9c_0%m9eP%1Cw5CbGcpF$i3rtcKR!({a4EuZ0)vgT0&IV z^5I|o@yn~xQHNgi?-$lP_QL+R_RQkbuC7?r{Mf0KpI=+v^Mf5dbWPk(K7Zii@;{q( zZQz#jew*H5(EGcNM4c$kUfe3?b3=>RL(jG!_+-cjZ;D~?+#y@vFm*57ayJWygKHYF%RY~%x@d68GGrQSI0NZT4_8J(!1r3 zpE69%E~LL>8$auh5$hHTE!=lCnRiI{L*j+>ulw)$w&gF=wrMwfd!^^$4)b1K+we;B zZmu_v{c+^Ahx@;OTigJ5oNH)7pVLFnte-Niq@mbgQo9_k!_g+A4jo^1<@f;In~6*Q zifqwq=7;qMRsS~V@3X4@ZjnrXF@m?@|0nb}{t8WGp}-9SYbO28$+tM6!0mr-yI+^~ zeq~weJBJU1Khu5Y#9pnBY(4tp_UDIpj(qd$L;t6`^MGn<+tx54bfov*i$Gw9jz|%Z zBGOTiBGM5AkzS-EbU{IS5eQvD=_tLUNE5|^ARrx)UZe>6cJP3D+CA^xd&YTpI0nO= zkiEmsT5JCEoBx`j)#M~udS3AS>;pG|mFy$#EByp2wRi@F*y3apfO`nTVa~T*QDDz^Hk@gGRx>B7g+s6F=I-fd`2814`b!nCvUl>Y#M!># zeW$Ef8rnb2t1QTKKUC|S?y)JTz-RlI7{DoP{Q{7_>;F2bVva4zfBOZ+I^H8aN8QTP zF`23uXH;y+c#hlLO`00OL7d6n2`(U0p~OdK1}~oT`T|L1(#8t`OAN}3k48C^>)K)Z znI+6#r{JDFX=Bnsaarb=RZK)>ds}JckUn;G{jWRNZLO??hpTrHxOx}-+3NkfiSSRW zH|fzL46febdX51glF@4`2Nog*K->(Z{JrHj%QDv-_`Br=lGOVFb=DbF-h;EQh8wsP zg4X{vtNvd~-NAcJnslSh1ga!H@}cs9=Ww65js~Pm(CwPB<1vb&zDD2ZYQdM}*-?%b z)drZ|nyeJsDI-4e69#o9hN+C>Opws~bzWj0aqX--WPq-dW~r2m!@dD}ni~l{eKiA2lNbI>h)d<}`h*_ajvB zeW~Zdwghz5d0CqnLud#niC+v+CU2gsu`#TG3C-?=g!bs1pG$X+zj9yd?8{HyO@*{R z7RQ&f(~gVcc+y)um0@;dL9P%!5vY5nSTVh`;8!)7d)G~@TC?LC2RXL~;Yr=7Yeve7 zl=T%Ar5d)KnF?P6z1adYr~sQe1@epZof+(G{fZy>Kh$lg_6WQj6b?Mg&8Ny~W;B0( zIe#=Iz3ar)1_X~A4%w0iTT>dcg-8EknY~OzmWO$zQx-*jQ=!TV`PKa!!dJ?_jOlbn zaXhtYNM{Nox5AWxm0XCfo?@G>D($kY^wP(Ec}h_Getc=3S5XBj(VhNnY#4<*yO8i* zJSUWK6ju{!Ik=1M^+#r{r>RS7@3$bJ!NV6~!9g*2awc(o(um-^GE0&zZaU>1!ZEv1;L;DLI0n}Rv{Af) z++h9nTc57Oem`&wbg!qg;6HTVt#2K}T(dX>+0g>c6=Zp~m)-2#Ee;@_xewUtUdYiE z{61dSYgbr*jIJPQ#X>?ba74=VrTQti7rH9SQE_L3b4;~mxh+Lv;*N1c7vCE>|^0+eww+T;Ntt)r=Rq@N1W@tGLZVesHTL%Z% zO@*`pQvL$vvvb7WA)=)wmRIHpv7>9?r3( zo5*Jt_c*eX_o@7~9G|}J%tCVO07y*E@1GA29EnK1HvXLECQE;i-?IC{bn=<$7^0Q^or;VNgEg|uWn7I;*sF^ zajbxt6+KosZ8Ep;sT2%@tW@8c4=>a1zAxQU5T04Ot+A&znJK1@=e<4CuJMlO&PV4b zdse%8%i01@XK-qVvvrZ`%U?wuvNji~KEYPK6dmxrd*0@m5>*U6a}0j*iVY-tcWS zm~fDSnoNC>Tpi#m5G0}!=lDp`0T1ozaaBJCE4r>YOIjYpmZM%Vr$p6zziM?;G+?T3 ztFXeRIw%=ibN;+%75%)j3!kzR+Y>r6LpRF{`0SJ0QKN=(F#>bxzN}iJ%$95nRlzb! z@}U>A6&wL1C)f8WiUolqIl+O#Np^Iv(UmJsOaRI81spqY67(&+6siibhGJoY(+x2B z{$j9tg~mjGSiL#`89%%hixtB!m09fCj#4TFTIebx_kPKKA!_c>x&5PVDie+z2m1-L=|1Xi?AEA_hlWB|!x)DvoBs5&-<@@V|rw#|aJ%&d4UeWgxLK)3|G>K`FiE6TEkl41Quc zqV){yRGzBo2zkbGkqxitD?h$-zGpRVtTnHe59m@wKCHm2j?^X`3HWS}k9uc|)xJ7b z?@r4q-{>Wfa(-lKQ*cC9VaJFCD27npC`DH3pe7uxziiPG+yopoP84*ki^G=81W>UU zWsGMhy0^Y^as|AJZXQW;>MN&t_=HiZfV0aVF*5_Np?+&B``m7+u~)S}RG|1BFNu>UW(u(+r&zo?Y8q@{?B2uxT+3~V_jVIvMR7qPI0iCaoq zib{yVEk!NFe_|=BPFB;}*aOHy8VL#ZD#yEB?wARGTp0V^y46)@*Mr&^IJ%FhEqXYc zp?|;t2^b)Gz*Ec*Zve z?1myieH}h=%u11I#H2V%I)t+-UH)~0&3$77v9}v@Cz?;EkZKH9#!Pj4=pgV{D&jOY zHp!1ufpCq1igG*;Jr7%#h~2fff+7btgXKb-9bV+A}cIu&sa{EeW8>-);6#f zSbdV~!=>_-b2ab8&S1knI@wy*i#67B?lh0gwl723wJEWydNM1Y@ntxkC^ajH^9q9zITp%N5P|xZBTiGV;|y@d>$alybnn$ zInB$-&$@XZkOsD}IP98mm^A?c8O&6#GA5h?O_{9YG~x`;BR>@{LTW{0^i-*Pu`HBV z3H+zA#`%INPUH}iz~lf5);DTHmu-c0Cf^$eBsA~S2ozOIo7G#7m)nVEk6kP1yIgKwK=Y1r z9srQIJ|Hf)I7BVPF)t260rXdc1we%VEwKQ^;ooAx@$a$Vk9qp{r~n7C3>X!}K<^|P z%7`Bv!ofNr2xb21sD;G*t!V)hZCbztO$*?hD_YrgA!QVNFz8f>B-IeDU3+NAmZ`wN z=kUQuyRaHZk{(N~HlXbo^N57QWAZ`570K2VoYGFIR}f0L!axL(w^f*b{3XsSB^hdW zKGvEF|%@*NlKdVrBWIEU`2z4*A zTkm#UKVgzZy`fzDUefBmla-iPKFX2=HXt9jJvoX?^vc9LPwn_DQ6q}NvoT&~`ST>d z5h;ttdERUvm-pl+9;sa%TwGMV9rV_>+&7frt!!D8>0E?XF#TGV(1ocOX;`s{VaFrc zFTw*AP?@swl2}Rq7wLflo0{iuvx#xFNjX^s=+)jKc|gYz+`Ul?4ZXQ#w$iWL6cry< z-^}L5Wk$>Uu!o0Nk}Fy2jQF$bWwFJK90m7m7R*`f$9dJ#&B7ef zZdiDkl?w&GF}P#WH`yaGKE!&5gv;dS%p=hesW;sYNgzegJ-pm%iILem5BXNm1Y@ZuOgz z1=>S&=>Kqmoxp_0DArJ7YmUzsJb^#wh!6#g(7(Wf1x6x33#EZl4^{i?B?EB!g7X%n z`e5S39hkcUnuo_=uv7SJ#e+XNzZ-zaM8A1fEJPB3hzAh2 zkD#3(6qJd8+~MUgA=Dy&b~$wLwz_KR3OZXZJDR(ATROW6+POIb@<%7Y1VovIS((5L z`>}iCFWaE~xDGG^xFT9V%v{;)73dnA^xjc6a`qC&jN6;nRq;fB3{qnG76T4VmlBsoY zj9;X(_PN1hvDaVFt23^jREX5=FWq;Tl*JW1o;D-HkoU5R$unMR;)B&wB^fUtj&%yZ z{1~^On;YHSn96*uH%RMp?_nQHTeRD?6HGwy$?-WDs#&t&Uv@G|PMiwbXpUZ2<{#aY z?r#uVcH%0YF5y}1A6X|ZN#RXO)*zN4T*Hfa&C(`JJF(IM>od-%RFfijOwjuHLGg6? z+hMA3ozsSr!q<4{1Ijjdwnqg{uwG6szYt;PbhY4-Tbn#KPCkT>R~9Kppq&X|>Z+tEfh_$P z$2-c>^5jl?GK)Qak)56CdzKfOFq_ZvDS|S&&v$!r zObh-#tv7a51hzp=c^s{$n8o zAZgUgjPm8oSFX%vz65wMZN1q>>ZcuQ753yoGpueRX zNQev$;o-0$1px;VA^=h&UN8o7f+OgcQwLulfI>jm%+F{9i`C#(rt?kcqvqIzid?Q+ z430TvFRf;Lc1=Yt_Ya->MCgx+V^ELm(4wcM0Ar6hs0_csEO9|e2@q)Ef)@zHglwP? zHMxj~|F{)a5l|L|i2#z~Fs0MVl7G<(i$W6jfdJwimH!3}VJODmYZrxiaNrqQVcnEm_LK&b$vU({zSYPqj8;xp7yb6VpxcjeN0RwJi&S|3l`Aro>ZxiPw zVZ%$xYlbSJ!okdL=C-%Z5c*}$;?BmlwC9^XH>YsoeN4X-8dx`GzNg&6!^CMz?n2LY zqeR^*LffLubl;OjX2rn$e!~p~4SAjCBzJIRZ38xKnxTnG{>%O9`%crTU1fLIeE109 zD?^$%w;zV%iOqR2mVQX-Jvl3+R9mp|&Z;37(!z=#=heGc-~3d`^dj{g2(}ix#v!_y z0q@krpGwtNmM2WWekSXRzSor*Iz;S@LLATinSbT4OVwAO&UZ)8Wt&*oJ(r1(F@DpR zO56VLI`xjEB$~+pFhCsSLkBYTKj75+gL8ut6?Gs{(Iz590)U7G5Ya!WR0JS%Br+k? z@IO?k+UjcGB&wpeo{*Kbjk)_3x4)oKwSUs6=x>%me@Di@)Tp>K;W|VUkKZTDA+AAX z(sQ!du2y`^@7aVmKEs~4_hr|>kq)S6?XY=jKejo$;??lvZU)!nPW_^Tz5yP;4pNEy zbB$h9&&DP48y-FK@~6_?nb3SS*||s~mdtrx-ECQcw6mls;Bmm3grAWd<64UMncKP; zC6XjGn?l|Q>$mr+9HyT1ed_YTxT+2)ak8I$wg}BjjJ|NZ{^35>xO1lP8J_)fB|={w zPTvx#voK2?Hjw|Y+LvMz6ZO^JB4DKB4B>6=38@9TGuJHBec2q_?&M^j+zs$cw2$%1 zcYHB^XNPG|axi}5aUMZpXl_FWeKD3oP<2&c8oMNG71y-Y!q!HUzvTrk5+6Y+1CQ)$ zg*Z1zBB*Q43&^@t2Z>cu13UVA3;LJ+f>y?*ui=lGx+SyYnGcm}Ghc039mg00o0{gv zo!GKg@tWJdt6H=*>f&$iaMkl3qPqb9zEs`91fRZCx$#gZ z(YX|#Fjr~3nPk}f&G#aLCAMJLiNuKu?3fjP}*Wc%lJHuuPXe-nDJ_d#D@pWqDS4r)yTnsGXz!*T7Re-BsQ&8 zvNe0UqkFP~T}XZ%eg4@c{o6(uQDLxu8Xyk;Wg`sOhWwYq|IX;yV-FaUn1+JWjtc(QR$_t(PtN4Ty*!FEzWCf9c#qmUK(u~>wUg# zaIX2x9iudqv2u8H71^=X36^b{{TxG9NoY6LDDQD$1*Xuc%%$sn&rf+dKgd=IkX(+t zd_9WJ(j~lNJVN_qI=<$LrOsL`{*!592K9lO1l%CXypjg-1>6-u55CHGJ2LET96)<5 z4dRng`3=2``sbtLjGv8tq#a|F=~4<0I1PCT(ZBR^jE>raUoN3X?_v45J@H05-H%E_ zy-9*DaWxike$>vyH$8B(M}mxa3rI|9W4H!6Kvy!xqbxUx^+zyR!2V&NVgTJ(i;nZ&6361JnB#f8zbW8@K*VA2_Hqz#TAXANaTE0uq1te=55C!MT5p z`u~-`=79f$TVu4{vNDoW0BRTGXzN&;R5BFa#;awgE9dCx`>`myh0)K?b*5*2Ke z@wp9%?#{ih3&Ga{P)fs+(hQUdTh1X5as=M+Hex#_y>!FzRh~=e-K1ct9L#=ui%a5* zzZPa>zCJ`^F^rI}w2mhdMyAs^%_p>(c>3(*DzoZH)4(r*_#e%}-W1TFkjwnXB>m?w1v7 z@V~Zb5I+gK9>2#mNuaPC4D$}_G{jln7nv;v5xTtq#9sQFu@_pdf;{B}p6roP_6jzF+Kt~g8)ej0#@iHR!0`GMo#Bs=@h zlATrWs=JG&HLHfXs~fnVQ1QxDONSpL4(L9>#Ksu5g(v{*v?NFAAJ{AgE^2i20Jxjz z-~qrLkp7-)f<=1J&<_y&asrUe19;#MOeplieRS3cZa$@{{r0q-ZY%m}@gH7{2?s0L zM^2uD)D=bW%L%9_FJ&G6A2;hKYt7-VG`AtMXtGaR63&y2m4PgZZJ|{ucW<^~*gW!4 z^Rr+9zbT_z=M0_v*E`(t_E{p5EB2RnebU6Uy_6W5jcPXvislDvqzg;K({8)CKW3EJ zEk*@3OZYMOHg4TJcV|y8)xK`t09A4;gcO6{PN%vRwvo_Iygn_s@M*x~EiLbK`Orkq zqs8Y8+XJiJtY6+`S!FKhBwnE`pM&$ZoEOQIGV$o6<)i3s&Enu`AkrM3%@&!qP~$rD zC1AO!hWR2RAzu@Vyu^j4q>0pfwjs8$>BEM8>%IA5x2Sja221Ed24_s8tgT(|$wo?T zcV`FHKWXMT)AZ}HyA0Uo4{8iX6xH^8r4FX3SRzk zs)9iWZ4?p;gBLpFPt5g)@G#}XtnxmIFXEVQ?x7g1UZl^i@Vv2e_wg{H2<5U}5+*ur@6n8G{QRgb8n;ebar+Zl(M@MX*Zm6m L$Nu@fVi^AedJjBZ diff --git a/binaries/x86/php_sqlsrv_7_nts.dll b/binaries/x86/php_sqlsrv_7_nts.dll deleted file mode 100644 index d75692d9a6f7db1a2f7da0cbc1c161bc91672c20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 331480 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

    SELECT : Scrollable => SQLSRV_CURSOR_KEYSET

    "; +$sql = "SELECT * FROM Person.Address"; +// $tsql = "SELECT * FROM HumanResources.Employee"; + + +$params = array(); +$options = array("Scrollable" => SQLSRV_CURSOR_KEYSET); +$options = array(); +$stmt = sqlsrv_query($conn, $sql, $params, $options); + +// sqlsrv_execute ( $stmt ); + +// $stmt = sqlsrv_query( $conn, $sql, array(), array() ); + +// PRINT RESULT SET +$numRowsPrint = 3; +echo "

    Printing first $numRowsPrint rows

    "; +echo "

    "; +$count = 0; +// while( ($row = sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC)) && $count <$numRowsPrint) +while( ($row = sqlsrv_fetch_array($stmt,SQLSRV_FETCH_NUMERIC)) && $count <$numRowsPrint) +{ + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + $count++; +} +echo "
    " . $row[0] . "" . $row[1] . "" . $row[2] . "" . $row[3] . "" . $row[4] . "" . $row[7] . "
    "; + + + +/* Cancel the pending results. The statement can be reused. */ +sqlsrv_cancel( $stmt); + +// PRINT RESULT SET +echo "

    SQLSRV_CANCEL + Print next 5 rows of the result set (MUST BE EMPTY)

    "; +echo "

    "; +while( ($row = sqlsrv_fetch_array($stmt,SQLSRV_FETCH_NUMERIC)) && $count <($numRowsPrint + 5)) +{ + echo "

    IF sqlsrv_cancel() is executed, YOU SHOUL NOT SEE THIS"; + + echo "

    "; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + $count++; +} +echo "
    " . $row[0] . "" . $row[1] . "" . $row[3] . "" . $row[4] . "" . $row[7] . "
    "; + +echo "

    Finished successfully"; + + + +?> +--EXPECT-- +

    SELECT : Scrollable => SQLSRV_CURSOR_KEYSET

    Printing first 3 rows

    11970 Napa Ct.Bothell799AADCB0D-36CF-483F-84D8-585C2D4EC6E9
    29833 Mt. Dias Blv.Bothell7932A54B9E-E034-4BFB-B573-A71CDE60D8C0
    37484 Roundtree DriveBothell794C506923-6D1B-452C-A07C-BAA6F5B142A4

    SQLSRV_CANCEL + Print next 5 rows of the result set (MUST BE EMPTY)

    Finished successfully \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt new file mode 100644 index 00000000..b0d42566 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt @@ -0,0 +1,35 @@ +--TEST-- +client information. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +if( $client_info = sqlsrv_client_info( $conn)) +{ + foreach( $client_info as $key => $value) + { + echo $key.": ".$value."\n"; + } +} +else +{ + echo "Client info error.\n"; +} + +/* Close connection resources. */ +sqlsrv_close( $conn); +?> +--EXPECTREGEX-- +DriverDllName: msodbcsql[0-9]{2}\.dll|libmsodbcsql-[0-9]{2}\.[0-9]\.so\.[0-9]\.[0-9] +DriverODBCVer: [0-9]{1,2}\.[0-9]{1,2} +DriverVer: [0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4} +ExtensionVer: [0-9]\.[0-9]\.[0-9](\-((rc)|(preview))(\.[0-9]+)?)?(\+[0-9]+)? \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_close.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_close.phpt new file mode 100644 index 00000000..0b95facc --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_close.phpt @@ -0,0 +1,35 @@ +--TEST-- +closes a connection. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Prepare and execute the query. */ +$tsql = "SELECT OrderQty, UnitPrice FROM Sales.SalesOrderDetail"; +$stmt = sqlsrv_prepare( $conn, $tsql); +if( $stmt === false ) +{ + echo "Error in statement preparation.\n"; + die( print_r( sqlsrv_errors(), true)); +} +if( sqlsrv_execute( $stmt ) === false) +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Close the connection. */ +sqlsrv_close( $conn); +echo "Connection closed.\n"; +?> +--EXPECT-- +Connection closed. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_commit.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_commit.phpt new file mode 100644 index 00000000..ddcff9fc --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_commit.phpt @@ -0,0 +1,71 @@ +--TEST-- +executes two queries as part of a transaction. If both queries are successful, the transaction is committed. +--SKIPIF-- + +?> +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true )); +} + +/* Initiate transaction. */ +/* Exit script if transaction cannot be initiated. */ +if (sqlsrv_begin_transaction( $conn) === false) +{ + echo "Could not begin transaction.\n"; + die( print_r( sqlsrv_errors(), true )); +} + +/* Initialize parameter values. */ +$orderId = 43659; $qty = 5; $productId = 709; +$offerId = 1; $price = 5.70; + +/* Set up and execute the first query. */ +$tsql1 = "INSERT INTO Sales.SalesOrderDetail + (SalesOrderID, + OrderQty, + ProductID, + SpecialOfferID, + UnitPrice) + VALUES (?, ?, ?, ?, ?)"; +$params1 = array( $orderId, $qty, $productId, $offerId, $price); +$stmt1 = sqlsrv_query( $conn, $tsql1, $params1 ); + +/* Set up and execute the second query. */ +$tsql2 = "UPDATE Production.ProductInventory + SET Quantity = (Quantity - ?) + WHERE ProductID = ?"; +$params2 = array($qty, $productId); +$stmt2 = sqlsrv_query( $conn, $tsql2, $params2 ); + +/* If both queries were successful, commit the transaction. */ +/* Otherwise, rollback the transaction. */ +if( $stmt1 && $stmt2 ) +{ + sqlsrv_commit( $conn ); + echo "Transaction was committed.\n"; +} +else +{ + sqlsrv_rollback( $conn ); + echo "Transaction was rolled back.\n"; +} + +/* Revert the changes */ +$d_sql = "DELETE FROM Sales.SalesOrderDetail WHERE SalesOrderID=43659 AND OrderQty=5 AND ProductID=709 AND SpecialOfferID=1 AND Unitprice=5.70"; +$stmt3 = sqlsrv_query($conn, $d_sql); + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt1); +sqlsrv_free_stmt( $stmt2); +sqlsrv_free_stmt($stmt3); +sqlsrv_close( $conn); +?> +--EXPECT-- +Transaction was committed. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_configure.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_configure.phpt new file mode 100644 index 00000000..3e7aa37f --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_configure.phpt @@ -0,0 +1,69 @@ +--TEST-- +disables the default error-handling behaviour using configure +--SKIPIF-- + +?> +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* The Transact-SQL PRINT statement can be used to return +informational or warning messages*/ +$tsql = "PRINT 'The PRINT statement can be used "; +$tsql .= "to return user-defined warnings.'"; + +/* Execute the query and print any errors. */ +$stmt1 = sqlsrv_query( $conn, $tsql); +if($stmt1 === false) +{ + echo "By default, warnings are treated as errors:\n"; + /* Dump errors in the error collection. */ + print_r(sqlsrv_errors(SQLSRV_ERR_ERRORS)); +} + +/* Disable warnings as errors behavior. */ +sqlsrv_configure("WarningsReturnAsErrors", 0); + +/* Execute the same query and print any errors. */ +$stmt2 = sqlsrv_query( $conn, $tsql); +if($stmt2 === false) +{ + /* Dump errors in the error collection. */ + /* Since the warning generated by the query will not be treated as + an error, this block of code will not be executed. */ + print_r(sqlsrv_errors(SQLSRV_ERR_ERRORS)); +} +else +{ + echo "After calling "; + echo "sqlsrv_configure('WarningsReturnAsErrors', 0), "; + echo "warnings are not treated as errors."; +} + +/*Close the connection. */ +sqlsrv_close($conn); +?> +--EXPECTREGEX-- +By default, warnings are treated as errors: +Array +\( + \[0\] => Array + \( + \[0\] => 01000 + \[SQLSTATE\] => 01000 + \[1\] => 0 + \[code\] => 0 + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]The PRINT statement can be used to return user-defined warnings. + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]The PRINT statement can be used to return user-defined warnings. + \) + +\) +After calling sqlsrv_configure\('WarningsReturnAsErrors', 0\), warnings are not treated as errors. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt new file mode 100644 index 00000000..1920cc48 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt @@ -0,0 +1,176 @@ +--TEST-- +disables the default error-handling behaviour using configure and returns warnings +--SKIPIF-- + +?> +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); + +/* If the connection fails, display errors and exit the script. */ +if( $conn === false ) +{ + DisplayErrors(); + die; +} +/* Display any warnings. */ +DisplayWarnings(); + +/* Revert previous updates */ +$r_sql="UPDATE HumanResources.Employee SET VacationHours=61 WHERE BusinessEntityID=7; + UPDATE HumanResources.Employee SET VacationHours=62 WHERE BusinessEntityID=8; + UPDATE HumanResources.Employee SET VacationHours=63 WHERE BusinessEntityID=9; + UPDATE HumanResources.Employee SET VacationHours=7 WHERE BusinessEntityID=11"; +$stmt4=sqlsrv_query($conn, $r_sql); +sqlsrv_free_stmt( $stmt4 ); + +/* Drop the stored procedure if it already exists. */ +$tsql1 = "IF OBJECT_ID('SubtractVacationHours', 'P') IS NOT NULL + DROP PROCEDURE SubtractVacationHours"; +$stmt1 = sqlsrv_query($conn, $tsql1); + +/* If the query fails, display errors and exit the script. */ +if( $stmt1 === false) +{ + DisplayErrors(); + die; +} +/* Display any warnings. */ +DisplayWarnings(); + +/* Free the statement resources. */ +sqlsrv_free_stmt( $stmt1 ); + +/* Create the stored procedure. */ +$tsql2 = "CREATE PROCEDURE SubtractVacationHours + @BusinessEntityId int, + @VacationHours smallint OUTPUT + AS + UPDATE HumanResources.Employee + SET VacationHours = VacationHours - @VacationHours + WHERE BusinessEntityId = @BusinessEntityId; + SET @VacationHours = (SELECT VacationHours + FROM HumanResources.Employee + WHERE BusinessEntityId = @BusinessEntityId); + IF @VacationHours < 0 + BEGIN + PRINT 'WARNING: Vacation hours are now less than zero.' + END;"; +$stmt2 = sqlsrv_query( $conn, $tsql2 ); + +/* If the query fails, display errors and exit the script. */ +if( $stmt2 === false) +{ + DisplayErrors(); + die; +} +/* Display any warnings. */ +DisplayWarnings(); + +/* Free the statement resources. */ +sqlsrv_free_stmt( $stmt2 ); + +/* Set up the array that maps employee ID to used vacation hours. */ +$emp_hrs = array (7=>4, 8=>5, 9=>8, 11=>50); + +/* Initialize variables that will be used as parameters. */ +$businessEntityId = 0; +$vacationHrs = 0; + +/* Set up the parameter array. */ +$params = array( + array(&$businessEntityId, SQLSRV_PARAM_IN), + array(&$vacationHrs, SQLSRV_PARAM_INOUT) + ); + +/* Define and prepare the query to substract used vacation hours. */ +$tsql3 = "{call SubtractVacationHours(?, ?)}"; +$stmt3 = sqlsrv_prepare($conn, $tsql3, $params); + +/* If the statement preparation fails, display errors and exit the script. */ +if( $stmt3 === false) +{ + DisplayErrors(); + die; +} +/* Display any warnings. */ +DisplayWarnings(); + +/* Loop through the employee=>vacation hours array. Update parameter + values before statement execution. */ +foreach(array_keys($emp_hrs) as $businessEntityId) +{ + $vacationHrs = $emp_hrs[$businessEntityId]; + /* Execute the query. If it fails, display the errors. */ + if( sqlsrv_execute($stmt3) === false) + { + DisplayErrors(); + die; + } + /* Display any warnings. */ + DisplayWarnings(); + + /*Move to the next result returned by the stored procedure. */ + if( sqlsrv_next_result($stmt3) === false) + { + DisplayErrors(); + die; + } + /* Display any warnings. */ + DisplayWarnings(); + + /* Display updated vacation hours. */ + echo "BusinessEntityId $businessEntityId has $vacationHrs "; + echo "remaining vacation hours.\n"; +} + +/* Free the statement*/ +sqlsrv_free_stmt( $stmt3 ); + +/* close connection resources. */ +sqlsrv_close( $conn ); + +/* ------------- Error Handling Functions --------------*/ +function DisplayErrors() +{ + $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); + foreach( $errors as $error ) + { + echo "Error: ".$error['message']."\n"; + } +} + +function DisplayWarnings() +{ + $warnings = sqlsrv_errors(SQLSRV_ERR_WARNINGS); + if(!is_null($warnings)) + { + foreach( $warnings as $warning ) + { + echo "Warning: ".$warning['message']."\n"; + } + } +} +?> +--EXPECTREGEX-- +Warning: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Changed database context to 'AdventureWorks2014'. +Warning: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Changed language setting to us_english. +BusinessEntityId 7 has 57 remaining vacation hours. +BusinessEntityId 8 has 57 remaining vacation hours. +BusinessEntityId 9 has 55 remaining vacation hours. +Error: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]The UPDATE statement conflicted with the CHECK constraint "CK_Employee_VacationHours". The conflict occurred in database "AdventureWorks2014", table "HumanResources.Employee", column 'VacationHours'. +Error: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]The statement has been terminated. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_connect.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_connect.phpt new file mode 100644 index 00000000..6e8baef5 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_connect.phpt @@ -0,0 +1,39 @@ +--TEST-- +creates and opens a connection using Windows Authentication. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); + +if( $conn ) +{ + echo "Connection established.\n"; +} +else +{ + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Prepare and execute the query. */ +$tsql = "SELECT OrderQty, UnitPrice FROM Sales.SalesOrderDetail"; +$stmt = sqlsrv_prepare( $conn, $tsql); +if( $stmt === false ) +{ + echo "Error in statement preparation.\n"; + die( print_r( sqlsrv_errors(), true)); +} +if( sqlsrv_execute( $stmt ) === false) +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Close the connection. */ +sqlsrv_close( $conn); +?> +--EXPECT-- +Connection established. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt new file mode 100644 index 00000000..9b357732 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt @@ -0,0 +1,29 @@ +--TEST-- +disables MARS support. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); + +/* Connect to the local server using Windows Authentication and +specify the AdventureWorks database as the database in use. */ +$serverName = "sql-2k14-sp1-1.galaxy.ad"; +$connectionInfo = array( "Database"=>"AdventureWorks2014", "UID"=>"sa", "PWD"=>"Moonshine4me", 'MultipleActiveResultSets'=> false); +$conn = sqlsrv_connect( $serverName, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} +else +{ + echo "Connection established.\n"; +} + +sqlsrv_close( $conn); +?> +--EXPECT-- +Connection established. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDateAsStrings.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDateAsStrings.phpt new file mode 100644 index 00000000..8d7900de --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDateAsStrings.phpt @@ -0,0 +1,23 @@ +--TEST-- +specifies to retrieve date and time types as string when connecting. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} +else +{ + echo "Connection established.\n"; +} + +sqlsrv_close( $conn); +?> +--EXPECT-- +Connection established. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDatesAsStrings_utf8.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDatesAsStrings_utf8.phpt new file mode 100644 index 00000000..7bf2a839 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDatesAsStrings_utf8.phpt @@ -0,0 +1,38 @@ +--TEST-- +retrieves dates as string by specifying UTF-8 and ReturnDatesAsStrings when connecting. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd", 'ReturnDatesAsStrings'=> true, "CharacterSet" => 'utf-8'); +$conn = sqlsrv_connect( $server, $connectionInfo); + +if( $conn === false ) { + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +$tsql = "SELECT VersionDate FROM AWBuildVersion"; + +$stmt = sqlsrv_query( $conn, $tsql); + +if ( $stmt === false ) { + echo "Error in statement preparation/execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +sqlsrv_fetch( $stmt ); + +// retrieve date as string +$date = sqlsrv_get_field( $stmt, 0 ); + +if ( $date === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +echo $date; +sqlsrv_close( $conn); +?> +--EXPECT-- +2014-02-20 04:26:00.000 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_connect_utf8.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_connect_utf8.phpt new file mode 100644 index 00000000..d6adce7c --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_connect_utf8.phpt @@ -0,0 +1,83 @@ +--TEST-- +retrieves UTF-8 encoded data by specifying the UTF-8 character set when making the connection +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd", "CharacterSet" => 'UTF-8'); +$conn = sqlsrv_connect( $server, $connectionInfo); + +if ( $conn === false ) { + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +// Set up the Transact-SQL query. +// +$tsql1 = "UPDATE Production.ProductReview + SET Comments = ? + WHERE ProductReviewID = ?"; + +// Set the parameter values and put them in an array. Note that +// $comments is converted to UTF-8 encoding with the PHP function +// utf8_encode to simulate an application that uses UTF-8 encoded data. +// +$reviewID = 3; + +$comments = utf8_encode("testing 1, 2, 3, 4. Testing."); +$params1 = array( + array( $comments, null ), + array( $reviewID, null ) + ); + +// Execute the query. +// +$stmt1 = sqlsrv_query($conn, $tsql1, $params1); + +if ( $stmt1 === false ) { + echo "Error in statement execution.
    "; + die( print_r( sqlsrv_errors(), true)); +} +else { + echo "The update was successfully executed.
    "; +} + +// Retrieve the newly updated data. +// +$tsql2 = "SELECT Comments + FROM Production.ProductReview + WHERE ProductReviewID = ?"; + +// Set up the parameter array. +// +$params2 = array($reviewID); + +// Execute the query. +// +$stmt2 = sqlsrv_query($conn, $tsql2, $params2); +if ( $stmt2 === false ) { + echo "Error in statement execution.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +// Retrieve and display the data. +// +if ( sqlsrv_fetch($stmt2) ) { + echo "Comments: "; + $data = sqlsrv_get_field( $stmt2, 0 ); + echo $data."
    "; +} +else { + echo "Error in fetching data.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +// Free statement and connection resources. +// +sqlsrv_free_stmt( $stmt1 ); +sqlsrv_free_stmt( $stmt2 ); +sqlsrv_close( $conn); +?> +--EXPECT-- +The update was successfully executed.
    Comments: testing 1, 2, 3, 4. Testing.
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt new file mode 100644 index 00000000..a0013c49 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt @@ -0,0 +1,40 @@ +--TEST-- +retrieve date as PHP type with ReturnDatesAsStrings off by default. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); + +if( $conn === false ) { + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +$tsql = "SELECT VersionDate FROM AWBuildVersion"; + +$stmt = sqlsrv_query( $conn, $tsql); + +if ( $stmt === false ) { + echo "Error in statement preparation/execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +sqlsrv_fetch( $stmt ); + +// retrieve date as string +$date = sqlsrv_get_field( $stmt, 0 ); + +if ( $date === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$date_string = date_format( $date, 'jS, F Y' ); +echo "Date = $date_string\n"; + +sqlsrv_close( $conn); +?> +--EXPECT-- +Date = 20th, February 2014 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_errors.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_errors.phpt new file mode 100644 index 00000000..4c296046 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_errors.phpt @@ -0,0 +1,39 @@ +--TEST-- +displays errors that occur during a failed statement execution +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up a query to select an invalid column name. */ +$tsql = "SELECT InvalidColumnName FROM Sales.SalesOrderDetail"; + +/* Attempt execution. */ +/* Execution will fail because of the invalid column name. */ +$stmt = sqlsrv_query( $conn, $tsql); +if( $stmt === false ) +{ + if( ($errors = sqlsrv_errors() ) != null) + { + foreach( $errors as $error) + { + echo "SQLSTATE: ".$error[ 'SQLSTATE']."
    "; + echo "code: ".$error[ 'code']."
    "; + echo "message: ".$error[ 'message']."
    "; + } + } +} + +/* Free connection resources */ +sqlsrv_close( $conn); +?> +--EXPECTREGEX-- +SQLSTATE: 42S22
    code: 207
    message: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid column name 'InvalidColumnName'.
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_execute.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_execute.phpt new file mode 100644 index 00000000..a44cd93a --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_execute.phpt @@ -0,0 +1,63 @@ +--TEST-- +executes a statement that updates a field. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + + +/* Set up the Transact-SQL query. */ +$tsql = "UPDATE Sales.SalesOrderDetail + SET OrderQty = (?) + WHERE SalesOrderDetailID = (?)"; + +/* Set up the parameters array. Parameters correspond, in order, to +question marks in $tsql. */ +$params = array(5, 10); + + + +/* Create the statement. */ +$stmt = sqlsrv_prepare( $conn, $tsql, $params); +if( $stmt ) +{ + echo "Statement prepared.\n"; +} +else +{ + echo "Error in preparing statement.\n"; + die( print_r( sqlsrv_errors(), true)); +} + + +/* Execute the statement. Display any errors that occur. */ +if( sqlsrv_execute( $stmt)) +{ + echo "Statement executed.\n"; +} +else +{ + echo "Error in executing statement.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/*revert the update */ +$r_sql = "UPDATE Sales.SalesOrderDetail SET OrderQty=6 WHERE SalesOrderDetailID=10"; +$stmt2 = sqlsrv_query($conn, $r_sql); + +/* Free the statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_free_stmt($stmt2); +sqlsrv_close( $conn); +?> +--EXPECT-- +Statement prepared. +Statement executed. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_execute_datetime.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_execute_datetime.phpt new file mode 100644 index 00000000..caac6ede --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_execute_datetime.phpt @@ -0,0 +1,134 @@ +--TEST-- +execute with datetime type in bind parameters. +--SKIPIF-- + +?> +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Prepare with datetime type in parameter. */ +$tsql = "SELECT * FROM Sales.CurrencyRate WHERE CurrencyRateDate=(?)"; + +//Pass in parameters directly +$params = array( '2014-05-31 00:00:00.000'); +$stmt = sqlsrv_prepare( $conn, $tsql, $params); + +//Pass in parameters through reference +$crd='2014-05-31 00:00:00.000'; +$stmt = sqlsrv_prepare( $conn, $tsql, array(&$crd)); + +echo "Datetime Type, Select Query:
    "; +if( $stmt ) +{ + echo "Statement prepared.
    "; +} +else +{ + echo "Error in preparing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Execute the statement. Display any errors that occur. */ +if( sqlsrv_execute( $stmt)) +{ + echo "Statement executed.
    "; +} +else +{ + echo "Error in executing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} +while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC)) +{ + echo $row['CurrencyRateID'].", ".date_format($row['CurrencyRateDate'], 'Y-m-d H:i:s')."
    "; +} +echo "
    "; + +sqlsrv_free_stmt( $stmt); + + +/* Prepare with datetime type in parameter. */ +$tsql = "UPDATE Sales.CurrencyRate SET FromCurrencyCode='CAD' WHERE CurrencyRateDate=(?)"; + +//Pass in parameters directly +$params = array( '2011-08-15 00:00:00.000'); +$stmt = sqlsrv_prepare( $conn, $tsql, $params); + +//Pass in parameters through reference +$crd='2011-08-15 00:00:00.000'; +$stmt = sqlsrv_prepare( $conn, $tsql, array(&$crd)); + +echo "Datetime Type, Update Query:
    "; +if( $stmt ) +{ + echo "Statement prepared.
    "; +} +else +{ + echo "Error in preparing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Execute the statement. Display any errors that occur. */ +if( sqlsrv_execute( $stmt)) +{ + echo "Statement executed.
    "; +} +else +{ + echo "Error in executing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} +echo sqlsrv_rows_affected( $stmt)." rows affected.

    "; +sqlsrv_free_stmt( $stmt); + + +/* Revert the Update*/ +$tsql = "UPDATE Sales.CurrencyRate SET FromCurrencyCode='USD' WHERE CurrencyRateDate=(?)"; + +//Pass in parameters directly +$params = array( '2011-08-15 00:00:00.000'); +$stmt = sqlsrv_prepare( $conn, $tsql, $params); + +//Pass in parameters through reference +$crd='2011-08-15 00:00:00.000'; +$stmt = sqlsrv_prepare( $conn, $tsql, array(&$crd)); + +echo "Datetime Type, Update Query:
    "; +if( $stmt ) +{ + echo "Statement prepared.
    "; +} +else +{ + echo "Error in preparing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Execute the statement. Display any errors that occur. */ +if( sqlsrv_execute( $stmt)) +{ + echo "Statement executed.
    "; +} +else +{ + echo "Error in executing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} +echo sqlsrv_rows_affected( $stmt)." rows affected.
    "; + + +/* Free the statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +Datetime Type, Select Query:
    Statement prepared.
    Statement executed.
    12425, 2014-05-31 00:00:00
    12426, 2014-05-31 00:00:00
    12427, 2014-05-31 00:00:00
    12428, 2014-05-31 00:00:00
    12429, 2014-05-31 00:00:00
    12430, 2014-05-31 00:00:00
    12431, 2014-05-31 00:00:00
    12432, 2014-05-31 00:00:00
    12433, 2014-05-31 00:00:00
    12434, 2014-05-31 00:00:00
    13532, 2014-05-31 00:00:00
    12435, 2014-05-31 00:00:00

    Datetime Type, Update Query:
    Statement prepared.
    Statement executed.
    14 rows affected.

    Datetime Type, Update Query:
    Statement prepared.
    Statement executed.
    14 rows affected.
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_execute_string.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_execute_string.phpt new file mode 100644 index 00000000..c9effa18 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_execute_string.phpt @@ -0,0 +1,152 @@ +--TEST-- +execute with string type in bind parameters. +--SKIPIF-- + +?> +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Prepare with string type in parameter. */ +$tsql = "SELECT * FROM Sales.SalesOrderDetail WHERE CarrierTrackingNumber=(?)"; + +//Pass in parameters directly +$params = array( '8650-4A20-B1'); +$stmt = sqlsrv_prepare( $conn, $tsql, $params); + +//Pass in parameters through reference +//$ctn="8650-4A20-B1"; +//$stmt = sqlsrv_prepare( $conn, $tsql, array(&$ctn)); + +echo "String Type, Select Query:
    "; +if( $stmt ) +{ + echo "Statement prepared.
    "; +} +else +{ + echo "Error in preparing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Execute the statement. Display any errors that occur. */ +if( sqlsrv_execute( $stmt)) +{ + echo "Statement executed.
    "; +} +else +{ + echo "Error in executing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +$soID = 0; +$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC); +while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC)) +{ + echo $row['SalesOrderID'].", ".$row['CarrierTrackingNumber']."
    "; + $soID = $row['SalesOrderID']; +} +echo "
    "; + +sqlsrv_free_stmt( $stmt); + +/* Prepare with string type in parameter. */ +$tsql = "UPDATE Sales.SalesOrderDetail + SET OrderQty=(?) + WHERE CarrierTrackingNumber=(?)"; + +// RevisionNumber in SalesOrderHeader is subject to a trigger incrementing it whenever +// changes are made to SalesOrderDetail. Since RevisonNumber is a tinyint, it can +// overflow quickly if this test is often run. So we change it directly here first +// before it can overflow. +$stmt0 = sqlsrv_query( $conn, "UPDATE Sales.SalesOrderHeader SET RevisionNumber = 2 WHERE SalesOrderID = $soID" ); + +//Pass in parameters directly +$params = array(5, '8650-4A20-B1'); +$stmt = sqlsrv_prepare( $conn, $tsql, $params); + +//Pass in parameters through reference +//$oq=5; +//$ctn="8650-4A20-B1"; +//$stmt = sqlsrv_prepare( $conn, $tsql, array(&$oq, &$ctn)); + +echo "String Type, Update Query:
    "; +if( $stmt ) +{ + echo "Statement prepared.
    "; +} +else +{ + echo "Error in preparing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Execute the statement. Display any errors that occur. */ + +if( sqlsrv_execute( $stmt)) +{ + echo "Statement executed.
    "; +} +else +{ + echo "Error in executing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} +echo sqlsrv_rows_affected( $stmt)." rows affected.

    "; +sqlsrv_free_stmt( $stmt); + + +/* Revert back the Update. */ +$tsql = "UPDATE Sales.SalesOrderDetail + SET OrderQty=(?) + WHERE CarrierTrackingNumber=(?)"; + +//Pass in parameters directly +$params = array(1, '8650-4A20-B1'); +$stmt = sqlsrv_prepare( $conn, $tsql, $params); + +//Pass in parameters through reference +//$oq=1; +//$ctn="8650-4A20-B1"; +//$stmt = sqlsrv_prepare( $conn, $tsql, array(&$oq, &$ctn)); + +echo "String Type, Update Query:
    "; +if( $stmt ) +{ + echo "Statement prepared.
    "; +} +else +{ + echo "Error in preparing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Execute the statement. Display any errors that occur. */ + +if( sqlsrv_execute( $stmt)) +{ + echo "Statement executed.
    "; +} +else +{ + echo "Error in executing statement.
    "; + die( print_r( sqlsrv_errors(), true)); +} +echo sqlsrv_rows_affected( $stmt)." rows affected.
    "; +sqlsrv_free_stmt( $stmt); + + +/* Free the statement and connection resources. */ +//sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +String Type, Select Query:
    Statement prepared.
    Statement executed.
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1
    51108, 8650-4A20-B1

    String Type, Update Query:
    Statement prepared.
    Statement executed.
    52 rows affected.

    String Type, Update Query:
    Statement prepared.
    Statement executed.
    52 rows affected.
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_fetch.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_fetch.phpt new file mode 100644 index 00000000..a5162be1 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_fetch.phpt @@ -0,0 +1,63 @@ +--TEST-- +retrieve a row of data. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up and execute the query. Note that both ReviewerName and +Comments are of SQL Server type nvarchar. */ +$tsql = "SELECT ReviewerName, Comments + FROM Production.ProductReview + WHERE ProductReviewID=1"; +$stmt = sqlsrv_query( $conn, $tsql); +if( $stmt === false ) +{ + echo "Error in statement preparation/execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Make the first row of the result set available for reading. */ +if( sqlsrv_fetch( $stmt ) === false) +{ + echo "Error in retrieving row.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Note: Fields must be accessed in order. +Get the first field of the row. Note that no return type is +specified. Data will be returned as a string, the default for +a field of type nvarchar.*/ +$name = sqlsrv_get_field( $stmt, 0); +echo "$name: "; + +/*Get the second field of the row as a stream. +Because the default return type for a nvarchar field is a +string, the return type must be specified as a stream. */ +$stream = sqlsrv_get_field( $stmt, 1, + SQLSRV_PHPTYPE_STREAM( SQLSRV_ENC_CHAR)); +while( !feof( $stream )) +{ + $str = fread( $stream, 10000); + echo $str; +} + +/* Free the statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); + +?> +--EXPECT-- +John Smith: I can't believe I'm singing the praises of a pair of socks, but I just came back from a grueling +3-day ride and these socks really helped make the trip a blast. They're lightweight yet really cushioned my feet all day. +The reinforced toe is nearly bullet-proof and I didn't experience any problems with rubbing or blisters like I have with +other brands. I know it sounds silly, but it's always the little stuff (like comfortable feet) that makes or breaks a long trip. +I won't go on another trip without them! \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_fetch_array.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_fetch_array.phpt new file mode 100644 index 00000000..b355f74a --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_fetch_array.phpt @@ -0,0 +1,39 @@ +--TEST-- +retrieves each row of a result set as an associative array. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up and execute the query. */ +$tsql = "SELECT FirstName, LastName + FROM Person.Person + WHERE LastName='Alan'"; +$stmt = sqlsrv_query( $conn, $tsql); +if( $stmt === false) +{ + echo "Error in query preparation/execution.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Retrieve each row as an associative array and display the results.*/ +while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC)) +{ + echo $row['LastName'].", ".$row['FirstName']."
    "; +} + +/* Free statement and connection resources. */ + +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +Alan, Alisha
    Alan, Bob
    Alan, Cheryl
    Alan, Jamie
    Alan, Kari
    Alan, Kelvin
    Alan, Meghan
    Alan, Stanley
    Alan, Xavier
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_fetch_array_2.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_fetch_array_2.phpt new file mode 100644 index 00000000..4d803987 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_fetch_array_2.phpt @@ -0,0 +1,61 @@ +--TEST-- +retrieves each row of a result set as a numerically indexed array. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Define the query. */ +$tsql = "SELECT ProductID, + UnitPrice, + StockedQty + FROM Purchasing.PurchaseOrderDetail + WHERE StockedQty < 3 + AND DueDate='2014-01-29'"; + +/* Execute the query. */ +$stmt = sqlsrv_query( $conn, $tsql); +if ( $stmt ) +{ + echo "Statement executed.\n"; +} +else +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Iterate through the result set printing a row of data upon each +iteration.*/ +while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC)) +{ + echo "ProdID: ".$row[0]."\n"; + echo "UnitPrice: ".$row[1]."\n"; + echo "StockedQty: ".$row[2]."\n"; + echo "-----------------\n"; +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +Statement executed. +ProdID: 931 +UnitPrice: 34.3455 +StockedQty: .00 +----------------- +ProdID: 932 +UnitPrice: 39.2385 +StockedQty: .00 +----------------- \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_fetch_object.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_fetch_object.phpt new file mode 100644 index 00000000..77cc759e --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_fetch_object.phpt @@ -0,0 +1,39 @@ +--TEST-- +retrieves each row of a result set as a PHP object +--SKIPIF-- + +?> +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up and execute the query. */ +$tsql = "SELECT FirstName, LastName + FROM Person.Person + WHERE LastName='Alan'"; +$stmt = sqlsrv_query( $conn, $tsql); +if( $stmt === false ) +{ + echo "Error in query preparation/execution.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Retrieve each row as a PHP object and display the results.*/ +while( $obj = sqlsrv_fetch_object( $stmt)) +{ + echo $obj->LastName.", ".$obj->FirstName."
    "; +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +Alan, Alisha
    Alan, Bob
    Alan, Cheryl
    Alan, Jamie
    Alan, Kari
    Alan, Kelvin
    Alan, Meghan
    Alan, Stanley
    Alan, Xavier
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_fetch_object_2.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_fetch_object_2.phpt new file mode 100644 index 00000000..54177f37 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_fetch_object_2.phpt @@ -0,0 +1,108 @@ +--TEST-- +retrieves each row of a result set as an instance of the Product class defined in the script. +--SKIPIF-- + +--FILE-- +objID = $ID; + } + public $objID; + public $name; + public $StockedQty; + public $SafetyStockLevel; + private $UnitPrice; + function getPrice() + { + return $this->UnitPrice; + } +} + +/* Connect to the local server using Windows Authentication, and +specify the AdventureWorks database as the database in use. */ +require('connect.inc'); +$connectionInfo = array( "Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Define the query. */ +$tsql = "SELECT Name, + SafetyStockLevel, + StockedQty, + UnitPrice, + Color + FROM Purchasing.PurchaseOrderDetail AS pdo + JOIN Production.Product AS p + ON pdo.ProductID = p.ProductID + WHERE pdo.StockedQty < ? + AND pdo.DueDate= ?"; + +/* Set the parameter values. */ +$params = array(3, '2014-01-29'); + +/* Execute the query. */ +$stmt = sqlsrv_query( $conn, $tsql, $params); +if ( $stmt ) +{ + echo "Statement executed.\n"; +} +else +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Iterate through the result set, printing a row of data upon each + iteration. Note the following: + 1) $product is an instance of the Product class. + 2) The $ctorParams parameter is required in the call to + sqlsrv_fetch_object, because the Product class constructor is + explicity defined and requires parameter values. + 3) The "Name" property is added to the $product instance because + the existing "name" property does not match. + 4) The "Color" property is added to the $product instance + because there is no matching property. + 5) The private property "UnitPrice" is populated with the value + of the "UnitPrice" field.*/ +$i=0; //Used as the $objID in the Product class constructor. +while( $product = sqlsrv_fetch_object( $stmt, "Product", array($i))) +{ + echo "Object ID: ".$product->objID."\n"; + echo "Product Name: ".$product->Name."\n"; + echo "Stocked Qty: ".$product->StockedQty."\n"; + echo "Safety Stock Level: ".$product->SafetyStockLevel."\n"; + echo "Product Color: ".$product->Color."\n"; + echo "Unit Price: ".$product->getPrice()."\n"; + echo "-----------------\n"; + $i++; +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +Statement executed. +Object ID: +Product Name: LL Road Tire +Stocked Qty: .00 +Safety Stock Level: 500 +Product Color: +Unit Price: 34.3455 +----------------- +Object ID: +Product Name: ML Road Tire +Stocked Qty: .00 +Safety Stock Level: 500 +Product Color: +Unit Price: 39.2385 +----------------- \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_field_metadata.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_field_metadata.phpt new file mode 100644 index 00000000..5e596774 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_field_metadata.phpt @@ -0,0 +1,38 @@ +--TEST-- +creates a statement resource then retrieves and displays the field metadata +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Prepare the statement. */ +$tsql = "SELECT ReviewerName, Comments FROM Production.ProductReview"; +$stmt = sqlsrv_prepare( $conn, $tsql); + +/* Get and display field metadata. */ +foreach( sqlsrv_field_metadata( $stmt) as $fieldMetadata) +{ + foreach( $fieldMetadata as $name => $value) + { + echo "$name: $value
    "; + } + echo "
    "; +} + +/* Note: sqlsrv_field_metadata can be called on any statement +resource, pre- or post-execution. */ + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +Name: ReviewerName
    Type: -9
    Size: 50
    Precision:
    Scale:
    Nullable: 0

    Name: Comments
    Type: -9
    Size: 3850
    Precision:
    Scale:
    Nullable: 1

    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_free_stmt.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_free_stmt.phpt new file mode 100644 index 00000000..db66ad54 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_free_stmt.phpt @@ -0,0 +1,42 @@ +--TEST-- +creates a statement resource, executes a simple query, and free all resources associated with the statement +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +$stmt = sqlsrv_query( $conn, "SELECT * FROM Person.Person"); +if( $stmt ) +{ + echo "Statement executed.
    "; +} +else +{ + echo "Query could not be executed.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Get and display field metadata. */ +foreach( sqlsrv_field_metadata( $stmt) as $fieldMetadata) +{ + foreach( $fieldMetadata as $name => $value) + { + echo "$name: $value
    "; + } + echo "
    "; +} + +/* Free the statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +Statement executed.
    Name: BusinessEntityID
    Type: 4
    Size:
    Precision: 10
    Scale:
    Nullable: 0

    Name: PersonType
    Type: -8
    Size: 2
    Precision:
    Scale:
    Nullable: 0

    Name: NameStyle
    Type: -7
    Size:
    Precision: 1
    Scale:
    Nullable: 0

    Name: Title
    Type: -9
    Size: 8
    Precision:
    Scale:
    Nullable: 1

    Name: FirstName
    Type: -9
    Size: 50
    Precision:
    Scale:
    Nullable: 0

    Name: MiddleName
    Type: -9
    Size: 50
    Precision:
    Scale:
    Nullable: 1

    Name: LastName
    Type: -9
    Size: 50
    Precision:
    Scale:
    Nullable: 0

    Name: Suffix
    Type: -9
    Size: 10
    Precision:
    Scale:
    Nullable: 1

    Name: EmailPromotion
    Type: 4
    Size:
    Precision: 10
    Scale:
    Nullable: 0

    Name: AdditionalContactInfo
    Type: -152
    Size: 0
    Precision:
    Scale:
    Nullable: 1

    Name: Demographics
    Type: -152
    Size: 0
    Precision:
    Scale:
    Nullable: 1

    Name: rowguid
    Type: -11
    Size: 36
    Precision:
    Scale:
    Nullable: 0

    Name: ModifiedDate
    Type: 93
    Size:
    Precision: 23
    Scale: 3
    Nullable: 0

    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_get_field.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_get_field.phpt new file mode 100644 index 00000000..c663a2c1 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_get_field.phpt @@ -0,0 +1,63 @@ +--TEST-- +retrieves a row of data +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up and execute the query. Note that both ReviewerName and +Comments are of the SQL Server nvarchar type. */ +$tsql = "SELECT ReviewerName, Comments + FROM Production.ProductReview + WHERE ProductReviewID=1"; +$stmt = sqlsrv_query( $conn, $tsql); +if( $stmt === false ) +{ + echo "Error in statement preparation/execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Make the first row of the result set available for reading. */ +if( sqlsrv_fetch( $stmt ) === false ) +{ + echo "Error in retrieving row.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Note: Fields must be accessed in order. +Get the first field of the row. Note that no return type is +specified. Data will be returned as a string, the default for +a field of type nvarchar.*/ +$name = sqlsrv_get_field( $stmt, 0); +echo "$name: "; + +/*Get the second field of the row as a stream. +Because the default return type for a nvarchar field is a +string, the return type must be specified as a stream. */ +$stream = sqlsrv_get_field( $stmt, 1, + SQLSRV_PHPTYPE_STREAM( SQLSRV_ENC_CHAR)); +while( !feof( $stream)) +{ + $str = fread( $stream, 10000); + echo $str; +} + + +/* Free the statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +John Smith: I can't believe I'm singing the praises of a pair of socks, but I just came back from a grueling +3-day ride and these socks really helped make the trip a blast. They're lightweight yet really cushioned my feet all day. +The reinforced toe is nearly bullet-proof and I didn't experience any problems with rubbing or blisters like I have with +other brands. I know it sounds silly, but it's always the little stuff (like comfortable feet) that makes or breaks a long trip. +I won't go on another trip without them! \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream.phpt new file mode 100644 index 00000000..a2e1b5a9 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream.phpt @@ -0,0 +1,73 @@ +--TEST-- +retrieves datatime as string and nvarchar as stream. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/*revert inserts from previous tests*/ +$d_sql = "DELETE FROM Production.ProductReview WHERE EmailAddress!='john@fourthcoffee.com' AND ProductID=709"; +$stmt4 = sqlsrv_query($conn, $d_sql); + +/* Set up and execute the query. Note that both ReviewerName and + Comments are of the SQL Server nvarchar type. */ + $tsql = "SELECT ReviewerName, + ReviewDate, + Rating, + Comments + FROM Production.ProductReview + WHERE ProductID = ? + ORDER BY ReviewDate DESC"; + +/* Set the parameter value. */ +$productID = 709; +$params = array( $productID); + +/* Execute the query. */ +$stmt = sqlsrv_query($conn, $tsql, $params); +if( $stmt === false ) +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Retrieve and display the data. The first and third fields are +retrieved according to their default types, strings. The second field +is retrieved as a string with 8-bit character encoding. The fourth +field is retrieved as a stream with 8-bit character encoding.*/ +while ( sqlsrv_fetch( $stmt)) +{ + echo "Name: ".sqlsrv_get_field( $stmt, 0 )."\n"; + echo "Date: ".sqlsrv_get_field( $stmt, 1, + SQLSRV_PHPTYPE_STRING( SQLSRV_ENC_CHAR))."\n"; + echo "Rating: ".sqlsrv_get_field( $stmt, 2 )."\n"; + echo "Comments: "; + $comments = sqlsrv_get_field( $stmt, 3, + SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR)); + fpassthru( $comments); + echo "\n"; +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close($conn); +?> +--EXPECT-- +Name: John Smith +Date: 2013-09-18 00:00:00.000 +Rating: 5 +Comments: I can't believe I'm singing the praises of a pair of socks, but I just came back from a grueling +3-day ride and these socks really helped make the trip a blast. They're lightweight yet really cushioned my feet all day. +The reinforced toe is nearly bullet-proof and I didn't experience any problems with rubbing or blisters like I have with +other brands. I know it sounds silly, but it's always the little stuff (like comfortable feet) that makes or breaks a long trip. +I won't go on another trip without them! \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_binary.php b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_binary.php new file mode 100644 index 00000000..751229f1 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_binary.php @@ -0,0 +1,48 @@ +"AdventureWorks2014", "UID"=>"sa", "PWD"=>"Moonshine4me"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up the Transact-SQL query. */ +$tsql = "SELECT LargePhoto + FROM Production.ProductPhoto + WHERE ProductPhotoID = ?"; + +/* Set the parameter values and put them in an array. */ +$productPhotoID = 70; +$params = array( $productPhotoID); + +/* Execute the query. */ +$stmt = sqlsrv_query($conn, $tsql, $params); +if( $stmt === false ) +{ + echo "Error in statement execution.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Retrieve and display the data. +The return data is retrieved as a binary stream. */ +if ( sqlsrv_fetch( $stmt ) ) +{ + $image = sqlsrv_get_field( $stmt, 0, + SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY)); + header("Content-Type: image/jpg"); + fpassthru($image); +} +else +{ + echo "Error in retrieving data.
    "; + die(print_r( sqlsrv_errors(), true)); +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_char.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_char.phpt new file mode 100644 index 00000000..257fa109 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_char.phpt @@ -0,0 +1,66 @@ +--TEST-- +retrieves row as a stream specified as a character stream. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up the Transact-SQL query. */ +$tsql = "SELECT ReviewerName, + CONVERT(varchar(32), ReviewDate, 107) AS [ReviewDate], + Rating, + Comments + FROM Production.ProductReview + WHERE ProductReviewID = ? "; + +/* Set the parameter value. */ +$productReviewID = 1; +$params = array( $productReviewID); + +/* Execute the query. */ +$stmt = sqlsrv_query($conn, $tsql, $params); +if( $stmt === false ) +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Retrieve and display the data. The first three fields are retrieved +as strings and the fourth as a stream with character encoding. */ +if(sqlsrv_fetch( $stmt ) === false ) +{ + echo "Error in retrieving row.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +echo "Name: ".sqlsrv_get_field( $stmt, 0 )."\n"; +echo "Date: ".sqlsrv_get_field( $stmt, 1 )."\n"; +echo "Rating: ".sqlsrv_get_field( $stmt, 2 )."\n"; +echo "Comments: "; +$comments = sqlsrv_get_field( $stmt, 3, + SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR)); +fpassthru($comments); + +/* Free the statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +Name: John Smith +Date: Sep 18, 2013 +Rating: 5 +Comments: I can't believe I'm singing the praises of a pair of socks, but I just came back from a grueling +3-day ride and these socks really helped make the trip a blast. They're lightweight yet really cushioned my feet all day. +The reinforced toe is nearly bullet-proof and I didn't experience any problems with rubbing or blisters like I have with +other brands. I know it sounds silly, but it's always the little stuff (like comfortable feet) that makes or breaks a long trip. +I won't go on another trip without them! \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_get_field_string_utf8.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_string_utf8.phpt new file mode 100644 index 00000000..22ab346c --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_string_utf8.phpt @@ -0,0 +1,40 @@ +--TEST-- +retrieves dates as strings by specifying UTF-8 when fetching the string. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd", "ReturnDatesAsStrings" => false); +$conn = sqlsrv_connect( $server, $connectionInfo); + + +if( $conn === false ) { + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +$tsql = "SELECT VersionDate FROM AWBuildVersion"; + +$stmt = sqlsrv_query( $conn, $tsql); + +if ( $stmt === false ) { + echo "Error in statement preparation/execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +sqlsrv_fetch( $stmt ); + +// retrieve date as string +$date = sqlsrv_get_field( $stmt, 0, SQLSRV_PHPTYPE_STRING("UTF-8")); + +if( $date === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +echo $date; + +sqlsrv_close( $conn); +?> +--EXPECT-- +2014-02-20 04:26:00.000 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_has_rows.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_has_rows.phpt new file mode 100644 index 00000000..7b514105 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_has_rows.phpt @@ -0,0 +1,24 @@ +--TEST-- +indicate if the result set has one or more rows. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); + +$stmt = sqlsrv_query( $conn, "select * from Person.Person where PersonType = 'EM'" , array()); + +if ($stmt !== NULL) { + $rows = sqlsrv_has_rows( $stmt ); + + if ($rows === true) + echo "\nthere are rows\n"; + else + echo "\nno rows\n"; +} + +?> +--EXPECT-- +there are rows \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_next_result.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_next_result.phpt new file mode 100644 index 00000000..039bbadd --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_next_result.phpt @@ -0,0 +1,151 @@ +--TEST-- +first result is consumed without calling next_result, the next result is made available by calling next_result +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/*revert inserts from previous tests*/ +$d_sql = "DELETE FROM Production.ProductReview WHERE EmailAddress!='john@fourthcoffee.com' AND ProductID=709"; +$stmt4 = sqlsrv_query($conn, $d_sql); + +/* Drop the stored procedure if it already exists. */ +$tsql_dropSP = "IF OBJECT_ID('InsertProductReview', 'P') IS NOT NULL + DROP PROCEDURE InsertProductReview"; +$stmt1 = sqlsrv_query( $conn, $tsql_dropSP); +if( $stmt1 === false ) +{ + echo "Error in executing statement 1.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Create the stored procedure. */ +$tsql_createSP = " CREATE PROCEDURE InsertProductReview + @ProductID int, + @ReviewerName nvarchar(50), + @ReviewDate datetime, + @EmailAddress nvarchar(50), + @Rating int, + @Comments nvarchar(3850) + AS + BEGIN + INSERT INTO Production.ProductReview + (ProductID, + ReviewerName, + ReviewDate, + EmailAddress, + Rating, + Comments) + VALUES + (@ProductID, + @ReviewerName, + @ReviewDate, + @EmailAddress, + @Rating, + @Comments); + SELECT * FROM Production.ProductReview + WHERE ProductID = @ProductID; + END"; +$stmt2 = sqlsrv_query( $conn, $tsql_createSP); + +if( $stmt2 === false) +{ + echo "Error in executing statement 2.\n"; + die( print_r( sqlsrv_errors(), true)); +} +/*-------- The next few steps call the stored procedure. --------*/ + +/* Define the Transact-SQL query. Use question marks (?) in place of the +parameters to be passed to the stored procedure */ +$tsql_callSP = "{call InsertProductReview(?, ?, ?, ?, ?, ?)}"; + +/* Define the parameter array. */ +$productID = 709; +$reviewerName = "Morris Gogh"; +$reviewDate = "2008-02-12"; +$emailAddress = "customer@email.com"; +$rating = 3; +$comments = "[Insert comments here.]"; +$params = array( + $productID, + $reviewerName, + $reviewDate, + $emailAddress, + $rating, + $comments + ); + +/* Execute the query. */ +$stmt3 = sqlsrv_query( $conn, $tsql_callSP, $params); +if( $stmt3 === false) +{ + echo "Error in executing statement 3.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +echo "

    "; + +/* Consume the first result (rows affected by INSERT query in the +stored procedure) without calling sqlsrv_next_result. */ +echo "Rows affectd: ".sqlsrv_rows_affected($stmt3)."-----\n"; + +echo "

    "; + +/* Move to the next result and display results. */ +$next_result = sqlsrv_next_result($stmt3); +if( $next_result ) +{ + echo "

    "; + echo "\nReview information for product ID ".$productID.".---\n"; + while( $row = sqlsrv_fetch_array( $stmt3, SQLSRV_FETCH_ASSOC)) + { + echo "
    ReviewerName: ".$row['ReviewerName']."\n"; + echo "
    ReviewDate: ".date_format($row['ReviewDate'], + "M j, Y")."\n"; + echo "
    EmailAddress: ".$row['EmailAddress']."\n"; + echo "
    Rating: ".$row['Rating']."\n\n"; + } +} +elseif( is_null($next_result)) +{ + echo "

    "; + echo "No more results.\n"; +} +else +{ + echo "Error in moving to next result.\n"; + die(print_r(sqlsrv_errors(), true)); +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt1 ); +sqlsrv_free_stmt( $stmt2 ); +sqlsrv_free_stmt( $stmt3 ); +sqlsrv_free_stmt( $stmt4 ); + + +sqlsrv_close( $conn ); +?> +--EXPECT-- +

    Rows affectd: 1----- +

    +Review information for product ID 709.--- +
    ReviewerName: John Smith +
    ReviewDate: Sep 18, 2013 +
    EmailAddress: john@fourthcoffee.com +
    Rating: 5 + +
    ReviewerName: Morris Gogh +
    ReviewDate: Feb 12, 2008 +
    EmailAddress: customer@email.com +
    Rating: 3 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_next_result_2.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_next_result_2.phpt new file mode 100644 index 00000000..0a191780 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_next_result_2.phpt @@ -0,0 +1,111 @@ +--TEST-- +executes a batch query that retrieves information, insert an entry, then again retrieves information +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Revert inserts from previous msdn_next_result_2 tests */ +$d_sql = "DELETE FROM Production.ProductReview WHERE EmailAddress!='laura@treyresearch.net' AND ProductID=798"; +$stmt = sqlsrv_query($conn, $d_sql); + +/* Define the batch query. */ +$tsql = "--Query 1 + SELECT ProductID, ReviewerName, Rating + FROM Production.ProductReview + WHERE ProductID=?; + + --Query 2 + INSERT INTO Production.ProductReview (ProductID, + ReviewerName, + ReviewDate, + EmailAddress, + Rating) + VALUES (?, ?, ?, ?, ?); + + --Query 3 + SELECT ProductID, ReviewerName, Rating + FROM Production.ProductReview + WHERE ProductID=?;"; + +/* Assign parameter values and execute the query. */ +$params = array(798, + 798, + 'CustomerName', + '2008-4-15', + 'test@customer.com', + 3, + 798 ); +$stmt = sqlsrv_query($conn, $tsql, $params); +if( $stmt === false ) +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + + +/* Retrieve and display the first result. */ +echo "Query 1 result:\n"; +while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC )) +{ + print_r($row); +} + +echo "

    "; + +/* Move to the next result of the batch query. */ +sqlsrv_next_result($stmt); + +/* Display the result of the second query. */ +echo "Query 2 result:\n"; +echo "Rows Affected: ".sqlsrv_rows_affected($stmt)."\n"; + +echo "

    "; + +/* Move to the next result of the batch query. */ +sqlsrv_next_result($stmt); + +/* Retrieve and display the third result. */ +echo "Query 3 result:\n"; +while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC )) +{ + print_r($row); +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--EXPECT-- +Query 1 result: +Array +( + [0] => 798 + [1] => Laura Norman + [2] => 5 +) +

    Query 2 result: +Rows Affected: 1 +

    Query 3 result: +Array +( + [0] => 798 + [1] => Laura Norman + [2] => 5 +) +Array +( + [0] => 798 + [1] => CustomerName + [2] => 3 +) diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_num_fields.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_num_fields.phpt new file mode 100644 index 00000000..6f5ef96b --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_num_fields.phpt @@ -0,0 +1,45 @@ +--TEST-- +retrieve all fields +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Define and execute the query. */ +$tsql = "SELECT TOP (3) * FROM HumanResources.Department"; +$stmt = sqlsrv_query($conn, $tsql); +if( $stmt === false) +{ + echo "Error in executing query.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Retrieve the number of fields. */ +$numFields = sqlsrv_num_fields( $stmt ); + +/* Iterate through each row of the result set. */ +while( sqlsrv_fetch( $stmt )) +{ + /* Iterate through the fields of each row. */ + for($i = 0; $i < $numFields; $i++) + { + echo sqlsrv_get_field($stmt, $i, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR))." "; + } + echo "
    "; +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--EXPECT-- +1 Engineering Research and Development 2008-04-30 00:00:00.000
    2 Tool Design Research and Development 2008-04-30 00:00:00.000
    3 Sales Sales and Marketing 2008-04-30 00:00:00.000
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_num_rows.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_num_rows.phpt new file mode 100644 index 00000000..e37ee376 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_num_rows.phpt @@ -0,0 +1,27 @@ +--TEST-- +num_rows with a ekyset cursor should work. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +$stmt = sqlsrv_query( $conn, "select * from Sales.SalesOrderHeader where CustomerID = 29565" , array(), array( "Scrollable" => SQLSRV_CURSOR_KEYSET )); + + $row_count = sqlsrv_num_rows( $stmt ); + + if ($row_count === false) + echo "\nerror\n"; + else if ($row_count >=0) + echo "\n$row_count\n"; +?> +--EXPECT-- + +8 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_num_rows_2.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_num_rows_2.phpt new file mode 100644 index 00000000..76d7afda --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_num_rows_2.phpt @@ -0,0 +1,36 @@ +--TEST-- +when there is a batch query, the number of rows is only available when use a client-side cursor. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +// $tsql = "select * from HumanResources.Department"; + +// Client-side cursor and batch statements +$tsql = "select top 8 * from HumanResources.EmployeePayHistory;select top 2 * from HumanResources.Employee;"; + +// works +$stmt = sqlsrv_query($conn, $tsql, array(), array("Scrollable"=>"buffered")); + +// fails +// $stmt = sqlsrv_query($conn, $tsql); +// $stmt = sqlsrv_query($conn, $tsql, array(), array("Scrollable"=>"forward")); +// $stmt = sqlsrv_query($conn, $tsql, array(), array("Scrollable"=>"static")); +// $stmt = sqlsrv_query($conn, $tsql, array(), array("Scrollable"=>"keyset")); +// $stmt = sqlsrv_query($conn, $tsql, array(), array("Scrollable"=>"dynamic")); + +$row_count = sqlsrv_num_rows( $stmt ); +echo "

    \nRow count first result set = $row_count
    "; + +sqlsrv_next_result($stmt); + +$row_count = sqlsrv_num_rows( $stmt ); +echo "

    \nRow count second result set = $row_count
    "; +?> +--EXPECT-- +

    +Row count first result set = 8

    +Row count second result set = 2
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_prepare.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_prepare.phpt new file mode 100644 index 00000000..54a1535d --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_prepare.phpt @@ -0,0 +1,60 @@ +--TEST-- +Prepares and executes a statement. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up Transact-SQL query. */ +$tsql = "UPDATE Sales.SalesOrderDetail + SET OrderQty = ? + WHERE SalesOrderDetailID = ?"; + +/* Assign parameter values. */ +$param1 = 5; +$param2 = 10; +$params = array( &$param1, &$param2); + +/* Prepare the statement. */ +if( $stmt = sqlsrv_prepare( $conn, $tsql, $params)) +{ + echo "Statement prepared.
    "; +} +else +{ + echo "Statement could not be prepared.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Execute the statement. */ +if( sqlsrv_execute( $stmt)) +{ + echo "Statement executed.
    "; +} +else +{ + echo "Statement could not be executed.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/*revert the update */ +$r_sql = "UPDATE Sales.SalesOrderDetail SET OrderQty=6 WHERE SalesOrderDetailID=10"; +$stmt2 = sqlsrv_query($conn, $r_sql); + +/* Free the statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_free_stmt($stmt2); +sqlsrv_close( $conn); +?> +--EXPECT-- +Statement prepared.
    Statement executed.
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_prepare_2.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_prepare_2.phpt new file mode 100644 index 00000000..b22724eb --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_prepare_2.phpt @@ -0,0 +1,107 @@ +--TEST-- +Prepares a statement and then re-execute it with different parameter values. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Define the parameterized query. */ +$tsql = "UPDATE Sales.SalesOrderDetail + SET OrderQty = ? + WHERE SalesOrderDetailID = ?"; + +/* Initialize parameters and prepare the statement. Variables $qty +and $id are bound to the statement, $stmt1. */ +$qty = 0; $id = 0; +$stmt1 = sqlsrv_prepare( $conn, $tsql, array( &$qty, &$id)); +if( $stmt1 ) +{ + echo "Statement 1 prepared.
    "; +} +else +{ + echo "Error in statement preparation.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up the SalesOrderDetailID and OrderQty information. This array +maps the order ID to order quantity in key=>value pairs. */ +$orders = array( 20=>10, 21=>20, 22=>30); + +/* Execute the statement for each order. */ +foreach( $orders as $id => $qty) +{ + // Because $id and $qty are bound to $stmt1, their updated + // values are used with each execution of the statement. + if( sqlsrv_execute( $stmt1) === false ) + { + echo "Error in statement execution.
    "; + die( print_r( sqlsrv_errors(), true)); + } +} +echo "Orders updated.
    "; + +/* Free $stmt1 resources. This allows $id and $qty to be bound to a different statement.*/ +sqlsrv_free_stmt( $stmt1); + +/* Now verify that the results were successfully written by selecting +the newly inserted rows. */ +$tsql = "SELECT OrderQty + FROM Sales.SalesOrderDetail + WHERE SalesOrderDetailID = ?"; + +/* Prepare the statement. Variable $id is bound to $stmt2. */ +$stmt2 = sqlsrv_prepare( $conn, $tsql, array( &$id)); +if( $stmt2 ) +{ + echo "Statement 2 prepared.
    "; +} +else +{ + echo "Error in statement preparation.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Execute the statement for each order. */ +foreach( array_keys($orders) as $id) +{ + /* Because $id is bound to $stmt2, its updated value + is used with each execution of the statement. */ + if( sqlsrv_execute( $stmt2)) + { + sqlsrv_fetch( $stmt2); + $quantity = sqlsrv_get_field( $stmt2, 0); + echo "Order $id is for $quantity units.
    "; + } + else + { + echo "Error in statement execution.
    "; + die( print_r( sqlsrv_errors(), true)); + } +} + +/* revert the update */ +$r_sql3 = "UPDATE Sales.SalesOrderDetail SET OrderQty = 2 WHERE SalesOrderDetailID = 20"; +$stmt3 = sqlsrv_query($conn, $r_sql3); +$r_sql4 = "UPDATE Sales.SalesOrderDetail SET OrderQty = 3 WHERE SalesOrderDetailID = 21"; +$stmt4 = sqlsrv_query($conn, $r_sql4); +$r_sql5 = "UPDATE Sales.SalesOrderDetail SET OrderQty = 2 WHERE SalesOrderDetailID = 22"; +$stmt5 = sqlsrv_query($conn, $r_sql5); + +/* Free $stmt2 and connection resources. */ +sqlsrv_free_stmt( $stmt2); +sqlsrv_free_stmt( $stmt3); +sqlsrv_free_stmt( $stmt4); +sqlsrv_free_stmt( $stmt5); +sqlsrv_close( $conn); +?> +--EXPECT-- +Statement 1 prepared.
    Orders updated.
    Statement 2 prepared.
    Order 20 is for 10 units.
    Order 21 is for 20 units.
    Order 22 is for 30 units.
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_prepare_bind_param.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_prepare_bind_param.phpt new file mode 100644 index 00000000..56b2bf00 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_prepare_bind_param.phpt @@ -0,0 +1,88 @@ +--TEST-- +binding of variables using prepare function +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +$tsql = "INSERT INTO Sales.SalesOrderDetail (SalesOrderID, + OrderQty, + ProductID, + SpecialOfferID, + UnitPrice) + VALUES (?, ?, ?, ?, ?)"; + +/* Each sub array here will be a parameter array for a query. +The values in each sub array are, in order, SalesOrderID, OrderQty, + ProductID, SpecialOfferID, UnitPrice. */ +$parameters = array( array(43659, 8, 711, 1, 20.19), + array(43660, 6, 762, 1, 419.46), + array(43661, 4, 741, 1, 818.70) + ); + +/* Initialize parameter values. */ +$orderId = 0; +$qty = 0; +$prodId = 0; +$specialOfferId = 0; +$price = 0.0; + +/* Prepare the statement. $params is implicitly bound to $stmt. */ +$stmt = sqlsrv_prepare( $conn, $tsql, array( &$orderId, + &$qty, + &$prodId, + &$specialOfferId, + &$price)); +if( $stmt === false ) +{ + echo "Statement could not be prepared.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Execute a statement for each set of params in $parameters. +Because $params is bound to $stmt, as the values are changed, the +new values are used in the subsequent execution. */ +foreach( $parameters as $params) +{ + list($orderId, $qty, $prodId, $specialOfferId, $price) = $params; + if( sqlsrv_execute($stmt) === false ) + { + echo "Statement could not be executed.\n"; + die( print_r( sqlsrv_errors(), true)); + } + else + { + /* Verify that the row was successfully inserted. */ + echo "Rows affected: ".sqlsrv_rows_affected( $stmt )."\n"; + } +} + +/* Revert the changes */ +$d_sql2 = "DELETE FROM Sales.SalesOrderDetail WHERE SalesOrderID=43659 AND OrderQty=8 AND ProductID=711 AND SpecialOfferID=1 AND Unitprice=20.19"; +$stmt2 = sqlsrv_query($conn, $d_sql2); +$d_sql3 = "DELETE FROM Sales.SalesOrderDetail WHERE SalesOrderID=43660 AND OrderQty=6 AND ProductID=762 AND SpecialOfferID=1 AND Unitprice=419.46"; +$stmt3 = sqlsrv_query($conn, $d_sql3); +$d_sql4 = "DELETE FROM Sales.SalesOrderDetail WHERE SalesOrderID=43661 AND OrderQty=4 AND ProductID=741 AND SpecialOfferID=1 AND Unitprice=818.70"; +$stmt4 = sqlsrv_query($conn, $d_sql4); + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_free_stmt( $stmt2); +sqlsrv_free_stmt( $stmt3); +sqlsrv_free_stmt( $stmt4); +sqlsrv_close( $conn); +?> +--EXPECT-- +Rows affected: 1 +Rows affected: 1 +Rows affected: 1 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_prepare_scrollable_buffered.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_prepare_scrollable_buffered.phpt new file mode 100644 index 00000000..50d071e9 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_prepare_scrollable_buffered.phpt @@ -0,0 +1,37 @@ +--TEST-- +server side cursor specified when preparing +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if ( $conn === false ) { + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +$tsql = "select * from HumanResources.Employee"; +$stmt = sqlsrv_prepare( $conn, $tsql, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED)); + +if (! $stmt ) { + echo "Statement could not be prepared.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +sqlsrv_execute( $stmt); + +$row_count = sqlsrv_num_rows( $stmt ); +if ($row_count) + echo "\nRow count = $row_count\n"; + +$row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_FIRST); +if ($row ) { + $EmployeeID = sqlsrv_get_field( $stmt, 0); + echo "Employee ID = $EmployeeID \n"; +} +?> +--EXPECT-- +Row count = 290 +Employee ID = 1 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query.phpt new file mode 100644 index 00000000..cd5a19c4 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query.phpt @@ -0,0 +1,55 @@ +--TEST-- +Query insert into a table +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up the parameterized query. */ +$tsql = "INSERT INTO Sales.SalesOrderDetail + (SalesOrderID, + OrderQty, + ProductID, + SpecialOfferID, + UnitPrice, + UnitPriceDiscount) + VALUES + (?, ?, ?, ?, ?, ?)"; + +/* Set parameter values. */ +$params = array(75123, 5, 741, 1, 818.70, 0.00); + +// RevisionNumber in SalesOrderHeader is subject to a trigger incrementing it whenever +// changes are made to SalesOrderDetail. Since RevisonNumber is a tinyint, it can +// overflow quickly if this test is often run. So we change it directly here first +// before it can overflow. +$stmt0 = sqlsrv_query( $conn, "UPDATE Sales.SalesOrderHeader SET RevisionNumber = 2 WHERE SalesOrderID = $params[0]"); + +/* Prepare and execute the query. */ +$stmt = sqlsrv_query( $conn, $tsql, $params); +if( $stmt ) +{ + echo "Row successfully inserted.\n"; +} +else +{ + echo "Row insertion failed.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +Row successfully inserted. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query_2.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query_2.phpt new file mode 100644 index 00000000..8b95c87d --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query_2.phpt @@ -0,0 +1,44 @@ +--TEST-- +Query update a field in a table +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up the parameterized query. */ +$tsql = "UPDATE Sales.SalesOrderDetail + SET OrderQty = ( ?) + WHERE SalesOrderDetailID = ( ?)"; + +/* Assign literal parameter values. */ +$params = array( 5, 10); + +/* Execute the query. */ +if( sqlsrv_query( $conn, $tsql, $params)) +{ + echo "Statement executed.\n"; +} +else +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/*revert the update */ +$r_sql = "UPDATE Sales.SalesOrderDetail SET OrderQty=6 WHERE SalesOrderDetailID=10"; +$stmt = sqlsrv_query($conn, $r_sql); + +/* Free connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +Statement executed. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query_bind_param.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query_bind_param.phpt new file mode 100644 index 00000000..ee43e3e3 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query_bind_param.phpt @@ -0,0 +1,63 @@ +--TEST-- +updates the quantity in a table, the quantity and product ID are parameters in the UPDATE query. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Define the Transact-SQL query. +Use question marks as parameter placeholders. */ +$tsql1 = "UPDATE Production.ProductInventory + SET Quantity = ? + WHERE ProductID = ?"; + +/* Initialize $qty and $productId */ +$qty = 10; $productId = 709; + +/* Execute the statement with the specified parameter values. */ +$stmt1 = sqlsrv_query( $conn, $tsql1, array($qty, $productId)); +if( $stmt1 === false ) +{ + echo "Statement 1 could not be executed.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Free statement resources. */ +sqlsrv_free_stmt( $stmt1); + +/* Now verify the updated quantity. +Use a question mark as parameter placeholder. */ +$tsql2 = "SELECT Quantity + FROM Production.ProductInventory + WHERE ProductID = ?"; + +/* Execute the statement with the specified parameter value. +Display the returned data if no errors occur. */ +$stmt2 = sqlsrv_query( $conn, $tsql2, array($productId)); +if( $stmt2 === false ) +{ + echo "Statement 2 could not be executed.\n"; + die( print_r(sqlsrv_errors(), true)); +} +else +{ + $qty = sqlsrv_fetch_array( $stmt2); + echo "There are $qty[0] of product $productId in inventory.\n"; +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt2); +sqlsrv_close( $conn); +?> +--EXPECT-- +There are 10 of product 709 in inventory. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query_param_inout.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query_param_inout.phpt new file mode 100644 index 00000000..693b2739 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query_param_inout.phpt @@ -0,0 +1,88 @@ +--TEST-- +queries a call procedure with an in-out parameter. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} +/* Drop the stored procedure if it already exists. */ +$tsql_dropSP = "IF OBJECT_ID('SubtractVacationHours', 'P') IS NOT NULL + DROP PROCEDURE SubtractVacationHours"; +$stmt1 = sqlsrv_query( $conn, $tsql_dropSP); +if( $stmt1 === false ) +{ + echo "Error in executing statement 1.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Create the stored procedure. */ +$tsql_createSP = "CREATE PROCEDURE SubtractVacationHours + @BusinessEntityID int, + @VacationHrs smallint OUTPUT + AS + UPDATE HumanResources.Employee + SET VacationHours = VacationHours - @VacationHrs + WHERE BusinessEntityID = @BusinessEntityID; + SET @VacationHrs = (SELECT VacationHours + FROM HumanResources.Employee + WHERE BusinessEntityID = @BusinessEntityID)"; + +$stmt2 = sqlsrv_query( $conn, $tsql_createSP); +if( $stmt2 === false ) +{ + echo "Error in executing statement 2.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/*--------- The next few steps call the stored procedure. ---------*/ + +/* Define the Transact-SQL query. Use question marks (?) in place of +the parameters to be passed to the stored procedure */ +$tsql_callSP = "{call SubtractVacationHours( ?, ?)}"; + +/* Define the parameter array. By default, the first parameter is an +INPUT parameter. The second parameter is specified as an INOUT +parameter. Initializing $vacationHrs to 8 sets the returned PHPTYPE to +integer. To ensure data type integrity, output parameters should be +initialized before calling the stored procedure, or the desired +PHPTYPE should be specified in the $params array.*/ + +$employeeId = 4; +$vacationHrs = 1; +$params = array( + array($employeeId, SQLSRV_PARAM_IN), + array(&$vacationHrs, SQLSRV_PARAM_INOUT) + ); + +/* Execute the query. */ +$stmt3 = sqlsrv_query( $conn, $tsql_callSP, $params); +if( $stmt3 === false ) +{ + echo "Error in executing statement 3.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Display the value of the output parameter $vacationHrs. */ +sqlsrv_next_result($stmt3); +echo "Remaining vacation hours: ".$vacationHrs; + +/* Revert the update in vacation hours */ +$r_sql = "UPDATE HumanResources.Employee SET VacationHours=48 WHERE BusinessEntityID=4"; +$stmt4 = sqlsrv_query($conn, $r_sql); + +/*Free the statement and connection resources. */ +sqlsrv_free_stmt( $stmt1); +sqlsrv_free_stmt( $stmt2); +sqlsrv_free_stmt( $stmt3); +sqlsrv_free_stmt( $stmt4); +sqlsrv_close( $conn); +?> +--EXPECT-- +Remaining vacation hours: 47 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query_param_out.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query_param_out.phpt new file mode 100644 index 00000000..62662dda --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query_param_out.phpt @@ -0,0 +1,82 @@ +--TEST-- +queries a call to procedure with input and output parameters. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Drop the stored procedure if it already exists. */ +$tsql_dropSP = "IF OBJECT_ID('GetEmployeeSalesYTD', 'P') IS NOT NULL + DROP PROCEDURE GetEmployeeSalesYTD"; +$stmt1 = sqlsrv_query( $conn, $tsql_dropSP); +if( $stmt1 === false ) +{ + echo "Error in executing statement 1.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Create the stored procedure. */ +$tsql_createSP = " CREATE PROCEDURE GetEmployeeSalesYTD + @SalesPerson nvarchar(50), + @SalesYTD money OUTPUT + AS + SELECT @SalesYTD = SalesYTD + FROM Sales.SalesPerson AS sp + JOIN HumanResources.vEmployee AS e + ON e.BusinessEntityID = sp.BusinessEntityID + WHERE LastName = @SalesPerson"; +$stmt2 = sqlsrv_query( $conn, $tsql_createSP); +if( $stmt2 === false ) +{ + echo "Error in executing statement 2.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/*--------- The next few steps call the stored procedure. ---------*/ + +/* Define the Transact-SQL query. Use question marks (?) in place of + the parameters to be passed to the stored procedure */ +$tsql_callSP = "{call GetEmployeeSalesYTD( ?, ? )}"; + +/* Define the parameter array. By default, the first parameter is an +INPUT parameter. The second parameter is specified as an OUTPUT +parameter. Initializing $salesYTD to 0.0 sets the returned PHPTYPE to +float. To ensure data type integrity, output parameters should be +initialized before calling the stored procedure, or the desired +PHPTYPE should be specified in the $params array.*/ +$lastName = "Blythe"; +$salesYTD = 0.0; +$params = array( + array(&$lastName, SQLSRV_PARAM_IN), + array(&$salesYTD, SQLSRV_PARAM_OUT) + ); + +/* Execute the query. */ +$stmt3 = sqlsrv_query( $conn, $tsql_callSP, $params); +if( $stmt3 === false ) +{ + echo "Error in executing statement 3.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Display the value of the output parameter $salesYTD. */ +echo "YTD sales for ".$lastName." are ". $salesYTD. "."; + +/*Free the statement and connection resources. */ +sqlsrv_free_stmt( $stmt1); +sqlsrv_free_stmt( $stmt2); +sqlsrv_free_stmt( $stmt3); +sqlsrv_close( $conn); +?> +--EXPECT-- +YTD sales for Blythe are 3763178.1787. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable.phpt new file mode 100644 index 00000000..e9444ea9 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable.phpt @@ -0,0 +1,74 @@ +--TEST-- +server side cursor specified when querying +--SKIPIF-- + +--FILE-- + "tempdb", "UID"=>"sa", "PWD"=>"Moonshine4me")); + +require('connect.inc'); +$connectionInfo = array( "Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if ( $conn === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$stmt = sqlsrv_query( $conn, "DROP TABLE dbo.ScrollTest" ); +if ( $stmt !== false ) { + sqlsrv_free_stmt( $stmt ); +} + +$stmt = sqlsrv_query( $conn, "CREATE TABLE ScrollTest (id int, value char(10))" ); +if ( $stmt === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$stmt = sqlsrv_query( $conn, "INSERT INTO ScrollTest (id, value) VALUES(?,?)", array( 1, "Row 1" )); +if ( $stmt === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$stmt = sqlsrv_query( $conn, "INSERT INTO ScrollTest (id, value) VALUES(?,?)", array( 2, "Row 2" )); +if ( $stmt === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$stmt = sqlsrv_query( $conn, "INSERT INTO ScrollTest (id, value) VALUES(?,?)", array( 3, "Row 3" )); +if ( $stmt === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$stmt = sqlsrv_query( $conn, "SELECT * FROM ScrollTest", array(), array( "Scrollable" => 'keyset' )); +// $stmt = sqlsrv_query( $conn, "SELECT * FROM ScrollTest", array(), array( "Scrollable" => 'dynamic' )); +// $stmt = sqlsrv_query( $conn, "SELECT * FROM ScrollTest", array(), array( "Scrollable" => 'static' )); + +$rows = sqlsrv_has_rows( $stmt ); +if ( $rows != true ) { + die( "Should have rows" ); +} + +$result = sqlsrv_fetch( $stmt, SQLSRV_SCROLL_LAST ); +$field1 = sqlsrv_get_field( $stmt, 0 ); +$field2 = sqlsrv_get_field( $stmt, 1 ); +echo "\n$field1 $field2\n"; + +//$stmt2 = sqlsrv_query( $conn, "delete from ScrollTest where id = 3" ); +// or +$stmt2 = sqlsrv_query( $conn, "UPDATE ScrollTest SET id = 4 WHERE id = 3" ); +if ( $stmt2 !== false ) { + sqlsrv_free_stmt( $stmt2 ); +} + +$result = sqlsrv_fetch( $stmt, SQLSRV_SCROLL_LAST ); +$field1 = sqlsrv_get_field( $stmt, 0 ); +$field2 = sqlsrv_get_field( $stmt, 1 ); +echo "\n$field1 $field2\n"; + +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--EXPECT-- +3 Row 3 + +4 Row 3 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable_buffered.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable_buffered.phpt new file mode 100644 index 00000000..a0789ea4 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable_buffered.phpt @@ -0,0 +1,66 @@ +--TEST-- +client side buffered cursor +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if ( $conn === false ) { + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +$tsql = "select * from HumanResources.Department"; + +// Execute the query with client-side cursor. +$stmt = sqlsrv_query($conn, $tsql, array(), array("Scrollable"=>"buffered")); +if (! $stmt) { + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// row count is always available with a client-side cursor +$row_count = sqlsrv_num_rows( $stmt ); +echo "\nRow count = $row_count\n"; + +// Move to a specific row in the result set. +$row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_FIRST); +$EmployeeID = sqlsrv_get_field( $stmt, 0); +echo "Employee ID = $EmployeeID \n"; + +// Client-side cursor and batch statements +$tsql = "select top 2 * from HumanResources.Employee;Select top 3 * from HumanResources.EmployeePayHistory"; + +$stmt = sqlsrv_query($conn, $tsql, array(), array("Scrollable"=>"buffered")); +if (! $stmt) { + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +$row_count = sqlsrv_num_rows( $stmt ); +echo "\nRow count for first result set = $row_count\n"; + +$row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_FIRST); +$EmployeeID = sqlsrv_get_field( $stmt, 0); +echo "Employee ID = $EmployeeID \n"; + +sqlsrv_next_result($stmt); + +$row_count = sqlsrv_num_rows( $stmt ); +echo "\nRow count for second result set = $row_count\n"; + +$row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_LAST); +$BusinessEntityID = sqlsrv_get_field( $stmt, 0); +echo "Business Entity ID = $BusinessEntityID \n"; +?> +--EXPECT-- +Row count = 16 +Employee ID = 1 + +Row count for first result set = 2 +Employee ID = 1 + +Row count for second result set = 3 +Business Entity ID = 3 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query_sqltype.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query_sqltype.phpt new file mode 100644 index 00000000..0ecd18e6 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query_sqltype.phpt @@ -0,0 +1,87 @@ +--TEST-- +sqlsrv types are specified for the parameters in query. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Define the query. */ +$tsql1 = "INSERT INTO HumanResources.EmployeePayHistory (BusinessEntityID, + RateChangeDate, + Rate, + PayFrequency) + VALUES (?, ?, ?, ?)"; + +/* Construct the parameter array. */ +$businessEntityId = 6; +$changeDate = "2005-06-07"; +$rate = 30; +$payFrequency = 2; +$params1 = array( + array($businessEntityId, null), + array($changeDate, null, null, SQLSRV_SQLTYPE_DATETIME), + array($rate, null, null, SQLSRV_SQLTYPE_MONEY), + array($payFrequency, null, null, SQLSRV_SQLTYPE_TINYINT) + ); + +/* Execute the INSERT query. */ +$stmt1 = sqlsrv_query($conn, $tsql1, $params1); +if( $stmt1 === false ) +{ + echo "Error in execution of INSERT.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Retrieve the newly inserted data. */ +/* Define the query. */ +$tsql2 = "SELECT BusinessEntityID, RateChangeDate, Rate, PayFrequency + FROM HumanResources.EmployeePayHistory + WHERE BusinessEntityID = ? AND RateChangeDate = ?"; + +/* Construct the parameter array. */ +$params2 = array($businessEntityId, $changeDate); + +/*Execute the SELECT query. */ +$stmt2 = sqlsrv_query($conn, $tsql2, $params2); +if( $stmt2 === false ) +{ + echo "Error in execution of SELECT.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Retrieve and display the results. */ +$row = sqlsrv_fetch_array( $stmt2 ); +if( $row === false ) +{ + echo "Error in fetching data.\n"; + die( print_r( sqlsrv_errors(), true)); +} +echo "BusinessEntityID: ".$row['BusinessEntityID']."\n"; +echo "Change Date: ".date_format($row['RateChangeDate'], "Y-m-d")."\n"; +echo "Rate: ".$row['Rate']."\n"; +echo "PayFrequency: ".$row['PayFrequency']."\n"; + +/* Revert the insert */ +$d_sql = "delete from HumanResources.EmployeePayHistory where BusinessEntityId=6 and RateChangeDate='2005-06-07 00:00:00.000'"; +$stmt = sqlsrv_query($conn, $d_sql); + +/* Free statement and connection resources. */ +sqlsrv_free_stmt($stmt1); +sqlsrv_free_stmt($stmt2); +sqlsrv_close($conn); +?> +--EXPECT-- +BusinessEntityID: 6 +Change Date: 2005-06-07 +Rate: 30.0000 +PayFrequency: 2 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query_stream.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query_stream.phpt new file mode 100644 index 00000000..4f1b737a --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query_stream.phpt @@ -0,0 +1,56 @@ +--TEST-- +insert stream. +--SKIPIF-- + +?> +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up the Transact-SQL query. */ +$tsql = "INSERT INTO Production.ProductReview (ProductID, + ReviewerName, + ReviewDate, + EmailAddress, + Rating, + Comments) + VALUES (?, ?, ?, ?, ?, ?)"; + +/* Set the parameter values and put them in an array. +Note that $comments is opened as a stream. */ +$productID = '709'; +$name = 'Customer Name'; +$date = date("Y-m-d"); +$email = 'customer@name.com'; +$rating = 3; +$comments = fopen( "data://text/plain,[ Insert lengthy comment here.]", + "r"); +$params = array($productID, $name, $date, $email, $rating, $comments); + +/* Execute the query. All stream data is sent upon execution.*/ +$stmt = sqlsrv_query($conn, $tsql, $params); +if( $stmt === false ) +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} +else +{ + echo "The query was successfully executed."; +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +The query was successfully executed. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query_utf8.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query_utf8.phpt new file mode 100644 index 00000000..100d1ce4 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query_utf8.phpt @@ -0,0 +1,91 @@ +--TEST-- +specify the UTF-8 character set when querying +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if ( $conn === false ) { + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +// Set up the Transact-SQL query. +// +$tsql1 = "UPDATE Production.ProductReview + SET Comments = ? + WHERE ProductReviewID = ?"; + +// Set the parameter values and put them in an array. Note that +// $comments is converted to UTF-8 encoding with the PHP function +// utf8_encode to simulate an application that uses UTF-8 encoded data. +// +$reviewID = 3; +$comments = utf8_encode("testing"); +$params1 = array( + array($comments, + SQLSRV_PARAM_IN, + SQLSRV_PHPTYPE_STRING('UTF-8') + ), + array($reviewID) + ); + +// Execute the query. +// +$stmt1 = sqlsrv_query($conn, $tsql1, $params1); + +if ( $stmt1 === false ) { + echo "Error in statement execution.
    "; + die( print_r( sqlsrv_errors(), true)); +} +else { + echo "The update was successfully executed.
    "; +} + +// Retrieve the newly updated data. +// +$tsql2 = "SELECT Comments + FROM Production.ProductReview + WHERE ProductReviewID = ?"; + +// Set up the parameter array. +// +$params2 = array($reviewID); + +// Execute the query. +// +$stmt2 = sqlsrv_query($conn, $tsql2, $params2); +if ( $stmt2 === false ) { + echo "Error in statement execution.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +// Retrieve and display the data. +// +if ( sqlsrv_fetch($stmt2) ) { + echo "Comments: "; + $data = sqlsrv_get_field($stmt2, + 0, + SQLSRV_PHPTYPE_STRING('UTF-8') + ); + echo $data."
    "; +} +else { + echo "Error in fetching data.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +// Free statement and connection resources. +// +sqlsrv_free_stmt( $stmt1 ); +sqlsrv_free_stmt( $stmt2 ); +sqlsrv_close( $conn); +?> +--EXPECT-- +The update was successfully executed.
    Comments: testing
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_rollback.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_rollback.phpt new file mode 100644 index 00000000..d96c8373 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_rollback.phpt @@ -0,0 +1,70 @@ +--TEST-- +Rolls back the current transaction on the specified connection and returns the connection to the auto-commit mode. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true )); +} + +/* Initiate transaction. */ +/* Exit script if transaction cannot be initiated. */ +if ( sqlsrv_begin_transaction( $conn) === false ) +{ + echo "Could not begin transaction.\n"; + die( print_r( sqlsrv_errors(), true )); +} + +/* Initialize parameter values. */ +$orderId = 43659; $qty = 5; $productId = 709; +$offerId = 1; $price = 5.70; + +/* Set up and execute the first query. */ +$tsql1 = "INSERT INTO Sales.SalesOrderDetail + (SalesOrderID, + OrderQty, + ProductID, + SpecialOfferID, + UnitPrice) + VALUES (?, ?, ?, ?, ?)"; +$params1 = array( $orderId, $qty, $productId, $offerId, $price); +$stmt1 = sqlsrv_query( $conn, $tsql1, $params1 ); + +/* Set up and executee the second query. */ +$tsql2 = "UPDATE Production.ProductInventory + SET Quantity = (Quantity - ?) + WHERE ProductID = ?"; +$params2 = array($qty, $productId); +$stmt2 = sqlsrv_query( $conn, $tsql2, $params2 ); + +/* If both queries were successful, commit the transaction. */ +/* Otherwise, rollback the transaction. */ +if( $stmt1 && $stmt2 ) +{ + sqlsrv_commit( $conn ); + echo "Transaction was committed.\n"; +} +else +{ + sqlsrv_rollback( $conn ); + echo "Transaction was rolled back.\n"; +} + +/* Revert the changes */ +$d_sql = "DELETE FROM Sales.SalesOrderDetail WHERE SalesOrderID=43659 AND OrderQty=5 AND ProductID=709 AND SpecialOfferID=1 AND Unitprice=5.70"; +$stmt3 = sqlsrv_query($conn, $d_sql); + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt1); +sqlsrv_free_stmt( $stmt2); +sqlsrv_free_stmt($stmt3); +sqlsrv_close( $conn); +?> +--EXPECT-- +Transaction was committed. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_rows_affected.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_rows_affected.phpt new file mode 100644 index 00000000..983812ab --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_rows_affected.phpt @@ -0,0 +1,59 @@ +--TEST-- +Returns the number of rows modified by the last statement executed. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up Transact-SQL query. */ +$tsql = "UPDATE Sales.SalesOrderDetail + SET SpecialOfferID = ? + WHERE ProductID = ?"; + +/* Set parameter values. */ +$params = array(2, 709); + +/* Execute the statement. */ +$stmt = sqlsrv_query( $conn, $tsql, $params); + +/* Get the number of rows affected and display appropriate message.*/ +$rows_affected = sqlsrv_rows_affected( $stmt); +if( $rows_affected === false) +{ + echo "Error in calling sqlsrv_rows_affected.\n"; + die( print_r( sqlsrv_errors(), true)); +} +elseif( $rows_affected == -1) +{ + echo "No information available.\n"; +} +else +{ + echo $rows_affected." rows were updated.\n"; +} + +/*revert the update */ +$r_sql2 = "UPDATE Sales.SalesOrderDetail SET SpecialOfferID=1 WHERE ProductID=709 AND UnitPriceDiscount=0.00"; +$stmt2 = sqlsrv_query($conn, $r_sql2); +$r_sql3 = "UPDATE Sales.SalesOrderDetail SET SpecialOfferID=3 WHERE ProductID=709 AND UnitPriceDiscount=0.05"; +$stmt3 = sqlsrv_query($conn, $r_sql3); +$r_sql4 = "UPDATE Sales.SalesOrderDetail SET SpecialOfferID=4 WHERE ProductID=709 AND UnitPriceDiscount=0.10"; +$stmt4 = sqlsrv_query($conn, $r_sql4); + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_free_stmt( $stmt2); +sqlsrv_free_stmt( $stmt3); +sqlsrv_free_stmt( $stmt4); +sqlsrv_close( $conn); +?> +--EXPECTREGEX-- +[0-9]+ rows were updated. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_send_stream_data.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_send_stream_data.phpt new file mode 100644 index 00000000..eab20a3d --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_send_stream_data.phpt @@ -0,0 +1,48 @@ +--TEST-- +Sends data from parameter streams to the server +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Define the query. */ +$tsql = "UPDATE Production.ProductReview + SET Comments = ( ?) + WHERE ProductReviewID = 3"; + +/* Open parameter data as a stream and put it in the $params array. */ +$comment = fopen( "data://text/plain,[ Insert lengthy comment.]", "r"); +$params = array( &$comment); + +/* Prepare the statement. Use the $options array to turn off the +default behavior, which is to send all stream data at the time of query +execution. */ +$options = array("SendStreamParamsAtExec"=>0); +$stmt = sqlsrv_prepare( $conn, $tsql, $params, $options); + +/* Execute the statement. */ +sqlsrv_execute( $stmt); + +/* Send up to 8K of parameter data to the server with each call to +sqlsrv_send_stream_data. Count the calls. */ +$i = 1; +while( sqlsrv_send_stream_data( $stmt)) +{ + echo "$i call(s) made.
    "; + $i++; +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +1 call(s) made.
    2 call(s) made.
    3 call(s) made.
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_send_stream_data_no_sendStreamParamsAtExec.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_send_stream_data_no_sendStreamParamsAtExec.phpt new file mode 100644 index 00000000..f324c3c7 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_send_stream_data_no_sendStreamParamsAtExec.phpt @@ -0,0 +1,67 @@ +--TEST-- +sned stream data with SendStreamParamsAtExec turned off. +--SKIPIF-- + +?> +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set up the Transact-SQL query. */ +$tsql = "INSERT INTO Production.ProductReview (ProductID, + ReviewerName, + ReviewDate, + EmailAddress, + Rating, + Comments) + VALUES (?, ?, ?, ?, ?, ?)"; + +/* Set the parameter values and put them in an array. +Note that $comments is opened as a stream. */ +$productID = '709'; +$name = 'Customer Name'; +$date = date("Y-m-d"); +$email = 'customer@name.com'; +$rating = 3; +$comments = fopen( "data://text/plain,[ Insert lengthy comment here.]", + "r"); +$params = array($productID, $name, $date, $email, $rating, $comments); + +/* Turn off the default behavior of sending all stream data at +execution. */ +$options = array("SendStreamParamsAtExec" => 0); + +/* Execute the query. */ +$stmt = sqlsrv_query($conn, $tsql, $params, $options); +if( $stmt === false ) +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Send up to 8K of parameter data to the server with each call to +sqlsrv_send_stream_data. Count the calls. */ +$i = 1; +while( sqlsrv_send_stream_data( $stmt)) +{ + echo "$i call(s) made.\n"; + $i++; +} + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt); +sqlsrv_close( $conn); +?> +--EXPECT-- +1 call(s) made. +2 call(s) made. +3 call(s) made. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_server_info.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_server_info.phpt new file mode 100644 index 00000000..4d15718b --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_server_info.phpt @@ -0,0 +1,35 @@ +--TEST-- +Returns information about the server. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +$server_info = sqlsrv_server_info( $conn); +if( $server_info ) +{ + foreach( $server_info as $key => $value) + { + echo $key.": ".$value."
    "; + } +} +else +{ + echo "Error in retrieving server info.
    "; + die( print_r( sqlsrv_errors(), true)); +} + +/* Free connection resources. */ +sqlsrv_close( $conn); +?> +--EXPECT-- +CurrentDatabase: AdventureWorks2014
    SQLServerVersion: 12.00.4100
    SQLServerName: SQL-2K14-SP1-1
    \ No newline at end of file diff --git a/test/bvt/sqlsrv/readme.txt b/test/bvt/sqlsrv/readme.txt new file mode 100644 index 00000000..1fc27ef1 --- /dev/null +++ b/test/bvt/sqlsrv/readme.txt @@ -0,0 +1,3 @@ +This folder mainly contains tests that are derived from the code examples on + +https://docs.microsoft.com/en-us/sql/connect/php/sqlsrv-driver-api-reference \ No newline at end of file diff --git a/test/bvt/sqlsrv/sqlsrv_param_inout.phpt b/test/bvt/sqlsrv/sqlsrv_param_inout.phpt new file mode 100644 index 00000000..603f0ab5 --- /dev/null +++ b/test/bvt/sqlsrv/sqlsrv_param_inout.phpt @@ -0,0 +1,67 @@ +--TEST-- +call a stored procedure (SQLSRV Driver) and retrieve the errorNumber that is returned +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); + $conn = sqlsrv_connect( $server, $connectionInfo); + + if ($conn === false){ + echo "Could not connect.\n"; + die (print_r (sqlsrv_errors(), true)); + } + + // Drop the stored procedure if it already exists + $tsql_dropSP = "IF OBJECT_ID('sp_Test', 'P') IS NOT NULL + DROP PROCEDURE sp_Test"; + $stmt1 = sqlsrv_query($conn, $tsql_dropSP); + if ($stmt1 === false) { + echo "Error in executing statement 1.\n"; + die (print_r (sqlsrv_errors(), true)); + } + + // Create the stored procedure + $tsql_createSP = "CREATE PROCEDURE sp_Test + @ErrorNumber INT = 0 OUTPUT + AS + BEGIN + SET @ErrorNumber = -1 + SELECT 1,2,3 + END"; + $stmt2 = sqlsrv_query($conn, $tsql_createSP); + if ($stmt2 === false) { + echo "Error in executing statement 2.\n"; + die (print_r (sqlsrv_errors(), true)); + } + + // Call the stored procedure + $tsql_callSP = "{CALL sp_Test (?)}"; + + // Define the parameter array + $errorNumber = -5; + $params = array( + array(&$errorNumber, SQLSRV_PARAM_INOUT) + ); + + // Execute the query + $stmt3 = sqlsrv_query($conn, $tsql_callSP, $params); + if ($stmt3 === false) { + echo "Error in executing statement 3.\n"; + die (print_r (sqlsrv_errors(), true)); + } + + // Display the value of the output parameter $errorNumber + sqlsrv_next_result($stmt3); + print("Error Number: $errorNumber\n\n"); + + // Free the statement and connection resources. */ + sqlsrv_free_stmt( $stmt1); + sqlsrv_free_stmt( $stmt2); + sqlsrv_free_stmt( $stmt3); + sqlsrv_close( $conn); +?> +--EXPECT-- +Error Number: -1 From 4a0a89777df77e4742cf2f22132c6e9c0a814a86 Mon Sep 17 00:00:00 2001 From: yitam Date: Mon, 5 Jun 2017 08:32:43 -0700 Subject: [PATCH 41/43] merged two tests --- ...21_extended_ascii_strings_fetch_array.phpt | 3 ++ ...022_extended_ascii_strings_num_fields.phpt | 47 ------------------- 2 files changed, 3 insertions(+), 47 deletions(-) delete mode 100644 test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt diff --git a/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt b/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt index bccb470d..5c36561d 100644 --- a/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt +++ b/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt @@ -34,6 +34,8 @@ $sql = "SELECT * FROM $tableName"; $stmt = sqlsrv_query( $conn, $sql ); if( $stmt === false) { die( print_r( sqlsrv_errors(), true) ); } +echo sqlsrv_num_fields($stmt)."\n"; + // Fetch array while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC) ) { printf("%s %d\n",$row[0], $row[1]); @@ -45,6 +47,7 @@ sqlsrv_close($conn); ?> --EXPECT-- +2 Paris 1911 London 2012 Berlin 1990 diff --git a/test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt b/test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt deleted file mode 100644 index bc6a90ab..00000000 --- a/test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt +++ /dev/null @@ -1,47 +0,0 @@ ---TEST-- -Query with extended ASCII column names, sqlsrv_num_fields() ---DESCRIPTION-- -Create a temporary table with column names that contain extended ASCII characters. Get number of fields -using sqlsrv_num_fields. ---SKIPIF-- - ---FILE-- - - ---EXPECT-- -2 -Done From fae0e476a53d158b29c10d738c0830bcab19a8b4 Mon Sep 17 00:00:00 2001 From: yitam Date: Mon, 5 Jun 2017 08:41:01 -0700 Subject: [PATCH 42/43] modified another test --- .../sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt b/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt index 7ffa8aa5..67ee9338 100644 --- a/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt +++ b/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt @@ -17,13 +17,15 @@ $stmt = sqlsrv_query($conn, $query); // Insert data $query = "INSERT INTO $tableName VALUES ('Aå_Ð×Æ×Ø_B')"; -$stmt = sqlsrv_query($conn, $query) ?: die(print_r( sqlsrv_errors(), true)); +$stmt = sqlsrv_query($conn, $query); +if (! $stmt) + die(print_r( sqlsrv_errors(), true)); // Fetch data $query = "SELECT * FROM $tableName"; $stmt = sqlsrv_query($conn, $query, [], array("Scrollable"=>"buffered")); -if( $stmt === false) - die( print_r(sqlsrv_errors(), true)); +if (! $stmt) + die(print_r( sqlsrv_errors(), true)); // Fetch $row = sqlsrv_fetch_array($stmt); From 65a2a31ac78c740ddc06549656a59a916ba89a34 Mon Sep 17 00:00:00 2001 From: yitam Date: Mon, 5 Jun 2017 09:26:02 -0700 Subject: [PATCH 43/43] one new test with binary data --- test/pdo_sqlsrv/pdo_033_binary_unicode.phpt | 48 +++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 test/pdo_sqlsrv/pdo_033_binary_unicode.phpt diff --git a/test/pdo_sqlsrv/pdo_033_binary_unicode.phpt b/test/pdo_sqlsrv/pdo_033_binary_unicode.phpt new file mode 100644 index 00000000..18f43904 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_033_binary_unicode.phpt @@ -0,0 +1,48 @@ +--TEST-- +Insert binary HEX data then fetch it back as string +--DESCRIPTION-- +Insert binary HEX data into an nvarchar field then read it back as UTF-8 string +--SKIPIF-- + +--FILE-- +exec($sql); + + $input = pack( "H*", '49006427500048005000' ); // I'LOVE_SYMBOL'PHP + + $stmt = $conn->prepare("INSERT INTO $tableName (c1) VALUES (?)"); + $stmt->bindParam(1, $input, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_BINARY); + $result = $stmt->execute(); + if (! $result) + echo "Failed to insert!\n"; + + $stmt = $conn->query("SELECT * FROM $tableName"); + $utf8 = $stmt->fetchColumn(); + + echo "\n". $utf8 ."\n"; + + $stmt = null; + $conn = null; +} +catch (Exception $e) +{ + echo $e->getMessage(); +} + +print "Done"; + +?> + +--EXPECT-- +I❤PHP +Done

    _9o-;T%&dSuh_iQ~G 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 diff --git a/pdo_sqlsrv/CREDITS b/pdo_sqlsrv/CREDITS deleted file mode 100644 index dc85ce17..00000000 --- a/pdo_sqlsrv/CREDITS +++ /dev/null @@ -1 +0,0 @@ -Microsoft Drivers 4.1 for PHP for SQL Server (PDO driver) diff --git a/pdo_sqlsrv/core_sqlsrv.h b/pdo_sqlsrv/core_sqlsrv.h deleted file mode 100644 index c511b057..00000000 --- a/pdo_sqlsrv/core_sqlsrv.h +++ /dev/null @@ -1,2263 +0,0 @@ -#ifndef CORE_SQLSRV_H -#define CORE_SQLSRV_H - -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_sqlsrv.h -// -// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -//********************************************************************************************************************************* -// Includes -//********************************************************************************************************************************* - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef PHP_WIN32 -#define PHP_SQLSRV_API __declspec(dllexport) -#else -#define PHP_SQLSRV_API -#endif - -// OACR is an internal Microsoft static code analysis tool -#if defined(OACR) -#include -OACR_WARNING_PUSH -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) -OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." ) -#else -// define to eliminate static analysis hints in the code -#define OACR_WARNING_SUPPRESS( warning, msg ) -#endif - -extern "C" { - -#pragma warning(push) -#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) - -#ifdef ZTS -#include "TSRM.h" -#endif - -#if _MSC_VER >= 1400 -// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. -// php.h defines this constant as unsigned int which causes a compile error -// in ws2tcpip.h. Fortunately php.h allows an override by defining -// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define -// socklen_t here and override the php.h version. -typedef int socklen_t; -#define HAVE_SOCKLEN_T -#endif - -#include "php.h" -#include "php_globals.h" -#include "php_ini.h" -#include "ext/standard/php_standard.h" -#include "ext/standard/info.h" - -#pragma warning(pop) - -#if ZEND_DEBUG -// debug build causes warning C4505 to pop up from the Zend header files -#pragma warning( disable: 4505 ) -#endif - -} // extern "C" - -#if defined(OACR) -OACR_WARNING_POP -#endif - -#include -#include - -#if !defined(WC_ERR_INVALID_CHARS) -// imported from winnls.h as it isn't included by 5.3.0 -#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars -#endif - -// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when -// we use std::copy, which causes compilation to fail since we compile with warnings as errors. -#if defined(ZEND_DEBUG) && defined(inline) -#undef inline -#endif - -#include -#include -#include -#include -#include -#include -#include -// included for SQL Server specific constants -#include "msodbcsql.h" - -//********************************************************************************************************************************* -// Constants and Types -//********************************************************************************************************************************* - -// constants for maximums in SQL Server -const int SS_MAXCOLNAMELEN = 128; -const int SQL_SERVER_MAX_FIELD_SIZE = 8000; -const int SQL_SERVER_MAX_PRECISION = 38; -const int SQL_SERVER_MAX_TYPE_SIZE = 0; -const int SQL_SERVER_MAX_PARAMS = 2100; - -// max size of a date time string when converting from a DateTime object to a string -const int MAX_DATETIME_STRING_LEN = 256; - -// precision and scale for the date time types between servers -const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; -const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; -const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; -const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; - -// types for conversions on output parameters (though they can be used for input parameters, they are ignored) -enum SQLSRV_PHPTYPE { - MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type - SQLSRV_PHPTYPE_NULL = 1, - SQLSRV_PHPTYPE_INT, - SQLSRV_PHPTYPE_FLOAT, - SQLSRV_PHPTYPE_STRING, - SQLSRV_PHPTYPE_DATETIME, - SQLSRV_PHPTYPE_STREAM, - MAX_SQLSRV_PHPTYPE, // highest value for a php type - SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid -}; - -// encodings supported by this extension. These basically translate into the use of SQL_C_CHAR or SQL_C_BINARY when getting -// information as a string or a stream. -enum SQLSRV_ENCODING { - SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables. - SQLSRV_ENCODING_DEFAULT, // use what is the connection's default for a statement, use system if a connection - SQLSRV_ENCODING_BINARY, // use SQL_C_BINARY when using SQLGetData - SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData - SQLSRV_ENCODING_SYSTEM = SQLSRV_ENCODING_CHAR, - SQLSRV_ENCODING_UTF8 = CP_UTF8, -}; - -// the array keys used when returning a row via sqlsrv_fetch_array and sqlsrv_fetch_object. -enum SQLSRV_FETCH_TYPE { - MIN_SQLSRV_FETCH = 1, // lowest value for fetch type - SQLSRV_FETCH_NUMERIC = 1, // return an array with only numeric indices - SQLSRV_FETCH_ASSOC = 2, // return an array with keys made from the field names - SQLSRV_FETCH_BOTH = 3, // return an array indexed with both numbers and keys - MAX_SQLSRV_FETCH = 3, // highest value for fetch type -}; - -// buffer size of a sql state (including the null character) -const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1; - -// buffer size allocated to retrieve data from a PHP stream. This number -// was chosen since PHP doesn't return more than 8k at a time even if -// the amount requested was more. -const int PHP_STREAM_BUFFER_SIZE = 8192; - -// SQL types for parameters encoded in an integer. The type corresponds to the SQL type ODBC constants. -// The size is the column size or precision, and scale is the decimal digits for precise numeric types. - -union sqlsrv_sqltype { - struct typeinfo_t { - int type:9; - int size:14; - int scale:8; - } typeinfo; - - zend_long value; -}; - - -// SQLSRV PHP types (as opposed to the Zend PHP type constants). Contains the type (see SQLSRV_PHPTYPE) -// and the encoding for strings and streams (see SQLSRV_ENCODING) - -union sqlsrv_phptype { - - struct typeinfo_t { - unsigned type:8; - unsigned encoding:16; - } typeinfo; - - zend_long value; -}; - -// static assert for enforcing compile time conditions -template -struct sqlsrv_static_assert; - -template <> -struct sqlsrv_static_assert { static const int value = 1; }; - -#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() ) - - -//********************************************************************************************************************************* -// Logging -//********************************************************************************************************************************* -// log_callback -// a driver specific callback for logging messages -// severity - severity of the message: notice, warning, or error -// msg - the message to log in a FormatMessage style formatting -// print_args - args to the message -typedef void (*log_callback)( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); - -// each driver must register a log callback. This should be the first thing a driver does. -void core_sqlsrv_register_logger( log_callback ); - -// a simple wrapper around a PHP error logging function. -void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ... ); - -// a macro to make it convenient to use the function. -#define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, __VA_ARGS__ ) - -// mask for filtering which severities are written to the log -enum logging_severity { - SEV_ERROR = 0x01, - SEV_WARNING = 0x02, - SEV_NOTICE = 0x04, - SEV_ALL = -1, -}; - -// Kill the PHP process and log the message to PHP -void die( const char* msg, ... ); -#define DIE( msg, ... ) { die( msg, __VA_ARGS__ ); } - - -//********************************************************************************************************************************* -// Resource/Memory Management -//********************************************************************************************************************************* - -// the macro max is defined and overrides the call to max in the allocator class -#pragma push_macro( "max" ) -#undef max - -// new memory allocation/free debugging facilities to help us verify that all allocations are being -// released in a timely manner and not just at the end of the script. -// Zend has memory logging and checking, but it can generate a lot of noise for just one extension. -// It's meant for internal use but might be useful for people adding features to our extension. -// To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases -// must be done with sqlsrv_malloc and sqlsrv_free. -// #define SQLSRV_MEM_DEBUG 1 -#if defined( PHP_DEBUG ) && !defined( ZTS ) && defined( SQLSRV_MEM_DEBUG ) - -inline void* sqlsrv_malloc_trace( size_t size, const char* file, int line ) -{ - void* ptr = emalloc( size ); - LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); - return ptr; -} - -inline void* sqlsrv_malloc_trace( size_t element_count, size_t element_size, size_t extra, const char* file, int line ) -{ - OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); - - if(( element_count > 0 && element_size > 0 ) && - ( element_count > element_size * element_count || element_size > element_size * element_count )) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count > element_size * element_count + extra ) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count + extra == 0 ) { - DIE( "Allocation size must be more than 0" ); - } - - void* ptr = emalloc( element_size * element_count + extra ); - LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); - return ptr; -} - -inline void* sqlsrv_realloc_trace( void* buffer, size_t size, const char* file, int line ) -{ - void* ptr = erealloc( original, size ); - LOG( SEV_NOTICE, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original ); - return ptr; -} - -inline void sqlsrv_free_trace( void* ptr, const char* file, int line ) -{ - LOG( SEV_NOTICE, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line ); - efree( ptr ); -} - -#define sqlsrv_malloc( size ) sqlsrv_malloc_trace( size, __FILE__, __LINE__ ) -#define sqlsrv_malloc( count, size, extra ) sqlsrv_malloc_trace( count, size, extra, __FILE__, __LINE__ ) -#define sqlsrv_realloc( buffer, size ) sqlsrv_realloc_trace( buffer, size, __FILE__, __LINE__ ) -#define sqlsrv_free( ptr ) sqlsrv_free_trace( ptr, __FILE__, __LINE__ ) - -#else - -inline void* sqlsrv_malloc( size_t size ) -{ - return emalloc( size ); -} - -inline void* sqlsrv_malloc( size_t element_count, size_t element_size, size_t extra ) -{ - OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); - - if(( element_count > 0 && element_size > 0 ) && - ( element_count > element_size * element_count || element_size > element_size * element_count )) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count > element_size * element_count + extra ) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count + extra == 0 ) { - DIE( "Allocation size must be more than 0" ); - } - - return emalloc( element_size * element_count + extra ); -} - -inline void* sqlsrv_realloc( void* buffer, size_t size ) -{ - return erealloc( buffer, size ); -} - -inline void sqlsrv_free( void* ptr ) -{ - efree( ptr ); -} - -#endif - -// trait class that allows us to assign const types to an auto_ptr -template -struct remove_const { - typedef T type; -}; - -template -struct remove_const { - typedef T* type; -}; - -// allocator that uses the zend memory manager to manage memory -// this allows us to use STL classes that still work with Zend objects -template -struct sqlsrv_allocator { - - // typedefs used by the STL classes - typedef T value_type; - typedef value_type* pointer; - typedef const value_type* const_pointer; - typedef value_type& reference; - typedef const value_type& const_reference; - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; - - // conversion typedef (used by list and other STL classes) - template - struct rebind { - typedef sqlsrv_allocator other; - }; - - inline sqlsrv_allocator() {} - inline ~sqlsrv_allocator() {} - inline sqlsrv_allocator( sqlsrv_allocator const& ) {} - template - inline sqlsrv_allocator( sqlsrv_allocator const& ) {} - - // address (doesn't work if the class defines operator&) - inline pointer address( reference r ) - { - return &r; - } - - inline const_pointer address( const_reference r ) - { - return &r; - } - - // memory allocation/deallocation - inline pointer allocate( size_type cnt, - typename std::allocator::const_pointer = 0 ) - { - return reinterpret_cast( sqlsrv_malloc(cnt, sizeof (T), 0)); - } - - inline void deallocate( pointer p, size_type ) - { - sqlsrv_free(p); - } - - // size - inline size_type max_size( void ) const - { - return std::numeric_limits::max() / sizeof(T); - } - - // object construction/destruction - inline void construct( pointer p, const T& t ) - { - new(p) T(t); - } - - inline void destroy(pointer p) - { - p->~T(); - } - - // equality operators - inline bool operator==( sqlsrv_allocator const& ) - { - return true; - } - - inline bool operator!=( sqlsrv_allocator const& a ) - { - return !operator==(a); - } -}; - - -// base class for auto_ptrs that we define below. It provides common operators and functions -// used by all the classes. -template -class sqlsrv_auto_ptr { - -public: - - sqlsrv_auto_ptr( void ) : _ptr( NULL ) - { - } - - ~sqlsrv_auto_ptr( void ) - { - static_cast(this)->reset( NULL ); - } - - // call when ownership is transferred - void transferred( void ) - { - _ptr = NULL; - } - - // explicit function to get the pointer. - T* get( void ) const - { - return _ptr; - } - - // cast operator to allow auto_ptr to be used where a normal const * can be. - operator const T* () const - { - return _ptr; - } - - // cast operator to allow auto_ptr to be used where a normal pointer can be. - operator typename remove_const::type () const - { - return _ptr; - } - - operator bool() const - { - return _ptr != NULL; - } - - // 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[]( int index ) const - { - 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[]( unsigned int index ) const - { - 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[]( long index ) const - { - return _ptr[ index ]; - } - - - #ifdef __WIN64 - // 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[](std::size_t index) const - { - return _ptr[index]; - } - #endif - - // 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[]( unsigned short index ) const - { - return _ptr[ index ]; - } - - // access elements of a structure through the auto ptr - T* const operator->( void ) const - { - return _ptr; - } - - // value from reference operator (i.e., i = *(&i); or *i = blah;) - T& operator*() const - { - return *_ptr; - } - - // allow the use of the address-of operator to simulate a **. - // Note: this operator conflicts with storing these within an STL container. If you need - // to do that, then redefine this as getpp and change instances of &auto_ptr to auto_ptr.getpp() - T** operator&( void ) - { - return &_ptr; - } - -protected: - - sqlsrv_auto_ptr( T* ptr ) : - _ptr( ptr ) - { - } - - sqlsrv_auto_ptr( sqlsrv_auto_ptr& src ) - { - if( _ptr ) { - static_cast(this)->reset( src._ptr ); - } - src.transferred(); - } - - // assign a new pointer to the auto_ptr. It will free the previous memory block - // because ownership is deemed finished. - T* operator=( T* ptr ) - { - static_cast( this )->reset( ptr ); - - return ptr; - } - - T* _ptr; -}; - -// an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer -// in a variable of sqlsrv_malloc_auto_ptr. sqlsrv_malloc_auto_ptr will "own" that block and assure that it is -// freed until the variable is destroyed (out of scope) or ownership is transferred using the function -// "transferred". -// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. - -template -class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr > { - -public: - - sqlsrv_malloc_auto_ptr( void ) : - sqlsrv_auto_ptr >( NULL ) - { - } - - sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) : - sqlsrv_auto_ptr >( src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( T* ptr = NULL ) - { - if( _ptr ) - sqlsrv_free( (void*) _ptr ); - _ptr = ptr; - } - - T* operator=( T* ptr ) - { - return sqlsrv_auto_ptr >::operator=( ptr ); - } - - void operator=( sqlsrv_malloc_auto_ptr& src ) - { - T* p = src.get(); - src.transferred(); - this->_ptr = p; - } - - // DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. - // has the same parameter list as sqlsrv_realloc: new_size is the size in bytes of the newly allocated buffer - void resize( size_t new_size ) - { - _ptr = reinterpret_cast( sqlsrv_realloc( _ptr, new_size )); - } -}; - - -// auto ptr for Zend hash tables. Used to clean up a hash table allocated when -// something caused an early exit from the function. This is used when the hash_table is -// allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr. - -class hash_auto_ptr : public sqlsrv_auto_ptr { - -public: - - hash_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( HashTable* ptr = NULL ) - { - if( _ptr ) { - zend_hash_destroy( _ptr ); - FREE_HASHTABLE( _ptr ); - } - _ptr = ptr; - } - - HashTable* operator=( HashTable* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - -private: - - hash_auto_ptr( HashTable const& hash ); - - hash_auto_ptr( hash_auto_ptr const& hash ); -}; - - -// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr. -// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed -// (out of scope) or ownership is transferred using the function "transferred". - -class zval_auto_ptr : public sqlsrv_auto_ptr { - -public: - - zval_auto_ptr( void ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( zval* ptr = NULL ) - { - if( _ptr ) - zval_ptr_dtor(_ptr ); - _ptr = ptr; - } - - zval* operator=( zval* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - - -private: - - zval_auto_ptr( const zval_auto_ptr& src ); -}; - -#pragma pop_macro( "max" ) - - -//********************************************************************************************************************************* -// sqlsrv_error -//********************************************************************************************************************************* - -// *** PHP specific errors *** -// sqlsrv errors are held in a structure of this type used by the driver handle_error functions -// format is a flag that tells the driver error handler functions if there are parameters to use with FormatMessage -// into the error message before returning it. - -// base class which can be instatiated with aggregates (see error constants) -struct sqlsrv_error_const { - - SQLCHAR* sqlstate; - SQLCHAR* native_message; - SQLINTEGER native_code; - bool format; -}; - -// subclass which is used by the core layer to instantiate ODBC errors -struct sqlsrv_error : public sqlsrv_error_const { - - sqlsrv_error( void ) - { - sqlstate = NULL; - native_message = NULL; - native_code = -1; - format = false; - } - - sqlsrv_error( SQLCHAR* sql_state, SQLCHAR* message, SQLINTEGER code, bool printf_format = false ) - { - sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); - strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); - strcpy_s( reinterpret_cast( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast( message )); - native_code = code; - format = printf_format; - } - - sqlsrv_error( sqlsrv_error_const const& prototype ) - { - sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format ); - } - - ~sqlsrv_error( void ) - { - if( sqlstate != NULL ) { - sqlsrv_free( sqlstate ); - } - if( native_message != NULL ) { - sqlsrv_free( native_message ); - } - } -}; - - -// an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete -class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr { - -public: - - sqlsrv_error_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - sqlsrv_error_auto_ptr( sqlsrv_error_auto_ptr const& src ) : - sqlsrv_auto_ptr( (sqlsrv_error_auto_ptr&) src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( sqlsrv_error* ptr = NULL ) - { - if( _ptr ) { - _ptr->~sqlsrv_error(); - sqlsrv_free( (void*) _ptr ); - } - _ptr = ptr; - } - - sqlsrv_error* operator=( sqlsrv_error* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - - // unlike traditional assignment operators, the chained assignment of an auto_ptr doesn't make much - // sense. Only the last one would have anything in it. - void operator=( sqlsrv_error_auto_ptr& src ) - { - sqlsrv_error* p = src.get(); - src.transferred(); - this->_ptr = p; - } -}; - - -//********************************************************************************************************************************* -// Context -//********************************************************************************************************************************* - -class sqlsrv_context; -struct sqlsrv_conn; - -// error_callback -// a driver specific callback for processing errors. -// ctx - the context holding the handles -// sqlsrv_error_code - specific error code to return. -typedef bool (*error_callback)( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool error TSRMLS_DC, va_list* print_args ); - -// sqlsrv_context -// a context holds relevant information to be passed with a connection and statement objects. - -class sqlsrv_context { - - public: - - sqlsrv_context( SQLSMALLINT type, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : - handle_( SQL_NULL_HANDLE ), - handle_type_( type ), - err_( e ), - name_( NULL ), - driver_( drv ), - last_error_(), - encoding_( encoding ) - { - } - - sqlsrv_context( SQLHANDLE h, SQLSMALLINT t, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : - handle_( h ), - handle_type_( t ), - err_( e ), - name_( NULL ), - driver_( drv ), - last_error_(), - encoding_( encoding ) - { - } - - sqlsrv_context( sqlsrv_context const& ctx ) : - handle_( ctx.handle_ ), - handle_type_( ctx.handle_type_ ), - err_( ctx.err_ ), - name_( ctx.name_ ), - driver_( ctx.driver_ ), - last_error_( ctx.last_error_ ) - { - } - - void set_func( const char* f ) - { - name_ = f; - } - - void set_last_error( sqlsrv_error_auto_ptr& last_error ) - { - last_error_ = last_error; - } - - sqlsrv_error_auto_ptr& last_error( void ) - { - return last_error_; - } - - // since the primary responsibility of a context is to hold an ODBC handle, we - // provide these convenience operators for using them interchangeably - operator SQLHANDLE ( void ) const - { - return handle_; - } - - error_callback error_handler( void ) const - { - return err_; - } - - SQLHANDLE handle( void ) const - { - return handle_; - } - - SQLSMALLINT handle_type( void ) const - { - return handle_type_; - } - - const char* func( void ) const - { - return name_; - } - - void* driver( void ) const - { - return driver_; - } - - void set_driver( void* driver ) - { - this->driver_ = driver; - } - - void invalidate( void ) - { - if( handle_ != SQL_NULL_HANDLE ) { - ::SQLFreeHandle( handle_type_, handle_ ); - - last_error_.reset(); - } - handle_ = SQL_NULL_HANDLE; - } - - bool valid( void ) - { - return handle_ != SQL_NULL_HANDLE; - } - - SQLSRV_ENCODING encoding( void ) const - { - return encoding_; - } - - void set_encoding( SQLSRV_ENCODING e ) - { - encoding_ = e; - } - - private: - SQLHANDLE handle_; // ODBC handle for this context - SQLSMALLINT handle_type_; // type of the ODBC handle - const char* name_; // function name currently executing this context - error_callback err_; // driver error callback if error occurs in core layer - void* driver_; // points back to the driver for PDO - sqlsrv_error_auto_ptr last_error_; // last error that happened on this object - SQLSRV_ENCODING encoding_; // encoding of the context -}; - -const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista - -// maps an IANA encoding to a code page -struct sqlsrv_encoding { - - const char* iana; - size_t iana_len; - unsigned int code_page; - bool not_for_connection; - - sqlsrv_encoding( const char* iana, unsigned int code_page, bool not_for_conn = false ): - iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn ) - { - } -}; - - -//********************************************************************************************************************************* -// Initialization -//********************************************************************************************************************************* - -// variables set during initialization -extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in -extern HashTable* g_encodings; // encodings supported by this driver - -void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ); -void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ); - -// environment context used by sqlsrv_connect for when a connection error occurs. -struct sqlsrv_henv { - - sqlsrv_context ctx; - - sqlsrv_henv( SQLHANDLE handle, error_callback e, void* drv ) : - ctx( handle, SQL_HANDLE_ENV, e, drv ) - { - } -}; - - -//********************************************************************************************************************************* -// Connection -//********************************************************************************************************************************* - -// supported server versions (determined at connection time) -enum SERVER_VERSION { - SERVER_VERSION_UNKNOWN = -1, - SERVER_VERSION_2000 = 8, - SERVER_VERSION_2005, - SERVER_VERSION_2008, // use this for anything 2008 or later -}; - -// supported driver versions. -enum DRIVER_VERSION : size_t { - MIN = 0, - ODBC_DRIVER_13 = MIN, - ODBC_DRIVER_11 = 1, - MAX = ODBC_DRIVER_11, -}; - -// forward decl -struct sqlsrv_stmt; -struct stmt_option; - -// *** connection resource structure *** -// this is the resource structure returned when a connection is made. -struct sqlsrv_conn : public sqlsrv_context { - - // instance variables - SERVER_VERSION server_version; // version of the server that we're connected to - - DRIVER_VERSION driver_version; - - // initialize with default values - sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) : - sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) - { - } - - // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be - // called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating - // the sqlsrv_conn with a connection. -}; - -enum SQLSRV_STMT_OPTIONS { - - SQLSRV_STMT_OPTION_INVALID, - SQLSRV_STMT_OPTION_QUERY_TIMEOUT, - SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, - SQLSRV_STMT_OPTION_SCROLLABLE, - SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, - - // Driver specific connection options - SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, - -}; - -namespace ODBCConnOptions { - -const char APP[] = "APP"; -const char ApplicationIntent[] = "ApplicationIntent"; -const char AttachDBFileName[] = "AttachDbFileName"; -const char CharacterSet[] = "CharacterSet"; -const char ConnectionPooling[] = "ConnectionPooling"; -const char Database[] = "Database"; -const char Encrypt[] = "Encrypt"; -const char Failover_Partner[] = "Failover_Partner"; -const char LoginTimeout[] = "LoginTimeout"; -const char MARS_ODBC[] = "MARS_Connection"; -const char MultiSubnetFailover[] = "MultiSubnetFailover"; -const char QuotedId[] = "QuotedId"; -const char TraceFile[] = "TraceFile"; -const char TraceOn[] = "TraceOn"; -const char TrustServerCertificate[] = "TrustServerCertificate"; -const char TransactionIsolation[] = "TransactionIsolation"; -const char WSID[] = "WSID"; -const char UID[] = "UID"; -const char PWD[] = "PWD"; -const char SERVER[] = "Server"; - -} - -enum SQLSRV_CONN_OPTIONS { - - SQLSRV_CONN_OPTION_INVALID, - SQLSRV_CONN_OPTION_APP, - SQLSRV_CONN_OPTION_CHARACTERSET, - SQLSRV_CONN_OPTION_CONN_POOLING, - SQLSRV_CONN_OPTION_DATABASE, - SQLSRV_CONN_OPTION_ENCRYPT, - SQLSRV_CONN_OPTION_FAILOVER_PARTNER, - SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, - SQLSRV_CONN_OPTION_MARS, - SQLSRV_CONN_OPTION_QUOTED_ID, - SQLSRV_CONN_OPTION_TRACE_FILE, - SQLSRV_CONN_OPTION_TRACE_ON, - SQLSRV_CONN_OPTION_TRANS_ISOLATION, - SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, - SQLSRV_CONN_OPTION_WSID, - SQLSRV_CONN_OPTION_ATTACHDBFILENAME, - SQLSRV_CONN_OPTION_APPLICATION_INTENT, - SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, - - // Driver specific connection options - SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, - -}; - - -#define NO_ATTRIBUTE -1 - -// type of connection attributes -enum CONN_ATTR_TYPE { - CONN_ATTR_INT, - CONN_ATTR_BOOL, - CONN_ATTR_STRING, - CONN_ATTR_INVALID, -}; - -// a connection option that includes the callback function that handles that option (e.g., adds it to the connection string or -// sets an attribute) -struct connection_option { - // the name of the option as passed in by the user - const char * sqlsrv_name; - unsigned int sqlsrv_len; - - unsigned int conn_option_key; - // the name of the option in the ODBC connection string - const char * odbc_name; - unsigned int odbc_len; - enum CONN_ATTR_TYPE value_type; - - // process the connection type - // return whether or not the function was successful in processing the connection option - void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC ); -}; - -// connection attribute functions -template -struct str_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), - static_cast(Z_STRLEN_P( value )) TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } - } -}; - -// simply add the parsed value to the connection string -struct conn_str_append_func { - - static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); -}; - -struct conn_null_func { - - static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ - TSRMLS_DC ); -}; - -// factory to create a connection (since they are subclassed to instantiate statements) -typedef sqlsrv_conn* (*driver_conn_factory)( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); - -// *** connection functions *** -sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, - const char* server, const char* uid, const char* pwd, - HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[], - void* driver, const char* driver_func TSRMLS_DC ); -void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ); -void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC ); -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ); -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); -bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); -size_t core_str_zval_is_true( zval* str_zval ); - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -struct stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ); -}; - -struct stmt_option_query_timeout : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_send_at_exec : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_buffered_query_limit : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -// used to hold the table for statment options -struct stmt_option { - - const char * name; // name of the statement option - unsigned int name_len; // name length - unsigned int key; - std::unique_ptr func; // callback that actually handles the work of the option - -}; - -// holds the stream param and the encoding that it was assigned -struct sqlsrv_stream { - - zval* stream_z; - SQLSRV_ENCODING encoding; - SQLUSMALLINT field_index; - SQLSMALLINT sql_type; - sqlsrv_stmt* stmt; - std::size_t stmt_index; - - sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) : - stream_z( str_z ), encoding( enc ) - { - } - - sqlsrv_stream() : stream_z( NULL ), encoding( SQLSRV_ENCODING_INVALID ), stmt( NULL ) - { - } -}; - -// close any active stream -void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); - -extern php_stream_wrapper g_sqlsrv_stream_wrapper; - -// resource constants used when registering the stream type with PHP -#define SQLSRV_STREAM_WRAPPER "sqlsrv" -#define SQLSRV_STREAM "sqlsrv_stream" - -// holds the output parameter information. Strings also need the encoding and other information for -// after processing. Only integer, float, and strings are allowable output parameters. -struct sqlsrv_output_param { - - zval* param_z; - SQLSRV_ENCODING encoding; - SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement - SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer - bool is_bool; - - // string output param constructor - sqlsrv_output_param( zval* p_z, SQLSRV_ENCODING enc, int num, SQLUINTEGER buffer_len ) : - param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false ) - { - } - - // every other type output parameter constructor - sqlsrv_output_param( zval* p_z, int num, bool is_bool ) : - param_z( p_z ), - param_num( num ), - encoding( SQLSRV_ENCODING_INVALID ), - original_buffer_len( -1 ), - is_bool( is_bool ) - { - } -}; - -// forward decls -struct sqlsrv_result_set; - -// *** Statement resource structure *** -struct sqlsrv_stmt : public sqlsrv_context { - - void free_param_data( TSRMLS_D ); - virtual void new_result_set( TSRMLS_D ); - - sqlsrv_conn* conn; // Connection that created this statement - - bool executed; // Whether the statement has been executed yet (used for error messages) - bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row - sqlsrv_result_set* current_results; // Current result set - SQLULEN cursor_type; // Type of cursor for the current result set - bool has_rows; // Has_rows is set if there are actual rows in the row set - bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called - int last_field_index; // last field retrieved by core_sqlsrv_get_field - bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the - // 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) - - // 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 - // memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold - std::deque param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter - zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP - zval output_params; // hold all the output parameters - zval param_streams; // track which streams to send data to the server - zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects - bool send_streams_at_exec; // send all stream data right after execution before returning - sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter - unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string - // to the server) - zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals - zval active_stream; // the currently active stream reading data from the database - - sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); - virtual ~sqlsrv_stmt( void ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string = false ) = 0; - -}; - -// *** field metadata struct *** -struct field_meta_data { - - sqlsrv_malloc_auto_ptr field_name; - SQLSMALLINT field_name_len; - SQLSMALLINT field_type; - SQLULEN field_size; - SQLULEN field_precision; - SQLSMALLINT field_scale; - SQLSMALLINT field_is_nullable; - - field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0), - field_scale (0), field_is_nullable(0) - { - } - - ~field_meta_data() - { - } -}; - -// *** statement constants *** -// unknown column size used by core_sqlsrv_bind_param when the user doesn't supply a value -const SQLULEN SQLSRV_UNKNOWN_SIZE = 0xffffffff; -const int SQLSRV_DEFAULT_SIZE = -1; // size given for an output parameter that doesn't really need one (e.g., int) - -// uninitialized query timeout value -const unsigned int QUERY_TIMEOUT_INVALID = 0xffffffff; - -// special buffered query constant -const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant - -// factory to create a statement -typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); - -// *** statement functions *** -sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, - const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ); -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, - SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, - SQLSMALLINT decimal_digits TSRMLS_DC ); -void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 ); -field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ); -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ); -void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, - _Out_ void*& field_value, _Out_ SQLLEN* field_length, bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC); -bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ); -void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true ); -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC ); -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ); -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ); -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); - - -//********************************************************************************************************************************* -// Result Set -//********************************************************************************************************************************* - -// Abstract the result set so that a result set can either be used as is from ODBC or buffered. -// This is not a complete abstraction of a result set. Only enough is abstracted to allow for -// information and capabilities normally not available when a result set is not buffered -// (e.g., forward only vs buffered means row count is available and cursor movement is possible). -// Otherwise, normal ODBC calls are still valid and should be used to get information about the -// result set (e.g., SQLNumResultCols). - -struct sqlsrv_result_set { - - sqlsrv_stmt* odbc; - - explicit sqlsrv_result_set( sqlsrv_stmt* ); - virtual ~sqlsrv_result_set( void ) { } - - virtual bool cached( int field_index ) = 0; - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0; - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC )= 0; - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0; - virtual SQLLEN row_count( TSRMLS_D ) = 0; -}; - -struct sqlsrv_odbc_result_set : public sqlsrv_result_set { - - explicit sqlsrv_odbc_result_set( sqlsrv_stmt* ); - virtual ~sqlsrv_odbc_result_set( void ); - - virtual bool cached( int field_index ) { return false; } - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ); - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); - virtual SQLLEN row_count( TSRMLS_D ); - - private: - // prevent invalid instantiations and assignments - sqlsrv_odbc_result_set( void ); - sqlsrv_odbc_result_set( sqlsrv_odbc_result_set& ); - sqlsrv_odbc_result_set& operator=( sqlsrv_odbc_result_set& ); -}; - -struct sqlsrv_buffered_result_set : public sqlsrv_result_set { - - struct meta_data { - SQLSMALLINT type; - SQLSMALLINT c_type; // convenience - SQLULEN offset; // in bytes - SQLULEN length; // in bytes - SQLSMALLINT scale; - - static const SQLULEN SIZE_UNKNOWN = 0; - }; - - // default maximum amount of memory that a buffered query can consume - #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings - static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB - static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; - - explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC ); - virtual ~sqlsrv_buffered_result_set( void ); - - virtual bool cached( int field_index ) { return true; } - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ); - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); - virtual SQLLEN row_count( TSRMLS_D ); - - // buffered result set specific - SQLSMALLINT column_count( void ) - { - return col_count; - } - - struct meta_data& col_meta_data( SQLSMALLINT i ) - { - return meta[i]; - } - - private: - // prevent invalid instantiations and assignments - sqlsrv_buffered_result_set( void ); - sqlsrv_buffered_result_set( sqlsrv_buffered_result_set& ); - sqlsrv_buffered_result_set& operator=( sqlsrv_buffered_result_set& ); - - HashTable* cache; // rows of data kept in index based hash table - SQLSMALLINT col_count; // number of columns in the current result set - meta_data* meta; // metadata for fields in the cache - SQLLEN current; // 1 based, 0 means before first row - sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here - SQLUSMALLINT last_field_index; // the last field data retrieved from - SQLLEN read_so_far; // position within string to read from (for partial reads of strings) - sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use - SQLLEN temp_length; // number of bytes in the temp conversion buffer - - typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; - - // two dimentional sparse matrix that holds the [from][to] functions that do conversions - static conv_matrix_t conv_matrix; - - // string conversion functions - SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // long conversion functions - SQLRETURN to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // double conversion functions - SQLRETURN to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // string to number conversion functions - // Future: See if these can be converted directly to template member functions - SQLRETURN string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // utility functions for conversions - unsigned char* get_row( void ); -}; - -//********************************************************************************************************************************* -// Utility -//********************************************************************************************************************************* - -// Simple macro to alleviate unused variable warnings. These are optimized out by the compiler. -// We use this since the unused variables are buried in the PHP_FUNCTION macro. -#define SQLSRV_UNUSED( var ) var; - -// do a heap check in debug mode, but only print errors, not all of the allocations -#define MEMCHECK_SILENT 1 - -// utility functions shared by multiple callers across files -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len); -bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len); -bool validate_string(char* string, SQLLEN& len); -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ); -wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, - unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); - -//********************************************************************************************************************************* -// Error handling routines and Predefined Errors -//********************************************************************************************************************************* - -enum SQLSRV_ERROR_CODES { - - SQLSRV_ERROR_ODBC, - SQLSRV_ERROR_DRIVER_NOT_INSTALLED, - SQLSRV_ERROR_ZEND_HASH, - SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, - SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, - SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, - SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, - SQLSRV_ERROR_ZEND_STREAM, - SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, - SQLSRV_ERROR_FETCH_PAST_END, - SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, - SQLSRV_ERROR_NO_FIELDS, - SQLSRV_ERROR_INVALID_TYPE, - SQLSRV_ERROR_FETCH_NOT_CALLED, - SQLSRV_ERROR_NO_DATA, - SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, - SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, - SQLSRV_ERROR_NEXT_RESULT_PAST_END, - SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, - SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, - SQLSRV_ERROR_INVALID_CONNECTION_KEY, - SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, - SQLSRV_ERROR_INVALID_OPTION_KEY, - SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, - SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, - SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, - SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, - SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, - SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, - SQLSRV_ERROR_STREAM_CREATE, - SQLSRV_ERROR_MARS_OFF, - SQLSRV_ERROR_FIELD_INDEX_ERROR, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, - SQLSRV_ERROR_INVALID_BUFFER_LIMIT, - - // Driver specific error codes starts from here. - SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, - -}; - -// the message returned by ODBC Driver 11 for SQL Server -static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command", - "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" }; - -// SQLSTATE for all internal errors -extern SQLCHAR IMSSP[]; - -// SQLSTATE for all internal warnings -extern SQLCHAR SSPWARN[]; - -// flags passed to sqlsrv_errors to filter its return values -enum error_handling_flags { - SQLSRV_ERR_ERRORS, - SQLSRV_ERR_WARNINGS, - SQLSRV_ERR_ALL -}; - -// *** internal error macros and functions *** -// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the -// errno is 1 based. It returns it as an array with 3 members: -// 1/SQLSTATE) sqlstate -// 2/code) driver specific error code -// 3/message) driver specific error message -// The fetch type determines if the indices are numeric, associative, or both. -bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, _Out_ sqlsrv_error_auto_ptr& error, - logging_severity severity TSRMLS_DC ); - -// format and return a driver specfic error -void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, - sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ); - - -// return the message for the HRESULT returned by GetLastError. Some driver errors use this to -// return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails. -const char* get_last_error_message( DWORD last_error = 0 ); - -// a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer -DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ); - -// convenience functions that overload either a reference or a pointer so we can use -// either in the CHECK_* functions. -inline bool call_error_handler( sqlsrv_context& ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) -{ - va_list print_params; - va_start( print_params, warning ); - bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); - va_end( print_params ); - return ignored; -} - -inline bool call_error_handler( sqlsrv_context* ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) -{ - va_list print_params; - va_start( print_params, warning ); - bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); - va_end( print_params ); - return ignored; -} - -// PHP equivalent of ASSERT. C asserts cause a dialog to show and halt the process which -// we don't want on a web server - -#define SQLSRV_ASSERT( condition, msg, ...) if( !(condition)) DIE( msg, __VA_ARGS__ ); - -#if defined( PHP_DEBUG ) - -#define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) \ - if( !(condition)) { \ - DIE (msg, __VA_ARGS__ ); \ - } - -#else - - #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) ((void)0) - -#endif - -// check to see if the sqlstate is 01004, truncated field retrieved. Used for retrieving large fields. -inline bool is_truncated_warning( SQLCHAR* state ) -{ -#if defined(ZEND_DEBUG) - if( state == NULL || strlen( reinterpret_cast( state )) != 5 ) { \ - DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \ - } -#endif - return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4'); -} - -// Macros for handling errors. These macros are simplified if statements that take boilerplate -// code down to a single line to avoid distractions in the code. - -#define CHECK_ERROR_EX( unique, condition, context, ssphp, ... ) \ - bool flag##unique = (condition); \ - bool ignored##unique = true; \ - if (flag##unique) { \ - ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, __VA_ARGS__ ); \ - } \ - if( !ignored##unique ) - -#define CHECK_ERROR_UNIQUE( unique, condition, context, ssphp, ...) \ - CHECK_ERROR_EX( unique, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_ERROR( condition, context, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, NULL, __VA_ARGS__ ) - -#define CHECK_CUSTOM_ERROR( condition, context, ssphp, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_SQL_ERROR( result, context, ... ) \ - SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ - CHECK_ERROR( result == SQL_ERROR, context, __VA_ARGS__ ) - -#define CHECK_WARNING_AS_ERROR_UNIQUE( unique, condition, context, ssphp, ... ) \ - bool ignored##unique = true; \ - if( condition ) { \ - ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, __VA_ARGS__ ); \ - } \ - if( !ignored##unique ) - -#define CHECK_SQL_WARNING_AS_ERROR( result, context, ... ) \ - CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__,( result == SQL_SUCCESS_WITH_INFO ), context, SQLSRV_ERROR_ODBC, __VA_ARGS__ ) - -#define CHECK_SQL_WARNING( result, context, ... ) \ - if( result == SQL_SUCCESS_WITH_INFO ) { \ - (void)call_error_handler( context, NULL TSRMLS_CC, /*warning*/ true, __VA_ARGS__ ); \ - } - -#define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \ - CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_ZEND_ERROR( zr, ctx, error, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, ( zr == FAILURE ), ctx, error, __VA_ARGS__ ) \ - -#define CHECK_SQL_ERROR_OR_WARNING( result, context, ... ) \ - SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ - bool ignored = true; \ - if( result == SQL_ERROR ) { \ - ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, false, __VA_ARGS__ ); \ - } \ - else if( result == SQL_SUCCESS_WITH_INFO ) { \ - ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, true TSRMLS_CC, __VA_ARGS__ ); \ - } \ - if( !ignored ) - -// throw an exception after it has been hooked into the custom error handler -#define THROW_CORE_ERROR( ctx, custom, ... ) \ - (void)call_error_handler( ctx, custom TSRMLS_CC, /*warning*/ false, __VA_ARGS__ ); \ - throw core::CoreException(); - -//********************************************************************************************************************************* -// ODBC/Zend function wrappers -//********************************************************************************************************************************* - -namespace core { - - // base exception for the driver - struct CoreException : public std::exception { - - CoreException() - { - } - }; - - inline void check_for_mars_error( sqlsrv_stmt* stmt, SQLRETURN r TSRMLS_DC ) - { - // We check for the 'connection busy' error caused by having MultipleActiveResultSets off - // and return a more helpful message prepended to the ODBC errors if that error occurs - if( !SQL_SUCCEEDED( r )) { - - SQLCHAR err_msg[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLSMALLINT len = 0; - - SQLRETURN r = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, - err_msg, SQL_MAX_MESSAGE_LENGTH, &len ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - - throw CoreException(); - } - std::size_t driver_version = stmt->conn->driver_version; - if( !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); - } - } - } - - // *** ODBC wrappers *** - - // wrap the ODBC functions to throw exceptions rather than use the return value to signal errors - // some of the signatures have been altered to be more convenient since the return value is no longer - // required to return the status of the call (e.g., SQLNumResultCols). - // These functions take the sqlsrv_context type. However, since the error handling code can alter - // the context to hold the error, they are not passed as const. - - inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) - { - SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, - diag_info_buffer, buffer_length, out_buffer_length ); - - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - - return r; - } - - inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle, - _Out_writes_(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); - CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) { - throw CoreException(); - } - } - - inline void SQLBindParameter( sqlsrv_stmt* stmt, - SQLUSMALLINT ParameterNumber, - SQLSMALLINT InputOutputType, - SQLSMALLINT ValueType, - SQLSMALLINT ParameterType, - SQLULEN ColumnSize, - SQLSMALLINT DecimalDigits, - _Inout_ SQLPOINTER ParameterValuePtr, - SQLLEN BufferLength, - _Inout_ SQLLEN * StrLen_Or_IndPtr - TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, - DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, - _Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC ) - { - SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, - buffer_length, out_buffer_length, field_type_num ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLCHAR* col_name, SQLSMALLINT col_name_length, - _Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size, - _Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, - data_type, col_size, decimal_digits, nullable); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLEndTran( SQLSMALLINT handleType, sqlsrv_conn* conn, SQLSMALLINT completionType TSRMLS_DC ) - { - SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success - inline SQLRETURN SQLExecDirect( sqlsrv_stmt* stmt, char* sql TSRMLS_DC ) - { - SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast( sql ), SQL_NTS ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - - throw CoreException(); - } - return r; - } - - inline SQLRETURN SQLExecDirectW( sqlsrv_stmt* stmt, wchar_t* wsql TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast( wsql ), SQL_NTS ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - // SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success - inline SQLRETURN SQLExecute( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLExecute( stmt->handle() ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return r; - } - - inline SQLRETURN SQLFetchScroll( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) - { - SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - - // wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine - inline void SQLFreeHandle( sqlsrv_context& ctx TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() ); - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} - } - - inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) - { - SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); - - if( r == SQL_NO_DATA ) - return r; - - CHECK_SQL_ERROR( r, stmt ) { - throw CoreException(); - } - - if( handle_warning ) { - CHECK_SQL_WARNING_AS_ERROR( r, stmt ) { - throw CoreException(); - } - } - - return r; - } - - - inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, _Out_ SQLPOINTER info_value, SQLSMALLINT buffer_len, - _Out_ SQLSMALLINT* str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - - inline void SQLGetTypeInfo( sqlsrv_stmt* stmt, SQLUSMALLINT data_type TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLGetTypeInfo( stmt->handle(), data_type ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - // SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set. - inline SQLRETURN SQLMoreResults( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r = ::SQLMoreResults( stmt->handle() ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return r; - } - - inline SQLSMALLINT SQLNumResultCols( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - SQLSMALLINT num_cols; - r = ::SQLNumResultCols( stmt->handle(), &num_cols ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return num_cols; - } - - // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more - // parameters or when the parameters are all processed. - inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, _Out_ SQLPOINTER* value_ptr_ptr TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - inline void SQLPrepareW( sqlsrv_stmt* stmt, SQLWCHAR * sql, SQLINTEGER sql_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLPrepareW( stmt->handle(), sql, sql_len ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLPutData( sqlsrv_stmt* stmt, SQLPOINTER data_ptr, SQLLEN strlen_or_ind TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline SQLLEN SQLRowCount( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - SQLLEN rows_affected; - - r = ::SQLRowCount( stmt->handle(), &rows_affected ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return rows_affected; - } - - - inline void SQLSetConnectAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - } - - - inline void SQLSetEnvAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len ); - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - } - - inline void SQLSetConnectAttr( sqlsrv_conn* conn, SQLINTEGER attribute, SQLPOINTER value_ptr, SQLINTEGER value_len TSRMLS_DC ) - { - SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - inline void SQLSetStmtAttr( sqlsrv_stmt* stmt, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - // *** zend wrappers *** - - //zend_resource_dtor sets the type of destroyed resources to -1 - #define RSRC_INVALID_TYPE -1 - - // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string - // so allocated memory inside of value_z should be released before assigning it to the new string - inline void sqlsrv_zval_stringl(zval* value_z, const char* str, const std::size_t str_len) - { - if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { - zend_string* temp_zstr = zend_string_init(str, str_len, 0); - zend_string_release(Z_STR_P(value_z)); - ZVAL_NEW_STR(value_z, temp_zstr); - } - else { - ZVAL_STRINGL(value_z, str, str_len); - } - } - - - // exception thrown when a zend function wrapped here fails. - - // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw - // exceptions when an error occurs. They are prefaced with sqlsrv_ because many of the zend functions are - // actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system. - // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error - // that can be thrown from it. - - inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, zend_ulong index, zval* value TSRMLS_DC) - { - int zr = ::add_index_zval( array, index, value ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_next_index_zval( sqlsrv_context& ctx, zval* array, zval* value TSRMLS_DC) - { - int zr = ::add_next_index_zval( array, value ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, const char* key TSRMLS_DC ) - { - int zr = ::add_assoc_null( array_z, key ); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, const char* key, zend_long val TSRMLS_DC ) - { - int zr = ::add_assoc_long( array_z, key, val ); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, const char* key, char* val, bool duplicate TSRMLS_DC ) - { - int zr = ::add_assoc_string(array_z, key, val); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - if (duplicate == 0) { - sqlsrv_free(val); - } - } - - inline void sqlsrv_array_init( sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) - { - int zr = ::array_init(new_array); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval* stream_z TSRMLS_DC ) - { - // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment - php_stream_from_zval_no_verify( stream, stream_z ); - CHECK_CUSTOM_ERROR( stream == NULL, ctx, SQLSRV_ERROR_ZEND_STREAM ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_get_current_data(sqlsrv_context& ctx, HashTable* ht, _Out_ zval*& output_data TSRMLS_DC) - { - int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_get_current_data_ptr(sqlsrv_context& ctx, HashTable* ht, _Out_ void*& output_data TSRMLS_DC) - { - int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, zend_ulong index TSRMLS_DC ) - { - int zr = ::zend_hash_index_del( ht, index ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, zend_ulong index, zval* data_z TSRMLS_DC ) - { - int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_update_ptr(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData TSRMLS_DC) - { - int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - - inline void sqlsrv_zend_hash_index_update_mem(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData, std::size_t size TSRMLS_DC) - { - int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, zval* data TSRMLS_DC ) - { - int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert_mem(sqlsrv_context& ctx, HashTable* ht, void* data, uint data_size TSRMLS_DC) - { - int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert_ptr(sqlsrv_context& ctx, HashTable* ht, void* data TSRMLS_DC) - { - int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, HashTable* ht, uint32_t initial_size, - dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) - { - ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); - } - - inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, zend_string* key, unsigned int key_len, zval* data, - unsigned int data_size, zval* pDest TSRMLS_DC ) - { - int zr = (pDest = ::zend_hash_add(ht, key, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - -template -sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) -{ - return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC ); -} - -template -sqlsrv_conn* allocate_conn( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) -{ - return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC ); -} - -} // namespace core - -#endif // CORE_SQLSRV_H diff --git a/pdo_sqlsrv/core_stmt.cpp b/pdo_sqlsrv/core_stmt.cpp deleted file mode 100644 index a4ef0d9d..00000000 --- a/pdo_sqlsrv/core_stmt.cpp +++ /dev/null @@ -1,2471 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_stmt.cpp -// -// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -namespace { - -// certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the -// results of every field request, and if it is out of order, we cache those for preceding fields. -struct field_cache { - - void* value; - SQLLEN len; - sqlsrv_phptype type; - - field_cache( void* field_value, SQLLEN field_len, sqlsrv_phptype t ) - : type( t ) - { - // if the value is NULL, then just record a NULL pointer - if( field_value != NULL ) { - value = sqlsrv_malloc( field_len ); - memcpy_s( value, field_len, field_value, field_len ); - len = field_len; - } - else { - value = NULL; - len = 0; - } - } - - // no destructor because we don't want to release the memory when it goes out of scope, but instead we - // rely on the hash table destructor to free the memory -}; - -const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field - -// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads -const unsigned int UTF8_MIDBYTE_MASK = 0xc0; -const unsigned int UTF8_MIDBYTE_TAG = 0x80; -const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0; -const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0; -const unsigned int UTF8_3BYTESEQ_TAG = 0xe0; -const unsigned int UTF8_4BYTESEQ_TAG = 0xf0; -const unsigned int UTF8_NBYTESEQ_MASK = 0xf0; - -// constants used to convert from a DateTime object to a string which is sent to the server. -// Using the format defined by the ODBC documentation at http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx -namespace DateTime { - -const char DATETIME_CLASS_NAME[] = "DateTime"; -const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1; -const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P"; -const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT ); -const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u"; -const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT ); -const char DATE_FORMAT[] = "Y-m-d"; -const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT ); - -} - -// *** internal functions *** -// Only declarations are put here. Functions contain the documentation they need at their definition sites. -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ); -size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ); -bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC ); -bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z ); -void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC); -// returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); -void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); -// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) -void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); -void field_cache_dtor( zval* data_z ); -void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ); -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ); -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ); -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); -// assure there is enough space for the output parameter string -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, - SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, - SQLLEN& buffer_len TSRMLS_DC ); -void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ); -// send all the stream data -void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ); -// called when a bound output string parameter is to be destroyed -void sqlsrv_output_param_dtor( zval* data ); -// called when a bound stream parameter is to be destroyed. -void sqlsrv_stream_dtor( zval* data ); -bool is_streamable_type( SQLINTEGER sql_type ); - -} - -// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier. -sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : - sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ), - conn( c ), - executed( false ), - past_fetch_end( false ), - current_results( NULL ), - cursor_type( SQL_CURSOR_FORWARD_ONLY ), - has_rows( false ), - fetch_called( false ), - last_field_index( -1 ), - past_next_result_end( false ), - param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte - send_streams_at_exec( true ), - current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), - current_stream_read( 0 ), - query_timeout( QUERY_TIMEOUT_INVALID ), - buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) -{ - ZVAL_UNDEF( &active_stream ); - // initialize the input string parameters array (which holds zvals) - core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); - - // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) - ZVAL_NEW_ARR( ¶m_streams ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC); - - // initialize the (input only) datetime parameters of converted date time objects to strings - array_init( ¶m_datetime_buffers ); - - // initialize the output string parameters (which holds sqlsrv_output_param structures) - ZVAL_NEW_ARR( &output_params ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC); - - // initialize the field cache - ZVAL_NEW_ARR( &field_cache ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC); -} - -// desctructor for sqlsrv statement. -sqlsrv_stmt::~sqlsrv_stmt( void ) -{ - if( Z_TYPE( active_stream ) != IS_UNDEF ) { - TSRMLS_FETCH(); - close_active_stream( this TSRMLS_CC ); - } - - // delete any current results - if( current_results ) { - current_results->~sqlsrv_result_set(); - efree( current_results ); - current_results = NULL; - } - - invalidate(); - zval_ptr_dtor( ¶m_input_strings ); - zval_ptr_dtor( &output_params ); - zval_ptr_dtor( ¶m_streams ); - zval_ptr_dtor( ¶m_datetime_buffers ); - zval_ptr_dtor( &field_cache ); -} - - -// centralized place to release (without destroying the hash tables -// themselves) all the parameter data that accrues during the -// execution phase. -void sqlsrv_stmt::free_param_data( TSRMLS_D ) -{ - SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY, - "sqlsrv_stmt::free_param_data: Param zvals aren't arrays." ); - zend_hash_clean( Z_ARRVAL( param_input_strings )); - zend_hash_clean( Z_ARRVAL( output_params )); - zend_hash_clean( Z_ARRVAL( param_streams )); - zend_hash_clean( Z_ARRVAL( param_datetime_buffers )); - zend_hash_clean( Z_ARRVAL( field_cache )); -} - - -// to be called whenever a new result set is created, such as after an -// execute or next_result. Resets the state variables. - -void sqlsrv_stmt::new_result_set( TSRMLS_D ) -{ - this->fetch_called = false; - this->has_rows = false; - this->past_next_result_end = false; - this->past_fetch_end = false; - this->last_field_index = -1; - - // delete any current results - if( current_results ) { - current_results->~sqlsrv_result_set(); - efree( current_results ); - current_results = NULL; - } - - // create a new result set - if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { - current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC ); - } - else { - current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this ); - } -} - -// core_sqlsrv_create_stmt -// Common code to allocate a statement from either driver. Returns a valid driver statement object or -// throws an exception if an error occurs. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// stmt_factory - factory method to create a statement. -// options_ht - A HashTable of user provided options to be set on the statement. -// valid_stmt_opts - An array of valid driver supported statement options. -// err - callback for error handling -// driver - reference to caller -// Return -// Returns the created statement - -sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, - const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ) -{ - sqlsrv_malloc_auto_ptr stmt; - SQLHANDLE stmt_h = SQL_NULL_HANDLE; - - try { - - core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC ); - - stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC ); - - stmt->conn = conn; - - // handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free - // in the catch block below. - stmt_h = SQL_NULL_HANDLE; - - // process the options array given to core_sqlsrv_prepare. - if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - zend_ulong index = -1; - zend_string *key = NULL; - zval* value_z = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) { - - int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); - - const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); - - // if the key didn't match, then return the error to the script. - // The driver layer should ensure that the key is valid. - DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); - - // perform the actions the statement option needs done. - (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - } - - sqlsrv_stmt* return_stmt = stmt; - stmt.transferred(); - - return return_stmt; - } - catch( core::CoreException& ) - { - if( stmt ) { - - conn->set_last_error( stmt->last_error() ); - stmt->~sqlsrv_stmt(); - } - - // if allocating the handle failed before the statement was allocated, free the handle - if( stmt_h != SQL_NULL_HANDLE) { - ::SQLFreeHandle( SQL_HANDLE_STMT, stmt_h ); - } - - throw; - } - catch( ... ) { - - DIE( "core_sqlsrv_allocate_stmt: Unknown exception caught." ); - } -} - - -// core_sqlsrv_bind_param -// Binds a parameter using SQLBindParameter. It allocates memory and handles other details -// in translating between the driver and ODBC. -// Parameters: -// param_num - number of the parameter, 0 based -// param_z - zval of the parameter -// php_out_type - type to return for output parameter -// sql_type - ODBC constant for the SQL Server type (SQL_UNKNOWN_TYPE = 0 means not known, so infer defaults) -// column_size - length of the field on the server (SQLSRV_UKNOWN_SIZE means not known, so infer defaults) -// decimal_digits - if column_size is valid and the type contains a scale, this contains the scale -// Return: -// Nothing, though an exception is thrown if an error occurs -// The php type of the parameter is taken from the zval. -// The sql type is given as a hint if the driver provides it. - -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, - SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, - SQLSMALLINT decimal_digits TSRMLS_DC ) -{ - SQLSMALLINT c_type; - SQLPOINTER buffer = NULL; - SQLLEN buffer_len = 0; - - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT, - "core_sqlsrv_bind_param: Invalid parameter direction." ); - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || php_out_type != SQLSRV_PHPTYPE_INVALID, - "core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param." ); - - try { - - // check is only < because params are 0 based - CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) { - throw core::CoreException(); - } - - // resize the statements array of int_ptrs if the parameter isn't already set. - 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 ]; - - zval* param_ref = param_z; - if ( Z_ISREF_P( param_z ) ) { - ZVAL_DEREF( param_z ); - } - bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL ); - bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE ); - // if the user asks for for a specific type for input and output, make sure the data type we send matches the data we - // type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so - // we always let that match if they want a string back. - if( direction == SQL_PARAM_INPUT_OUTPUT ) { - bool match = false; - switch( php_out_type ) { - case SQLSRV_PHPTYPE_INT: - if( zval_was_null || zval_was_bool ) { - convert_to_long( param_z ); - } - match = Z_TYPE_P( param_z ) == IS_LONG; - break; - case SQLSRV_PHPTYPE_FLOAT: - if( zval_was_null ) { - convert_to_double( param_z ); - } - match = Z_TYPE_P( param_z ) == IS_DOUBLE; - break; - case SQLSRV_PHPTYPE_STRING: - // anything can be converted to a string - convert_to_string( param_z ); - match = true; - break; - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_DATETIME: - case SQLSRV_PHPTYPE_STREAM: - SQLSRV_ASSERT( false, "Invalid type for an output parameter." ); - break; - default: - SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." ); - break; - } - CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) { - throw core::CoreException(); - } - } - - // if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so - // when the buffer is filled, the type is correct - if( direction == SQL_PARAM_OUTPUT ) { - switch( php_out_type ) { - case SQLSRV_PHPTYPE_INT: - convert_to_long( param_z ); - break; - case SQLSRV_PHPTYPE_FLOAT: - convert_to_double( param_z ); - break; - case SQLSRV_PHPTYPE_STRING: - convert_to_string( param_z ); - break; - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_DATETIME: - case SQLSRV_PHPTYPE_STREAM: - SQLSRV_ASSERT( false, "Invalid type for an output parameter" ); - break; - default: - SQLSRV_ASSERT( false, "Uknown SQLSRV_PHPTYPE_* constant given" ); - break; - } - } - - SQLSRV_ASSERT(( Z_TYPE_P( param_z ) != IS_STRING && Z_TYPE_P( param_z ) != IS_RESOURCE ) || - ( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 || - encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" ); - - // if the sql type is unknown, then set the default based on the PHP type passed in - if( sql_type == SQL_UNKNOWN_TYPE ) { - default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); - } - - // if the size is unknown, then set the default based on the PHP type passed in - if( column_size == SQLSRV_UNKNOWN_SIZE ) { - default_sql_size_and_scale( stmt, static_cast( param_num ), param_z, encoding, column_size, decimal_digits TSRMLS_CC ); - } - - // determine the ODBC C type - c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC ); - - // set the buffer based on the PHP parameter type - switch( Z_TYPE_P( param_z )) { - - case IS_NULL: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - ind_ptr = SQL_NULL_DATA; - buffer = NULL; - buffer_len = 0; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - { - // if it is boolean, set the lval to 0 or 1 - convert_to_long( param_z ); - buffer = ¶m_z->value; - buffer_len = sizeof( Z_LVAL_P( param_z )); - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool ); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - } - } - break; - case IS_DOUBLE: - { - buffer = ¶m_z->value; - buffer_len = sizeof( Z_DVAL_P( param_z )); - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_ref, static_cast( param_num ), false ); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - } - } - break; - case IS_STRING: - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) - if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) { - - zval wbuffer_z; - ZVAL_NULL( &wbuffer_z ); - - bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - param_num + 1, get_last_error_message() ) { - throw core::CoreException(); - } - buffer = Z_STRVAL_P( &wbuffer_z ); - buffer_len = Z_STRLEN_P( &wbuffer_z ); - core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC); - } - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, - // we reallocate the string if it's interned - if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) { - core::sqlsrv_zval_stringl( param_z, static_cast(buffer), buffer_len ); - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - } - - // if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR) - // or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type, - // convert it to wchar first - if( direction == SQL_PARAM_INPUT_OUTPUT && - ( c_type == SQL_C_WCHAR || - ( c_type == SQL_C_BINARY && - ( sql_type == SQL_WCHAR || - sql_type == SQL_WVARCHAR || - sql_type == SQL_WLONGVARCHAR )))) { - - bool converted = convert_input_param_to_utf16( param_z, param_z ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - param_num + 1, get_last_error_message() ) { - throw core::CoreException(); - } - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - ind_ptr = buffer_len; - } - - // since this is an output string, assure there is enough space to hold the requested size and - // set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr) - resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, - buffer, buffer_len TSRMLS_CC ); - - // save the parameter to be adjusted and/or converted after the results are processed - sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len )); - - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - - // For output parameters, if we set the column_size to be same as the buffer_len, - // then if there is a truncation due to the data coming from the server being - // greater than the column_size, we don't get any truncation error. In order to - // avoid this silent truncation, we set the column_size to be "MAX" size for - // string types. This will guarantee that there is no silent truncation for - // output parameters. - if( direction == SQL_PARAM_OUTPUT ) { - - switch( sql_type ) { - - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_WVARCHAR: - column_size = SQL_SS_LENGTH_UNLIMITED; - break; - - default: - break; - } - } - } - break; - case IS_RESOURCE: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - sqlsrv_stream stream_encoding( param_z, encoding ); - HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC ); - buffer = reinterpret_cast( param_num ); - Z_TRY_ADDREF_P( param_z ); // so that it doesn't go away while we're using it - buffer_len = 0; - ind_ptr = SQL_DATA_AT_EXEC; - } - break; - case IS_OBJECT: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - zval function_z; - zval buffer_z; - zval format_z; - zval params[1]; - ZVAL_UNDEF( &function_z ); - ZVAL_UNDEF( &buffer_z ); - ZVAL_UNDEF( &format_z ); - ZVAL_UNDEF( params ); - - bool valid_class_name_found = false; - - zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC ); - - while( class_entry != NULL ) { - - if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && - stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) { - valid_class_name_found = true; - break; - } - - else { - - // Check the parent - class_entry = class_entry->parent; - } - } - - CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - - // if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error' - // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' - // sql type, it lacks the timezone. - if( sql_type == SQL_SS_TIMESTAMPOFFSET ) { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), - DateTime::DATETIMEOFFSET_FORMAT_LEN ); - } - else if( sql_type == SQL_TYPE_DATE ) { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); - } - else { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); - } - // call the DateTime::format member function to convert the object to a string that SQL Server understands - core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); - params[0] = format_z; - // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the - // DateTime object and $format_z is the format string. - int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); - zend_string_release( Z_STR( format_z )); - zend_string_release( Z_STR( function_z )); - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - buffer = Z_STRVAL( buffer_z ); - zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z ); - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - buffer_len = Z_STRLEN( buffer_z ) - 1; - ind_ptr = buffer_len; - break; - } - case IS_ARRAY: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ); - break; - default: - DIE( "core_sqlsrv_bind_param: Unsupported PHP type. Only string, float, int, and streams (resource) are supported. " - "It is the responsibilty of the driver layer to convert a parameter to one of these types." ); - break; - } - - if( zval_was_null ) { - ind_ptr = SQL_NULL_DATA; - } - - core::SQLBindParameter( stmt, param_num + 1, direction, - c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); - } - catch( core::CoreException& e ) { - stmt->free_param_data( TSRMLS_C ); - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - throw e; - } -} - - -// core_sqlsrv_execute -// Executes the statement previously prepared -// Parameters: -// stmt - the core sqlsrv_stmt structure that contains the ODBC handle -// Return: -// true if there is data, false if there is not - -void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len ) -{ - SQLRETURN r; - - try { - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - if( sql ) { - - sqlsrv_malloc_auto_ptr wsql_string; - unsigned int wsql_len = 0; - if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { - wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t ))); - wsql_string[0] = L'\0'; - wsql_len = 0; - } - else { - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : - stmt->encoding() ); - wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - sql_len, &wsql_len ); - CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - get_last_error_message() ) { - throw core::CoreException(); - } - } - r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC ); - } - else { - r = core::SQLExecute( stmt TSRMLS_CC ); - } - - // if data is needed (streams were bound) and they should be sent at execute time, then do so now - if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) { - - send_param_streams( stmt TSRMLS_CC ); - } - - stmt->new_result_set( TSRMLS_C ); - stmt->executed = true; - - // if all the data has been sent and no data was returned then finalize the output parameters - if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { - - finalize_output_parameters( stmt TSRMLS_CC ); - } - // stream parameters are sent, clean the Hashtable - if ( stmt->send_streams_at_exec ) { - zend_hash_clean( Z_ARRVAL( stmt->param_streams )); - } - } - catch( core::CoreException& e ) { - - // if the statement executed but failed in a subsequent operation before returning, - // we need to cancel the statement and deref the output and stream parameters - if ( stmt->send_streams_at_exec ) { - zend_hash_clean( Z_ARRVAL( stmt->output_params )); - zend_hash_clean( Z_ARRVAL( stmt->param_streams )); - } - if( stmt->executed ) { - SQLCancel( stmt->handle() ); - // stmt->executed = false; should this be reset if something fails? - } - - throw e; - } -} - - -// core_sqlsrv_fetch -// Moves the cursor according to the parameters (by default, moves to the next row) -// Parameters: -// stmt - the sqlsrv_stmt of the cursor -// fetch_orientation - method to move the cursor -// fetch_offset - if the method has a parameter (such as number of rows to move or literal row number) -// Returns: -// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the -// user scrolls past a non-scrollable result set - -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ) -{ - // pre-condition check - SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE, - "core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." ); - - try { - - // clear the field cache of the previous fetch - zend_hash_clean( Z_ARRVAL( stmt->field_cache )); - - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) { - throw core::CoreException(); - } - - SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); - - CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { - throw core::CoreException(); - } - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - // if the statement has rows and is not scrollable but doesn't yet have - // fetch_called, this must be the first time we've called sqlsrv_fetch. - if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->has_rows && !stmt->fetch_called ) { - stmt->fetch_called = true; - return true; - } - - // move to the record requested. For absolute records, we use a 0 based offset, so +1 since - // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. - SQLRETURN r = stmt->current_results->fetch( fetch_orientation, - ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 - TSRMLS_CC ); - if( r == SQL_NO_DATA ) { - // if this is a forward only cursor, mark that we've passed the end so future calls result in an error - if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { - stmt->past_fetch_end = true; - } - return false; - } - - // mark that we called fetch (which get_field, et. al. uses) and reset our last field retrieved - stmt->fetch_called = true; - stmt->last_field_index = -1; - stmt->has_rows = true; // since we made it this far, we must have at least one row - } - catch (core::CoreException& e) { - throw e; - } - catch ( ... ) { - DIE( "core_sqlsrv_fetch: Unexpected exception occurred." ); - } - - return true; -} - - -// Retrieves metadata for a field of a prepared statement. -// Parameters: -// colno - the index of the field for which to return the metadata. columns are 0 based in PDO -// Return: -// A field_meta_data* consisting of the field metadata. - -field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ) -{ - // pre-condition check - SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." ); - - sqlsrv_malloc_auto_ptr meta_data; - SQLSMALLINT field_name_len = 0; - - meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data(); - meta_data->field_name = static_cast( sqlsrv_malloc( SS_MAXCOLNAMELEN + 1 )); - - try { - core::SQLDescribeCol( stmt, colno + 1, meta_data->field_name.get(), SS_MAXCOLNAMELEN, &field_name_len, - &(meta_data->field_type), &(meta_data->field_size), &(meta_data->field_scale), - &(meta_data->field_is_nullable) TSRMLS_CC ); - } - catch( core::CoreException& e ) { - throw e; - } - - // depending on field type, we add the values into size or precision/scale. - switch( meta_data->field_type ) { - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_BIT: - case SQL_TINYINT: - case SQL_SMALLINT: - case SQL_INTEGER: - case SQL_BIGINT: - case SQL_REAL: - case SQL_FLOAT: - case SQL_DOUBLE: - { - meta_data->field_precision = meta_data->field_size; - meta_data->field_size = 0; - break; - } - default: { - break; - } - } - - // Set the field name lenth - meta_data->field_name_len = field_name_len; - - field_meta_data* result_field_meta_data = meta_data; - meta_data.transferred(); - return result_field_meta_data; -} - - -// core_sqlsrv_get_field -// Return the value of a column from ODBC -// Parameters: -// stmt - the sqlsrv_stmt from which to retrieve the column -// field_index - 0 based index for the column to retrieve -// sqlsrv_php_type_in - sqlsrv_php_type structure that tells what format to return the data in -// field_value - pointer to the data retrieved -// field_len - length of the data in the field_value buffer -// Returns: -// Nothing, excpetion thrown if an error occurs - -void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string, - _Out_ void*& field_value, _Out_ SQLLEN* field_len, bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) -{ - try { - - // close the stream to release the resource - close_active_stream(stmt TSRMLS_CC); - - // if the field has been retrieved before, return the previous result - field_cache* cached = NULL; - if (NULL != ( cached = static_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast( field_index ))))) { - // the field value is NULL - if( cached->value == NULL ) { - field_value = NULL; - *field_len = 0; - if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } - } - else { - - field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); - 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'; - } - *field_len = cached->len; - if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } - } - return; - } - - sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; - - SQLLEN sql_field_type = 0; - SQLLEN sql_field_len = 0; - - // Make sure that the statement was executed and not just prepared. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they - // may also be retrieved. - if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { - sqlsrv_phptype invalid; - invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { - SQLSRV_ASSERT((cached = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->field_cache), i))) == NULL, - "Field already cached." ); - core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, - sqlsrv_php_type_out TSRMLS_CC ); - // delete the value returned since we only want it cached, not the actual value - if( field_value ) { - efree( field_value ); - field_value = NULL; - *field_len = 0; - } - } - } - - // If the php type was not specified set the php type to be the default type. - if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { - - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Get the length of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); - - // Get the corresponding php type from the sql type. - sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast( sql_field_type ), static_cast( sql_field_len ), prefer_string ); - } - - // Verify that we have an acceptable type to convert. - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { - throw core::CoreException(); - } - - if( sqlsrv_php_type_out != NULL ) - *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); - - // Retrieve the data - core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - - // if the user wants us to cache the field, we'll do it - if( cache_field ) { - field_cache cache( field_value, *field_len, sqlsrv_php_type ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); - } - } - - catch( core::CoreException& e ) { - throw e; - } -} - -// core_sqlsrv_has_any_result -// return if any result set or rows affected message is waiting -// to be consumed and moved over by sqlsrv_next_result. -// Parameters: -// stmt - The statement object on which to check for results. -// Return: -// true if any results are present, false otherwise. - -bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - // Use SQLNumResultCols to determine if we have rows or not. - SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - // use SQLRowCount to determine if there is a rows status waiting - SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); - return (num_cols != 0) || (rows_affected > 0); -} - -// core_sqlsrv_next_result -// Advances to the next result set from the last executed query -// Parameters -// stmt - the sqlsrv_stmt structure -// Returns -// Nothing, exception thrown if problem occurs - -void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params, bool throw_on_errors ) -{ - try { - - // make sure that the statement has been executed. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) { - throw core::CoreException(); - } - - close_active_stream( stmt TSRMLS_CC ); - - SQLRETURN r; - if( throw_on_errors ) { - r = core::SQLMoreResults( stmt TSRMLS_CC ); - } - else { - r = SQLMoreResults( stmt->handle() ); - } - - if( r == SQL_NO_DATA ) { - - if( &(stmt->output_params) && finalize_output_params ) { - // if we're finished processing result sets, handle the output parameters - finalize_output_parameters( stmt TSRMLS_CC ); - } - - // mark we are past the end of all results - stmt->past_next_result_end = true; - return; - } - - stmt->new_result_set( TSRMLS_C ); - } - catch( core::CoreException& e ) { - - SQLCancel( stmt->handle() ); - throw e; - } -} - - -// core_sqlsrv_post_param -// Performs any actions post execution for each parameter. For now it cleans up input parameters memory from the statement -// Parameters: -// stmt - the sqlsrv_stmt structure -// param_num - 0 based index of the parameter -// param_z - parameter value itself. -// Returns: -// Nothing, exception thrown if problem occurs - -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* param_z TSRMLS_DC ) -{ - SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); - SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); - - // if the parameter was an input string, delete it from the array holding input parameter strings - if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) { - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC ); - } - - // if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams - // PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it - // with sqlsrv_send_stream_data. - if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) { - sqlsrv_stream* stream_encoding = NULL; - stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->param_streams), param_num)); - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC ); - } -} - -//Calls SQLSetStmtAttr to set a cursor. -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ) -{ - try { - - switch( cursor_type ) { - - case SQL_CURSOR_STATIC: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_DYNAMIC: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_KEYSET_DRIVEN: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_FORWARD_ONLY: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQLSRV_CURSOR_BUFFERED: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); - break; - } - - stmt->cursor_type = cursor_type; - - } - catch( core::CoreException& ) { - throw; - } -} - -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - if( Z_TYPE_P( value_z ) != IS_LONG ) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); - } - - core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); -} - -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ) -{ - if( limit <= 0 ) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); - } - - stmt->buffered_query_limit = limit; -} - - -// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout -// which accepts timeout parameter as a long. If the zval is not of type long -// than throws error. -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - try { - - // validate the value - if( Z_TYPE_P( value_z ) != IS_LONG || Z_LVAL_P( value_z ) < 0 ) { - - convert_to_string( value_z ); - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) ); - } - - core_sqlsrv_set_query_timeout( stmt, static_cast( Z_LVAL_P( value_z )) TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } -} - -// Overloaded. Accepts the timeout as a long. -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." ); - - // set the statement attribute - core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC ); - - // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which - // is represented by -1. - long lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); - - // set the LOCK_TIMEOUT on the server. - char lock_timeout_sql[ 32 ]; - int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", - lock_timeout ); - - SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), - "stmt_option_query_timeout: sprintf_s failed. Shouldn't ever fail." ); - - core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC ); - - stmt->query_timeout = timeout; - } - catch( core::CoreException& ) { - throw; - } -} - -void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - TSRMLS_C; - - // zend_is_true does not fail. It either returns true or false. - stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false; -} - - -// core_sqlsrv_send_stream_packet -// send a single packet from a stream parameter to the database using -// ODBC. This will also handle the transition between parameters. It -// returns true if it is not done sending, false if it is finished. -// return_value is what should be returned to the script if it is -// given. Any errors that occur are posted here. -// Parameters: -// stmt - query to send the next packet for -// Returns: -// true if more data remains to be sent, false if all data processed - -bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - SQLRETURN r = SQL_SUCCESS; - - // if there no current parameter to process, get the next one - // (probably because this is the first call to sqlsrv_send_stream_data) - if( stmt->current_stream.stream_z == NULL ) { - - if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) { - - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); - stmt->current_stream_read = 0; - return false; - } - } - - try { - - // get the stream from the zval we bound - php_stream* param_stream = NULL; - core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); - - // if we're at the end, then release our current parameter - if( php_stream_eof( param_stream )) { - // if no data was actually sent prior, then send a NULL - if( stmt->current_stream_read == 0 ) { - // send an empty string, which is what a 0 length does. - char buff[1]; // temp storage to hand to SQLPutData - core::SQLPutData( stmt, buff, 0 TSRMLS_CC ); - } - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); - stmt->current_stream_read = 0; - } - // 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 ]; - 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 ); - - if (read > UINT_MAX) - { - LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); - throw core::CoreException(); - } - - stmt->current_stream_read += static_cast( read ); - if( read > 0 ) { - // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character - // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it - // twice. - // If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion - // since all other MBCS supported by SQL Server are 2 byte maximum size. - if( stmt->current_stream.encoding == CP_UTF8 ) { - - // 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. - wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; - // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate - int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, static_cast( read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); - if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) { - - // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more - // in, then reattempt the conversion. If it fails the second time, then an error is returned. - size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC ); - // read the missing bytes - size_t new_read = php_stream_read( param_stream, static_cast( buffer ) + read, - need_to_read ); - // if the bytes couldn't be read, then we return an error - CHECK_CUSTOM_ERROR( new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { - throw core::CoreException(); - } - // try the conversion again with the complete character - wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, static_cast( read + new_read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); - // something else must be wrong if it failed - CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { - throw core::CoreException(); - } - } - core::SQLPutData( stmt, wbuffer, wsize * sizeof( wchar_t ) TSRMLS_CC ); - } - else { - core::SQLPutData( stmt, buffer, read TSRMLS_CC ); - } - } - } - - } - catch( core::CoreException& e ) { - stmt->free_param_data( TSRMLS_C ); - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - SQLCancel( stmt->handle() ); - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT ); - stmt->current_stream_read = 0; - throw e; - } - - return true; -} - -void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) -{ - TSRMLS_C; - - // This implementation should never get called. - DIE( "Not implemented." ); -} - -void stmt_option_query_timeout:: operator()( sqlsrv_stmt* stmt, stmt_option const* /**/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_send_at_exec:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC ); -} - - -// internal function to release the active stream. Called by each main API function -// that will alter the statement and cancel any retrieval of data from a stream. -void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) -{ - // if there is no active stream, return - if( Z_TYPE( stmt->active_stream ) == IS_UNDEF ) { - return; - } - - php_stream* stream = NULL; - - // we use no verify since verify would return immediately and we want to assert, not return. - php_stream_from_zval_no_verify( stream, &( stmt->active_stream )); - - SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." ); - - php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here. - - SQLSRV_ASSERT( Z_TYPE( stmt->active_stream ) == IS_UNDEF, "close_active_stream: Active stream not closed." ); - -} - -// local routines not shared by other files (arranged alphabetically) - -namespace { - -bool is_streamable_type( SQLLEN sql_type ) -{ - switch( sql_type ) { - case SQL_CHAR: - case SQL_WCHAR: - case SQL_BINARY: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_SS_XML: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - return true; - } - - return false; -} - -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ) -{ - try { - - switch( sql_type ) { - // for types that are fixed in size or for which the size is unknown, return the display size. - case SQL_BIGINT: - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - case SQL_GUID: - case SQL_FLOAT: - case SQL_DOUBLE: - case SQL_REAL: - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_BINARY: - case SQL_CHAR: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_SS_XML: - case SQL_SS_UDT: - case SQL_WLONGVARCHAR: - case SQL_DATETIME: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - { - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC ); - break; - } - - // for wide char types for which the size is known, return the octet length instead, since it will include the - // the number of bytes necessary for the string, not just the characters - case SQL_WCHAR: - case SQL_WVARCHAR: - { - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC ); - break; - } - - default: - DIE ( "Unexpected SQL type encountered in calc_string_size." ); - } - } - catch( core::CoreException& e ) { - throw e; - } -} - - -// calculates how many characters were cut off from the end of a buffer when reading -// in UTF-8 encoded text - -size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ) -{ - const char* last_char = buffer + buffer_end - 1; - size_t need_to_read = 0; - - // rewind until we are at the byte that starts the cut off character - while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) { - --last_char; - ++need_to_read; - } - - // determine how many bytes we need to read in based on the number of bytes in the character - // (# of high bits set) versus the number of bytes we've already read. - switch( *last_char & UTF8_NBYTESEQ_MASK ) { - case UTF8_2BYTESEQ_TAG1: - case UTF8_2BYTESEQ_TAG2: - need_to_read = 1 - need_to_read; - break; - case UTF8_3BYTESEQ_TAG: - need_to_read = 2 - need_to_read; - break; - case UTF8_4BYTESEQ_TAG: - need_to_read = 3 - need_to_read; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )); - break; - } - - return need_to_read; -} - - -// Caller is responsible for freeing the memory allocated for the field_value. -// The memory allocation has to happen in the core layer because otherwise -// the driver layer would have to calculate size of the field_value -// to decide the amount of memory allocation. -void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) -{ - try { - - close_active_stream( stmt TSRMLS_CC ); - - // make sure that fetch is called before trying to retrieve. - CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { - throw core::CoreException(); - } - - // make sure that fields are not retrieved incorrectly. - CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, - stmt->last_field_index ) { - throw core::CoreException(); - } - - switch( sqlsrv_php_type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_INT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( *field_len == SQL_NULL_DATA ) { - field_value = NULL; - break; - } - - field_value = field_value_temp; - field_value_temp.transferred(); - break; - } - - case SQLSRV_PHPTYPE_FLOAT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( *field_len == SQL_NULL_DATA ) { - field_value = NULL; - break; - } - - field_value = field_value_temp; - field_value_temp.transferred(); - break; - } - - case SQLSRV_PHPTYPE_STRING: - { - get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - break; - } - - // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and - // convert it to a DateTime object and return the created object - case SQLSRV_PHPTYPE_DATETIME: - { - char field_value_temp[ MAX_DATETIME_STRING_LEN ]; - zval params[1]; - zval field_value_temp_z; - zval function_z; - - ZVAL_UNDEF( &field_value_temp_z ); - ZVAL_UNDEF( &function_z ); - ZVAL_UNDEF( params ); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, - MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - zval_auto_ptr return_value_z; - return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); - ZVAL_UNDEF( return_value_z ); - - if( *field_len == SQL_NULL_DATA ) { - ZVAL_NULL( return_value_z ); - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - break; - } - - // Convert the string date to a DateTime object - core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); - core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 ); - params[0] = field_value_temp_z; - - if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, - params TSRMLS_CC ) == FAILURE) { - THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); - } - - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - zend_string_free( Z_STR( field_value_temp_z )); - zend_string_free( Z_STR( function_z )); - break; - } - - // create a stream wrapper around the field and return that object to the PHP script. calls to fread - // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file - // for how these fields are used. - case SQLSRV_PHPTYPE_STREAM: - { - - php_stream* stream = NULL; - sqlsrv_stream* ss = NULL; - SQLLEN sql_type; - - SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { - throw core::CoreException(); - } - - stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); - - CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { - throw core::CoreException(); - } - - ss = static_cast( stream->abstract ); - ss->stmt = stmt; - ss->field_index = field_index; - ss->sql_type = static_cast( sql_type ); - ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); - - zval_auto_ptr return_value_z; - return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); - ZVAL_UNDEF( return_value_z ); - - // turn our stream into a zval to be returned - php_stream_to_zval( stream, return_value_z ); - - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - break; - } - - case SQLSRV_PHPTYPE_NULL: - field_value = NULL; - *field_len = 0; - break; - - default: - DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); - break; - } - - // sucessfully retrieved the field, so update our last retrieved field - if( stmt->last_field_index < field_index ) { - stmt->last_field_index = field_index; - } - } - catch( core::CoreException& e ) { - throw e; - } -} - - -// check_for_next_stream_parameter -// see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise -// returns false -bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) -{ - int stream_index = 0; - SQLRETURN r = SQL_SUCCESS; - sqlsrv_stream* stream_encoding = NULL; - zval* param_z = NULL; - - // get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param - r = core::SQLParamData( stmt, reinterpret_cast( &stream_index ) TSRMLS_CC ); - // if no more data, we've exhausted the bound parameters, so return that we're done - if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - - // we're all done, so return false - return false; - } - - HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); - - // pull out the sqlsrv_encoding struct - stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(streams_ht, stream_index)); - SQLSRV_ASSERT(stream_encoding != NULL, "Stream parameter does not exist"); // if the index isn't in the hash, that's a serious error - - param_z = stream_encoding->stream_z; - - // make the next stream current - stmt->current_stream = sqlsrv_stream( param_z, stream_encoding->encoding ); - stmt->current_stream_read = 0; - - // there are more parameters - return true; -} - - -// utility routine to convert an input parameter from UTF-8 to UTF-16 - -bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z ) -{ - SQLSRV_ASSERT( input_param_z == converted_param_z || Z_TYPE_P( converted_param_z ) == IS_NULL, - "convert_input_param_z called with invalid parameter states" ); - - const char* buffer = Z_STRVAL_P( input_param_z ); - std::size_t buffer_len = Z_STRLEN_P( input_param_z ); - int wchar_size; - - if (buffer_len > INT_MAX) - { - LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); - throw core::CoreException(); - } - - // if the string is empty, then just return that the conversion succeeded as - // MultiByteToWideChar will "fail" on an empty string. - if( buffer_len == 0 ) { - core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); - return true; - } - - // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string - wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, - reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); - - // if there was a problem determining the size of the string, return false - if( wchar_size == 0 ) { - return false; - } - sqlsrv_malloc_auto_ptr wbuffer; - wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) )); - // convert the utf-8 string to a wchar string in the new buffer - int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), - static_cast( buffer_len ), wbuffer, wchar_size ); - // if there was a problem converting the string, then free the memory and return false - if( r == 0 ) { - return false; - } - - // null terminate the string, set the size within the zval, and return success - wbuffer[ wchar_size ] = L'\0'; - core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), - wchar_size * sizeof( wchar_t ) ); - sqlsrv_free(wbuffer); - wbuffer.transferred(); - - return true; -} - -// returns the ODBC C type constant that matches the PHP type and encoding given - -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) -{ - SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P( param_z ); - - switch( php_type ) { - - case IS_NULL: - switch( encoding ) { - // The c type is set to match to the corresponding sql_type. For NULL cases, if the server type - // is a binary type, than the server expects the sql_type to be binary type as well, otherwise - // an error stating "Implicit conversion not allowed.." is thrown by the server. - // For all other server types, setting the sql_type to sql_char works fine. - case SQLSRV_ENCODING_BINARY: - sql_c_type = SQL_C_BINARY; - break; - default: - sql_c_type = SQL_C_CHAR; - break; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - //ODBC 64-bit long and integer type are 4 byte values. - if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) - { - sql_c_type = SQL_C_SBIGINT; - } - else - { - sql_c_type = SQL_C_SLONG; - } - break; - case IS_DOUBLE: - sql_c_type = SQL_C_DOUBLE; - break; - case IS_STRING: - case IS_RESOURCE: - switch( encoding ) { - case SQLSRV_ENCODING_CHAR: - sql_c_type = SQL_C_CHAR; - break; - case SQLSRV_ENCODING_BINARY: - sql_c_type = SQL_C_BINARY; - break; - case CP_UTF8: - sql_c_type = SQL_C_WCHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); - break; - } - break; - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in core_sqlsrv_bind_param. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - sql_c_type = SQL_C_CHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } - - return sql_c_type; -} - - -// given a zval and encoding, determine the appropriate sql type - -void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLSMALLINT& sql_type TSRMLS_DC ) -{ - sql_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P(param_z); - switch( php_type ) { - - case IS_NULL: - switch( encoding ) { - // Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases, - // if the server type is a binary type, than the server expects the sql_type to be binary type - // as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the - // server. For all other server types, setting the sql_type to sql_char works fine. - case SQLSRV_ENCODING_BINARY: - sql_type = SQL_BINARY; - break; - default: - sql_type = SQL_CHAR; - break; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - //ODBC 64-bit long and integer type are 4 byte values. - if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) - { - sql_type = SQL_BIGINT; - } - else - { - sql_type = SQL_INTEGER; - } - - break; - case IS_DOUBLE: - sql_type = SQL_FLOAT; - break; - case IS_RESOURCE: - case IS_STRING: - switch( encoding ) { - case SQLSRV_ENCODING_CHAR: - sql_type = SQL_VARCHAR; - break; - case SQLSRV_ENCODING_BINARY: - sql_type = SQL_VARBINARY; - break; - case CP_UTF8: - sql_type = SQL_WVARCHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); - break; - } - break; - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in the calling function. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - // if the user is sending this type to SQL Server 2005 or earlier, make it appear - // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET - // since these are the date types of the highest precision for their respective server versions - if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { - sql_type = SQL_TYPE_TIMESTAMP; - } - else { - sql_type = SQL_SS_TIMESTAMPOFFSET; - } - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } - -} - - -// given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate) - -void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) -{ - int php_type = Z_TYPE_P( param_z ); - column_size = 0; - decimal_digits = 0; - - switch( php_type ) { - - case IS_NULL: - column_size = 1; - break; - // size is not necessary for these types, they are inferred by ODBC - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - case IS_DOUBLE: - case IS_RESOURCE: - break; - case IS_STRING: - { - size_t char_size = ( encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( wchar_t ) : sizeof( char ); - SQLULEN byte_len = Z_STRLEN_P( param_z ) * char_size; - if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { - column_size = SQL_SERVER_MAX_TYPE_SIZE; - } - else { - column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size; - } - break; - } - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in the calling function. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - // if the user is sending this type to SQL Server 2005 or earlier, make it appear - // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET - // since these are the date types of the highest precision for their respective server versions - if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { - column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION; - decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE; - } - else { - column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION; - decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE; - } - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } -} - -void field_cache_dtor( zval* data_z ) -{ - field_cache* cache = static_cast( Z_PTR_P( data_z )); - if( cache->value ) - { - sqlsrv_free( cache->value ); - } - sqlsrv_free( cache ); -} - - -// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output -// parameters will be present until all results are processed (since output parameters can depend on results -// while being processed). This function updates the lengths of output parameter strings from the ind_ptr -// parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary. -// For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server - -void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - if( Z_ISUNDEF(stmt->output_params) ) - return; - - bool converted = true; - HashTable* params_ht = Z_ARRVAL( stmt->output_params ); - zend_ulong index = -1; - zend_string* key = NULL; - void* output_param_temp = NULL; - - ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { - sqlsrv_output_param* output_param = static_cast( output_param_temp ); - zval* value_z = Z_REFVAL_P( output_param->param_z ); - switch( Z_TYPE_P( value_z )) { - case IS_STRING: - { - // 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 ]; - if( str_len == SQL_NULL_DATA ) { - zend_string_release( Z_STR_P( value_z )); - ZVAL_NULL( value_z ); - continue; - } - - // if there was more to output than buffer size to hold it, then throw a truncation error - int null_size = 0; - switch( output_param->encoding ) { - case SQLSRV_ENCODING_UTF8: - null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16 - break; - case SQLSRV_ENCODING_SYSTEM: - null_size = 1; - break; - case SQLSRV_ENCODING_BINARY: - null_size = 0; - break; - default: - SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); - break; - } - CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { - throw core::CoreException(); - } - - // if it's not in the 8 bit encodings, then it's in UTF-16 - if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { - bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { - // 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'; - core::sqlsrv_zval_stringl(value_z, str, str_len); - } - else { - core::sqlsrv_zval_stringl(value_z, str, str_len); - } - } - 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 ) { - ZVAL_NULL( value_z ); - } - else if( output_param->is_bool ) { - convert_to_boolean( value_z ); - } - else - { - ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); - } - break; - case IS_DOUBLE: - // 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 ) { - ZVAL_NULL( value_z ); - } - break; - default: - DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); - break; - } - value_z = NULL; - } ZEND_HASH_FOREACH_END(); - - // empty the hash table since it's been processed - zend_hash_clean( Z_ARRVAL( stmt->output_params )); - return; -} - -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) -{ - SQLRETURN r; - SQLSMALLINT c_type; - SQLLEN sql_field_type = 0; - SQLSMALLINT extra = 0; - SQLLEN field_len_temp; - SQLLEN sql_display_size = 0; - char* field_value_temp = NULL; - - try { - - DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, - "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); - - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); - } - - // Set the C type and account for null characters at the end of the data. - switch( sqlsrv_php_type.typeinfo.encoding ) { - case CP_UTF8: - c_type = SQL_C_WCHAR; - extra = sizeof( SQLWCHAR ); - break; - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - extra = 0; - break; - default: - c_type = SQL_C_CHAR; - extra = sizeof( SQLCHAR ); - break; - } - - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Calculate the field size. - calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); - - // if this is a large type, then read the first few bytes to get the actual length from SQLGetData - if( sql_display_size == 0 || sql_display_size == INT_MAX || - sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { - - field_len_temp = INITIAL_FIELD_STRING_LEN; - - field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); - - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), - &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( field_len_temp == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( r == SQL_SUCCESS_WITH_INFO ) { - - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - - stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - - if( is_truncated_warning( state )) { - - SQLLEN dummy_field_len; - - // for XML (and possibly other conditions) the field length returned is not the real field length, so - // in every pass, we double the allocation size to retrieve all the contents. - if( field_len_temp == SQL_NO_TOTAL ) { - - // reset the field_len_temp - field_len_temp = INITIAL_FIELD_STRING_LEN; - - do { - SQLLEN initial_field_len = field_len_temp; - // Double the size. - field_len_temp *= 2; - - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - field_len_temp -= initial_field_len; - - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, - field_len_temp + extra, &dummy_field_len, - false /*handle_warning*/ TSRMLS_CC ); - - // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL - // so we calculate the actual length of the string with that. - if( dummy_field_len != SQL_NO_TOTAL ) - field_len_temp += dummy_field_len; - else - field_len_temp += initial_field_len; - - if( r == SQL_SUCCESS_WITH_INFO ) { - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - } - - } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); - } - else { - - // We got the field_len_temp from SQLGetData call. - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - // We have already recieved INITIAL_FIELD_STRING_LEN size data. - field_len_temp -= INITIAL_FIELD_STRING_LEN; - - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, - field_len_temp + extra, &dummy_field_len, - true /*handle_warning*/ TSRMLS_CC ); - - if( dummy_field_len == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - field_len_temp += INITIAL_FIELD_STRING_LEN; - } - - } // if( is_truncation_warning ( state ) ) - else { - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - } - } // if( r == SQL_SUCCESS_WITH_INFO ) - - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { - - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) - - else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { - - // only allow binary retrievals for char and binary types. All others get a string converted - // to the encoding type they asked for. - - // null terminator - if( c_type == SQL_C_CHAR ) { - sql_display_size += sizeof( SQLCHAR ); - } - - // For WCHAR multiply by sizeof(WCHAR) and include the null terminator - else if( c_type == SQL_C_WCHAR ) { - sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); - } - - field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); - - // get the data - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, - &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); - CHECK_SQL_ERROR( r, stmt ) { - throw core::CoreException(); - } - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( field_len_temp == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { - - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) - - else { - - DIE( "Invalid sql_display_size" ); - return; // to eliminate a warning - } - - field_value = field_value_temp; - *field_len = field_len_temp; - - // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP - // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. - // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary - // operator to set add 1 to fill the null terminator - field_value_temp[field_len_temp] = '\0'; - } - - catch( core::CoreException& ) { - - field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - catch ( ... ) { - - field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - -} - - -// return the option from the stmt_opts array that matches the key. If no option found, -// NULL is returned. - -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ) -{ - 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 ]; - } - } - - return NULL; // no option found -} - -// is_fixed_size_type -// returns true if the SQL data type is a fixed length, as opposed to a variable length data type such as varchar or varbinary - -bool is_fixed_size_type( SQLINTEGER sql_type ) -{ - switch( sql_type ) { - - case SQL_BINARY: - case SQL_CHAR: - case SQL_WCHAR: - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_VARBINARY: - case SQL_LONGVARBINARY: - case SQL_SS_XML: - case SQL_SS_UDT: - return false; - } - - return true; -} - -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) -{ - switch( type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - case SQLSRV_PHPTYPE_DATETIME: - return true; - case SQLSRV_PHPTYPE_STRING: - case SQLSRV_PHPTYPE_STREAM: - { - if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR - || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - return true; - } - break; - } - } - - return false; -} - - -// verify there is enough space to hold the output string parameter, and allocate it if needed. The param_z -// is updated to have the new buffer with the correct size and its reference is incremented. The output -// string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and -// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter - -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, - SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, - SQLLEN& buffer_len TSRMLS_DC ) -{ - SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); - buffer_len = Z_STRLEN_P( param_z ); - SQLLEN expected_len; - SQLLEN buffer_null_extra; - SQLLEN elem_size; - SQLLEN without_null_len; - - // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, - // as is a n(var)char/ntext field being returned as a binary field. - elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1; - - // account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning - expected_len = column_size * elem_size + elem_size; - - // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations - buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; - - // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter - without_null_len = column_size * elem_size; - - // increment to include the null terminator since the Zend length doesn't include the null terminator - buffer_len += elem_size; - - // if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new - // length. - if( buffer_len < expected_len ) { - SQLSRV_ASSERT( expected_len >= expected_len - buffer_null_extra, - "Integer overflow/underflow caused a corrupt field length." ); - - // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since - // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about - // not having a NULL terminator on a string. - zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); - - // A zval string len doesn't include the null. This calculates the length it should be - // regardless of whether the ODBC type contains the NULL or not. - - // null terminate the string to avoid a warning in debug PHP builds - ZSTR_VAL(param_z_string)[without_null_len] = '\0'; - ZVAL_NEW_STR(param_z, param_z_string); - - // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the - // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY - buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; - - // Zend string length doesn't include the null terminator - ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; - } - - buffer = Z_STRVAL_P(param_z); - - // 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); - } -} - -// output parameters have their reference count incremented so that they do not disappear -// while the query is executed and processed. They are saved in the statement so that -// their reference count may be decremented later (after results are processed) - -void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ) -{ - HashTable* param_ht = Z_ARRVAL( stmt->output_params ); - zend_ulong paramno = static_cast( param.param_num ); - core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, ¶m, sizeof( sqlsrv_output_param )); - Z_TRY_ADDREF_P( param.param_z ); // we have a reference to the param -} - - -// send all the stream data - -void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { } -} - - -// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed -void sqlsrv_output_param_dtor( zval* data ) -{ - sqlsrv_output_param *output_param = static_cast( Z_PTR_P( data )); - zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold - sqlsrv_free( output_param ); -} - -// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed -void sqlsrv_stream_dtor( zval* data ) -{ - sqlsrv_stream* stream_encoding = static_cast( Z_PTR_P( data )); - zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold - sqlsrv_free( stream_encoding ); -} - -} diff --git a/pdo_sqlsrv/core_util.cpp b/pdo_sqlsrv/core_util.cpp deleted file mode 100644 index 8bc410f3..00000000 --- a/pdo_sqlsrv/core_util.cpp +++ /dev/null @@ -1,400 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_util.cpp -// -// Contents: Utility functions used by both connection or statement functions for both the PDO and sqlsrv drivers -// -// Comments: Mostly error handling and some type handling -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include - -namespace { - -// *** internal constants *** -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 - -// routine used by utf16_string_from_mbcs_string -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, - unsigned int mbcs_len, - _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, - unsigned int utf16_len ); -} - -// SQLSTATE for all internal errors -SQLCHAR IMSSP[] = "IMSSP"; - -// SQLSTATE for all internal warnings -SQLCHAR SSPWARN[] = "01SSP"; - -// write to the php log if the severity and subsystem match the filters currently set in the INI or -// the script (sqlsrv_configure). -void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ...) -{ - SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." ); - - va_list args; - va_start( args, msg ); - - g_driver_log( severity TSRMLS_CC, msg, &args ); - - va_end( args ); -} - -void core_sqlsrv_register_logger( log_callback driver_logger ) -{ - g_driver_log = driver_logger; -} - - -// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new -// length in the len parameter. If no errors occurred during convertion, true is returned and the original -// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed -// and false is returned. - -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len) -{ - SQLSRV_ASSERT( string != NULL, "String must be specified" ); - - if (validate_string(*string, len)) { - return true; - } - - char* outString = NULL; - SQLLEN outLen = 0; - - bool result = convert_string_from_utf16( encoding, - reinterpret_cast(*string), int(len / sizeof(wchar_t)), &outString, outLen); - - if (result) - { - sqlsrv_free( *string ); - *string = outString; - len = outLen; - } - - return result; -} - -bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len) -{ - char* string = Z_STRVAL_P(value_z); - - if (validate_string(string, len)) { - return true; - } - - char* outString = NULL; - SQLLEN outLen = 0; - - bool result = convert_string_from_utf16(encoding, - reinterpret_cast(string), int(len / sizeof(wchar_t)), &outString, outLen); - - if (result) - { - core::sqlsrv_zval_stringl(value_z, outString, outLen); - sqlsrv_free(outString); - len = outLen; - } - - return result; -} - -bool validate_string(char* string, SQLLEN& len) -{ - SQLSRV_ASSERT(string != NULL, "String must be specified"); - - //for the empty string, we simply returned we converted it - if (len == 0 && string[0] == '\0') { - return true; - } - - if ((len / sizeof(wchar_t)) > INT_MAX) - { - LOG(SEV_ERROR, "UTP-16 (wide character) string mapping: buffer length exceeded."); - throw core::CoreException(); - } - - return false; -} - -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ) -{ - SQLSRV_ASSERT( inString != NULL, "Input string must be specified" ); - SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" ); - SQLSRV_ASSERT( *outString == NULL, "Output buffer pointer must not be set" ); - - if (cchInLen == 0 && inString[0] == L'\0') { - *outString = reinterpret_cast( sqlsrv_malloc ( 1 ) ); - *outString[0] = '\0'; - cchOutLen = 0; - return true; - } - - // flags set to 0 by default, which means that any invalid characters are dropped rather than causing - // an error. This happens only on XP. - DWORD flags = 0; - if( encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { - // Vista (and later) will detect invalid UTF-16 characters and raise an error. - flags = WC_ERR_INVALID_CHARS; - } - - // calculate the number of characters needed - cchOutLen = WideCharToMultiByte( encoding, flags, - inString, cchInLen, - NULL, 0, NULL, NULL ); - if( cchOutLen == 0 ) { - return false; - } - - // Create a buffer to fit the encoded string - char* newString = reinterpret_cast( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ )); - int rc = WideCharToMultiByte( encoding, flags, - inString, cchInLen, - newString, static_cast(cchOutLen), NULL, NULL ); - if( rc == 0 ) { - cchOutLen = 0; - sqlsrv_free( newString ); - return false; - } - - *outString = newString; - newString[cchOutLen] = '\0'; // null terminate the encoded string - - return true; -} - -// thin wrapper around convert_string_from_default_encoding that handles -// allocation of the destination string. An empty string passed in returns -// failure since it's a failure case for convert_string_from_default_encoding. -wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len, - unsigned int* utf16_len ) -{ - *utf16_len = (mbcs_len + 1); - wchar_t* utf16_string = reinterpret_cast( sqlsrv_malloc( *utf16_len * sizeof( wchar_t ))); - *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, - utf16_string, *utf16_len ); - if( *utf16_len == 0 ) { - // we preserve the error and reset it because sqlsrv_free resets the last error - DWORD last_error = GetLastError(); - sqlsrv_free( utf16_string ); - SetLastError( last_error ); - return NULL; - } - - return utf16_string; -} - -// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the -// errno is 1 based. It returns it as an array with 3 members: -// 1/SQLSTATE) sqlstate -// 2/code) driver specific error code -// 3/message) driver specific error message -// The fetch type determines if the indices are numeric, associative, or both. - -bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_error_auto_ptr& error, logging_severity severity - TSRMLS_DC ) -{ - SQLHANDLE h = ctx.handle(); - SQLSMALLINT h_type = ctx.handle_type(); - - if( h == NULL ) { - return false; - } - - zval* ssphp_z = NULL; - int zr = SUCCESS; - zval* temp = NULL; - SQLRETURN r = SQL_SUCCESS; - SQLSMALLINT wmessage_len = 0; - SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ]; - SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLSRV_ENCODING enc = ctx.encoding(); - - switch( h_type ) { - - case SQL_HANDLE_STMT: - { - sqlsrv_stmt* stmt = static_cast( &ctx ); - if( stmt->current_results != NULL ) { - - error = stmt->current_results->get_diag_rec( record_number ); - // don't use the CHECK* macros here since it will trigger reentry into the error handling system - if( error == NULL ) { - return false; - } - break; - } - - // convert the error into the encoding of the context - if( enc == SQLSRV_ENCODING_DEFAULT ) { - enc = stmt->conn->encoding(); - } - } - - - default: - - error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); - r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message, - SQL_MAX_MESSAGE_LENGTH + 1, &wmessage_len ); - // don't use the CHECK* macros here since it will trigger reentry into the error handling system - if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - return false; - } - - SQLLEN sqlstate_len = 0; - convert_string_from_utf16(enc, wsqlstate, sizeof(wsqlstate), (char**)&error->sqlstate, sqlstate_len); - - SQLLEN message_len = 0; - convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); - break; - } - - - // log the error first - LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate ); - LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), error->native_code ); - LOG( severity, "%1!s!: message = %2!s!", ctx.func(), error->native_message ); - - error->format = false; - - return true; -} - -// format and return a driver specfic error -void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, - sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ) -{ - // allocate space for the formatted message - formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); - formatted_error->sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - formatted_error->native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast( custom_error->native_message ), 0, 0, - reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, args ); - if( rc == 0 ) { - strcpy_s( reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, - reinterpret_cast( INTERNAL_FORMAT_ERROR )); - } - - strcpy_s( reinterpret_cast( formatted_error->sqlstate ), SQL_SQLSTATE_BUFSIZE, - reinterpret_cast( custom_error->sqlstate )); - formatted_error->native_code = custom_error->native_code; - - // log the error - LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), formatted_error->sqlstate ); - LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), formatted_error->native_code ); - LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); -} - -DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ) -{ - va_list format_args; - va_start( format_args, format ); - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast(output_buffer), SQL_MAX_MESSAGE_LENGTH, &format_args ); - - va_end( format_args ); - - return rc; -} - -// return an error message for GetLastError using FormatMessage. -// this function returns the msg pointer so that it may be used within -// another function call such as handle_error -const char* get_last_error_message( DWORD last_error ) -{ - if( last_error == 0 ) { - last_error = GetLastError(); - } - - DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), - last_err_msg, sizeof( last_err_msg ), NULL ); - - if( r == 0 ) { - SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( last_err_msg )); - std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), last_err_msg ); - } - - return last_err_msg; -} - - -// die -// Terminate the PHP request with an error message -// We use this function rather than php_error directly because we use the FormatMessage syntax in most other -// places within the extension (e.g., LOG), whereas php_error uses the printf format syntax. There were -// places where we were using the FormatMessage syntax inadvertently with DIE which left messages without -// proper information. Rather than convert those messages and try and remember the difference between LOG and -// DIE, it is simpler to make the format syntax common between them. -void die( const char* msg, ... ) -{ - va_list format_args; - 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 )); - } - - php_error( E_ERROR, last_err_msg ); -} - -namespace { - -// convert from the default encoding specified by the "CharacterSet" -// connection option to UTF-16. mbcs_len and utf16_len are sizes in -// bytes. The return is the number of UTF-16 characters in the string -// returned in utf16_out_string. An empty string passed in will result as -// a failure since MBTWC returns 0 for both an empty string and failure -// to convert. -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, - unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, - unsigned int utf16_len ) -{ - unsigned int win_encoding = CP_ACP; - switch( php_encoding ) { - case SQLSRV_ENCODING_CHAR: - win_encoding = CP_ACP; - break; - // this shouldn't ever be set - case SQLSRV_ENCODING_BINARY: - DIE( "Invalid encoding." ); - break; - default: - win_encoding = php_encoding; - break; - } - unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len, - utf16_out_string, utf16_len ); - if( required_len == 0 ) { - return 0; - } - utf16_out_string[ required_len ] = '\0'; - - return required_len; -} - -} diff --git a/pdo_sqlsrv/msodbcsql.h b/pdo_sqlsrv/msodbcsql.h deleted file mode 100644 index 8bcf83d0..00000000 --- a/pdo_sqlsrv/msodbcsql.h +++ /dev/null @@ -1,2343 +0,0 @@ -//----------------------------------------------------------------------------- -// File: msodbcsql.h -// -// Copyright: Copyright (c) Microsoft Corporation -// -// Contents: ODBC driver for SQL Server specific definitions. -// -//----------------------------------------------------------------------------- -#ifndef __msodbcsql_h__ -#define __msodbcsql_h__ - -#if !defined(SQLODBC_VER) -#define SQLODBC_VER 1100 -#endif - -#if SQLODBC_VER >= 1100 - -#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server" - -#define SQLODBC_FILE_NAME_ANSI "msodbcsql" -#define SQLODBC_FILE_NAME_VER_ANSI "msodbcsql11" -#define SQLODBC_FILE_NAME_FULL_ANSI "msodbcsql11.dll" - -#define SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_FULL_UNICODE L"Microsoft ODBC Driver for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE L"ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_UNICODE L"ODBC Driver for SQL Server" - -#define SQLODBC_FILE_NAME_UNICODE L"msodbcsql" -#define SQLODBC_FILE_NAME_VER_UNICODE L"msodbcsql11" -#define SQLODBC_FILE_NAME_FULL_UNICODE L"msodbcsql11.dll" - -// define the character type agnostic constants -#if defined(_UNICODE) || defined(UNICODE) - -#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE -#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_UNICODE -#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE -#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_UNICODE - -#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_UNICODE -#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_UNICODE -#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_UNICODE - -#else // _UNICODE || UNICODE - -#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI -#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI -#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI -#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI - -#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_ANSI -#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_ANSI -#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_ANSI - -#endif // _UNICODE || UNICODE - -#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER - -#endif // SQLODBC_VER - -#ifndef __sqlncli_h__ - -#if !defined(SQLNCLI_VER) -#define SQLNCLI_VER 1100 -#endif - -#if SQLNCLI_VER >= 1100 - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli11" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli11.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli11" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli11.dll" - -#elif SQLNCLI_VER >= 1000 - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli10" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli10.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli10" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli10.dll" - -#else - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli.dll" - -#endif // SQLNCLI_VER >= 1100 - -// define the character type agnostic constants -#if defined(_UNICODE) || defined(UNICODE) - -#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE -#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_UNICODE -#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE -#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_UNICODE - -#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_UNICODE -#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_UNICODE -#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_UNICODE - - -#else // _UNICODE || UNICODE - -#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI -#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_ANSI -#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI -#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_ANSI - -#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_ANSI -#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_ANSI -#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_ANSI - -#endif // _UNICODE || UNICODE - -#define SQLNCLI_DRIVER_NAME SQLNCLI_PRODUCT_NAME_SHORT_VER - - -#ifdef ODBCVER - -#ifdef __cplusplus -extern "C" { -#endif - -// max SQL Server identifier length -#define SQL_MAX_SQLSERVERNAME 128 - -// SQLSetConnectAttr driver specific defines. -// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage. -// Connection attributes -#define SQL_COPT_SS_BASE 1200 -#define SQL_COPT_SS_REMOTE_PWD (SQL_COPT_SS_BASE+1) // dbrpwset SQLSetConnectOption only -#define SQL_COPT_SS_USE_PROC_FOR_PREP (SQL_COPT_SS_BASE+2) // Use create proc for SQLPrepare -#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) // Force integrated security on login -#define SQL_COPT_SS_PRESERVE_CURSORS (SQL_COPT_SS_BASE+4) // Preserve server cursors after SQLTransact -#define SQL_COPT_SS_USER_DATA (SQL_COPT_SS_BASE+5) // dbgetuserdata/dbsetuserdata -#define SQL_COPT_SS_ENLIST_IN_DTC SQL_ATTR_ENLIST_IN_DTC // Enlist in a DTC transaction -#define SQL_COPT_SS_ENLIST_IN_XA SQL_ATTR_ENLIST_IN_XA // Enlist in a XA transaction -#define SQL_COPT_SS_FALLBACK_CONNECT (SQL_COPT_SS_BASE+10) // Enables FallBack connections -#define SQL_COPT_SS_PERF_DATA (SQL_COPT_SS_BASE+11) // Used to access SQL Server ODBC driver performance data -#define SQL_COPT_SS_PERF_DATA_LOG (SQL_COPT_SS_BASE+12) // Used to set the logfile name for the Performance data -#define SQL_COPT_SS_PERF_QUERY_INTERVAL (SQL_COPT_SS_BASE+13) // Used to set the query logging threshold in milliseconds. -#define SQL_COPT_SS_PERF_QUERY_LOG (SQL_COPT_SS_BASE+14) // Used to set the logfile name for saving queryies. -#define SQL_COPT_SS_PERF_QUERY (SQL_COPT_SS_BASE+15) // Used to start and stop query logging. -#define SQL_COPT_SS_PERF_DATA_LOG_NOW (SQL_COPT_SS_BASE+16) // Used to make a statistics log entry to disk. -#define SQL_COPT_SS_QUOTED_IDENT (SQL_COPT_SS_BASE+17) // Enable/Disable Quoted Identifiers -#define SQL_COPT_SS_ANSI_NPW (SQL_COPT_SS_BASE+18) // Enable/Disable ANSI NULL, Padding and Warnings -#define SQL_COPT_SS_BCP (SQL_COPT_SS_BASE+19) // Allow BCP usage on connection -#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) // Perform code page translation -#define SQL_COPT_SS_ATTACHDBFILENAME (SQL_COPT_SS_BASE+21) // File name to be attached as a database -#define SQL_COPT_SS_CONCAT_NULL (SQL_COPT_SS_BASE+22) // Enable/Disable CONCAT_NULL_YIELDS_NULL -#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) // Allow strong encryption for data -#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) // Multiple active result set per connection -#define SQL_COPT_SS_FAILOVER_PARTNER (SQL_COPT_SS_BASE+25) // Failover partner server -#define SQL_COPT_SS_OLDPWD (SQL_COPT_SS_BASE+26) // Old Password, used when changing password during login -#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) // Used to set/get any driver-specific or ODBC-defined TXN iso level -#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) // Trust server certificate -#define SQL_COPT_SS_SERVER_SPN (SQL_COPT_SS_BASE+29) // Server SPN -#define SQL_COPT_SS_FAILOVER_PARTNER_SPN (SQL_COPT_SS_BASE+30) // Failover partner server SPN -#define SQL_COPT_SS_INTEGRATED_AUTHENTICATION_METHOD (SQL_COPT_SS_BASE+31) // The integrated authentication method used for the connection -#define SQL_COPT_SS_MUTUALLY_AUTHENTICATED (SQL_COPT_SS_BASE+32) // Used to decide if the connection is mutually authenticated -#define SQL_COPT_SS_CLIENT_CONNECTION_ID (SQL_COPT_SS_BASE+33) // Post connection attribute used to get the ConnectionID -// Define old names -#define SQL_REMOTE_PWD SQL_COPT_SS_REMOTE_PWD -#define SQL_USE_PROCEDURE_FOR_PREPARE SQL_COPT_SS_USE_PROC_FOR_PREP -#define SQL_INTEGRATED_SECURITY SQL_COPT_SS_INTEGRATED_SECURITY -#define SQL_PRESERVE_CURSORS SQL_COPT_SS_PRESERVE_CURSORS - -// SQLSetStmtAttr SQL Server Native Client driver specific defines. -// Statement attributes -#define SQL_SOPT_SS_BASE 1225 -#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) // Text pointer logging -#define SQL_SOPT_SS_CURRENT_COMMAND (SQL_SOPT_SS_BASE+1) // dbcurcmd SQLGetStmtOption only -#define SQL_SOPT_SS_HIDDEN_COLUMNS (SQL_SOPT_SS_BASE+2) // Expose FOR BROWSE hidden columns -#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) // Set NOBROWSETABLE option -#define SQL_SOPT_SS_REGIONALIZE (SQL_SOPT_SS_BASE+4) // Regionalize output character conversions -#define SQL_SOPT_SS_CURSOR_OPTIONS (SQL_SOPT_SS_BASE+5) // Server cursor options -#define SQL_SOPT_SS_NOCOUNT_STATUS (SQL_SOPT_SS_BASE+6) // Real vs. Not Real row count indicator -#define SQL_SOPT_SS_DEFER_PREPARE (SQL_SOPT_SS_BASE+7) // Defer prepare until necessary -#define SQL_SOPT_SS_QUERYNOTIFICATION_TIMEOUT (SQL_SOPT_SS_BASE+8) // Notification timeout -#define SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT (SQL_SOPT_SS_BASE+9) // Notification message text -#define SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS (SQL_SOPT_SS_BASE+10)// SQL service broker name -#define SQL_SOPT_SS_PARAM_FOCUS (SQL_SOPT_SS_BASE+11)// Direct subsequent calls to parameter related methods to set properties on constituent columns/parameters of container types -#define SQL_SOPT_SS_NAME_SCOPE (SQL_SOPT_SS_BASE+12)// Sets name scope for subsequent catalog function calls -#define SQL_SOPT_SS_MAX_USED SQL_SOPT_SS_NAME_SCOPE -// Define old names -#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING -#define SQL_COPT_SS_BASE_EX 1240 -#define SQL_COPT_SS_BROWSE_CONNECT (SQL_COPT_SS_BASE_EX+1) // Browse connect mode of operation -#define SQL_COPT_SS_BROWSE_SERVER (SQL_COPT_SS_BASE_EX+2) // Single Server browse request. -#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) // Issues warning when data from the server had a loss during code page conversion. -#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) // dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check -#define SQL_COPT_SS_BROWSE_CACHE_DATA (SQL_COPT_SS_BASE_EX+5) // Determines if we should cache browse info. Used when returned buffer is greater then ODBC limit (32K) -#define SQL_COPT_SS_RESET_CONNECTION (SQL_COPT_SS_BASE_EX+6) // When this option is set, we will perform connection reset on next packet -#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) // Application Intent -#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) // Multi-subnet Failover -#define SQL_COPT_SS_EX_MAX_USED SQL_COPT_SS_MULTISUBNET_FAILOVER - -// SQLColAttributes driver specific defines. -// SQLSetDescField/SQLGetDescField driver specific defines. -// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_CA_SS_BASE 1200 -#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) // dbcoltype/dbalttype -#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) // dbcolutype/dbaltutype -#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) // dbnumorders -#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) // dbordercol -#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) // dbvarylen -#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) // dbnumcompute -#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) // dbnextrow status return -#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) // dbbylist -#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) // dbaltcolid -#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) // dbaltop -#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) // dbcollen -#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) // Column is hidden (FOR BROWSE) -#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) // Column is key column (FOR BROWSE) -//#define SQL_DESC_BASE_COLUMN_NAME_OLD (SQL_CA_SS_BASE+13) // This is defined at another location. -#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) // Column collation (only for chars) -#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15) -#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16) -#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17) - -// XML, CLR UDT, and table valued parameter related metadata -#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) // UDT catalog name -#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) // UDT schema name -#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) // UDT type name -#define SQL_CA_SS_UDT_ASSEMBLY_TYPE_NAME (SQL_CA_SS_BASE+21) // Qualified name of the assembly containing the UDT class -#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) // Name of the catalog that contains XML Schema collection -#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) // Name of the schema that contains XML Schema collection -#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) // Name of the XML Schema collection -#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) // Catalog name -#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) // Schema name -#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) // Type name - -// table valued parameter related metadata -#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) // column is computed -#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) // column is part of a unique key -#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) // column sort order -#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) // column sort ordinal -#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) // column has default value for all rows of the table valued parameter - -// sparse column related metadata -#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) // column is a column-set column for sparse columns - -// Legacy datetime related metadata -#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) // column type to send on the wire for datetime types - -#define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+36) - -// Defines returned by SQL_ATTR_CURSOR_TYPE/SQL_CURSOR_TYPE -#define SQL_CURSOR_FAST_FORWARD_ONLY 8 // Only returned by SQLGetStmtAttr/Option -// Defines for use with SQL_COPT_SS_USE_PROC_FOR_PREP -#define SQL_UP_OFF 0L // Procedures won't be used for prepare -#define SQL_UP_ON 1L // Procedures will be used for prepare -#define SQL_UP_ON_DROP 2L // Temp procedures will be explicitly dropped -#define SQL_UP_DEFAULT SQL_UP_ON -// Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only -#define SQL_IS_OFF 0L // Integrated security isn't used -#define SQL_IS_ON 1L // Integrated security is used -#define SQL_IS_DEFAULT SQL_IS_OFF -// Defines for use with SQL_COPT_SS_PRESERVE_CURSORS -#define SQL_PC_OFF 0L // Cursors are closed on SQLTransact -#define SQL_PC_ON 1L // Cursors remain open on SQLTransact -#define SQL_PC_DEFAULT SQL_PC_OFF -// Defines for use with SQL_COPT_SS_USER_DATA -#define SQL_UD_NOTSET NULL // No user data pointer set -// Defines for use with SQL_COPT_SS_TRANSLATE -#define SQL_XL_OFF 0L // Code page translation is not performed -#define SQL_XL_ON 1L // Code page translation is performed -#define SQL_XL_DEFAULT SQL_XL_ON -// Defines for use with SQL_COPT_SS_FALLBACK_CONNECT - Pre-Connect Option only -#define SQL_FB_OFF 0L // FallBack connections are disabled -#define SQL_FB_ON 1L // FallBack connections are enabled -#define SQL_FB_DEFAULT SQL_FB_OFF -// Defines for use with SQL_COPT_SS_BCP - Pre-Connect Option only -#define SQL_BCP_OFF 0L // BCP is not allowed on connection -#define SQL_BCP_ON 1L // BCP is allowed on connection -#define SQL_BCP_DEFAULT SQL_BCP_OFF -// Defines for use with SQL_COPT_SS_QUOTED_IDENT -#define SQL_QI_OFF 0L // Quoted identifiers are enable -#define SQL_QI_ON 1L // Quoted identifiers are disabled -#define SQL_QI_DEFAULT SQL_QI_ON -// Defines for use with SQL_COPT_SS_ANSI_NPW - Pre-Connect Option only -#define SQL_AD_OFF 0L // ANSI NULLs, Padding and Warnings are enabled -#define SQL_AD_ON 1L // ANSI NULLs, Padding and Warnings are disabled -#define SQL_AD_DEFAULT SQL_AD_ON -// Defines for use with SQL_COPT_SS_CONCAT_NULL - Pre-Connect Option only -#define SQL_CN_OFF 0L // CONCAT_NULL_YIELDS_NULL is off -#define SQL_CN_ON 1L // CONCAT_NULL_YIELDS_NULL is on -#define SQL_CN_DEFAULT SQL_CN_ON -// Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING -#define SQL_TL_OFF 0L // No logging on text pointer ops -#define SQL_TL_ON 1L // Logging occurs on text pointer ops -#define SQL_TL_DEFAULT SQL_TL_ON -// Defines for use with SQL_SOPT_SS_HIDDEN_COLUMNS -#define SQL_HC_OFF 0L // FOR BROWSE columns are hidden -#define SQL_HC_ON 1L // FOR BROWSE columns are exposed -#define SQL_HC_DEFAULT SQL_HC_OFF -// Defines for use with SQL_SOPT_SS_NOBROWSETABLE -#define SQL_NB_OFF 0L // NO_BROWSETABLE is off -#define SQL_NB_ON 1L // NO_BROWSETABLE is on -#define SQL_NB_DEFAULT SQL_NB_OFF -// Defines for use with SQL_SOPT_SS_REGIONALIZE -#define SQL_RE_OFF 0L // No regionalization occurs on output character conversions -#define SQL_RE_ON 1L // Regionalization occurs on output character conversions -#define SQL_RE_DEFAULT SQL_RE_OFF -// Defines for use with SQL_SOPT_SS_CURSOR_OPTIONS -#define SQL_CO_OFF 0L // Clear all cursor options -#define SQL_CO_FFO 1L // Fast-forward cursor will be used -#define SQL_CO_AF 2L // Autofetch on cursor open -#define SQL_CO_FFO_AF (SQL_CO_FFO|SQL_CO_AF) // Fast-forward cursor with autofetch -#define SQL_CO_FIREHOSE_AF 4L // Auto fetch on fire-hose cursors -#define SQL_CO_DEFAULT SQL_CO_OFF -//SQL_SOPT_SS_NOCOUNT_STATUS -#define SQL_NC_OFF 0L -#define SQL_NC_ON 1L -//SQL_SOPT_SS_DEFER_PREPARE -#define SQL_DP_OFF 0L -#define SQL_DP_ON 1L -//SQL_SOPT_SS_NAME_SCOPE -#define SQL_SS_NAME_SCOPE_TABLE 0L -#define SQL_SS_NAME_SCOPE_TABLE_TYPE 1L -#define SQL_SS_NAME_SCOPE_EXTENDED 2L -#define SQL_SS_NAME_SCOPE_SPARSE_COLUMN_SET 3L -#define SQL_SS_NAME_SCOPE_DEFAULT SQL_SS_NAME_SCOPE_TABLE -//SQL_COPT_SS_ENCRYPT -#define SQL_EN_OFF 0L -#define SQL_EN_ON 1L -//SQL_COPT_SS_TRUST_SERVER_CERTIFICATE -#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L -#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L -//SQL_COPT_SS_BROWSE_CONNECT -#define SQL_MORE_INFO_NO 0L -#define SQL_MORE_INFO_YES 1L -//SQL_COPT_SS_BROWSE_CACHE_DATA -#define SQL_CACHE_DATA_NO 0L -#define SQL_CACHE_DATA_YES 1L -//SQL_COPT_SS_RESET_CONNECTION -#define SQL_RESET_YES 1L -//SQL_COPT_SS_WARN_ON_CP_ERROR -#define SQL_WARN_NO 0L -#define SQL_WARN_YES 1L -//SQL_COPT_SS_MARS_ENABLED -#define SQL_MARS_ENABLED_NO 0L -#define SQL_MARS_ENABLED_YES 1L -/* SQL_TXN_ISOLATION_OPTION bitmasks */ -#define SQL_TXN_SS_SNAPSHOT 0x00000020L - -// The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER -#define SQL_SS_ORDER_UNSPECIFIED 0L -#define SQL_SS_DESCENDING_ORDER 1L -#define SQL_SS_ASCENDING_ORDER 2L -#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED - -// Driver specific SQL data type defines. -// Microsoft has -150 thru -199 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_SS_VARIANT (-150) -#define SQL_SS_UDT (-151) -#define SQL_SS_XML (-152) -#define SQL_SS_TABLE (-153) -#define SQL_SS_TIME2 (-154) -#define SQL_SS_TIMESTAMPOFFSET (-155) - -// Local types to be used with SQL_CA_SS_SERVER_TYPE -#define SQL_SS_TYPE_DEFAULT 0L -#define SQL_SS_TYPE_SMALLDATETIME 1L -#define SQL_SS_TYPE_DATETIME 2L - -// Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager. -#define SQL_C_TYPES_EXTENDED 0x04000L -#define SQL_C_SS_TIME2 (SQL_C_TYPES_EXTENDED+0) -#define SQL_C_SS_TIMESTAMPOFFSET (SQL_C_TYPES_EXTENDED+1) - -#ifndef SQLNCLI_NO_BCP -// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application -// and you want to exclude the BCP-related definitions in this header file. - -// SQL Server Data Type defines. -// New types for SQL 6.0 and later servers -#define SQLTEXT 0x23 -#define SQLVARBINARY 0x25 -#define SQLINTN 0x26 -#define SQLVARCHAR 0x27 -#define SQLBINARY 0x2d -#define SQLIMAGE 0x22 -#define SQLCHARACTER 0x2f -#define SQLINT1 0x30 -#define SQLBIT 0x32 -#define SQLINT2 0x34 -#define SQLINT4 0x38 -#define SQLMONEY 0x3c -#define SQLDATETIME 0x3d -#define SQLFLT8 0x3e -#define SQLFLTN 0x6d -#define SQLMONEYN 0x6e -#define SQLDATETIMN 0x6f -#define SQLFLT4 0x3b -#define SQLMONEY4 0x7a -#define SQLDATETIM4 0x3a -// New types for SQL 6.0 and later servers -#define SQLDECIMAL 0x6a -#define SQLNUMERIC 0x6c -// New types for SQL 7.0 and later servers -#define SQLUNIQUEID 0x24 -#define SQLBIGCHAR 0xaf -#define SQLBIGVARCHAR 0xa7 -#define SQLBIGBINARY 0xad -#define SQLBIGVARBINARY 0xa5 -#define SQLBITN 0x68 -#define SQLNCHAR 0xef -#define SQLNVARCHAR 0xe7 -#define SQLNTEXT 0x63 -// New types for SQL 2000 and later servers -#define SQLINT8 0x7f -#define SQLVARIANT 0x62 -// New types for SQL 2005 and later servers -#define SQLUDT 0xf0 -#define SQLXML 0xf1 -// New types for SQL 2008 and later servers -#define SQLTABLE 0xf3 -#define SQLDATEN 0x28 -#define SQLTIMEN 0x29 -#define SQLDATETIME2N 0x2a -#define SQLDATETIMEOFFSETN 0x2b -// Define old names -#define SQLDECIMALN 0x6a -#define SQLNUMERICN 0x6c -#endif // SQLNCLI_NO_BCP - -// SQL_SS_LENGTH_UNLIMITED is used to describe the max length of -// VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns -#define SQL_SS_LENGTH_UNLIMITED 0 - -// User Data Type definitions. -// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE. -#define SQLudtBINARY 3 -#define SQLudtBIT 16 -#define SQLudtBITN 0 -#define SQLudtCHAR 1 -#define SQLudtDATETIM4 22 -#define SQLudtDATETIME 12 -#define SQLudtDATETIMN 15 -#define SQLudtDECML 24 -#define SQLudtDECMLN 26 -#define SQLudtFLT4 23 -#define SQLudtFLT8 8 -#define SQLudtFLTN 14 -#define SQLudtIMAGE 20 -#define SQLudtINT1 5 -#define SQLudtINT2 6 -#define SQLudtINT4 7 -#define SQLudtINTN 13 -#define SQLudtMONEY 11 -#define SQLudtMONEY4 21 -#define SQLudtMONEYN 17 -#define SQLudtNUM 10 -#define SQLudtNUMN 25 -#define SQLudtSYSNAME 18 -#define SQLudtTEXT 19 -#define SQLudtTIMESTAMP 80 -#define SQLudtUNIQUEIDENTIFIER 0 -#define SQLudtVARBINARY 4 -#define SQLudtVARCHAR 2 -#define MIN_USER_DATATYPE 256 -// Aggregate operator types. -// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP. -#define SQLAOPSTDEV 0x30 // Standard deviation -#define SQLAOPSTDEVP 0x31 // Standard deviation population -#define SQLAOPVAR 0x32 // Variance -#define SQLAOPVARP 0x33 // Variance population -#define SQLAOPCNT 0x4b // Count -#define SQLAOPSUM 0x4d // Sum -#define SQLAOPAVG 0x4f // Average -#define SQLAOPMIN 0x51 // Min -#define SQLAOPMAX 0x52 // Max -#define SQLAOPANY 0x53 // Any -#define SQLAOPNOOP 0x56 // None -// SQLGetInfo driver specific defines. -// Microsoft has 1151 thru 1200 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_INFO_SS_FIRST 1199 -#define SQL_INFO_SS_NETLIB_NAMEW (SQL_INFO_SS_FIRST+0) // dbprocinfo -#define SQL_INFO_SS_NETLIB_NAMEA (SQL_INFO_SS_FIRST+1) // dbprocinfo -#define SQL_INFO_SS_MAX_USED SQL_INFO_SS_NETLIB_NAMEA -#ifdef UNICODE -#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEW -#else -#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEA -#endif - -// SQLGetDiagField driver specific defines. -// Microsoft has -1150 thru -1199 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_DIAG_SS_BASE (-1150) -#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE) -#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1) -#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2) -#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3) -#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4) -// SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines. -// Microsoft has -200 thru -299 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_DIAG_DFC_SS_BASE (-200) -#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0) -#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1) -#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2) -#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3) -#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4) -#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5) -#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6) -#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7) -#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8) -#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9) -#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10) -#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11) -#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12) -#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13) -#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14) -#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15) -#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16) -#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17) -#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18) -#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19) -#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) -#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) -#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21) -#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) -#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) -#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23) -#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24) -#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25) -#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26) -#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26) -#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) -#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) -#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28) -#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) -#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) -#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30) -#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31) -#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32) -#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33) -#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34) -#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35) -#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36) -#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37) -#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38) -#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39) -#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40) -#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41) -#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42) -#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43) -#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44) -#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45) -#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46) -#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47) -#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48) -#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49) -#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50) -#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51) -#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52) -#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53) -#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54) -#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55) -#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56) - -// Severity codes for SQL_DIAG_SS_SEVERITY -#define EX_ANY 0 -#define EX_INFO 10 -#define EX_MAXISEVERITY EX_INFO -#define EX_MISSING 11 -#define EX_TYPE 12 -#define EX_DEADLOCK 13 -#define EX_PERMIT 14 -#define EX_SYNTAX 15 -#define EX_USER 16 -#define EX_RESOURCE 17 -#define EX_INTOK 18 -#define MAXUSEVERITY EX_INTOK -#define EX_LIMIT 19 -#define EX_CMDFATAL 20 -#define MINFATALERR EX_CMDFATAL -#define EX_DBFATAL 21 -#define EX_TABCORRUPT 22 -#define EX_DBCORRUPT 23 -#define EX_HARDWARE 24 -#define EX_CONTROL 25 -// Internal server datatypes - used when binding to SQL_C_BINARY -#ifndef MAXNUMERICLEN // Resolve ODS/DBLib conflicts -// DB-Library datatypes -#define DBMAXCHAR (8000+1) // Max length of DBVARBINARY and DBVARCHAR, etc. +1 for zero byte -#define MAXNAME (SQL_MAX_SQLSERVERNAME+1) // Max server identifier length including zero byte -#ifdef UNICODE -typedef wchar_t DBCHAR; -#else -typedef char DBCHAR; - -#endif -typedef short SQLSMALLINT; - -typedef unsigned short SQLUSMALLINT; - -typedef unsigned char DBBINARY; - -typedef unsigned char DBTINYINT; - -typedef short DBSMALLINT; - -typedef unsigned short DBUSMALLINT; - -typedef double DBFLT8; - -typedef unsigned char DBBIT; - -typedef unsigned char DBBOOL; - -typedef float DBFLT4; - -typedef DBFLT4 DBREAL; - -typedef UINT DBUBOOL; - -typedef struct dbmoney - { - LONG mnyhigh; - ULONG mnylow; - } DBMONEY; - -typedef struct dbdatetime - { - LONG dtdays; - ULONG dttime; - } DBDATETIME; - -typedef struct dbdatetime4 - { - USHORT numdays; - USHORT nummins; - } DBDATETIM4; - -typedef LONG DBMONEY4; - -#include // 8-byte structure packing - -// New Date Time Structures -// New Structure for TIME2 -typedef struct tagSS_TIME2_STRUCT -{ - SQLUSMALLINT hour; - SQLUSMALLINT minute; - SQLUSMALLINT second; - SQLUINTEGER fraction; -} SQL_SS_TIME2_STRUCT; -// New Structure for TIMESTAMPOFFSET -typedef struct tagSS_TIMESTAMPOFFSET_STRUCT -{ - SQLSMALLINT year; - SQLUSMALLINT month; - SQLUSMALLINT day; - SQLUSMALLINT hour; - SQLUSMALLINT minute; - SQLUSMALLINT second; - SQLUINTEGER fraction; - SQLSMALLINT timezone_hour; - SQLSMALLINT timezone_minute; -} SQL_SS_TIMESTAMPOFFSET_STRUCT; - -typedef struct tagDBTIME2 -{ - USHORT hour; - USHORT minute; - USHORT second; - ULONG fraction; -} DBTIME2; - -typedef struct tagDBTIMESTAMPOFFSET -{ - SHORT year; - USHORT month; - USHORT day; - USHORT hour; - USHORT minute; - USHORT second; - ULONG fraction; - SHORT timezone_hour; - SHORT timezone_minute; -} DBTIMESTAMPOFFSET; - -#include // restore original structure packing - -// Money value *10,000 -#define DBNUM_PREC_TYPE BYTE -#define DBNUM_SCALE_TYPE BYTE -#define DBNUM_VAL_TYPE BYTE - -#if (ODBCVER < 0x0300) -#define MAXNUMERICLEN 16 -typedef struct dbnumeric // Internal representation of NUMERIC data type -{ - DBNUM_PREC_TYPE precision; // Precision - DBNUM_SCALE_TYPE scale; // Scale - BYTE sign; // Sign (1 if positive, 0 if negative) - DBNUM_VAL_TYPE val[MAXNUMERICLEN];// Value -} DBNUMERIC; -typedef DBNUMERIC DBDECIMAL;// Internal representation of DECIMAL data type -#else // Use ODBC 3.0 definitions since same as DBLib -#define MAXNUMERICLEN SQL_MAX_NUMERIC_LEN -typedef SQL_NUMERIC_STRUCT DBNUMERIC; -typedef SQL_NUMERIC_STRUCT DBDECIMAL; -#endif // ODCBVER -#endif // MAXNUMERICLEN - -#ifndef INT -typedef int INT; -typedef LONG DBINT; -typedef DBINT * LPDBINT; -#ifndef _LPCBYTE_DEFINED -#define _LPCBYTE_DEFINED -typedef BYTE const* LPCBYTE; -#endif //_LPCBYTE_DEFINED -#endif // INT -/************************************************************************** -This struct is a global used for gathering statistical data on the driver. -Access to this structure is controlled via the pStatCrit; -***************************************************************************/ -typedef struct sqlperf -{ - // Application Profile Statistics - DWORD TimerResolution; - DWORD SQLidu; - DWORD SQLiduRows; - DWORD SQLSelects; - DWORD SQLSelectRows; - DWORD Transactions; - DWORD SQLPrepares; - DWORD ExecDirects; - DWORD SQLExecutes; - DWORD CursorOpens; - DWORD CursorSize; - DWORD CursorUsed; - LDOUBLE PercentCursorUsed; - LDOUBLE AvgFetchTime; - LDOUBLE AvgCursorSize; - LDOUBLE AvgCursorUsed; - DWORD SQLFetchTime; - DWORD SQLFetchCount; - DWORD CurrentStmtCount; - DWORD MaxOpenStmt; - DWORD SumOpenStmt; - // Connection Statistics - DWORD CurrentConnectionCount; - DWORD MaxConnectionsOpened; - DWORD SumConnectionsOpened; - DWORD SumConnectiontime; - LDOUBLE AvgTimeOpened; - // Network Statistics - DWORD ServerRndTrips; - DWORD BuffersSent; - DWORD BuffersRec; - DWORD BytesSent; - DWORD BytesRec; - // Time Statistics; - DWORD msExecutionTime; - DWORD msNetWorkServerTime; -} SQLPERF; -// The following are options for SQL_COPT_SS_PERF_DATA and SQL_COPT_SS_PERF_QUERY -#define SQL_PERF_START 1 // Starts the driver sampling performance data. -#define SQL_PERF_STOP 2 // Stops the counters from sampling performance data. -// The following are defines for SQL_COPT_SS_PERF_DATA_LOG -#define SQL_SS_DL_DEFAULT TEXT("STATS.LOG") -// The following are defines for SQL_COPT_SS_PERF_QUERY_LOG -#define SQL_SS_QL_DEFAULT TEXT("QUERY.LOG") -// The following are defines for SQL_COPT_SS_PERF_QUERY_INTERVAL -#define SQL_SS_QI_DEFAULT 30000 // 30,000 milliseconds - -#ifndef SQLNCLI_NO_BCP -// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application -// and you want to exclude the BCP-related definitions in this header file. - -// ODBC BCP prototypes and defines -// Return codes -#define SUCCEED 1 -#define FAIL 0 -#define SUCCEED_ABORT 2 -#define SUCCEED_ASYNC 3 -// Transfer directions -#define DB_IN 1 // Transfer from client to server -#define DB_OUT 2 // Transfer from server to client -// bcp_control option -#define BCPMAXERRS 1 // Sets max errors allowed -#define BCPFIRST 2 // Sets first row to be copied out -#define BCPLAST 3 // Sets number of rows to be copied out -#define BCPBATCH 4 // Sets input batch size -#define BCPKEEPNULLS 5 // Sets to insert NULLs for empty input values -#define BCPABORT 6 // Sets to have bcpexec return SUCCEED_ABORT -#define BCPODBC 7 // Sets ODBC canonical character output -#define BCPKEEPIDENTITY 8 // Sets IDENTITY_INSERT on -#if SQLNCLI_VER < 1000 -#define BCP6xFILEFMT 9 // DEPRECATED: Sets 6x file format on -#endif -#define BCPHINTSA 10 // Sets server BCP hints (ANSI string) -#define BCPHINTSW 11 // Sets server BCP hints (UNICODE string) -#define BCPFILECP 12 // Sets clients code page for the file -#define BCPUNICODEFILE 13 // Sets that the file contains unicode header -#define BCPTEXTFILE 14 // Sets BCP mode to expect a text file and to detect Unicode or ANSI automatically -#define BCPFILEFMT 15 // Sets file format version -#define BCPFMTXML 16 // Sets the format file type to xml -#define BCPFIRSTEX 17 // Starting Row for BCP operation (64 bit) -#define BCPLASTEX 18 // Ending Row for BCP operation (64 bit) -#define BCPROWCOUNT 19 // Total Number of Rows Copied (64 bit) -#define BCPDELAYREADFMT 20 // Delay reading format file unil bcp_exec -// BCPFILECP values -// Any valid code page that is installed on the client can be passed plus: -#define BCPFILECP_ACP 0 // Data in file is in Windows code page -#define BCPFILECP_OEMCP 1 // Data in file is in OEM code page (default) -#define BCPFILECP_RAW (-1)// Data in file is in Server code page (no conversion) -// bcp_collen definition -#define SQL_VARLEN_DATA (-10) // Use default length for column -// BCP column format properties -#define BCP_FMT_TYPE 0x01 -#define BCP_FMT_INDICATOR_LEN 0x02 -#define BCP_FMT_DATA_LEN 0x03 -#define BCP_FMT_TERMINATOR 0x04 -#define BCP_FMT_SERVER_COL 0x05 -#define BCP_FMT_COLLATION 0x06 -#define BCP_FMT_COLLATION_ID 0x07 -// bcp_setbulkmode properties -#define BCP_OUT_CHARACTER_MODE 0x01 -#define BCP_OUT_WIDE_CHARACTER_MODE 0x02 -#define BCP_OUT_NATIVE_TEXT_MODE 0x03 -#define BCP_OUT_NATIVE_MODE 0x04 - - - -// BCP functions -DBINT SQL_API bcp_batch (HDBC); -RETCODE SQL_API bcp_bind (HDBC, LPCBYTE, INT, DBINT, LPCBYTE, INT, INT, INT); -RETCODE SQL_API bcp_colfmt (HDBC, INT, BYTE, INT, DBINT, LPCBYTE, INT, INT); -RETCODE SQL_API bcp_collen (HDBC, DBINT, INT); -RETCODE SQL_API bcp_colptr (HDBC, LPCBYTE, INT); -RETCODE SQL_API bcp_columns (HDBC, INT); -RETCODE SQL_API bcp_control (HDBC, INT, void *); -DBINT SQL_API bcp_done (HDBC); -RETCODE SQL_API bcp_exec (HDBC, LPDBINT); -RETCODE SQL_API bcp_getcolfmt (HDBC, INT, INT, void *, INT, INT *); -RETCODE SQL_API bcp_initA (HDBC, LPCSTR, LPCSTR, LPCSTR, INT); -RETCODE SQL_API bcp_initW (HDBC, LPCWSTR, LPCWSTR, LPCWSTR, INT); -RETCODE SQL_API bcp_moretext (HDBC, DBINT, LPCBYTE); -RETCODE SQL_API bcp_readfmtA (HDBC, LPCSTR); -RETCODE SQL_API bcp_readfmtW (HDBC, LPCWSTR); -RETCODE SQL_API bcp_sendrow (HDBC); -RETCODE SQL_API bcp_setbulkmode (HDBC, INT, _In_reads_bytes_(cbField) void*, INT cbField, _In_reads_bytes_(cbRow) void *, INT cbRow); -RETCODE SQL_API bcp_setcolfmt (HDBC, INT, INT, void *, INT); -RETCODE SQL_API bcp_writefmtA (HDBC, LPCSTR); -RETCODE SQL_API bcp_writefmtW (HDBC, LPCWSTR); -CHAR* SQL_API dbprtypeA (INT); -WCHAR* SQL_API dbprtypeW (INT); -CHAR* SQL_API bcp_gettypenameA (INT, DBBOOL); -WCHAR* SQL_API bcp_gettypenameW (INT, DBBOOL); - -#ifdef UNICODE -#define bcp_init bcp_initW -#define bcp_readfmt bcp_readfmtW -#define bcp_writefmt bcp_writefmtW -#define dbprtype dbprtypeW -#define bcp_gettypename bcp_gettypenameW -#define BCPHINTS BCPHINTSW -#else -#define bcp_init bcp_initA -#define bcp_readfmt bcp_readfmtA -#define bcp_writefmt bcp_writefmtA -#define dbprtype dbprtypeA -#define bcp_gettypename bcp_gettypenameA -#define BCPHINTS BCPHINTSA -#endif // UNICODE - -#endif // SQLNCLI_NO_BCP - -// The following options have been deprecated -#define SQL_FAST_CONNECT (SQL_COPT_SS_BASE+0) -// Defines for use with SQL_FAST_CONNECT - only useable before connecting -#define SQL_FC_OFF 0L // Fast connect is off -#define SQL_FC_ON 1L // Fast connect is on -#define SQL_FC_DEFAULT SQL_FC_OFF -#define SQL_COPT_SS_ANSI_OEM (SQL_COPT_SS_BASE+6) -#define SQL_AO_OFF 0L -#define SQL_AO_ON 1L -#define SQL_AO_DEFAULT SQL_AO_OFF -#define SQL_CA_SS_BASE_COLUMN_NAME SQL_DESC_BASE_COLUMN_NAME - - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // ODBCVER - - - -#ifdef __cplusplus -extern "C" { -#endif -#include - -//The following facilitates opening a handle to a SQL filestream -typedef enum _SQL_FILESTREAM_DESIRED_ACCESS { - SQL_FILESTREAM_READ = 0, - SQL_FILESTREAM_WRITE = 1, - SQL_FILESTREAM_READWRITE = 2 -} SQL_FILESTREAM_DESIRED_ACCESS; -#define SQL_FILESTREAM_OPEN_FLAG_ASYNC 0x00000001L -#define SQL_FILESTREAM_OPEN_FLAG_NO_BUFFERING 0x00000002L -#define SQL_FILESTREAM_OPEN_FLAG_NO_WRITE_THROUGH 0x00000004L -#define SQL_FILESTREAM_OPEN_FLAG_SEQUENTIAL_SCAN 0x00000008L -#define SQL_FILESTREAM_OPEN_FLAG_RANDOM_ACCESS 0x00000010L - - -HANDLE __stdcall OpenSqlFilestream ( - LPCWSTR FilestreamPath, - SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess, - ULONG OpenOptions, - _In_reads_bytes_(FilestreamTransactionContextLength) - LPBYTE FilestreamTransactionContext, - SSIZE_T FilestreamTransactionContextLength, - PLARGE_INTEGER AllocationSize); -#define FSCTL_SQL_FILESTREAM_FETCH_OLD_CONTENT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 2392, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#ifdef __cplusplus -} // extern "C" -#endif - - -#endif //__sqlncli_h__ - -#define SQL_COPT_SS_CONNECT_RETRY_COUNT (SQL_COPT_SS_BASE+34) // Post connection attribute used to get ConnectRetryCount -#define SQL_COPT_SS_CONNECT_RETRY_INTERVAL (SQL_COPT_SS_BASE+35) // Post connection attribute used to get ConnectRetryInterval -#ifdef SQL_COPT_SS_MAX_USED -#undef SQL_COPT_SS_MAX_USED -#endif // SQL_COPT_SS_MAX_USED -#define SQL_COPT_SS_MAX_USED SQL_COPT_SS_CONNECT_RETRY_INTERVAL - - -#ifndef _SQLUSERINSTANCE_H_ -#define _SQLUSERINSTANCE_H_ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Recommended buffer size to store a LocalDB connection string -#define LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE 260 - -// type definition for LocalDBCreateInstance function -typedef HRESULT __cdecl FnLocalDBCreateInstance ( - // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - _In_z_ PCWSTR wszVersion, - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -); - -// type definition for pointer to LocalDBCreateInstance function -typedef FnLocalDBCreateInstance* PFnLocalDBCreateInstance; - -// type definition for LocalDBStartInstance function -typedef HRESULT __cdecl FnLocalDBStartInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags, - // O the buffer to store the connection string to the LocalDB instance - _Out_writes_opt_z_(*lpcchSqlConnection) LPWSTR wszSqlConnection, - // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. - _Inout_opt_ LPDWORD lpcchSqlConnection -); - -// type definition for pointer to LocalDBStartInstance function -typedef FnLocalDBStartInstance* PFnLocalDBStartInstance; - -// Flags for the LocalDBFormatMessage function -#define LOCALDB_TRUNCATE_ERR_MESSAGE 0x0001L - -// type definition for LocalDBFormatMessage function -typedef HRESULT __cdecl FnLocalDBFormatMessage( - // I the LocalDB error code - _In_ HRESULT hrLocalDB, - // I Available flags: - // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, - // the error message will be truncated to fit into the buffer - _In_ DWORD dwFlags, - // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - _In_ DWORD dwLanguageId, - // O the buffer to store the LocalDB error message - _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, - // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. If the function succeeds - // contains the number of characters in the message, excluding the trailing null - _Inout_ LPDWORD lpcchMessage -); - -// type definition for function pointer to LocalDBFormatMessage function -typedef FnLocalDBFormatMessage* PFnLocalDBFormatMessage; - - -// MessageId: LOCALDB_ERROR_NOT_INSTALLED -// -// MessageText: -// -// LocalDB is not installed. -// -#define LOCALDB_ERROR_NOT_INSTALLED ((HRESULT)0x89C50116L) - -//--------------------------------------------------------------------- -// Function: LocalDBCreateInstance -// -// Description: This function will create the new LocalDB instance. -// -// Available Flags: -// No flags available. Reserved for future use. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_VERSION, if the version parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INVALID_OPERATION, if the user tries to create a default instance -// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH -// LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED, if the specified service level is not installed -// LOCALDB_ERROR_INSTANCE_FOLDER_ALREADY_EXISTS, if the instance folder already exists and is not empty -// LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION, if the specified instance already exists but with lower version -// LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER, if a folder cannot be created under %userprofile% -// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified -// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created -// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed. -// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted -// -FnLocalDBCreateInstance LocalDBCreateInstance; - -//--------------------------------------------------------------------- -// Function: LocalDBStartInstance -// -// Description: This function will start the given LocalDB instance. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_CONNECTION, if the wszSqlConnection parameter is NULL -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the buffer wszSqlConnection is too small -// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH - -// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified -// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created -// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed. -// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted -// -FnLocalDBStartInstance LocalDBStartInstance; - -// type definition for LocalDBStopInstance function -typedef HRESULT __cdecl FnLocalDBStopInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I Available flags: - // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately - // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - _In_ DWORD dwFlags, - // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately - // without waiting for LocalDB instance to stop - _In_ ULONG ulTimeout -); - -// type definition for pointer to LocalDBStopInstance function -typedef FnLocalDBStopInstance* PFnLocalDBStopInstance; - -// Flags for the StopLocalDBInstance function -#define LOCALDB_SHUTDOWN_KILL_PROCESS 0x0001L -#define LOCALDB_SHUTDOWN_WITH_NOWAIT 0x0002L - -//--------------------------------------------------------------------- -// Function: LocalDBStopInstance -// -// Description: This function will shutdown the given LocalDB instance. -// If the flag LOCALDB_SHUTDOWN_KILL_PROCESS is set, the LocalDB instance will be killed immediately. -// IF the flag LOCALDB_SHUTDOWN_WITH_NOWAIT is set, the LocalDB instance will shutdown with NOWAIT option. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_WAIT_TIMEOUT - if this function has not finished in given time -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBStopInstance LocalDBStopInstance; - -// type definition for LocalDBDeleteInstance function -typedef HRESULT __cdecl FnLocalDBDeleteInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -); - -// type definition for pointer to LocalDBDeleteInstance function -typedef FnLocalDBDeleteInstance* PFnLocalDBDeleteInstance; - -//--------------------------------------------------------------------- -// Function: LocalDBDeleteInstance -// -// Description: This function will remove the given LocalDB instance. If the given instance is running this function will -// fail with the error code LOCALDB_ERROR_INSTANCE_BUSY. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INSTANCE_BUSY, if the given instance is running -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBDeleteInstance LocalDBDeleteInstance; - -// Function: LocalDBFormatMessage -// -// Description: This function will return the localized textual description for the given LocalDB error -// -// Available Flags: -// LOCALDB_TRUNCATE_ERR_MESSAGE - the error message should be truncated to fit into the provided buffer -// -// Return Value: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_UNKNOWN_HRESULT, if the given HRESULT is unknown -// LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID, if the given language id is unknown (0 is recommended for the // default language) -// LOCALDB_ERROR_UNKNOWN_ERROR_CODE, if the LocalDB error code is unknown -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the input buffer is too short and LOCALDB_TRUNCATE_ERR_MESSAGE flag -// is not set -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBFormatMessage LocalDBFormatMessage; - -#define MAX_LOCALDB_INSTANCE_NAME_LENGTH 128 -#define MAX_LOCALDB_PARENT_INSTANCE_LENGTH MAX_INSTANCE_NAME - -typedef WCHAR TLocalDBInstanceName[MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1]; -typedef TLocalDBInstanceName* PTLocalDBInstanceName; - -// type definition for LocalDBGetInstances function -typedef HRESULT __cdecl FnLocalDBGetInstances( - // O buffer for a LocalDB instance names - _Out_ PTLocalDBInstanceName pInstanceNames, - // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, - // has the number of existing LocalDB instances - _Inout_ LPDWORD lpdwNumberOfInstances -); - -// type definition for pointer to LocalDBGetInstances function -typedef FnLocalDBGetInstances* PFnLocalDBGetInstances; - -// Function: LocalDBGetInstances -// -// Description: This function returns names for all existing Local DB instances -// -// Usage Example: -// DWORD dwN = 0; -// LocalDBGetInstances(NULL, &dwN); - -// PTLocalDBInstanceName insts = (PTLocalDBInstanceName) malloc(dwN * sizeof(TLocalDBInstanceName)); -// LocalDBGetInstances(insts, &dwN); - -// for (int i = 0; i < dwN; i++) -// wprintf(L"%s\n", insts[i]); -// -// Return values: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBGetInstances LocalDBGetInstances; - -// SID string format: S - Revision(1B) - Authority ID (6B) {- Sub authority ID (4B)} * max 15 sub-authorities = 1 + 1 + 3 + 1 + 15 + (1 + 10) * 15 -#define MAX_STRING_SID_LENGTH 186 - -#pragma pack(push) -#pragma pack(8) - -// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetInstanceInfo in sqluserinstance.cpp file. -// -typedef struct _LocalDBInstanceInfo -{ - DWORD cbLocalDBInstanceInfoSize; - TLocalDBInstanceName wszInstanceName; - BOOL bExists; - BOOL bConfigurationCorrupted; - BOOL bIsRunning; - DWORD dwMajor; - DWORD dwMinor; - DWORD dwBuild; - DWORD dwRevision; - FILETIME ftLastStartDateUTC; - WCHAR wszConnection[LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE]; - BOOL bIsShared; - TLocalDBInstanceName wszSharedInstanceName; - WCHAR wszOwnerSID[MAX_STRING_SID_LENGTH + 1]; - BOOL bIsAutomatic; -} LocalDBInstanceInfo; - -#pragma pack(pop) - -typedef LocalDBInstanceInfo* PLocalDBInstanceInfo; - -// type definition for LocalDBGetInstanceInfo function -typedef HRESULT __cdecl FnLocalDBGetInstanceInfo( - // I the LocalDB instance name - _In_z_ PCWSTR wszInstanceName, - // O instance information - _Out_ PLocalDBInstanceInfo pInfo, - // I Size of LocalDBInstanceInfo structure in bytes - _In_ DWORD cbInfo); - -// type definition for pointer to LocalDBGetInstances function -typedef FnLocalDBGetInstanceInfo* PFnLocalDBGetInstanceInfo; - -// Function: LocalDBGetInstanceInfo -// -// Description: This function returns information about the given instance. -// -// Return values: -// S_OK, if the function succeeds -// -// ERROR_INVALID_PARAMETER, if some of the parameters is invalid -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBGetInstanceInfo LocalDBGetInstanceInfo; - -// Version has format: Major.Minor[.Build[.Revision]]. Each of components is 32bit integer which is at most 40 digits and 3 dots -// -#define MAX_LOCALDB_VERSION_LENGTH 43 - -typedef WCHAR TLocalDBVersion[MAX_LOCALDB_VERSION_LENGTH + 1]; -typedef TLocalDBVersion* PTLocalDBVersion; - -// type definition for LocalDBGetVersions function -typedef HRESULT __cdecl FnLocalDBGetVersions( - // O buffer for installed LocalDB versions - _Out_ PTLocalDBVersion pVersions, - // I/O on input has the number slots for versions in the pVersions buffer. On output, - // has the number of existing LocalDB versions - _Inout_ LPDWORD lpdwNumberOfVersions -); - -// type definition for pointer to LocalDBGetVersions function -typedef FnLocalDBGetVersions* PFnLocalDBGetVersions; - -// Function: LocalDBGetVersions -// -// Description: This function returns all installed LocalDB versions. Returned versions will be in format Major.Minor -// -// Usage Example: -// DWORD dwN = 0; -// LocalDBGetVersions(NULL, &dwN); - -// PTLocalDBVersion versions = (PTLocalDBVersion) malloc(dwN * sizeof(TLocalDBVersion)); -// LocalDBGetVersions(insts, &dwN); - -// for (int i = 0; i < dwN; i++) -// wprintf(L"%s\n", insts[i]); -// -// Return values: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurs. -// -FnLocalDBGetVersions LocalDBGetVersions; - -#pragma pack(push) -#pragma pack(8) - -// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetVersionInfo in sqluserinstance.cpp file. -// -typedef struct _LocalDBVersionInfo -{ - DWORD cbLocalDBVersionInfoSize; - TLocalDBVersion wszVersion; - BOOL bExists; - DWORD dwMajor; - DWORD dwMinor; - DWORD dwBuild; - DWORD dwRevision; -} LocalDBVersionInfo; - -#pragma pack(pop) - -typedef LocalDBVersionInfo* PLocalDBVersionInfo; - -// type definition for LocalDBGetVersionInfo function -typedef HRESULT __cdecl FnLocalDBGetVersionInfo( - // I LocalDB version string - _In_z_ PCWSTR wszVersion, - // O version information - _Out_ PLocalDBVersionInfo pVersionInfo, - // I Size of LocalDBVersionInfo structure in bytes - _In_ DWORD cbVersionInfo -); - -// type definition for pointer to LocalDBGetVersionInfo function -typedef FnLocalDBGetVersionInfo* PFnLocalDBGetVersionInfo; - -// Function: LocalDBGetVersionInfo -// -// Description: This function returns information about the given LocalDB version -// -// Return values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INTERNAL_ERROR, if some internal error occurred -// LOCALDB_ERROR_INVALID_PARAMETER, if a input parameter is invalid -// -FnLocalDBGetVersionInfo LocalDBGetVersionInfo; - -typedef HRESULT __cdecl FnLocalDBStartTracing(); -typedef FnLocalDBStartTracing* PFnLocalDBStartTracing; - -// Function: LocalDBStartTracing -// -// Description: This function will write in registry that Tracing sessions should be started for the current user. -// -// Return values: -// S_OK - on success -// Propper HRESULT in case of failure -// -FnLocalDBStartTracing LocalDBStartTracing; - -typedef HRESULT __cdecl FnLocalDBStopTracing(); -typedef FnLocalDBStopTracing* PFnFnLocalDBStopTracing; - -// Function: LocalDBStopTracing -// -// Description: This function will write in registry that Tracing sessions should be stopped for the current user. -// -// Return values: -// S_OK - on success -// Propper HRESULT in case of failure -// -FnLocalDBStopTracing LocalDBStopTracing; - -// type definition for LocalDBShareInstance function -typedef HRESULT __cdecl FnLocalDBShareInstance( - // I the SID of the LocalDB instance owner - _In_opt_ PSID pOwnerSID, - // I the private name of LocalDB instance which should be shared - _In_z_ PCWSTR wszPrivateLocalDBInstanceName, - // I the public shared name - _In_z_ PCWSTR wszSharedName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags); - -// type definition for pointer to LocalDBShareInstance function -typedef FnLocalDBShareInstance* PFnLocalDBShareInstance; - -// Function: LocalDBShareInstance -// -// Description: This function will share the given private instance of the given user with the given shared name. -// This function has to be executed elevated. -// -// Return values: -// HRESULT -// -FnLocalDBShareInstance LocalDBShareInstance; - -// type definition for LocalDBUnshareInstance function -typedef HRESULT __cdecl FnLocalDBUnshareInstance( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags); - -// type definition for pointer to LocalDBUnshareInstance function -typedef FnLocalDBUnshareInstance* PFnLocalDBUnshareInstance; - -// Function: LocalDBUnshareInstance -// -// Description: This function unshares the given LocalDB instance. -// If a shared name is given then that shared instance will be unshared. -// If a private name is given then we will check if the caller -// shares a private instance with the given private name and unshare it. -// -// Return values: -// HRESULT -// -FnLocalDBUnshareInstance LocalDBUnshareInstance; - -#ifdef __cplusplus -} // extern "C" -#endif - -#if defined(LOCALDB_DEFINE_PROXY_FUNCTIONS) -//--------------------------------------------------------------------- -// The following section is enabled only if the constant LOCALDB_DEFINE_PROXY_FUNCTIONS -// is defined. It provides an implementation of proxies for each of the LocalDB APIs. -// The proxy implementations use a common function to bind to entry points in the -// latest installed SqlUserInstance DLL, and then forward the requests. -// -// The current implementation loads the SqlUserInstance DLL on the first call into -// a proxy function. There is no provision for unloading the DLL. Note that if the -// process includes multiple binaries (EXE and one or more DLLs), each of them could -// load a separate instance of the SqlUserInstance DLL. -// -// For future consideration: allow the SqlUserInstance DLL to be unloaded dynamically. -// -// WARNING: these functions must not be called in DLL initialization, since a deadlock -// could result loading dependent DLLs. -//--------------------------------------------------------------------- - -// This macro provides the body for each proxy function. -// -#define LOCALDB_PROXY(LocalDBFn) static Fn##LocalDBFn* pfn##LocalDBFn = NULL; if (!pfn##LocalDBFn) {HRESULT hr = LocalDBGetPFn(#LocalDBFn, (FARPROC *)&pfn##LocalDBFn); if (FAILED(hr)) return hr;} return (*pfn##LocalDBFn) - -// Structure and function to parse the "Installed Versions" registry subkeys -// -typedef struct { - DWORD dwComponent[2]; - WCHAR wszKeyName[256]; -} Version; - -// The following algorithm is intended to match, in part, the .NET Version class. -// A maximum of two components are allowed, which must be separated with a period. -// Valid: "11", "11.0" -// Invalid: "", ".0", "11.", "11.0." -// -static BOOL ParseVersion(Version * pVersion) -{ - pVersion->dwComponent[0] = 0; - pVersion->dwComponent[1] = 0; - WCHAR * pwch = pVersion->wszKeyName; - - for (int i = 0; i<2; i++) - { - LONGLONG llVal = 0; - BOOL fHaveDigit = FALSE; - - while (*pwch >= L'0' && *pwch <= L'9') - { - llVal = llVal * 10 + (*pwch++ - L'0'); - fHaveDigit = TRUE; - - if (llVal > 0x7fffffff) - { - return FALSE; - } - } - - if (!fHaveDigit) - return FALSE; - - pVersion->dwComponent[i] = (DWORD) llVal; - - if (*pwch == L'\0') - return TRUE; - - if (*pwch != L'.') - return FALSE; - - pwch++; - } - // If we get here, the version string was terminated with L'.', which is not valid - // - return FALSE; -} - -#include - -// This function loads the correct LocalDB API DLL (if required) and returns a pointer to a procedure. -// Note that the first-loaded API DLL for the process will be used until process termination: installation of -// a new version of the API will not be recognized after first load. -// -static HRESULT LocalDBGetPFn(LPCSTR szLocalDBFn, FARPROC *pfnLocalDBFn) -{ - static volatile HMODULE hLocalDBDll = NULL; - - if (!hLocalDBDll) - { - LONG ec; - HKEY hkeyVersions = NULL; - HKEY hkeyVersion = NULL; - Version verHigh = {0}; - Version verCurrent; - DWORD cchKeyName; - DWORD dwValueType; - WCHAR wszLocalDBDll[MAX_PATH+1]; - DWORD cbLocalDBDll = sizeof(wszLocalDBDll) - sizeof(WCHAR); // to deal with RegQueryValueEx null-termination quirk - HMODULE hLocalDBDllTemp = NULL; - - if (ERROR_SUCCESS != (ec = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions", 0, KEY_READ, &hkeyVersions))) - { - goto Cleanup; - } - - for (int i = 0; ; i++) - { - cchKeyName = 256; - if (ERROR_SUCCESS != (ec = RegEnumKeyExW(hkeyVersions, i, verCurrent.wszKeyName, &cchKeyName, 0, NULL, NULL, NULL))) - { - if (ERROR_NO_MORE_ITEMS == ec) - { - break; - } - goto Cleanup; - } - - if (!ParseVersion(&verCurrent)) - { - continue; // invalid version syntax - } - - if (verCurrent.dwComponent[0] > verHigh.dwComponent[0] || - (verCurrent.dwComponent[0] == verHigh.dwComponent[0] && verCurrent.dwComponent[1] > verHigh.dwComponent[1])) - { - verHigh = verCurrent; - } - } - if (!verHigh.wszKeyName[0]) - { - // ec must be ERROR_NO_MORE_ITEMS here - // - assert(ec == ERROR_NO_MORE_ITEMS); - - // We will change the error code to ERROR_FILE_NOT_FOUND in order to indicate that - // LocalDB instalation is not found. Registry key "SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions" exists - // but it is empty. - // - ec = ERROR_FILE_NOT_FOUND; - goto Cleanup; - } - - if (ERROR_SUCCESS != (ec = RegOpenKeyExW(hkeyVersions, verHigh.wszKeyName, 0, KEY_READ, &hkeyVersion))) - { - goto Cleanup; - } - if (ERROR_SUCCESS != (ec = RegQueryValueExW(hkeyVersion, L"InstanceAPIPath", NULL, &dwValueType, (PBYTE) wszLocalDBDll, &cbLocalDBDll))) - { - goto Cleanup; - } - if (dwValueType != REG_SZ) - { - ec = ERROR_INVALID_DATA; - goto Cleanup; - } - // Ensure string value null-terminated - // Note that we left a spare character in the output buffer for RegQueryValueEx for this purpose - // - wszLocalDBDll[cbLocalDBDll/sizeof(WCHAR)] = L'\0'; - - hLocalDBDllTemp = LoadLibraryW(wszLocalDBDll); - if (NULL == hLocalDBDllTemp) - { - ec = GetLastError(); - goto Cleanup; - } - if (NULL == InterlockedCompareExchangePointer((volatile PVOID *)&hLocalDBDll, hLocalDBDllTemp, NULL)) - { - // We were the winner: we gave away our DLL handle - // - hLocalDBDllTemp = NULL; - } - ec = ERROR_SUCCESS; -Cleanup: - if (hLocalDBDllTemp) - FreeLibrary(hLocalDBDllTemp); - if (hkeyVersion) - RegCloseKey(hkeyVersion); - if (hkeyVersions) - RegCloseKey(hkeyVersions); - - // Error code ERROR_FILE_NOT_FOUND can occure if registry hive with installed LocalDB versions is missing. - // In that case we should return the LocalDB specific error code - // - if (ec == ERROR_FILE_NOT_FOUND) - return LOCALDB_ERROR_NOT_INSTALLED; - - if (ec != ERROR_SUCCESS) - return HRESULT_FROM_WIN32(ec); - } - - FARPROC pfn = GetProcAddress(hLocalDBDll, szLocalDBFn); - - if (!pfn) - { - return HRESULT_FROM_WIN32(GetLastError()); - } - *pfnLocalDBFn = pfn; - return S_OK; -} - -// The following proxy functions forward calls to the latest LocalDB API DLL. -// - -HRESULT __cdecl -LocalDBCreateInstance ( - // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - _In_z_ PCWSTR wszVersion, - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -) -{ - LOCALDB_PROXY(LocalDBCreateInstance)(wszVersion, pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBStartInstance( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags, - // O the buffer to store the connection string to the LocalDB instance - _Out_writes_z__opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, - // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. - _Inout_opt_ LPDWORD lpcchSqlConnection -) -{ - LOCALDB_PROXY(LocalDBStartInstance)(pInstanceName, dwFlags, wszSqlConnection, lpcchSqlConnection); -} - -HRESULT __cdecl -LocalDBStopInstance ( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I Available flags: - // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately - // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - _In_ DWORD dwFlags, - // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately - // without waiting for LocalDB instance to stop - _In_ ULONG ulTimeout -) -{ - LOCALDB_PROXY(LocalDBStopInstance)(pInstanceName, dwFlags, ulTimeout); -} - -HRESULT __cdecl -LocalDBDeleteInstance ( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -) -{ - LOCALDB_PROXY(LocalDBDeleteInstance)(pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBFormatMessage( - // I the LocalDB error code - _In_ HRESULT hrLocalDB, - // I Available flags: - // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, - // the error message will be truncated to fit into the buffer - _In_ DWORD dwFlags, - // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - _In_ DWORD dwLanguageId, - // O the buffer to store the LocalDB error message - _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, - // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. If the function succeeds - // contains the number of characters in the message, excluding the trailing null - _Inout_ LPDWORD lpcchMessage -) -{ - LOCALDB_PROXY(LocalDBFormatMessage)(hrLocalDB, dwFlags, dwLanguageId, wszMessage, lpcchMessage); -} - -HRESULT __cdecl -LocalDBGetInstances( - // O buffer with instance names - _Out_ PTLocalDBInstanceName pInstanceNames, - // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, - // has the number of existing LocalDB instances - _Inout_ LPDWORD lpdwNumberOfInstances -) -{ - LOCALDB_PROXY(LocalDBGetInstances)(pInstanceNames, lpdwNumberOfInstances); -} - -HRESULT __cdecl -LocalDBGetInstanceInfo( - // I the instance name - _In_z_ PCWSTR wszInstanceName, - // O instance information - _Out_ PLocalDBInstanceInfo pInfo, - // I Size of LocalDBInstanceInfo structure in bytes - _In_ DWORD cbInfo -) -{ - LOCALDB_PROXY(LocalDBGetInstanceInfo)(wszInstanceName, pInfo, cbInfo); -} - -HRESULT __cdecl -LocalDBStartTracing() -{ - LOCALDB_PROXY(LocalDBStartTracing)(); -} - -HRESULT __cdecl -LocalDBStopTracing() -{ - LOCALDB_PROXY(LocalDBStopTracing)(); -} - -HRESULT __cdecl -LocalDBShareInstance( - // I the SID of the LocalDB instance owner - _In_opt_ PSID pOwnerSID, - // I the private name of LocalDB instance which should be shared - _In_z_ PCWSTR wszLocalDBInstancePrivateName, - // I the public shared name - _In_z_ PCWSTR wszSharedName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags) -{ - LOCALDB_PROXY(LocalDBShareInstance)(pOwnerSID, wszLocalDBInstancePrivateName, wszSharedName, dwFlags); -} - -HRESULT __cdecl -LocalDBGetVersions( - // O buffer for installed LocalDB versions - _Out_ PTLocalDBVersion pVersions, - // I/O on input has the number slots for versions in the pVersions buffer. On output, - // has the number of existing LocalDB versions - _Inout_ LPDWORD lpdwNumberOfVersions -) -{ - LOCALDB_PROXY(LocalDBGetVersions)(pVersions, lpdwNumberOfVersions); -} - -HRESULT __cdecl -LocalDBUnshareInstance( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags) -{ - LOCALDB_PROXY(LocalDBUnshareInstance)(pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBGetVersionInfo( - // I LocalDB version string - _In_z_ PCWSTR wszVersion, - // O version information - _Out_ PLocalDBVersionInfo pVersionInfo, - // I Size of LocalDBVersionInfo structure in bytes - _In_ DWORD cbVersionInfo) -{ - LOCALDB_PROXY(LocalDBGetVersionInfo)(wszVersion, pVersionInfo, cbVersionInfo); -} - -#endif - -#endif // _SQLUSERINSTANCE_H_ - -//----------------------------------------------------------------------------- -// File: sqluserinstancemsgs.mc -// -// Copyright: Copyright (c) Microsoft Corporation -//----------------------------------------------------------------------------- -#ifndef _LOCALDB_MESSAGES_H_ -#define _LOCALDB_MESSAGES_H_ -// Header section -// -// Section with the LocalDB messages -// -// -// Values are 32 bit values laid out as follows: -// -// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 -// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 -// +-+-+-+-+-+---------------------+-------------------------------+ -// |S|R|C|N|r| Facility | Code | -// +-+-+-+-+-+---------------------+-------------------------------+ -// -// where -// -// S - Severity - indicates success/fail -// -// 0 - Success -// 1 - Fail (COERROR) -// -// R - reserved portion of the facility code, corresponds to NT's -// second severity bit. -// -// C - reserved portion of the facility code, corresponds to NT's -// C field. -// -// N - reserved portion of the facility code. Used to indicate a -// mapped NT status value. -// -// r - reserved portion of the facility code. Reserved for internal -// use. Used to indicate HRESULT values that are not status -// values, but are instead message ids for display strings. -// -// Facility - is the facility code -// -// Code - is the facility's status code -// -// -// Define the facility codes -// -#define FACILITY_LOCALDB 0x9C5 - - -// -// Define the severity codes -// -#define LOCALDB_SEVERITY_SUCCESS 0x0 -#define LOCALDB_SEVERITY_ERROR 0x2 - - -// -// MessageId: LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot create folder for the LocalDB instance at: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER ((HRESULT)0x89C50100L) - -// -// MessageId: LOCALDB_ERROR_INVALID_PARAMETER -// -// MessageText: -// -// The parameter for the LocalDB Instance API method is incorrect. Consult the API documentation. -// -#define LOCALDB_ERROR_INVALID_PARAMETER ((HRESULT)0x89C50101L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION -// -// MessageText: -// -// Unable to create the LocalDB instance with specified version. An instance with the same name already exists, but it has lower version than the specified version. -// -#define LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION ((HRESULT)0x89C50102L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER -// -// MessageText: -// -// Cannot access the user profile folder for local application data (%%LOCALAPPDATA%%). -// -#define LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER ((HRESULT)0x89C50103L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG -// -// MessageText: -// -// The full path length of the LocalDB instance folder is longer than MAX_PATH. The instance must be stored in folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG ((HRESULT)0x89C50104L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot access LocalDB instance folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50105L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY -// -// MessageText: -// -// Unexpected error occurred while trying to access the LocalDB instance registry configuration. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY ((HRESULT)0x89C50106L) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_INSTANCE -// -// MessageText: -// -// The specified LocalDB instance does not exist. -// -#define LOCALDB_ERROR_UNKNOWN_INSTANCE ((HRESULT)0x89C50107L) - -// -// MessageId: LOCALDB_ERROR_INTERNAL_ERROR -// -// MessageText: -// -// Unexpected error occurred inside a LocalDB instance API method call. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_INTERNAL_ERROR ((HRESULT)0x89C50108L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY -// -// MessageText: -// -// Unexpected error occurred while trying to modify the registry configuration for the LocalDB instance. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY ((HRESULT)0x89C50109L) - -// -// MessageId: LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED -// -// MessageText: -// -// Error occurred during LocalDB instance startup: SQL Server process failed to start. -// -#define LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED ((HRESULT)0x89C5010AL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT -// -// MessageText: -// -// LocalDB instance is corrupted. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT ((HRESULT)0x89C5010BL) - -// -// MessageId: LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS -// -// MessageText: -// -// Error occurred during LocalDB instance startup: unable to create the SQL Server process. -// -#define LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS ((HRESULT)0x89C5010CL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_VERSION -// -// MessageText: -// -// The specified LocalDB version is not available on this computer. -// -#define LOCALDB_ERROR_UNKNOWN_VERSION ((HRESULT)0x89C5010DL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID -// -// MessageText: -// -// Error getting the localized error message. The language specified by 'Language ID' parameter is unknown. -// -#define LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID ((HRESULT)0x89C5010EL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_STOP_FAILED -// -// MessageText: -// -// Stop operation for LocalDB instance failed to complete within the specified time. -// -#define LOCALDB_ERROR_INSTANCE_STOP_FAILED ((HRESULT)0x89C5010FL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_ERROR_CODE -// -// MessageText: -// -// Error getting the localized error message. The specified error code is unknown. -// -#define LOCALDB_ERROR_UNKNOWN_ERROR_CODE ((HRESULT)0x89C50110L) - -// -// MessageId: LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED -// -// MessageText: -// -// The LocalDB version available on this workstation is lower than the requested LocalDB version. -// -#define LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED ((HRESULT)0x89C50111L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_BUSY -// -// MessageText: -// -// Requested operation on LocalDB instance cannot be performed because specified instance is currently in use. Stop the instance and try again. -// -#define LOCALDB_ERROR_INSTANCE_BUSY ((HRESULT)0x89C50112L) - -// -// MessageId: LOCALDB_ERROR_INVALID_OPERATION -// -// MessageText: -// -// Default LocalDB instances cannot be created, stopped or deleted manually. -// -#define LOCALDB_ERROR_INVALID_OPERATION ((HRESULT)0x89C50113L) - -// -// MessageId: LOCALDB_ERROR_INSUFFICIENT_BUFFER -// -// MessageText: -// -// The buffer passed to the LocalDB instance API method has insufficient size. -// -#define LOCALDB_ERROR_INSUFFICIENT_BUFFER ((HRESULT)0x89C50114L) - -// -// MessageId: LOCALDB_ERROR_WAIT_TIMEOUT -// -// MessageText: -// -// Timeout occurred inside the LocalDB instance API method. -// -#define LOCALDB_ERROR_WAIT_TIMEOUT ((HRESULT)0x89C50115L) - -// MessageId=0x0116 message id is reserved. This message ID will be used for error LOCALDB_ERROR_NOT_INSTALLED. -// This message is specific since it has to be present in SqlUserIntsnace.h because it can be returned by discovery API. -// -// -// MessageId: LOCALDB_ERROR_XEVENT_FAILED -// -// MessageText: -// -// Failed to start XEvent engine within the LocalDB Instance API. -// -#define LOCALDB_ERROR_XEVENT_FAILED ((HRESULT)0x89C50117L) - -// -// MessageId: LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED -// -// MessageText: -// -// Cannot create an automatic instance. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED ((HRESULT)0x89C50118L) - -// -// MessageId: LOCALDB_ERROR_SHARED_NAME_TAKEN -// -// MessageText: -// -// Cannot create a shared instance. The specified shared instance name is already in use. -// -#define LOCALDB_ERROR_SHARED_NAME_TAKEN ((HRESULT)0x89C50119L) - -// -// MessageId: LOCALDB_ERROR_CALLER_IS_NOT_OWNER -// -// MessageText: -// -// API caller is not LocalDB instance owner. -// -#define LOCALDB_ERROR_CALLER_IS_NOT_OWNER ((HRESULT)0x89C5011AL) - -// -// MessageId: LOCALDB_ERROR_INVALID_INSTANCE_NAME -// -// MessageText: -// -// Specified LocalDB instance name is invalid. -// -#define LOCALDB_ERROR_INVALID_INSTANCE_NAME ((HRESULT)0x89C5011BL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_ALREADY_SHARED -// -// MessageText: -// -// The specified LocalDB instance is already shared with different shared name. -// -#define LOCALDB_ERROR_INSTANCE_ALREADY_SHARED ((HRESULT)0x89C5011CL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_NOT_SHARED -// -// MessageText: -// -// The specified LocalDB instance is not shared. -// -#define LOCALDB_ERROR_INSTANCE_NOT_SHARED ((HRESULT)0x89C5011DL) - -// -// MessageId: LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED -// -// MessageText: -// -// Administrator privileges are required in order to execute this operation. -// -#define LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED ((HRESULT)0x89C5011EL) - -// -// MessageId: LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES -// -// MessageText: -// -// There are too many shared instance and we cannot generate unique User Instance Name. Unshare some of the existing shared instances. -// -#define LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES ((HRESULT)0x89C5011FL) - -// -// MessageId: LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH -// -// MessageText: -// -// Cannot get a local application data path. Most probably a user profile is not loaded. If LocalDB is executed under IIS, make sure that profile loading is enabled for the current user. -// -#define LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH ((HRESULT)0x89C50120L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_LOAD_RESOURCES -// -// MessageText: -// -// Cannot load resources for this DLL. Resources for this DLL should be stored in a subfolder Resources, with the same file name as this DLL and the extension ".RLL". -// -#define LOCALDB_ERROR_CANNOT_LOAD_RESOURCES ((HRESULT)0x89C50121L) - - // Detailed error descriptions -// -// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING -// -// MessageText: -// -// The "DataDirectory" registry value is missing in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING ((HRESULT)0x89C50200L) - -// -// MessageId: LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot access LocalDB instance folder: %1 -// -#define LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50201L) - -// -// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG -// -// MessageText: -// -// The "DataDirectory" registry value is too long in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG ((HRESULT)0x89C50202L) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING -// -// MessageText: -// -// The "Parent Instance" registry value is missing in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING ((HRESULT)0x89C50203L) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG -// -// MessageText: -// -// The "Parent Instance" registry value is too long in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG ((HRESULT)0x89C50204L) - -// -// MessageId: LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID -// -// MessageText: -// -// Data directory for LocalDB instance is invalid: %1 -// -#define LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID ((HRESULT)0x89C50205L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_ASSERT -// -// MessageText: -// -// LocalDB instance API: XEvent engine assert: %1 in %2:%3 (%4) -// -#define LOCALDB_EDETAIL_XEVENT_ASSERT ((HRESULT)0x89C50206L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_ERROR -// -// MessageText: -// -// LocalDB instance API: XEvent error: %1 -// -#define LOCALDB_EDETAIL_XEVENT_ERROR ((HRESULT)0x89C50207L) - -// -// MessageId: LOCALDB_EDETAIL_INSTALLATION_CORRUPTED -// -// MessageText: -// -// LocalDB installation is corrupted. Reinstall the LocalDB. -// -#define LOCALDB_EDETAIL_INSTALLATION_CORRUPTED ((HRESULT)0x89C50208L) - -// -// MessageId: LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION -// -// MessageText: -// -// LocalDB XEvent error: cannot determine %ProgramFiles% folder location. -// -#define LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION ((HRESULT)0x89C50209L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE -// -// MessageText: -// -// LocalDB XEvent error: Cannot initialize XEvent engine. -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE ((HRESULT)0x89C5020AL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE -// -// MessageText: -// -// LocalDB XEvent error: Cannot find XEvents configuration file: %1 -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE ((HRESULT)0x89C5020BL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE -// -// MessageText: -// -// LocalDB XEvent error: Cannot configure XEvents engine with the configuration file: %1 -// HRESULT returned: %2 -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE ((HRESULT)0x89C5020CL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG -// -// MessageText: -// -// LocalDB XEvent error: XEvents engine configuration file too long -// -#define LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG ((HRESULT)0x89C5020DL) - -// -// MessageId: LOCALDB_EDETAIL_COINITIALIZEEX_FAILED -// -// MessageText: -// -// CoInitializeEx API failed. HRESULT returned: %1 -// -#define LOCALDB_EDETAIL_COINITIALIZEEX_FAILED ((HRESULT)0x89C5020EL) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID -// -// MessageText: -// -// LocalDB parent instance version is invalid: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID ((HRESULT)0x89C5020FL) - -// -// MessageId: LOCALDB_EDETAIL_WINAPI_ERROR -// -// MessageText: -// -// Windows API call %1 returned error code: %2. Windows system error message is: %3Reported at line: %4. %5 -// -#define LOCALDB_EDETAIL_WINAPI_ERROR ((HRESULT)0xC9C50210L) - -// -// MessageId: LOCALDB_EDETAIL_UNEXPECTED_RESULT -// -// MessageText: -// -// Function %1 returned %2 at line %3. -// -#define LOCALDB_EDETAIL_UNEXPECTED_RESULT ((HRESULT)0x89C50211L) - -// -#endif // _LOCALDB_MESSAGES_H_ - -#endif //__msodbcsql_h__ diff --git a/source/packagize.sh b/source/packagize.sh new file mode 100644 index 00000000..dc9051b3 --- /dev/null +++ b/source/packagize.sh @@ -0,0 +1,11 @@ +#!/bin/bash +if [ "$1" == "" ]; then + cp -rf $PWD/shared $PWD/sqlsrv + cp -rf $PWD/shared $PWD/pdo_sqlsrv +else + [[ -d $1 ]] || { echo "No such path!"; exit 1; } + cp -rf $PWD/sqlsrv $1 + cp -rf $PWD/pdo_sqlsrv $1 + cp -rf $PWD/shared $1/sqlsrv + cp -rf $PWD/shared $1/pdo_sqlsrv +fi diff --git a/source/pdo_sqlsrv/CREDITS b/source/pdo_sqlsrv/CREDITS new file mode 100644 index 00000000..b0c7112c --- /dev/null +++ b/source/pdo_sqlsrv/CREDITS @@ -0,0 +1 @@ +Microsoft Drivers for PHP for SQL Server (PDO driver) diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 new file mode 100644 index 00000000..411d405a --- /dev/null +++ b/source/pdo_sqlsrv/config.m4 @@ -0,0 +1,68 @@ +PHP_ARG_WITH(pdo_sqlsrv, for pdo_sqlsrv support, +[ --with-pdo_sqlsrv Include pdo_sqlsrv support]) + +if test "$PHP_PDO_SQLSRV" != "no"; then + if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then + AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) + fi + + ifdef([PHP_CHECK_PDO_INCLUDES], + [ + PHP_CHECK_PDO_INCLUDES + ],[ + AC_MSG_CHECKING([for PDO includes]) + if test -f $abs_srcdir/include/php/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$abs_srcdir/ext + elif test -f $abs_srcdir/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$abs_srcdir/ext + elif test -f $phpincludedir/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$phpincludedir/ext + else + AC_MSG_ERROR([Cannot find php_pdo_driver.h.]) + fi + AC_MSG_RESULT($pdo_cv_inc_path) + ]) + + pdo_sqlsrv_src_class="\ + pdo_dbh.cpp \ + pdo_parser.cpp \ + pdo_util.cpp \ + pdo_init.cpp \ + pdo_stmt.cpp \ + " + + shared_src_class="\ + shared/core_conn.cpp \ + shared/core_results.cpp \ + shared/core_stream.cpp \ + shared/core_init.cpp \ + shared/core_stmt.cpp \ + shared/core_util.cpp \ + shared/FormattedPrint.cpp \ + shared/localizationimpl.cpp \ + shared/StringFunctions.cpp \ + " + AC_MSG_CHECKING([for PDO_SQLSRV headers]) + if test -f $srcdir/ext/pdo_sqlsrv/shared/core_sqlsrv.h; then + pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/ + elif test -f $srcdir/shared/core_sqlsrv.h; then + pdo_sqlsrv_inc_path=$srcdir/shared/ + else + AC_MSG_ERROR([Cannot find PDO_SQLSRV headers]) + fi + AC_MSG_RESULT($pdo_sqlsrv_inc_path) + + + CXXFLAGS="$CXXFLAGS -std=c++11" + PHP_REQUIRE_CXX() + PHP_ADD_LIBRARY(stdc++, 1, PDO_SQLSRV_SHARED_LIBADD) + PHP_ADD_LIBRARY(odbc, 1, PDO_SQLSRV_SHARED_LIBADD) + PHP_ADD_LIBRARY(odbcinst, 1, PDO_SQLSRV_SHARED_LIBADD) + AC_DEFINE(HAVE_PDO_SQLSRV, 1, [ ]) + PHP_ADD_INCLUDE([$pdo_sqlsrv_inc_path]) + PHP_NEW_EXTENSION(pdo_sqlsrv, $pdo_sqlsrv_src_class $shared_src_class, $ext_shared,,-I$pdo_cv_inc_path -std=c++11) + PHP_SUBST(PDO_SQLSRV_SHARED_LIBADD) + PHP_ADD_EXTENSION_DEP(pdo_sqlsrv, pdo) + PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1) +fi + diff --git a/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 similarity index 57% rename from pdo_sqlsrv/config.w32 rename to source/pdo_sqlsrv/config.w32 index 0a598a51..f73b5be4 100644 --- a/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -1,41 +1,43 @@ -//---------------------------------------------------------------------------------------------------------------------------------- -// File: config.w32 -// -// Contents: JScript build configuration used by buildconf.bat -// -// Microsoft Drivers 4.0 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -ARG_WITH("pdo-sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (PDO driver)", "no"); - -if( PHP_PDO_SQLSRV != "no" ) { - - pdo_sqlsrv_src = "pdo_dbh.cpp pdo_init.cpp pdo_stmt.cpp pdo_util.cpp pdo_parser.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp"; - - if (CHECK_LIB("odbc32.lib", "pdo_sqlsrv") && CHECK_LIB("odbccp32.lib", "pdo_sqlsrv") && - CHECK_LIB("version.lib", "pdo_sqlsrv") && CHECK_LIB("psapi.lib", "pdo_sqlsrv")) { - - EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1") - - CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_PDO_SQLSRV_ODBC'); - CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_PDO_SQLSRV_ODBC'); - ADD_FLAG( 'LDFLAGS_PDO_SQLSRV', '/NXCOMPAT /DYNAMICBASE /debug' ); - ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/EHsc' ); - ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/GS' ); - ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/Zi' ); - ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' ); - ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo'); - } - -} +//---------------------------------------------------------------------------------------------------------------------------------- +// File: config.w32 +// +// Contents: JScript build configuration used by buildconf.bat +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +ARG_WITH("pdo-sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (PDO driver)", "no"); + +if( PHP_PDO_SQLSRV != "no" ) { + + pdo_sqlsrv_src_class = " pdo_dbh.cpp pdo_init.cpp pdo_stmt.cpp pdo_util.cpp pdo_parser.cpp "; + shared_src_class = " core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp "; + + if (CHECK_LIB("odbc32.lib", "pdo_sqlsrv") && CHECK_LIB("odbccp32.lib", "pdo_sqlsrv") && + CHECK_LIB("version.lib", "pdo_sqlsrv") && CHECK_LIB("psapi.lib", "pdo_sqlsrv")&& + CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_PDO_SQLSRV", configure_module_dirname + "\\shared")) { + CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_PDO_SQLSRV_ODBC"); + CHECK_HEADER_ADD_INCLUDE("sqlext.h", "CFLAGS_PDO_SQLSRV_ODBC"); + ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "pdo_sqlsrv" ); + ADD_FLAG( "LDFLAGS_PDO_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug" ); + ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/EHsc" ); + ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/GS" ); + ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/Zi" ); + ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" ); + ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo'); + EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src_class, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); + } else { + WARNING("pdo-sqlsrv not enabled; libraries and headers not found"); + } +} diff --git a/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp similarity index 85% rename from pdo_sqlsrv/pdo_dbh.cpp rename to source/pdo_sqlsrv/pdo_dbh.cpp index 27e2cc5b..0d8e18be 100644 --- a/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -1,1474 +1,1496 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// file: pdo_dbh.cpp -// -// Contents: Implements the PDO object for PDO_SQLSRV -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "pdo_sqlsrv.h" -#include -#include -#include - -#include -#include - - -typedef const zend_function_entry pdo_sqlsrv_function_entry; - -// *** internal variables and constants *** - -namespace { - -const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;"; -const size_t LAST_INSERT_ID_BUFF_LEN = 10; // size of the buffer to hold the string value of the last insert id integer -const char TABLE_LAST_INSERT_ID_QUERY[] = "SELECT IDENT_CURRENT(%s)"; -const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( TABLE_LAST_INSERT_ID_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes - -// List of PDO supported connection options. -namespace PDOConnOptionNames { - -const char Server[] = "Server"; -const char APP[] = "APP"; -const char ApplicationIntent[] = "ApplicationIntent"; -const char AttachDBFileName[] = "AttachDbFileName"; -const char ConnectionPooling[] = "ConnectionPooling"; -const char Database[] = "Database"; -const char Encrypt[] = "Encrypt"; -const char Failover_Partner[] = "Failover_Partner"; -const char LoginTimeout[] = "LoginTimeout"; -const char MARS_Option[] = "MultipleActiveResultSets"; -const char MultiSubnetFailover[] = "MultiSubnetFailover"; -const char QuotedId[] = "QuotedId"; -const char TraceFile[] = "TraceFile"; -const char TraceOn[] = "TraceOn"; -const char TrustServerCertificate[] = "TrustServerCertificate"; -const char TransactionIsolation[] = "TransactionIsolation"; -const char WSID[] = "WSID"; - -} - -enum PDO_CONN_OPTIONS { - - PDO_CONN_OPTION_SERVER = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC, - -}; - -enum PDO_STMT_OPTIONS { - - PDO_STMT_OPTION_ENCODING = SQLSRV_STMT_OPTION_DRIVER_SPECIFIC, - PDO_STMT_OPTION_DIRECT_QUERY, - PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, - PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, - PDO_STMT_OPTION_EMULATE_PREPARES, - PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, -}; - -// List of all the statement options supported by this driver. -const stmt_option PDO_STMT_OPTS[] = { - - { NULL, 0, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, std::unique_ptr( new stmt_option_query_timeout ) }, - { NULL, 0, SQLSRV_STMT_OPTION_SCROLLABLE, std::unique_ptr( new stmt_option_scrollable ) }, - { NULL, 0, PDO_STMT_OPTION_ENCODING, std::unique_ptr( new stmt_option_encoding ) }, - { NULL, 0, PDO_STMT_OPTION_DIRECT_QUERY, std::unique_ptr( new stmt_option_direct_query ) }, - { NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, std::unique_ptr( new stmt_option_cursor_scroll_type ) }, - { 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, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, -}; - -// boolean connection string -struct pdo_bool_conn_str_func -{ - static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); -}; - -struct pdo_txn_isolation_conn_attr_func -{ - static void func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ); -}; - -template -struct pdo_int_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - - SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "pdo_int_conn_attr_func: Unexpected zval type." ); - - size_t val = static_cast( atoi( Z_STRVAL_P( value )) ); - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( val ), SQL_IS_UINTEGER TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } - } -}; - -template -struct pdo_bool_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( core_str_zval_is_true( value )), - SQL_IS_UINTEGER TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } - } -}; - -// statement options related functions -void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, - zval** data TSRMLS_DC ); -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ); - -} // namespace - - -// List of all connection options supported by this driver. -const connection_option PDO_CONN_OPTS[] = { - { - PDOConnOptionNames::Server, - sizeof( PDOConnOptionNames::Server ), - PDO_CONN_OPTION_SERVER, - NULL, - 0, - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::APP, - sizeof( PDOConnOptionNames::APP ), - SQLSRV_CONN_OPTION_APP, - ODBCConnOptions::APP, - sizeof( ODBCConnOptions::APP ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::ApplicationIntent, - sizeof( PDOConnOptionNames::ApplicationIntent ), - SQLSRV_CONN_OPTION_APPLICATION_INTENT, - ODBCConnOptions::ApplicationIntent, - sizeof( ODBCConnOptions::ApplicationIntent ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::AttachDBFileName, - sizeof( PDOConnOptionNames::AttachDBFileName ), - SQLSRV_CONN_OPTION_ATTACHDBFILENAME, - ODBCConnOptions::AttachDBFileName, - sizeof( ODBCConnOptions::AttachDBFileName ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::ConnectionPooling, - sizeof( PDOConnOptionNames::ConnectionPooling ), - SQLSRV_CONN_OPTION_CONN_POOLING, - ODBCConnOptions::ConnectionPooling, - sizeof( ODBCConnOptions::ConnectionPooling ), - CONN_ATTR_BOOL, - conn_null_func::func - }, - { - PDOConnOptionNames::Database, - sizeof( PDOConnOptionNames::Database ), - SQLSRV_CONN_OPTION_DATABASE, - ODBCConnOptions::Database, - sizeof( ODBCConnOptions::Database ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::Encrypt, - sizeof( PDOConnOptionNames::Encrypt ), - SQLSRV_CONN_OPTION_ENCRYPT, - ODBCConnOptions::Encrypt, - sizeof( ODBCConnOptions::Encrypt ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func - }, - { - PDOConnOptionNames::Failover_Partner, - sizeof( PDOConnOptionNames::Failover_Partner ), - SQLSRV_CONN_OPTION_FAILOVER_PARTNER, - ODBCConnOptions::Failover_Partner, - sizeof( ODBCConnOptions::Failover_Partner ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::LoginTimeout, - sizeof( PDOConnOptionNames::LoginTimeout ), - SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, - ODBCConnOptions::LoginTimeout, - sizeof( ODBCConnOptions::LoginTimeout ), - CONN_ATTR_INT, - pdo_int_conn_attr_func::func - }, - { - PDOConnOptionNames::MARS_Option, - sizeof( PDOConnOptionNames::MARS_Option ), - SQLSRV_CONN_OPTION_MARS, - ODBCConnOptions::MARS_ODBC, - sizeof( ODBCConnOptions::MARS_ODBC ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func - }, - { - PDOConnOptionNames::MultiSubnetFailover, - sizeof( PDOConnOptionNames::MultiSubnetFailover ), - SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, - ODBCConnOptions::MultiSubnetFailover, - sizeof( ODBCConnOptions::MultiSubnetFailover ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func - }, - { - PDOConnOptionNames::QuotedId, - sizeof( PDOConnOptionNames::QuotedId ), - SQLSRV_CONN_OPTION_QUOTED_ID, - ODBCConnOptions::QuotedId, - sizeof( ODBCConnOptions::QuotedId ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func - }, - { - PDOConnOptionNames::TraceFile, - sizeof( PDOConnOptionNames::TraceFile ), - SQLSRV_CONN_OPTION_TRACE_FILE, - ODBCConnOptions::TraceFile, - sizeof( ODBCConnOptions::TraceFile ), - CONN_ATTR_STRING, - str_conn_attr_func::func - }, - { - PDOConnOptionNames::TraceOn, - sizeof( PDOConnOptionNames::TraceOn ), - SQLSRV_CONN_OPTION_TRACE_ON, - ODBCConnOptions::TraceOn, - sizeof( ODBCConnOptions::TraceOn ), - CONN_ATTR_BOOL, - pdo_bool_conn_attr_func::func - }, - { - PDOConnOptionNames::TransactionIsolation, - sizeof( PDOConnOptionNames::TransactionIsolation ), - SQLSRV_CONN_OPTION_TRANS_ISOLATION, - ODBCConnOptions::TransactionIsolation, - sizeof( ODBCConnOptions::TransactionIsolation ), - CONN_ATTR_INT, - pdo_txn_isolation_conn_attr_func::func - }, - { - PDOConnOptionNames::TrustServerCertificate, - sizeof( PDOConnOptionNames::TrustServerCertificate ), - SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, - ODBCConnOptions::TrustServerCertificate, - sizeof( ODBCConnOptions::TrustServerCertificate ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func - }, - { - PDOConnOptionNames::WSID, - sizeof( PDOConnOptionNames::WSID ), - SQLSRV_CONN_OPTION_WSID, - ODBCConnOptions::WSID, - sizeof( ODBCConnOptions::WSID ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table -}; - - -// close the connection -int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ); - -// execute queries -int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, - size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ); -zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ); - -// transaction support functions -int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ); -int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ); -int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ); - -// attribute functions -int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ); -int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ); - -// return more information -int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, - zval *info TSRMLS_DC); - -// return the last id generated by an executed SQL statement -char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, size_t* len TSRMLS_DC ); - -// additional methods are supported in this function -pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ); - -// quote a string, meaning put quotes around it and escape any quotes within it -int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquotedlen, char **quoted, size_t* quotedlen, - enum pdo_param_type paramtype TSRMLS_DC ); - -struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { - - pdo_sqlsrv_dbh_close, - pdo_sqlsrv_dbh_prepare, - pdo_sqlsrv_dbh_do, - pdo_sqlsrv_dbh_quote, - pdo_sqlsrv_dbh_begin, - pdo_sqlsrv_dbh_commit, - pdo_sqlsrv_dbh_rollback, - pdo_sqlsrv_dbh_set_attr, - pdo_sqlsrv_dbh_last_id, - pdo_sqlsrv_dbh_return_error, - pdo_sqlsrv_dbh_get_attr, - NULL, // check liveness not implemented - pdo_sqlsrv_get_driver_methods, - NULL, // request shutdown not implemented - NULL // in transaction not implemented -}; - - -// log a function entry point -#define PDO_LOG_DBH_ENTRY \ -{ \ - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ - driver_dbh->set_func( __FUNCTION__ ); \ - LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ -} - -// constructor for the internal object for connections -pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) : - sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ), - stmts( NULL ), - direct_query( false ), - query_timeout( QUERY_TIMEOUT_INVALID ), - client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )), - bind_param_encoding( SQLSRV_ENCODING_CHAR ), - fetch_numeric( false ) -{ - if( client_buffer_max_size < 0 ) { - client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; - LOG( SEV_WARNING, INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE " set to a invalid value. Resetting to default value." ); - } -} - -// pdo_sqlsrv_db_handle_factory -// Maps to PDO::__construct. -// Factory method called by the PDO driver manager to create a SQLSRV PDO connection. -// Does the following things: -// 1.Sets the error handling temporarily to PDO_ERRMODE_EXCEPTION. -// (If an error occurs in this function, the PDO specification mandates that -// an exception be thrown, regardless of the error mode setting.) -// 2. Processes the driver options. -// 3. Creates a core_conn object by calling core_sqlsrv_connect. -// 4. Restores the previous error mode on success. -// alloc_own_columns is set to 1 to tell the PDO driver manager that we manage memory -// Parameters: -// dbh - The PDO managed structure for the connection. -// driver_options - A HashTable (within the zval) of options to use when creating the connection. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) -{ - LOG( SEV_NOTICE, "pdo_sqlsrv_db_handle_factory: entering" ); - - hash_auto_ptr pdo_conn_options_ht; - pdo_error_mode prev_err_mode = dbh->error_mode; - - // must be done in all cases so that even a failed connection can query the - // object for errors. - dbh->methods = &pdo_sqlsrv_dbh_methods; - dbh->driver_data = NULL; - zval* temp_server_z = NULL; - sqlsrv_malloc_auto_ptr dsn_parser; - zval server_z; - ZVAL_UNDEF( &server_z ); - - try { - - // no matter what the error mode, we want exceptions thrown if the connection fails - // to happen (per the PDO spec) - dbh->error_mode = PDO_ERRMODE_EXCEPTION; - - g_henv_cp->set_driver( dbh ); - g_henv_ncp->set_driver( dbh ); - - CHECK_CUSTOM_ERROR( driver_options && Z_TYPE_P( driver_options ) != IS_ARRAY, *g_henv_cp, SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE ) { - throw core::CoreException(); - } - - // Initialize the options array to be passed to the core layer - ALLOC_HASHTABLE( pdo_conn_options_ht ); - - core::sqlsrv_zend_hash_init( *g_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, - ZVAL_INTERNAL_DTOR, 0 /*persistent*/ TSRMLS_CC ); - - // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. - dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_henv_cp, dbh->data_source, - static_cast( dbh->data_source_len ), pdo_conn_options_ht ); - dsn_parser->parse_conn_string( TSRMLS_C ); - - // Extract the server name - temp_server_z = zend_hash_index_find( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER); - - CHECK_CUSTOM_ERROR(( temp_server_z == NULL ), g_henv_cp, PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED ) { - - throw pdo::PDOException(); - } - - server_z = *temp_server_z; - - // Add a reference to the option value since we are deleting it from the hashtable - zval_add_ref( &server_z ); - zend_hash_index_del( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER ); - - sqlsrv_conn* conn = core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, core::allocate_conn, Z_STRVAL( server_z ), - dbh->username, dbh->password, pdo_conn_options_ht, pdo_sqlsrv_handle_dbh_error, - PDO_CONN_OPTS, dbh, "pdo_sqlsrv_db_handle_factory" TSRMLS_CC ); - - // Free the string in server_z after being used - zend_string_release( Z_STR( server_z )); - - SQLSRV_ASSERT( conn != NULL, "Invalid connection returned. Exception should have been thrown." ); - - // set the driver_data and methods to complete creation of the PDO object - dbh->driver_data = conn; - dbh->error_mode = prev_err_mode; // reset the error mode - dbh->alloc_own_columns = 1; // we do our own memory management for columns - dbh->native_case = PDO_CASE_NATURAL;// SQL Server supports mixed case types - - } - catch( core::CoreException& ) { - if ( Z_TYPE( server_z ) == IS_STRING ) { - zend_string_release( Z_STR( server_z )); - } - dbh->error_mode = prev_err_mode; // reset the error mode - g_henv_cp->last_error().reset(); // reset the last error; callee will check if last_error exist before freeing it and setting it to NULL - return 0; - } - catch( ... ) { - - DIE( "pdo_sqlsrv_db_handle_factory: Unknown exception caught" ); - } - - return 1; -} - -// pdo_sqlsrv_dbh_close -// Maps to PDO::__destruct. -// Called when a PDO object is to be destroyed. -// By the time this function is called, PDO has already made sure that -// all statements are disposed and the PDO object is the last item destroyed. -// Parameters: -// dbh - The PDO managed connection object. -// Return: -// Always returns 1 for success. -int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ) -{ - LOG( SEV_NOTICE, "pdo_sqlsrv_dbh_close: entering" ); - - // if the connection didn't complete properly, driver_data isn't initialized. - if( dbh->driver_data == NULL ) { - - return 1; - } - - PDO_RESET_DBH_ERROR; - - // call the core layer close - core_sqlsrv_close( reinterpret_cast( dbh->driver_data ) TSRMLS_CC ); - dbh->driver_data = NULL; - - // always return success that the connection is closed - return 1; -} - -// pdo_sqlsrv_dbh_prepare -// Called by PDO::prepare and PDOStatement::__construct. -// Creates a statement and prepares it for execution by PDO -// Paramters: -// dbh - The PDO managed connection object. -// sql - SQL query to be prepared. -// sql_len - Length of the sql query -// stmt - The PDO managed statement object. -// driver_options - User provided list of statement options. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, - size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - hash_auto_ptr pdo_stmt_options_ht; - sqlsrv_malloc_auto_ptr sql_rewrite; - size_t sql_rewrite_len = 0; - sqlsrv_malloc_auto_ptr driver_stmt; - - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); - SQLSRV_ASSERT(( driver_dbh != NULL ), "pdo_sqlsrv_dbh_prepare: dbh->driver_data was null"); - - try { - - // assign the methods for the statement object. This is necessary even if the - // statement fails so the user can retrieve the error information. - stmt->methods = &pdo_sqlsrv_stmt_methods; - stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; // we support parameterized queries with ?, not names - - // Initialize the options array to be passed to the core layer - ALLOC_HASHTABLE( pdo_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); - - // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. - validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht TSRMLS_CC ); - - driver_stmt = static_cast( core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, - pdo_stmt_options_ht, PDO_STMT_OPTS, - pdo_sqlsrv_handle_stmt_error, stmt TSRMLS_CC )); - - // if the user didn't set anything in the prepare options, then set the buffer limit - // to the value set on the connection. - if( driver_stmt->buffered_query_limit== sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) { - - driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size; - } - - // if the user didn't set anything in the prepare options, then set the query timeout - // to the value set on the connection. - if(( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ) && ( driver_dbh->query_timeout != QUERY_TIMEOUT_INVALID )) { - - core_sqlsrv_set_query_timeout( driver_stmt, driver_dbh->query_timeout TSRMLS_CC ); - } - - // rewrite named parameters in the query to positional parameters if we aren't letting PDO do the - // parameter substitution for us - if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { - - // rewrite the query to map named parameters to positional parameters. We do this rather than use the ODBC named - // parameters for consistency with the PDO MySQL and PDO ODBC drivers. - int zr = pdo_subst_named_params( stmt, const_cast( sql ), sql_len, &sql_rewrite, &sql_rewrite_len TSRMLS_CC ); - CHECK_ZEND_ERROR( zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE ) { - throw core::CoreException(); - } - // if parameter substitution happened, use that query instead of the original - if( sql_rewrite != NULL ) { - sql = sql_rewrite; - sql_len = sql_rewrite_len; - } - } - - if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { - - core_sqlsrv_prepare( driver_stmt, sql, sql_len TSRMLS_CC ); - } - else if( driver_stmt->direct_query ) { - - if( driver_stmt->direct_query_subst_string ) { - // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme - // and we use estrdup below to allocate the new string, which uses emalloc - efree( reinterpret_cast( const_cast( driver_stmt->direct_query_subst_string ))); - } - driver_stmt->direct_query_subst_string = estrdup( sql ); - driver_stmt->direct_query_subst_string_len = sql_len; - } - // else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be - // set to the substituted query - - - stmt->driver_data = driver_stmt; - driver_stmt.transferred(); - } - // everything is cleaned up by this point - // catch everything so the exception doesn't spill into the calling PDO code - catch( core::CoreException& ) { - - if( driver_stmt ) { - - driver_stmt->~pdo_sqlsrv_stmt(); - } - - // in the event that the statement caused an error that was copied to the connection, update the - // connection with the error's SQLSTATE. - if( driver_dbh->last_error() ) { - - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), - reinterpret_cast( driver_dbh->last_error()->sqlstate )); - } - - return 0; - } - - // catch any errant exception and die - catch(...) { - - DIE( "pdo_sqlsrv_dbh_prepare: Unknown exception caught." ); - } - - return 1; -} - - -// pdo_sqlsrv_dbh_do -// Maps to PDO::exec. -// Execute a SQL statement, such as an insert, update or delete, and return -// the number of rows affected. -// Parameters: -// dbh - the PDO connection object, which contains the ODBC handle -// sql - the query to execute -// sql_len - length of sql query -// Return -// # of rows affected, -1 for an error. -zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); - - sqlsrv_malloc_auto_ptr driver_stmt; - SQLLEN rows = 0; - - // verify that the data type sizes are the same. If we ever upgrade to 64 bit we don't want the wrong - // thing to happen here. - SQLSRV_STATIC_ASSERT( sizeof( rows ) == sizeof( SQLLEN )); - - try { - - SQLSRV_ASSERT( sql != NULL, "NULL or empty SQL string passed." ); - - // temp PDO statement used for error handling if something happens - pdo_stmt_t temp_stmt; - temp_stmt.dbh = dbh; - // allocate a full driver statement to take advantage of the error handling - driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, - NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); - driver_stmt->set_func( __FUNCTION__ ); - - core_sqlsrv_execute( driver_stmt TSRMLS_CC, sql, static_cast( sql_len ) ); - - // since the user can give us a compound statement, we return the row count for the last set, and since the row count - // isn't guaranteed to be valid until all the results have been fetched, we fetch them all first. - if( core_sqlsrv_has_any_result( driver_stmt TSRMLS_CC )) { - - SQLRETURN r = SQL_SUCCESS; - - do { - - rows = core::SQLRowCount( driver_stmt TSRMLS_CC ); - - r = core::SQLMoreResults( driver_stmt TSRMLS_CC ); - - } while( r != SQL_NO_DATA ); - } - - // returning -1 forces PDO to return false, which signals an error occurred. SQLRowCount returns -1 for a number of cases - // naturally, so we override that here with no rows returned. - if( rows == -1 ) { - rows = 0; - } - } - catch( core::CoreException& ) { - - // copy any errors on the statement to the connection so that the user sees them, since the statement is released - // before this method returns - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), - reinterpret_cast( driver_stmt->last_error()->sqlstate )); - driver_dbh->set_last_error( driver_stmt->last_error() ); - - if( driver_stmt ) { - driver_stmt->~sqlsrv_stmt(); - } - - return -1; - } - catch( ... ) { - - DIE( "pdo_sqlsrv_dbh_do: Unknown exception caught." ); - } - - if( driver_stmt ) { - driver_stmt->~sqlsrv_stmt(); - } - - return rows; -} - - -// transaction support functions - -// pdo_sqlsrv_dbh_begin -// Maps to PDO::beginTransaction. -// Begins a transaction. Turns off auto-commit mode. The pdo_dbh_t::in_txn -// flag is maintained by PDO so we dont have to worry about it. -// Parameters: -// dbh - The PDO managed connection object. -// Return: -// 0 for failure and 1 for success. -int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - try { - - SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_begin: pdo_dbh_t object was null" ); - - sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); - - SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_begin: driver_data object was null" ); - - DEBUG_SQLSRV_ASSERT( !dbh->in_txn, "pdo_sqlsrv_dbh_begin: Already in transaction" ); - - core_sqlsrv_begin_transaction( driver_conn TSRMLS_CC ); - - return 1; - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE ("pdo_sqlsrv_dbh_begin: Uncaught exception occurred."); - } -} - - - -// pdo_sqlsrv_dbh_commit -// Maps to PDO::commit. -// Commits a transaction. Returns the connection to auto-commit mode. -// PDO throws error if PDO::commit is called on a connection that is not in an active -// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have -// to worry about it here. -// Parameters: -// dbh - The PDO managed connection object. -// Return: -// 0 for failure and 1 for success. -int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - try { - - SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_commit: pdo_dbh_t object was null" ); - - sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); - - SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_commit: driver_data object was null" ); - - DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_commit: Not in transaction" ); - - core_sqlsrv_commit( driver_conn TSRMLS_CC ); - - return 1; - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE ("pdo_sqlsrv_dbh_commit: Uncaught exception occurred."); - } -} - -// pdo_sqlsrv_dbh_rollback -// Maps to PDO::rollback. -// Rolls back a transaction. Returns the connection in auto-commit mode. -// PDO throws error if PDO::rollBack is called on a connection that is not in an active -// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have -// to worry about it here. -// Parameters: -// dbh - The PDO managed connection object. -// Return: -// 0 for failure and 1 for success. -int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - try { - SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_rollback: pdo_dbh_t object was null" ); - - sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); - - SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_rollback: driver_data object was null" ); - - DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_rollback: Not in transaction" ); - - core_sqlsrv_rollback( driver_conn TSRMLS_CC ); - - return 1; - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE ("pdo_sqlsrv_dbh_rollback: Uncaught exception occurred."); - } -} - -// pdo_sqlsrv_dbh_set_attr -// Maps to PDO::setAttribute. Sets an attribute on the PDO connection object. -// PDO driver manager calls this function directly after calling the factory -// method for PDO, for any attribute which is specified in the PDO constructor. -// Parameters: -// dbh - The PDO connection object maintained by PDO. -// attr - The attribute to be set. -// val - The value of the attribute to be set. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); - - try { - - switch( attr ) { - - case SQLSRV_ATTR_ENCODING: - { - zend_long attr_value; - if( Z_TYPE_P( val ) != IS_LONG ) { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); - } - attr_value = Z_LVAL_P( val ); - switch( attr_value ) { - - case SQLSRV_ENCODING_DEFAULT: - // when default is applied to a connection, that means use UTF-8 encoding - driver_dbh->set_encoding( SQLSRV_ENCODING_UTF8 ); - break; - case SQLSRV_ENCODING_SYSTEM: - case SQLSRV_ENCODING_UTF8: - driver_dbh->set_encoding( static_cast( attr_value )); - break; - default: - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); - break; - } - } - break; - - case SQLSRV_ATTR_DIRECT_QUERY: - driver_dbh->direct_query = ( zend_is_true( val ) ) ? true : false; - break; - - case SQLSRV_ATTR_QUERY_TIMEOUT: - if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) < 0 ) { - convert_to_string( val ); - THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( val )); - } - driver_dbh->query_timeout = static_cast( Z_LVAL_P( val ) ); - break; - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) <= 0 ) { - convert_to_string( val ); - THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, Z_STRVAL_P( val )); - } - driver_dbh->client_buffer_max_size = Z_LVAL_P( val ); - break; - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - driver_dbh->fetch_numeric = (zend_is_true(val)) ? true : false; - break; - - // Not supported - case PDO_ATTR_FETCH_TABLE_NAMES: - case PDO_ATTR_FETCH_CATALOG_NAMES: - case PDO_ATTR_PREFETCH: - case PDO_ATTR_MAX_COLUMN_LEN: - case PDO_ATTR_CURSOR_NAME: - case PDO_ATTR_AUTOCOMMIT: - case PDO_ATTR_PERSISTENT: - case PDO_ATTR_TIMEOUT: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ); - } - - // Read-only - case PDO_ATTR_SERVER_VERSION: - case PDO_ATTR_SERVER_INFO: - case PDO_ATTR_CLIENT_VERSION: - case PDO_ATTR_DRIVER_NAME: - case PDO_ATTR_CONNECTION_STATUS: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR ); - } - - // Statement level only - case PDO_ATTR_EMULATE_PREPARES: - case PDO_ATTR_CURSOR: - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); - } - - default: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); - break; - } - } - } - catch( pdo::PDOException& ) { - - return 0; - } - - return 1; -} - - -// pdo_sqlsrv_dbh_get_attr -// Maps to PDO::getAttribute. Gets an attribute on the PDO connection object. -// Parameters: -// dbh - The PDO connection object maintained by PDO. -// attr - The attribute to get. -// return_value - zval in which to return the attribute value. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); - - try { - - switch( attr ) { - - // Not supported - case PDO_ATTR_FETCH_TABLE_NAMES: - case PDO_ATTR_FETCH_CATALOG_NAMES: - case PDO_ATTR_PREFETCH: - case PDO_ATTR_MAX_COLUMN_LEN: - case PDO_ATTR_CURSOR_NAME: - case PDO_ATTR_AUTOCOMMIT: - case PDO_ATTR_TIMEOUT: - { - // PDO does not throw "not supported" error message for these attributes. - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ); - } - - // Statement level only - case PDO_ATTR_EMULATE_PREPARES: - case PDO_ATTR_CURSOR: - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); - } - - case PDO_ATTR_STRINGIFY_FETCHES: - { - // For this attribute, if we dont set the return_value than PDO returns NULL. - ZVAL_BOOL(return_value, ( dbh->stringify ? 1 : 0 ) ); - break; - } - - case PDO_ATTR_SERVER_INFO: - { - core_sqlsrv_get_server_info( driver_dbh, return_value TSRMLS_CC ); - break; - } - - case PDO_ATTR_SERVER_VERSION: - { - core_sqlsrv_get_server_version( driver_dbh, return_value TSRMLS_CC ); - break; - } - - case PDO_ATTR_CLIENT_VERSION: - { - core_sqlsrv_get_client_info( driver_dbh, return_value TSRMLS_CC ); - - //Add the PDO SQLSRV driver's file version - core::sqlsrv_add_assoc_string( *driver_dbh, return_value, "ExtensionVer", VER_FILEVERSION_STR, 1 /*duplicate*/ - TSRMLS_CC ); - break; - } - - case SQLSRV_ATTR_ENCODING: - { - ZVAL_LONG( return_value, driver_dbh->encoding() ); - break; - } - - case SQLSRV_ATTR_QUERY_TIMEOUT: - { - ZVAL_LONG( return_value, ( driver_dbh->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_dbh->query_timeout )); - break; - } - - case SQLSRV_ATTR_DIRECT_QUERY: - { - ZVAL_BOOL( return_value, driver_dbh->direct_query ); - break; - } - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - { - ZVAL_LONG( return_value, driver_dbh->client_buffer_max_size ); - break; - } - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - { - ZVAL_BOOL( return_value, driver_dbh->fetch_numeric ); - break; - } - - default: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); - break; - } - } - - return 1; - } - catch( core::CoreException& ) { - return 0; - } -} - -// Called by PDO::errorInfo and PDOStatement::errorInfo. -// Returns the error info. -// Parameters: -// dbh - The PDO managed connection object. -// stmt - The PDO managed statement object. -// info - zval in which to return the error info. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, - zval *info TSRMLS_DC) -{ - SQLSRV_ASSERT( dbh != NULL || stmt != NULL, "Either dbh or stmt must not be NULL to dereference the error." ); - - sqlsrv_error* ctx_error = NULL; - if( stmt ) { - ctx_error = static_cast( stmt->driver_data )->last_error(); - } - else { - ctx_error = static_cast( dbh->driver_data )->last_error(); - } - - pdo_sqlsrv_retrieve_context_error( ctx_error, info ); - - return 1; -} - -// pdo_sqlsrv_dbh_last_id -// Maps to PDO::lastInsertId. -// Returns the last id generated by an executed SQL statement -// Parameters: -// dbh - The PDO managed connection object. -// name - Table name. -// len - Length of the name. -// Return: -// Returns the last insert id as a string. -char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, _Out_ size_t* len TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - // turn off any error handling for last_id - pdo_error_mode prev_err_mode = dbh->error_mode; - dbh->error_mode = PDO_ERRMODE_SILENT; - - sqlsrv_malloc_auto_ptr driver_stmt; - - pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); - - sqlsrv_malloc_auto_ptr id_str; - id_str = reinterpret_cast( sqlsrv_malloc( LAST_INSERT_ID_BUFF_LEN )); - - try { - - char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ]; - if( name == NULL ) { - strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY ); - } - else { - char* quoted_table = NULL; - size_t quoted_len = 0; - int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strlen( name ), "ed_table, "ed_len, PDO_PARAM_NULL TSRMLS_CC ); - SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name." ); - sprintf_s( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, TABLE_LAST_INSERT_ID_QUERY, quoted_table ); - sqlsrv_free( quoted_table ); - } - - // temp PDO statement used for error handling if something happens - pdo_stmt_t temp_stmt; - temp_stmt.dbh = dbh; - - // allocate a full driver statement to take advantage of the error handling - driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, - NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); - driver_stmt->set_func( __FUNCTION__ ); - - // execute the last insert id query - core::SQLExecDirect( driver_stmt, last_insert_id_query TSRMLS_CC ); - - core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); - SQLRETURN r = core::SQLGetData( driver_stmt, 1, SQL_C_CHAR, id_str, LAST_INSERT_ID_BUFF_LEN, - reinterpret_cast( len ), false TSRMLS_CC ); - - CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt, - PDO_SQLSRV_ERROR_LAST_INSERT_ID ) { - throw core::CoreException(); - } - - driver_stmt->~sqlsrv_stmt(); - } - catch( core::CoreException& ) { - - // copy any errors on the statement to the connection so that the user sees them, since the statement is released - // before this method returns - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), - reinterpret_cast( driver_stmt->last_error()->sqlstate )); - driver_dbh->set_last_error( driver_stmt->last_error() ); - - if( driver_stmt ) { - driver_stmt->~sqlsrv_stmt(); - } - - strcpy_s( id_str.get(), 1, "" ); - *len = 0; - } - - char* ret_id_str = id_str.get(); - id_str.transferred(); - - // restore error handling to its previous mode - dbh->error_mode = prev_err_mode; - - return ret_id_str; -} - -// pdo_sqlsrv_dbh_quote -// Maps to PDO::quote. As the name says, this function quotes a string. -// Always returns a valid string unless memory allocation fails. -// Parameters: -// dbh - The PDO managed connection object. -// unquoted - The unquoted string to be quoted. -// unquoted_len - Length of the unquoted string. -// quoted - Buffer for output string. -// quoted_len - Length of the output string. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquoted_len, char **quoted, size_t* quoted_len, - enum pdo_param_type /*paramtype*/ TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); - SQLSRV_ENCODING encoding = driver_dbh->bind_param_encoding; - - 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 ) { - os << std::hex << ( int )unquoted[index]; - } - std::basic_string str_hex = os.str(); - // each character is represented by 2 digits of hex - size_t unquoted_str_len = unquoted_len * 2; // length returned should not account for null terminator - char* unquoted_str = reinterpret_cast( sqlsrv_malloc( unquoted_str_len, sizeof( char ), 1 )); // include space for null terminator - strcpy_s( unquoted_str, unquoted_str_len + 1 /* include null terminator*/, str_hex.c_str()); - // include length of '0x' in the binary string - *quoted_len = unquoted_str_len + 2; - *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]; - } - // null terminator - ( *quoted )[out_current] = '\0'; - sqlsrv_free( unquoted_str ); - return 1; - } - else { - // count the number of quotes needed - unsigned int quotes_needed = 2; // the initial start and end quotes of course - // include the N proceeding the initial quote if encoding is UTF8 - if ( encoding == SQLSRV_ENCODING_UTF8 ) { - quotes_needed = 3; - } - - for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { - if ( unquoted[index] == '\'' ) { - ++quotes_needed; - } - } - - *quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator. - *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); // include space for null terminator. - unsigned int out_current = 0; - - // insert N if the encoding is UTF8 - if ( encoding == SQLSRV_ENCODING_UTF8 ) { - ( *quoted )[out_current++] = 'N'; - } - // insert initial quote - ( *quoted )[out_current++] = '\''; - - for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { - - if ( unquoted[index] == '\'' ) { - ( *quoted )[out_current++] = '\''; - ( *quoted )[out_current++] = '\''; - } - else { - ( *quoted )[out_current++] = unquoted[index]; - } - } - - // trailing quote and null terminator - ( *quoted )[out_current++] = '\''; - ( *quoted )[out_current] = '\0'; - - return 1; - } -} - -// This method is not implemented by this driver. -pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); - CHECK_CUSTOM_ERROR( true, driver_conn, PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED ) { - return NULL; - } - - return NULL; // to avoid a compiler warning -} - -namespace { - -// Maps the PDO driver specific statement option/attribute constants to the core layer -// statement option/attribute constants. -void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, - zval* data TSRMLS_DC ) -{ - zend_ulong option_key = -1; - switch( key ) { - - case PDO_ATTR_CURSOR: - option_key = SQLSRV_STMT_OPTION_SCROLLABLE; - break; - - case SQLSRV_ATTR_ENCODING: - option_key = PDO_STMT_OPTION_ENCODING; - break; - - case SQLSRV_ATTR_QUERY_TIMEOUT: - option_key = SQLSRV_STMT_OPTION_QUERY_TIMEOUT; - break; - - case PDO_ATTR_STATEMENT_CLASS: - break; - - case SQLSRV_ATTR_DIRECT_QUERY: - option_key = PDO_STMT_OPTION_DIRECT_QUERY; - break; - - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - option_key = PDO_STMT_OPTION_CURSOR_SCROLL_TYPE; - break; - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - option_key = PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE; - break; - - case PDO_ATTR_EMULATE_PREPARES: - option_key = PDO_STMT_OPTION_EMULATE_PREPARES; - break; - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; - - default: - CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { - throw core::CoreException(); - } - break; - } - - // if a PDO handled option makes it through (such as PDO_ATTR_STATEMENT_CLASS, just skip it - if( option_key != -1 ) { - zval_add_ref( data ); - core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data TSRMLS_CC ); - } -} - - -// validate_stmt_options -// Iterates through the list of statement options provided by the user and validates them -// against the list of statement options provided by this driver. After validation -// creates a Hashtable of statement options to be sent to the core layer for processing. -// Parameters: -// ctx - The current context. -// stmt_options - The user provided list of statement options. -// pdo_stmt_options_ht - Output hashtable of statement options. -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ) -{ - try { - - if( stmt_options ) { - - HashTable* options_ht = Z_ARRVAL_P( stmt_options ); - size_t int_key = -1; - zend_string *key = NULL; - zval* data = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - int result = 0; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_LONG ), ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { - throw core::CoreException(); - } - - add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - } - } - catch( core::CoreException& ) { - - throw; - } -} - - -void pdo_bool_conn_str_func::func(connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ) -{ - TSRMLS_C; - char const* val_str = "no"; - - if( core_str_zval_is_true( value ) ) { - - val_str = "yes"; - } - - conn_str += option->odbc_name; - conn_str += "={"; - conn_str += val_str; - conn_str += "};"; -} - -void pdo_txn_isolation_conn_attr_func::func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, - std::string& /*conn_str*/ TSRMLS_DC ) -{ - try { - - SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "pdo_txn_isolation_conn_attr_func: Unexpected zval type." ); - const char* val = Z_STRVAL_P( value_z ); - size_t val_len = Z_STRLEN_P( value_z ); - zend_long out_val = SQL_TXN_READ_COMMITTED; - - // READ_COMMITTED - if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_COMMITTED ) - 1 ) - && !_stricmp( val, PDOTxnIsolationValues::READ_COMMITTED ))) { - - out_val = SQL_TXN_READ_COMMITTED; - } - - // READ_UNCOMMITTED - else if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_UNCOMMITTED ) - 1 ) - && !_stricmp( val, PDOTxnIsolationValues::READ_UNCOMMITTED ))) { - - out_val = SQL_TXN_READ_UNCOMMITTED; - } - - // REPEATABLE_READ - else if(( val_len == ( sizeof( PDOTxnIsolationValues::REPEATABLE_READ ) - 1 ) - && !_stricmp( val, PDOTxnIsolationValues::REPEATABLE_READ ))) { - - out_val = SQL_TXN_REPEATABLE_READ; - } - - // SERIALIZABLE - else if(( val_len == ( sizeof( PDOTxnIsolationValues::SERIALIZABLE ) - 1 ) - && !_stricmp( val, PDOTxnIsolationValues::SERIALIZABLE ))) { - - out_val = SQL_TXN_SERIALIZABLE; - } - - // SNAPSHOT - else if(( val_len == ( sizeof( PDOTxnIsolationValues::SNAPSHOT ) - 1 ) - && !_stricmp( val, PDOTxnIsolationValues::SNAPSHOT ))) { - - out_val = SQL_TXN_SS_SNAPSHOT; - } - - else { - - CHECK_CUSTOM_ERROR( true, conn, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, PDOConnOptionNames::TransactionIsolation ) { - - throw core::CoreException(); - } - } - - core::SQLSetConnectAttr( conn, SQL_COPT_SS_TXN_ISOLATION, reinterpret_cast( out_val ), SQL_IS_UINTEGER TSRMLS_CC ); - - } - catch( core::CoreException& ) { - - throw; - } -} - -} // namespace +//--------------------------------------------------------------------------------------------------------------------------------- +// file: pdo_dbh.cpp +// +// Contents: Implements the PDO object for PDO_SQLSRV +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_pdo_sqlsrv.h" + +#include +#include + +typedef const zend_function_entry pdo_sqlsrv_function_entry; + +// *** internal variables and constants *** + +namespace { + +const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;"; +const size_t LAST_INSERT_ID_BUFF_LEN = 10; // size of the buffer to hold the string value of the last insert id integer +const char TABLE_LAST_INSERT_ID_QUERY[] = "SELECT IDENT_CURRENT(%s)"; +const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( TABLE_LAST_INSERT_ID_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes + +// List of PDO supported connection options. +namespace PDOConnOptionNames { + +const char Server[] = "Server"; +const char APP[] = "APP"; +const char ApplicationIntent[] = "ApplicationIntent"; +const char AttachDBFileName[] = "AttachDbFileName"; +const char ConnectionPooling[] = "ConnectionPooling"; +const char Database[] = "Database"; +const char Encrypt[] = "Encrypt"; +const char Failover_Partner[] = "Failover_Partner"; +const char LoginTimeout[] = "LoginTimeout"; +const char MARS_Option[] = "MultipleActiveResultSets"; +const char MultiSubnetFailover[] = "MultiSubnetFailover"; +const char QuotedId[] = "QuotedId"; +const char TraceFile[] = "TraceFile"; +const char TraceOn[] = "TraceOn"; +const char TrustServerCertificate[] = "TrustServerCertificate"; +const char TransactionIsolation[] = "TransactionIsolation"; +const char WSID[] = "WSID"; + +} + +enum PDO_CONN_OPTIONS { + + PDO_CONN_OPTION_SERVER = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC, + +}; + +enum PDO_STMT_OPTIONS { + + PDO_STMT_OPTION_ENCODING = SQLSRV_STMT_OPTION_DRIVER_SPECIFIC, + PDO_STMT_OPTION_DIRECT_QUERY, + PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, + PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, + PDO_STMT_OPTION_EMULATE_PREPARES, + PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, +}; + +// List of all the statement options supported by this driver. +const stmt_option PDO_STMT_OPTS[] = { + + { NULL, 0, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, std::unique_ptr( new stmt_option_query_timeout ) }, + { NULL, 0, SQLSRV_STMT_OPTION_SCROLLABLE, std::unique_ptr( new stmt_option_scrollable ) }, + { NULL, 0, PDO_STMT_OPTION_ENCODING, std::unique_ptr( new stmt_option_encoding ) }, + { NULL, 0, PDO_STMT_OPTION_DIRECT_QUERY, std::unique_ptr( new stmt_option_direct_query ) }, + { NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, std::unique_ptr( new stmt_option_cursor_scroll_type ) }, + { 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, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, +}; + +// boolean connection string +struct pdo_bool_conn_str_func +{ + static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); +}; + +struct pdo_txn_isolation_conn_attr_func +{ + static void func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ); +}; + +template +struct pdo_int_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + + SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "pdo_int_conn_attr_func: Unexpected zval type." ); + + size_t val = static_cast( atoi( Z_STRVAL_P( value )) ); + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( val ), SQL_IS_UINTEGER TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +template +struct pdo_bool_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( core_str_zval_is_true( value )), + SQL_IS_UINTEGER TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +// statement options related functions +void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, + zval** data TSRMLS_DC ); +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ); + +} // namespace + + +// List of all connection options supported by this driver. +const connection_option PDO_CONN_OPTS[] = { + { + PDOConnOptionNames::Server, + sizeof( PDOConnOptionNames::Server ), + PDO_CONN_OPTION_SERVER, + NULL, + 0, + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::APP, + sizeof( PDOConnOptionNames::APP ), + SQLSRV_CONN_OPTION_APP, + ODBCConnOptions::APP, + sizeof( ODBCConnOptions::APP ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::ApplicationIntent, + sizeof( PDOConnOptionNames::ApplicationIntent ), + SQLSRV_CONN_OPTION_APPLICATION_INTENT, + ODBCConnOptions::ApplicationIntent, + sizeof( ODBCConnOptions::ApplicationIntent ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::AttachDBFileName, + sizeof( PDOConnOptionNames::AttachDBFileName ), + SQLSRV_CONN_OPTION_ATTACHDBFILENAME, + ODBCConnOptions::AttachDBFileName, + sizeof( ODBCConnOptions::AttachDBFileName ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::ConnectionPooling, + sizeof( PDOConnOptionNames::ConnectionPooling ), + SQLSRV_CONN_OPTION_CONN_POOLING, + ODBCConnOptions::ConnectionPooling, + sizeof( ODBCConnOptions::ConnectionPooling ), + CONN_ATTR_BOOL, + conn_null_func::func + }, + { + PDOConnOptionNames::Database, + sizeof( PDOConnOptionNames::Database ), + SQLSRV_CONN_OPTION_DATABASE, + ODBCConnOptions::Database, + sizeof( ODBCConnOptions::Database ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::Encrypt, + sizeof( PDOConnOptionNames::Encrypt ), + SQLSRV_CONN_OPTION_ENCRYPT, + ODBCConnOptions::Encrypt, + sizeof( ODBCConnOptions::Encrypt ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::Failover_Partner, + sizeof( PDOConnOptionNames::Failover_Partner ), + SQLSRV_CONN_OPTION_FAILOVER_PARTNER, + ODBCConnOptions::Failover_Partner, + sizeof( ODBCConnOptions::Failover_Partner ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::LoginTimeout, + sizeof( PDOConnOptionNames::LoginTimeout ), + SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, + ODBCConnOptions::LoginTimeout, + sizeof( ODBCConnOptions::LoginTimeout ), + CONN_ATTR_INT, + pdo_int_conn_attr_func::func + }, + { + PDOConnOptionNames::MARS_Option, + sizeof( PDOConnOptionNames::MARS_Option ), + SQLSRV_CONN_OPTION_MARS, + ODBCConnOptions::MARS_ODBC, + sizeof( ODBCConnOptions::MARS_ODBC ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::MultiSubnetFailover, + sizeof( PDOConnOptionNames::MultiSubnetFailover ), + SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, + ODBCConnOptions::MultiSubnetFailover, + sizeof( ODBCConnOptions::MultiSubnetFailover ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::QuotedId, + sizeof( PDOConnOptionNames::QuotedId ), + SQLSRV_CONN_OPTION_QUOTED_ID, + ODBCConnOptions::QuotedId, + sizeof( ODBCConnOptions::QuotedId ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::TraceFile, + sizeof( PDOConnOptionNames::TraceFile ), + SQLSRV_CONN_OPTION_TRACE_FILE, + ODBCConnOptions::TraceFile, + sizeof( ODBCConnOptions::TraceFile ), + CONN_ATTR_STRING, + str_conn_attr_func::func + }, + { + PDOConnOptionNames::TraceOn, + sizeof( PDOConnOptionNames::TraceOn ), + SQLSRV_CONN_OPTION_TRACE_ON, + ODBCConnOptions::TraceOn, + sizeof( ODBCConnOptions::TraceOn ), + CONN_ATTR_BOOL, + pdo_bool_conn_attr_func::func + }, + { + PDOConnOptionNames::TransactionIsolation, + sizeof( PDOConnOptionNames::TransactionIsolation ), + SQLSRV_CONN_OPTION_TRANS_ISOLATION, + ODBCConnOptions::TransactionIsolation, + sizeof( ODBCConnOptions::TransactionIsolation ), + CONN_ATTR_INT, + pdo_txn_isolation_conn_attr_func::func + }, + { + PDOConnOptionNames::TrustServerCertificate, + sizeof( PDOConnOptionNames::TrustServerCertificate ), + SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, + ODBCConnOptions::TrustServerCertificate, + sizeof( ODBCConnOptions::TrustServerCertificate ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::WSID, + sizeof( PDOConnOptionNames::WSID ), + SQLSRV_CONN_OPTION_WSID, + ODBCConnOptions::WSID, + sizeof( ODBCConnOptions::WSID ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table +}; + + +// close the connection +int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ); + +// execute queries +int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, + size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ); +zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ); + +// transaction support functions +int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ); +int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ); +int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ); + +// attribute functions +int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ); +int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ); + +// return more information +int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, + zval *info TSRMLS_DC); + +// return the last id generated by an executed SQL statement +char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, size_t* len TSRMLS_DC ); + +// additional methods are supported in this function +pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ); + +// quote a string, meaning put quotes around it and escape any quotes within it +int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquotedlen, char **quoted, size_t* quotedlen, + enum pdo_param_type paramtype TSRMLS_DC ); + +struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { + + pdo_sqlsrv_dbh_close, + pdo_sqlsrv_dbh_prepare, + pdo_sqlsrv_dbh_do, + pdo_sqlsrv_dbh_quote, + pdo_sqlsrv_dbh_begin, + pdo_sqlsrv_dbh_commit, + pdo_sqlsrv_dbh_rollback, + pdo_sqlsrv_dbh_set_attr, + pdo_sqlsrv_dbh_last_id, + pdo_sqlsrv_dbh_return_error, + pdo_sqlsrv_dbh_get_attr, + NULL, // check liveness not implemented + pdo_sqlsrv_get_driver_methods, + NULL, // request shutdown not implemented + NULL // in transaction not implemented +}; + + +// log a function entry point +#ifndef _WIN32 +#define PDO_LOG_DBH_ENTRY \ +{ \ + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ + driver_dbh->set_func( __FUNCTION__ ); \ + int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ + char func[length+1]; \ + strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ + strcat_s( func, length+1, ": entering" ); \ + LOG( SEV_NOTICE, func ); \ +} +#else +#define PDO_LOG_DBH_ENTRY \ +{ \ + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ + driver_dbh->set_func( __FUNCTION__ ); \ + LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ +} +#endif + +// constructor for the internal object for connections +pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) : + sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ), + stmts( NULL ), + direct_query( false ), + query_timeout( QUERY_TIMEOUT_INVALID ), + client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )), + bind_param_encoding( SQLSRV_ENCODING_CHAR ), + fetch_numeric( false ) +{ + if( client_buffer_max_size < 0 ) { + client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; + LOG( SEV_WARNING, INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE " set to a invalid value. Resetting to default value." ); + } +} + +// pdo_sqlsrv_db_handle_factory +// Maps to PDO::__construct. +// Factory method called by the PDO driver manager to create a SQLSRV PDO connection. +// Does the following things: +// 1.Sets the error handling temporarily to PDO_ERRMODE_EXCEPTION. +// (If an error occurs in this function, the PDO specification mandates that +// an exception be thrown, regardless of the error mode setting.) +// 2. Processes the driver options. +// 3. Creates a core_conn object by calling core_sqlsrv_connect. +// 4. Restores the previous error mode on success. +// alloc_own_columns is set to 1 to tell the PDO driver manager that we manage memory +// Parameters: +// dbh - The PDO managed structure for the connection. +// driver_options - A HashTable (within the zval) of options to use when creating the connection. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) +{ + LOG( SEV_NOTICE, "pdo_sqlsrv_db_handle_factory: entering" ); + + hash_auto_ptr pdo_conn_options_ht; + pdo_error_mode prev_err_mode = dbh->error_mode; + + // must be done in all cases so that even a failed connection can query the + // object for errors. + dbh->methods = &pdo_sqlsrv_dbh_methods; + dbh->driver_data = NULL; + zval* temp_server_z = NULL; + sqlsrv_malloc_auto_ptr dsn_parser; + zval server_z; + ZVAL_UNDEF( &server_z ); + + try { + + // no matter what the error mode, we want exceptions thrown if the connection fails + // to happen (per the PDO spec) + dbh->error_mode = PDO_ERRMODE_EXCEPTION; + + g_henv_cp->set_driver( dbh ); + g_henv_ncp->set_driver( dbh ); + + CHECK_CUSTOM_ERROR( driver_options && Z_TYPE_P( driver_options ) != IS_ARRAY, *g_henv_cp, SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE ) { + throw core::CoreException(); + } + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( pdo_conn_options_ht ); + + core::sqlsrv_zend_hash_init( *g_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, + ZVAL_INTERNAL_DTOR, 0 /*persistent*/ TSRMLS_CC ); + + // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. + dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_henv_cp, dbh->data_source, + static_cast( dbh->data_source_len ), pdo_conn_options_ht ); + dsn_parser->parse_conn_string( TSRMLS_C ); + + // Extract the server name + temp_server_z = zend_hash_index_find( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER); + + CHECK_CUSTOM_ERROR(( temp_server_z == NULL ), g_henv_cp, PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED ) { + + throw pdo::PDOException(); + } + + server_z = *temp_server_z; + + // Add a reference to the option value since we are deleting it from the hashtable + zval_add_ref( &server_z ); + zend_hash_index_del( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER ); + + sqlsrv_conn* conn = core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, core::allocate_conn, Z_STRVAL( server_z ), + dbh->username, dbh->password, pdo_conn_options_ht, pdo_sqlsrv_handle_dbh_error, + PDO_CONN_OPTS, dbh, "pdo_sqlsrv_db_handle_factory" TSRMLS_CC ); + + // Free the string in server_z after being used + zend_string_release( Z_STR( server_z )); + + SQLSRV_ASSERT( conn != NULL, "Invalid connection returned. Exception should have been thrown." ); + + // set the driver_data and methods to complete creation of the PDO object + dbh->driver_data = conn; + dbh->error_mode = prev_err_mode; // reset the error mode + dbh->alloc_own_columns = 1; // we do our own memory management for columns + dbh->native_case = PDO_CASE_NATURAL;// SQL Server supports mixed case types + + } + catch( core::CoreException& ) { + + if ( Z_TYPE( server_z ) == IS_STRING ) { + zend_string_release( Z_STR( server_z )); + } + dbh->error_mode = prev_err_mode; // reset the error mode + g_henv_cp->last_error().reset(); // reset the last error; callee will check if last_error exist before freeing it and setting it to NULL + + return 0; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_db_handle_factory: Unknown exception caught" ); + } + + return 1; +} + +// pdo_sqlsrv_dbh_close +// Maps to PDO::__destruct. +// Called when a PDO object is to be destroyed. +// By the time this function is called, PDO has already made sure that +// all statements are disposed and the PDO object is the last item destroyed. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// Always returns 1 for success. +int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ) +{ + LOG( SEV_NOTICE, "pdo_sqlsrv_dbh_close: entering" ); + + // if the connection didn't complete properly, driver_data isn't initialized. + if( dbh->driver_data == NULL ) { + + return 1; + } + + PDO_RESET_DBH_ERROR; + + // call the core layer close + core_sqlsrv_close( reinterpret_cast( dbh->driver_data ) TSRMLS_CC ); + dbh->driver_data = NULL; + + // always return success that the connection is closed + return 1; +} + +// pdo_sqlsrv_dbh_prepare +// Called by PDO::prepare and PDOStatement::__construct. +// Creates a statement and prepares it for execution by PDO +// Paramters: +// dbh - The PDO managed connection object. +// sql - SQL query to be prepared. +// sql_len - Length of the sql query +// stmt - The PDO managed statement object. +// driver_options - User provided list of statement options. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, + size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + hash_auto_ptr pdo_stmt_options_ht; + sqlsrv_malloc_auto_ptr sql_rewrite; + size_t sql_rewrite_len = 0; + sqlsrv_malloc_auto_ptr driver_stmt; + + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); + SQLSRV_ASSERT(( driver_dbh != NULL ), "pdo_sqlsrv_dbh_prepare: dbh->driver_data was null"); + + try { + + // assign the methods for the statement object. This is necessary even if the + // statement fails so the user can retrieve the error information. + stmt->methods = &pdo_sqlsrv_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; // we support parameterized queries with ?, not names + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( pdo_stmt_options_ht ); + core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */, + ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + + // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. + validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht TSRMLS_CC ); + + driver_stmt = static_cast( core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, + pdo_stmt_options_ht, PDO_STMT_OPTS, + pdo_sqlsrv_handle_stmt_error, stmt TSRMLS_CC )); + + // if the user didn't set anything in the prepare options, then set the buffer limit + // to the value set on the connection. + if( driver_stmt->buffered_query_limit== sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) { + + driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size; + } + + // if the user didn't set anything in the prepare options, then set the query timeout + // to the value set on the connection. + if(( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ) && ( driver_dbh->query_timeout != QUERY_TIMEOUT_INVALID )) { + + core_sqlsrv_set_query_timeout( driver_stmt, driver_dbh->query_timeout TSRMLS_CC ); + } + + // rewrite named parameters in the query to positional parameters if we aren't letting PDO do the + // parameter substitution for us + if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { + + // rewrite the query to map named parameters to positional parameters. We do this rather than use the ODBC named + // parameters for consistency with the PDO MySQL and PDO ODBC drivers. + int zr = pdo_parse_params( stmt, const_cast( sql ), sql_len, &sql_rewrite, &sql_rewrite_len TSRMLS_CC ); + + CHECK_ZEND_ERROR( zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE) { + throw core::CoreException(); + } + // if parameter substitution happened, use that query instead of the original + if( sql_rewrite != 0) { + sql = sql_rewrite; + sql_len = sql_rewrite_len; + } + } + + if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { + + core_sqlsrv_prepare( driver_stmt, sql, sql_len TSRMLS_CC ); + } + else if( driver_stmt->direct_query ) { + + if( driver_stmt->direct_query_subst_string ) { + // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme + // and we use estrdup below to allocate the new string, which uses emalloc + efree( reinterpret_cast( const_cast( driver_stmt->direct_query_subst_string ))); + } + driver_stmt->direct_query_subst_string = estrdup( sql ); + driver_stmt->direct_query_subst_string_len = sql_len; + } + // else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be + // set to the substituted query + + + stmt->driver_data = driver_stmt; + driver_stmt.transferred(); + } + // everything is cleaned up by this point + // catch everything so the exception doesn't spill into the calling PDO code + catch( core::CoreException& ) { + + if( driver_stmt ) { + + driver_stmt->~pdo_sqlsrv_stmt(); + } + + // in the event that the statement caused an error that was copied to the connection, update the + // connection with the error's SQLSTATE. + if( driver_dbh->last_error() ) { + + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), + reinterpret_cast( driver_dbh->last_error()->sqlstate )); + } + + return 0; + } + + // catch any errant exception and die + catch(...) { + + DIE( "pdo_sqlsrv_dbh_prepare: Unknown exception caught." ); + } + + return 1; +} + + +// pdo_sqlsrv_dbh_do +// Maps to PDO::exec. +// Execute a SQL statement, such as an insert, update or delete, and return +// the number of rows affected. +// Parameters: +// dbh - the PDO connection object, which contains the ODBC handle +// sql - the query to execute +// sql_len - length of sql query +// Return +// # of rows affected, -1 for an error. +zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + sqlsrv_malloc_auto_ptr driver_stmt; + SQLLEN rows = 0; + + // verify that the data type sizes are the same. If we ever upgrade to 64 bit we don't want the wrong + // thing to happen here. + SQLSRV_STATIC_ASSERT( sizeof( rows ) == sizeof( SQLLEN )); + + try { + + SQLSRV_ASSERT( sql != NULL, "NULL or empty SQL string passed." ); + + // temp PDO statement used for error handling if something happens + pdo_stmt_t temp_stmt; + temp_stmt.dbh = dbh; + // allocate a full driver statement to take advantage of the error handling + driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, + NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); + driver_stmt->set_func( __FUNCTION__ ); + + core_sqlsrv_execute( driver_stmt TSRMLS_CC, sql, static_cast( sql_len ) ); + + // since the user can give us a compound statement, we return the row count for the last set, and since the row count + // isn't guaranteed to be valid until all the results have been fetched, we fetch them all first. + if( core_sqlsrv_has_any_result( driver_stmt TSRMLS_CC )) { + + SQLRETURN r = SQL_SUCCESS; + + do { + + rows = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + r = core::SQLMoreResults( driver_stmt TSRMLS_CC ); + + } while( r != SQL_NO_DATA ); + } + + // returning -1 forces PDO to return false, which signals an error occurred. SQLRowCount returns -1 for a number of cases + // naturally, so we override that here with no rows returned. + if( rows == -1 ) { + rows = 0; + } + } + catch( core::CoreException& ) { + + // copy any errors on the statement to the connection so that the user sees them, since the statement is released + // before this method returns + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), + reinterpret_cast( driver_stmt->last_error()->sqlstate )); + driver_dbh->set_last_error( driver_stmt->last_error() ); + + if( driver_stmt ) { + driver_stmt->~sqlsrv_stmt(); + } + + return -1; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_dbh_do: Unknown exception caught." ); + } + + if( driver_stmt ) { + driver_stmt->~sqlsrv_stmt(); + } + + return rows; +} + + +// transaction support functions + +// pdo_sqlsrv_dbh_begin +// Maps to PDO::beginTransaction. +// Begins a transaction. Turns off auto-commit mode. The pdo_dbh_t::in_txn +// flag is maintained by PDO so we dont have to worry about it. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// 0 for failure and 1 for success. +int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + try { + + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_begin: pdo_dbh_t object was null" ); + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + + SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_begin: driver_data object was null" ); + + DEBUG_SQLSRV_ASSERT( !dbh->in_txn, "pdo_sqlsrv_dbh_begin: Already in transaction" ); + + core_sqlsrv_begin_transaction( driver_conn TSRMLS_CC ); + + return 1; + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_dbh_begin: Uncaught exception occurred."); + } +} + + + +// pdo_sqlsrv_dbh_commit +// Maps to PDO::commit. +// Commits a transaction. Returns the connection to auto-commit mode. +// PDO throws error if PDO::commit is called on a connection that is not in an active +// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have +// to worry about it here. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// 0 for failure and 1 for success. +int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + try { + + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_commit: pdo_dbh_t object was null" ); + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + + SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_commit: driver_data object was null" ); + + DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_commit: Not in transaction" ); + + core_sqlsrv_commit( driver_conn TSRMLS_CC ); + + return 1; + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_dbh_commit: Uncaught exception occurred."); + } +} + +// pdo_sqlsrv_dbh_rollback +// Maps to PDO::rollback. +// Rolls back a transaction. Returns the connection in auto-commit mode. +// PDO throws error if PDO::rollBack is called on a connection that is not in an active +// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have +// to worry about it here. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// 0 for failure and 1 for success. +int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + try { + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_rollback: pdo_dbh_t object was null" ); + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + + SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_rollback: driver_data object was null" ); + + DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_rollback: Not in transaction" ); + + core_sqlsrv_rollback( driver_conn TSRMLS_CC ); + + return 1; + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_dbh_rollback: Uncaught exception occurred."); + } +} + +// pdo_sqlsrv_dbh_set_attr +// Maps to PDO::setAttribute. Sets an attribute on the PDO connection object. +// PDO driver manager calls this function directly after calling the factory +// method for PDO, for any attribute which is specified in the PDO constructor. +// Parameters: +// dbh - The PDO connection object maintained by PDO. +// attr - The attribute to be set. +// val - The value of the attribute to be set. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + try { + + switch( attr ) { + + case SQLSRV_ATTR_ENCODING: + { + zend_long attr_value; + if( Z_TYPE_P( val ) != IS_LONG ) { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + } + attr_value = Z_LVAL_P( val ); + switch( attr_value ) { + + case SQLSRV_ENCODING_DEFAULT: + // when default is applied to a connection, that means use UTF-8 encoding + driver_dbh->set_encoding( SQLSRV_ENCODING_UTF8 ); + break; + case SQLSRV_ENCODING_SYSTEM: + case SQLSRV_ENCODING_UTF8: + driver_dbh->set_encoding( static_cast( attr_value )); + break; + default: + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + break; + } + } + break; + + case SQLSRV_ATTR_DIRECT_QUERY: + driver_dbh->direct_query = ( zend_is_true( val ) ) ? true : false; + break; + + case SQLSRV_ATTR_QUERY_TIMEOUT: + if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) < 0 ) { + convert_to_string( val ); + THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( val )); + } + driver_dbh->query_timeout = static_cast( Z_LVAL_P( val ) ); + break; + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) <= 0 ) { + convert_to_string( val ); + THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, Z_STRVAL_P( val )); + } + driver_dbh->client_buffer_max_size = Z_LVAL_P( val ); + break; + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + driver_dbh->fetch_numeric = (zend_is_true(val)) ? true : false; + break; + + // Not supported + case PDO_ATTR_FETCH_TABLE_NAMES: + case PDO_ATTR_FETCH_CATALOG_NAMES: + case PDO_ATTR_PREFETCH: + case PDO_ATTR_MAX_COLUMN_LEN: + case PDO_ATTR_CURSOR_NAME: + case PDO_ATTR_AUTOCOMMIT: + case PDO_ATTR_PERSISTENT: + case PDO_ATTR_TIMEOUT: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ); + } + + // Read-only + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: + case PDO_ATTR_CLIENT_VERSION: + case PDO_ATTR_DRIVER_NAME: + case PDO_ATTR_CONNECTION_STATUS: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR ); + } + + // Statement level only + case PDO_ATTR_EMULATE_PREPARES: + case PDO_ATTR_CURSOR: + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); + } + + default: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); + break; + } + } + } + catch( pdo::PDOException& ) { + + return 0; + } + + return 1; +} + + +// pdo_sqlsrv_dbh_get_attr +// Maps to PDO::getAttribute. Gets an attribute on the PDO connection object. +// Parameters: +// dbh - The PDO connection object maintained by PDO. +// attr - The attribute to get. +// return_value - zval in which to return the attribute value. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + try { + + switch( attr ) { + + // Not supported + case PDO_ATTR_FETCH_TABLE_NAMES: + case PDO_ATTR_FETCH_CATALOG_NAMES: + case PDO_ATTR_PREFETCH: + case PDO_ATTR_MAX_COLUMN_LEN: + case PDO_ATTR_CURSOR_NAME: + case PDO_ATTR_AUTOCOMMIT: + case PDO_ATTR_TIMEOUT: + { + // PDO does not throw "not supported" error message for these attributes. + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ); + } + + // Statement level only + case PDO_ATTR_EMULATE_PREPARES: + case PDO_ATTR_CURSOR: + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); + } + + case PDO_ATTR_STRINGIFY_FETCHES: + { + // For this attribute, if we dont set the return_value than PDO returns NULL. + ZVAL_BOOL(return_value, ( dbh->stringify ? 1 : 0 ) ); + break; + } + + case PDO_ATTR_SERVER_INFO: + { + core_sqlsrv_get_server_info( driver_dbh, return_value TSRMLS_CC ); + break; + } + + case PDO_ATTR_SERVER_VERSION: + { + core_sqlsrv_get_server_version( driver_dbh, return_value TSRMLS_CC ); + break; + } + + case PDO_ATTR_CLIENT_VERSION: + { + core_sqlsrv_get_client_info( driver_dbh, return_value TSRMLS_CC ); + + //Add the PDO SQLSRV driver's file version + //Declarations below eliminate compiler warnings about string constant to char* conversions + const char* extver = "ExtensionVer"; + std::string filever = VER_FILEVERSION_STR; + core::sqlsrv_add_assoc_string( *driver_dbh, return_value, extver, &filever[0], 1 /*duplicate*/ + TSRMLS_CC ); + break; + } + + case SQLSRV_ATTR_ENCODING: + { + ZVAL_LONG( return_value, driver_dbh->encoding() ); + break; + } + + case SQLSRV_ATTR_QUERY_TIMEOUT: + { + ZVAL_LONG( return_value, ( driver_dbh->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_dbh->query_timeout )); + break; + } + + case SQLSRV_ATTR_DIRECT_QUERY: + { + ZVAL_BOOL( return_value, driver_dbh->direct_query ); + break; + } + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + { + ZVAL_LONG( return_value, driver_dbh->client_buffer_max_size ); + break; + } + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + { + ZVAL_BOOL( return_value, driver_dbh->fetch_numeric ); + break; + } + + default: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); + break; + } + } + + return 1; + } + catch( core::CoreException& ) { + return 0; + } +} + +// Called by PDO::errorInfo and PDOStatement::errorInfo. +// Returns the error info. +// Parameters: +// dbh - The PDO managed connection object. +// stmt - The PDO managed statement object. +// info - zval in which to return the error info. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, + zval *info TSRMLS_DC) +{ + SQLSRV_ASSERT( dbh != NULL || stmt != NULL, "Either dbh or stmt must not be NULL to dereference the error." ); + + sqlsrv_error* ctx_error = NULL; + if( stmt ) { + ctx_error = static_cast( stmt->driver_data )->last_error(); + } + else { + ctx_error = static_cast( dbh->driver_data )->last_error(); + } + + pdo_sqlsrv_retrieve_context_error( ctx_error, info ); + + return 1; +} + +// pdo_sqlsrv_dbh_last_id +// Maps to PDO::lastInsertId. +// Returns the last id generated by an executed SQL statement +// Parameters: +// dbh - The PDO managed connection object. +// name - Table name. +// len - Length of the name. +// Return: +// Returns the last insert id as a string. +char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, _Out_ size_t* len TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + // turn off any error handling for last_id + pdo_error_mode prev_err_mode = dbh->error_mode; + dbh->error_mode = PDO_ERRMODE_SILENT; + + sqlsrv_malloc_auto_ptr driver_stmt; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + sqlsrv_malloc_auto_ptr id_str; + id_str = reinterpret_cast( sqlsrv_malloc( LAST_INSERT_ID_BUFF_LEN )); + + try { + + char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ]; + if( name == NULL ) { + strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY ); + } + else { + char* quoted_table = NULL; + size_t quoted_len = 0; + int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strlen( name ), "ed_table, "ed_len, PDO_PARAM_NULL TSRMLS_CC ); + SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name."); + snprintf( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, TABLE_LAST_INSERT_ID_QUERY, quoted_table ); + sqlsrv_free( quoted_table ); + } + + // temp PDO statement used for error handling if something happens + pdo_stmt_t temp_stmt; + temp_stmt.dbh = dbh; + + // allocate a full driver statement to take advantage of the error handling + driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); + driver_stmt->set_func( __FUNCTION__ ); + + + sqlsrv_malloc_auto_ptr wsql_string; + unsigned int wsql_len; + wsql_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_CHAR, reinterpret_cast( last_insert_id_query ), strlen(last_insert_id_query), &wsql_len ); + + CHECK_CUSTOM_ERROR( wsql_string == 0, driver_stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) { + throw core::CoreException(); + } + + // execute the last insert id query + core::SQLExecDirectW( driver_stmt, wsql_string TSRMLS_CC ); + + core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); + SQLRETURN r = core::SQLGetData( driver_stmt, 1, SQL_C_CHAR, id_str, LAST_INSERT_ID_BUFF_LEN, + reinterpret_cast( len ), false TSRMLS_CC ); + + CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt, + PDO_SQLSRV_ERROR_LAST_INSERT_ID ) { + throw core::CoreException(); + } + + driver_stmt->~sqlsrv_stmt(); + } + catch( core::CoreException& ) { + + // copy any errors on the statement to the connection so that the user sees them, since the statement is released + // before this method returns + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), + reinterpret_cast( driver_stmt->last_error()->sqlstate )); + driver_dbh->set_last_error( driver_stmt->last_error() ); + + if( driver_stmt ) { + driver_stmt->~sqlsrv_stmt(); + } + + strcpy_s( id_str.get(), 1, "" ); + *len = 0; + } + + char* ret_id_str = id_str.get(); + id_str.transferred(); + + // restore error handling to its previous mode + dbh->error_mode = prev_err_mode; + + return ret_id_str; +} + +// pdo_sqlsrv_dbh_quote +// Maps to PDO::quote. As the name says, this function quotes a string. +// Always returns a valid string unless memory allocation fails. +// Parameters: +// dbh - The PDO managed connection object. +// unquoted - The unquoted string to be quoted. +// unquoted_len - Length of the unquoted string. +// quoted - Buffer for output string. +// quoted_len - Length of the output string. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquoted_len, char **quoted, size_t* quoted_len, + enum pdo_param_type /*paramtype*/ TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); + SQLSRV_ENCODING encoding = driver_dbh->bind_param_encoding; + + 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 ) { + os << std::hex << ( int )unquoted[ index ]; + } + std::basic_string str_hex = os.str(); + // each character is represented by 2 digits of hex + size_t unquoted_str_len = unquoted_len * 2; // length returned should not account for null terminator + char* unquoted_str = reinterpret_cast( sqlsrv_malloc( unquoted_str_len, sizeof( char ), 1 )); // include space for null terminator + strcpy_s( unquoted_str, unquoted_str_len + 1 /* include null terminator*/, str_hex.c_str() ); + // include length of '0x' in the binary string + *quoted_len = unquoted_str_len + 2; + *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 ]; + } + // null terminator + ( *quoted )[ out_current ] = '\0'; + sqlsrv_free( unquoted_str ); + return 1; + } + else { + // count the number of quotes needed + unsigned int quotes_needed = 2; // the initial start and end quotes of course + // include the N proceeding the initial quote if encoding is UTF8 + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + quotes_needed = 3; + } + for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + if ( unquoted[ index ] == '\'' ) { + ++quotes_needed; + } + } + + *quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator. + *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); // include space for null terminator. + unsigned int out_current = 0; + + // insert N if the encoding is UTF8 + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + ( *quoted )[ out_current++ ] = 'N'; + } + // insert initial quote + ( *quoted )[ out_current++ ] = '\''; + + for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + if ( unquoted[ index ] == '\'' ) { + ( *quoted )[ out_current++ ] = '\''; + ( *quoted )[ out_current++ ] = '\''; + } + else { + ( *quoted )[ out_current++ ] = unquoted[ index ]; + } + } + + // trailing quote and null terminator + ( *quoted )[ out_current++ ] = '\''; + ( *quoted )[ out_current ] = '\0'; + + return 1; + } +} + +// This method is not implemented by this driver. +pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + CHECK_CUSTOM_ERROR( true, driver_conn, PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED ) { + return NULL; + } + + return NULL; // to avoid a compiler warning +} + +namespace { + +// Maps the PDO driver specific statement option/attribute constants to the core layer +// statement option/attribute constants. +void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, + zval* data TSRMLS_DC ) +{ + zend_ulong option_key = -1; + switch( key ) { + + case PDO_ATTR_CURSOR: + option_key = SQLSRV_STMT_OPTION_SCROLLABLE; + break; + + case SQLSRV_ATTR_ENCODING: + option_key = PDO_STMT_OPTION_ENCODING; + break; + + case SQLSRV_ATTR_QUERY_TIMEOUT: + option_key = SQLSRV_STMT_OPTION_QUERY_TIMEOUT; + break; + + case PDO_ATTR_STATEMENT_CLASS: + break; + + case SQLSRV_ATTR_DIRECT_QUERY: + option_key = PDO_STMT_OPTION_DIRECT_QUERY; + break; + + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + option_key = PDO_STMT_OPTION_CURSOR_SCROLL_TYPE; + break; + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + option_key = PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE; + break; + + case PDO_ATTR_EMULATE_PREPARES: + option_key = PDO_STMT_OPTION_EMULATE_PREPARES; + break; + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; + break; + + default: + CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { + throw core::CoreException(); + } + break; + } + + // if a PDO handled option makes it through (such as PDO_ATTR_STATEMENT_CLASS, just skip it + if( option_key != -1 ) { + zval_add_ref( data ); + core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data TSRMLS_CC ); + } +} + + +// validate_stmt_options +// Iterates through the list of statement options provided by the user and validates them +// against the list of statement options provided by this driver. After validation +// creates a Hashtable of statement options to be sent to the core layer for processing. +// Parameters: +// ctx - The current context. +// stmt_options - The user provided list of statement options. +// pdo_stmt_options_ht - Output hashtable of statement options. +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ) +{ + try { + + if( stmt_options ) { + + HashTable* options_ht = Z_ARRVAL_P( stmt_options ); + size_t int_key = -1; + zend_string *key = NULL; + zval* data = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + int result = 0; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_LONG ), ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { + throw core::CoreException(); + } + + add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); + } + } + catch( core::CoreException& ) { + + throw; + } +} + + +void pdo_bool_conn_str_func::func(connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ) +{ + TSRMLS_C; + char const* val_str = "no"; + + if( core_str_zval_is_true( value ) ) { + + val_str = "yes"; + } + + conn_str += option->odbc_name; + conn_str += "={"; + conn_str += val_str; + conn_str += "};"; +} + +void pdo_txn_isolation_conn_attr_func::func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, + std::string& /*conn_str*/ TSRMLS_DC ) +{ + try { + + SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "pdo_txn_isolation_conn_attr_func: Unexpected zval type." ); + const char* val = Z_STRVAL_P( value_z ); + size_t val_len = Z_STRLEN_P( value_z ); + zend_long out_val = SQL_TXN_READ_COMMITTED; + + // READ_COMMITTED + if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_COMMITTED ) - 1 ) + && !strcasecmp( val, PDOTxnIsolationValues::READ_COMMITTED ))) { + + out_val = SQL_TXN_READ_COMMITTED; + } + + // READ_UNCOMMITTED + else if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_UNCOMMITTED ) - 1 ) + && !strcasecmp( val, PDOTxnIsolationValues::READ_UNCOMMITTED ))) { + + out_val = SQL_TXN_READ_UNCOMMITTED; + } + + // REPEATABLE_READ + else if(( val_len == ( sizeof( PDOTxnIsolationValues::REPEATABLE_READ ) - 1 ) + && !strcasecmp( val, PDOTxnIsolationValues::REPEATABLE_READ ))) { + + out_val = SQL_TXN_REPEATABLE_READ; + } + + // SERIALIZABLE + else if(( val_len == ( sizeof( PDOTxnIsolationValues::SERIALIZABLE ) - 1 ) + && !strcasecmp( val, PDOTxnIsolationValues::SERIALIZABLE ))) { + + out_val = SQL_TXN_SERIALIZABLE; + } + + // SNAPSHOT + else if(( val_len == ( sizeof( PDOTxnIsolationValues::SNAPSHOT ) - 1 ) + && !strcasecmp( val, PDOTxnIsolationValues::SNAPSHOT ))) { + + out_val = SQL_TXN_SS_SNAPSHOT; + } + + else { + + CHECK_CUSTOM_ERROR( true, conn, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, PDOConnOptionNames::TransactionIsolation ) { + + throw core::CoreException(); + } + } + + core::SQLSetConnectAttr( conn, SQL_COPT_SS_TXN_ISOLATION, reinterpret_cast( out_val ), SQL_IS_UINTEGER TSRMLS_CC ); + + } + catch( core::CoreException& ) { + + throw; + } +} + +} // namespace diff --git a/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp similarity index 63% rename from pdo_sqlsrv/pdo_init.cpp rename to source/pdo_sqlsrv/pdo_init.cpp index 7db1ab9a..b20388ed 100644 --- a/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -1,403 +1,307 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: pdo_init.cpp -// -// Contents: initialization routines for PDO_SQLSRV -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "pdo_sqlsrv.h" -#include - -#ifdef ZTS -ZEND_TSRMLS_CACHE_DEFINE(); -#endif -ZEND_GET_MODULE(g_pdo_sqlsrv) - -extern "C" { - -ZEND_DECLARE_MODULE_GLOBALS(pdo_sqlsrv); - -} - -// module global variables (initialized in minit and freed in mshutdown) -HashTable* g_pdo_errors_ht = NULL; - -// henv context for creating connections -sqlsrv_context* g_henv_cp; -sqlsrv_context* g_henv_ncp; - -namespace { - -pdo_driver_t pdo_sqlsrv_driver = { - - PDO_DRIVER_HEADER(sqlsrv), - pdo_sqlsrv_db_handle_factory -}; - -const char PDO_DLL_NAME[] = "php_pdo.dll"; - -// PHP_DEBUG is always defined as either 0 or 1 -#if PHP_DEBUG == 1 && !defined(ZTS) - -const char PHP_DLL_NAME[] = "php7_debug.dll"; - -#elif PHP_DEBUG == 1 && defined(ZTS) - -const char PHP_DLL_NAME[] = "php7ts_debug.dll"; - -#elif PHP_DEBUG == 0 && !defined(ZTS) - -const char PHP_DLL_NAME[] = "php7.dll"; - -#elif PHP_DEBUG == 0 && defined(ZTS) - -const char PHP_DLL_NAME[] = "php7ts.dll"; - -#else - -#error Invalid combination of PHP_DEBUG and ZTS macros - -#endif - -typedef PDO_API int (*pdo_register_func)(pdo_driver_t *); - -// function pointers to register and unregister this driver -pdo_register_func pdo_register_driver; -pdo_register_func pdo_unregister_driver; - -// functions to register SQLSRV constants with the PDO class -// (It's in all CAPS so it looks like the Zend macros that do similar work) -void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC ); -void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC ); - -// return the Zend class entry for the PDO dbh (connection) class -zend_class_entry* (*pdo_get_dbh_class)( void ); - -struct sqlsrv_attr_pdo_constant { - const char *name; - int value; -}; - -// forward decl for table -extern sqlsrv_attr_pdo_constant pdo_attr_constants[]; - -} - -static zend_module_dep pdo_sqlsrv_depends[] = { - ZEND_MOD_REQUIRED("pdo") - {NULL, NULL, NULL} -}; - - -// argument info structures for functions, arranged alphabetically. -// see zend_API.h in the PHP sources for more information about these macros - -// function table with associated arginfo structures -zend_function_entry pdo_sqlsrv_functions[] = { - {NULL, NULL, NULL} // no functions directly defined by this driver -}; - -// the structure returned to Zend that exposes the extension to the Zend engine. -// this structure is defined in zend_modules.h in the PHP sources - -zend_module_entry g_pdo_sqlsrv_module_entry = -{ - STANDARD_MODULE_HEADER_EX, - NULL, - pdo_sqlsrv_depends, - "pdo_sqlsrv", - pdo_sqlsrv_functions, // exported function table - // initialization and shutdown functions - PHP_MINIT(pdo_sqlsrv), - PHP_MSHUTDOWN(pdo_sqlsrv), - PHP_RINIT(pdo_sqlsrv), - PHP_RSHUTDOWN(pdo_sqlsrv), - PHP_MINFO(pdo_sqlsrv), - // version of the extension. Matches the version resource of the extension dll - VER_FILEVERSION_STR, - PHP_MODULE_GLOBALS(pdo_sqlsrv), - NULL, - NULL, - NULL, - STANDARD_MODULE_PROPERTIES_EX -}; - -// functions dynamically linked from the PDO (or PHP) dll and called by other parts of the driver -zend_class_entry* (*pdo_get_exception_class)( void ); -int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, size_t inquery_len, - char **outquery, size_t *outquery_len TSRMLS_DC); - -// called by Zend for each parameter in the g_pdo_errors_ht hash table when it is destroyed -void pdo_error_dtor(zval* elem) { - pdo_error* error_to_ignore = reinterpret_cast(Z_PTR_P(elem)); - pefree(error_to_ignore, 1); -} - -// Module initialization -// This function is called once per execution of the Zend engine - -PHP_MINIT_FUNCTION(pdo_sqlsrv) -{ - SQLSRV_UNUSED( type ); - - // our global variables are initialized in the RINIT function -#if defined(ZTS) - if( ts_allocate_id( &pdo_sqlsrv_globals_id, - sizeof( zend_pdo_sqlsrv_globals ), - (ts_allocate_ctor) NULL, - (ts_allocate_dtor) NULL ) == 0 ) - return FAILURE; - ZEND_TSRMLS_CACHE_UPDATE(); -#endif - - core_sqlsrv_register_logger( pdo_sqlsrv_log ); - - REGISTER_INI_ENTRIES(); - - LOG( SEV_NOTICE, "pdo_sqlsrv: entering minit" ); - - // PHP extensions may be either external DLLs loaded by PHP or statically compiled into the PHP dll - // This becomes an issue when we are dependent on other extensions, e.g. PDO. Normally this is solved - // by the build process by linking our extension to the appropriate import library (either php*.dll or php_pdo.dll) - // However, this leaves us with a problem that the extension has a dependency on that build type. - // Since we don't distribute our extension with PHP directly (yet), it would mean that we would have to have SKUs - // for both types of PDO builds, internal and external. Rather than this, we just dynamically link the PDO routines we call - // against either the PDO dll if it exists and is loaded, otherwise against the PHP dll directly. - - DWORD needed = 0; - HANDLE hprocess = GetCurrentProcess(); - HMODULE pdo_hmodule; - - pdo_hmodule = GetModuleHandle( PDO_DLL_NAME ); - if( pdo_hmodule == 0 ) { - - pdo_hmodule = GetModuleHandle( PHP_DLL_NAME ); - if( pdo_hmodule == NULL ) { - LOG( SEV_ERROR, "Failed to get PHP module handle." ); - return FAILURE; - } - } - - pdo_register_driver = reinterpret_cast( GetProcAddress( pdo_hmodule, "php_pdo_register_driver" )); - if( pdo_register_driver == NULL ) { - LOG( SEV_ERROR, "Failed to register driver." ); - return FAILURE; - } - - pdo_unregister_driver = reinterpret_cast( GetProcAddress( pdo_hmodule, "php_pdo_unregister_driver" )); - if( pdo_unregister_driver == NULL ) { - LOG( SEV_ERROR, "Failed to register driver." ); - return FAILURE; - } - - pdo_get_exception_class = reinterpret_cast( GetProcAddress( pdo_hmodule, - "php_pdo_get_exception" )); - if( pdo_get_exception_class == NULL ) { - LOG( SEV_ERROR, "Failed to register driver." ); - return FAILURE; - } - - pdo_get_dbh_class = reinterpret_cast( GetProcAddress( pdo_hmodule, "php_pdo_get_dbh_ce" )); - if( pdo_get_dbh_class == NULL ) { - LOG( SEV_ERROR, "Failed to register driver." ); - return FAILURE; - } - - pdo_subst_named_params = - reinterpret_cast( - GetProcAddress( pdo_hmodule, "pdo_parse_params" )); - if( pdo_subst_named_params == NULL ) { - LOG( SEV_ERROR, "Failed to register driver." ); - return FAILURE; - } - - // initialize list of pdo errors - 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 ) { - - void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[ i ].error_code, - &( PDO_ERRORS[ i ].sqlsrv_error ), sizeof( PDO_ERRORS[ i ].sqlsrv_error ) ); - if( zr == NULL ) { - - LOG( SEV_ERROR, "Failed to insert data into PDO errors hashtable." ); - return FAILURE; - } - } - - try { - - // register all attributes supported by this driver. - for( int i= 0; pdo_attr_constants[i].name != NULL; ++i ) { - - REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( pdo_attr_constants[i].name, pdo_attr_constants[i].value TSRMLS_CC ); - - } - - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_UNCOMMITTED", PDOTxnIsolationValues::READ_UNCOMMITTED TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_COMMITTED", PDOTxnIsolationValues::READ_COMMITTED TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_REPEATABLE_READ", PDOTxnIsolationValues::REPEATABLE_READ TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SERIALIZABLE", PDOTxnIsolationValues::SERIALIZABLE TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SNAPSHOT", PDOTxnIsolationValues::SNAPSHOT TSRMLS_CC ); - - // retrieve the handles for the environments - core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, pdo_sqlsrv_handle_env_error, "PHP_MINIT_FUNCTION for pdo_sqlsrv" TSRMLS_CC ); - - } - catch( ... ) { - - return FAILURE; - } - - pdo_register_driver( &pdo_sqlsrv_driver ); - - return SUCCESS; -} - -// Module shutdown function - -// Module shutdown function -// This function is called once per execution of the Zend engine - -PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv) -{ - try { - - SQLSRV_UNUSED( type ); - - UNREGISTER_INI_ENTRIES(); - - pdo_unregister_driver( &pdo_sqlsrv_driver ); - - // clean up the list of pdo errors - zend_hash_destroy( g_pdo_errors_ht ); - pefree( g_pdo_errors_ht, 1 /*persistent*/ ); - - core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp ); - - } - catch( ... ) { - - LOG( SEV_NOTICE, "Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)" ); - return FAILURE; - } - - return SUCCESS; -} - - -// Request initialization function -// This function is called once per PHP script execution - -PHP_RINIT_FUNCTION(pdo_sqlsrv) -{ - SQLSRV_UNUSED( module_number ); - SQLSRV_UNUSED( type ); - -#if defined(ZTS) - ZEND_TSRMLS_CACHE_UPDATE(); -#endif - - LOG( SEV_NOTICE, "pdo_sqlsrv: entering rinit" ); - - return SUCCESS; -} - - -// Request shutdown -// Called at the end of a script's execution - -PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv) -{ - SQLSRV_UNUSED( module_number ); - SQLSRV_UNUSED( type ); - - LOG( SEV_NOTICE, "pdo_sqlsrv: entering rshutdown" ); - - return SUCCESS; -} - -// Called for php_info(); -// Displays the INI settings registered and their current values - -PHP_MINFO_FUNCTION(pdo_sqlsrv) -{ - php_info_print_table_start(); - php_info_print_table_header(2, "pdo_sqlsrv support", "enabled"); - php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); - php_info_print_table_end(); - DISPLAY_INI_ENTRIES(); -} - -// *** internal init functions *** - -namespace { - - // mimic the functionality of the REGISTER_PDO_CLASS_CONST_LONG. We use this instead of the macro because - // we dynamically link the pdo_get_dbh_class function rather than use the static php_pdo_get_dbh_ce (see MINIT) - - void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC ) - { - zend_class_entry* zend_class = pdo_get_dbh_class(); - SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_LONG: pdo_get_dbh_class failed" ); - int zr = zend_declare_class_constant_long( zend_class, const_cast( name ), strlen( name ), value TSRMLS_CC ); - if( zr == FAILURE ) { - throw core::CoreException(); - } - } - - void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC ) - { - zend_class_entry* zend_class = pdo_get_dbh_class(); - SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_STRING: pdo_get_dbh_class failed" ); - int zr = zend_declare_class_constant_string( zend_class, const_cast( name ), strlen( name ), const_cast( value ) TSRMLS_CC ); - if( zr == FAILURE ) { - - throw core::CoreException(); - } - } - - // array of pdo constants. - sqlsrv_attr_pdo_constant pdo_attr_constants[] = { - - // driver specific attributes - { "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING }, - { "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT }, - { "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY }, - { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, - { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, - { "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE }, - - // used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int, - // PDO::PARAM_STR uses the size of the string in the variable - { "SQLSRV_PARAM_OUT_DEFAULT_SIZE" , -1 }, - - // encoding attributes - { "SQLSRV_ENCODING_DEFAULT" , SQLSRV_ENCODING_DEFAULT }, - { "SQLSRV_ENCODING_SYSTEM" , SQLSRV_ENCODING_SYSTEM }, - { "SQLSRV_ENCODING_BINARY" , SQLSRV_ENCODING_BINARY }, - { "SQLSRV_ENCODING_UTF8" , SQLSRV_ENCODING_UTF8 }, - - // cursor types (can be assigned to SQLSRV_ATTR_CURSOR_SCROLL_TYPE - { "SQLSRV_CURSOR_STATIC" , SQL_CURSOR_STATIC }, - { "SQLSRV_CURSOR_DYNAMIC" , SQL_CURSOR_DYNAMIC }, - { "SQLSRV_CURSOR_KEYSET" , SQL_CURSOR_KEYSET_DRIVEN }, - { "SQLSRV_CURSOR_BUFFERED" , static_cast(SQLSRV_CURSOR_BUFFERED) }, - - { NULL , 0 } // terminate the table - }; -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_init.cpp +// +// Contents: initialization routines for PDO_SQLSRV +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_pdo_sqlsrv.h" + +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE(); +#endif +ZEND_GET_MODULE(g_pdo_sqlsrv) + +extern "C" { + +ZEND_DECLARE_MODULE_GLOBALS(pdo_sqlsrv); + +} + +// module global variables (initialized in minit and freed in mshutdown) +HashTable* g_pdo_errors_ht = NULL; + +// henv context for creating connections +sqlsrv_context* g_henv_cp; +sqlsrv_context* g_henv_ncp; + +namespace { + +pdo_driver_t pdo_sqlsrv_driver = { + + PDO_DRIVER_HEADER(sqlsrv), + pdo_sqlsrv_db_handle_factory +}; + +// functions to register SQLSRV constants with the PDO class +// (It's in all CAPS so it looks like the Zend macros that do similar work) +void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC ); +void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC ); + +struct sqlsrv_attr_pdo_constant { + const char *name; + int value; +}; + +// forward decl for table +extern sqlsrv_attr_pdo_constant pdo_attr_constants[]; + +} + +static zend_module_dep pdo_sqlsrv_depends[] = { + ZEND_MOD_REQUIRED("pdo") + {NULL, NULL, NULL} +}; + + +// argument info structures for functions, arranged alphabetically. +// see zend_API.h in the PHP sources for more information about these macros + +// function table with associated arginfo structures +zend_function_entry pdo_sqlsrv_functions[] = { + {NULL, NULL, NULL} // no functions directly defined by this driver +}; + +// the structure returned to Zend that exposes the extension to the Zend engine. +// this structure is defined in zend_modules.h in the PHP sources + +zend_module_entry g_pdo_sqlsrv_module_entry = +{ + STANDARD_MODULE_HEADER_EX, + NULL, + pdo_sqlsrv_depends, + "pdo_sqlsrv", + pdo_sqlsrv_functions, // exported function table + // initialization and shutdown functions + PHP_MINIT(pdo_sqlsrv), + PHP_MSHUTDOWN(pdo_sqlsrv), + PHP_RINIT(pdo_sqlsrv), + PHP_RSHUTDOWN(pdo_sqlsrv), + PHP_MINFO(pdo_sqlsrv), + // version of the extension. Matches the version resource of the extension dll + VER_FILEVERSION_STR, + PHP_MODULE_GLOBALS(pdo_sqlsrv), + NULL, + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +// called by Zend for each parameter in the g_pdo_errors_ht hash table when it is destroyed +void pdo_error_dtor( zval* elem ) { + pdo_error* error_to_ignore = reinterpret_cast( Z_PTR_P( elem ) ); + pefree( error_to_ignore, 1 ); +} + +// Module initialization +// This function is called once per execution of the Zend engine + +PHP_MINIT_FUNCTION(pdo_sqlsrv) +{ + SQLSRV_UNUSED( type ); + + // our global variables are initialized in the RINIT function +#if defined(ZTS) + if( ts_allocate_id( &pdo_sqlsrv_globals_id, + sizeof( zend_pdo_sqlsrv_globals ), + (ts_allocate_ctor) NULL, + (ts_allocate_dtor) NULL ) == 0 ) + return FAILURE; + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + core_sqlsrv_register_logger( pdo_sqlsrv_log ); + + REGISTER_INI_ENTRIES(); + + LOG( SEV_NOTICE, "pdo_sqlsrv: entering minit" ); + + // initialize list of pdo errors + 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 ) { + + void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[ i ].error_code, + &( PDO_ERRORS[ i ].sqlsrv_error ), sizeof( PDO_ERRORS[ i ].sqlsrv_error ) ); + if( zr == NULL ) { + + LOG( SEV_ERROR, "Failed to insert data into PDO errors hashtable." ); + return FAILURE; + } + } + + try { + + // register all attributes supported by this driver. + for( int i= 0; pdo_attr_constants[i].name != NULL; ++i ) { + + REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( pdo_attr_constants[i].name, pdo_attr_constants[i].value TSRMLS_CC ); + + } + + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_UNCOMMITTED", PDOTxnIsolationValues::READ_UNCOMMITTED TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_COMMITTED", PDOTxnIsolationValues::READ_COMMITTED TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_REPEATABLE_READ", PDOTxnIsolationValues::REPEATABLE_READ TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SERIALIZABLE", PDOTxnIsolationValues::SERIALIZABLE TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SNAPSHOT", PDOTxnIsolationValues::SNAPSHOT TSRMLS_CC ); + + // retrieve the handles for the environments + core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, pdo_sqlsrv_handle_env_error, "PHP_MINIT_FUNCTION for pdo_sqlsrv" TSRMLS_CC ); + + } + catch( ... ) { + + return FAILURE; + } + + php_pdo_register_driver( &pdo_sqlsrv_driver ); + + return SUCCESS; +} + +// Module shutdown function +// This function is called once per execution of the Zend engine + +PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv) +{ + try { + + SQLSRV_UNUSED( type ); + + UNREGISTER_INI_ENTRIES(); + + php_pdo_unregister_driver( &pdo_sqlsrv_driver ); + + // clean up the list of pdo errors + zend_hash_destroy( g_pdo_errors_ht ); + pefree( g_pdo_errors_ht, 1 /*persistent*/ ); + + core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp ); + + } + catch( ... ) { + + LOG( SEV_NOTICE, "Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)" ); + return FAILURE; + } + + return SUCCESS; +} + + +// Request initialization function +// This function is called once per PHP script execution + +PHP_RINIT_FUNCTION(pdo_sqlsrv) +{ + SQLSRV_UNUSED( module_number ); + SQLSRV_UNUSED( type ); + +#if defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + LOG( SEV_NOTICE, "pdo_sqlsrv: entering rinit" ); + + return SUCCESS; +} + + +// Request shutdown +// Called at the end of a script's execution + +PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv) +{ + SQLSRV_UNUSED( module_number ); + SQLSRV_UNUSED( type ); + + LOG( SEV_NOTICE, "pdo_sqlsrv: entering rshutdown" ); + + return SUCCESS; +} + +// Called for php_info(); +// Displays the INI settings registered and their current values + +PHP_MINFO_FUNCTION(pdo_sqlsrv) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "pdo_sqlsrv support", "enabled"); + php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} + +// *** internal init functions *** + +namespace { + + // mimic the functionality of the REGISTER_PDO_CLASS_CONST_LONG. We use this instead of the macro because + // we dynamically link the pdo_get_dbh_class function rather than use the static php_pdo_get_dbh_ce (see MINIT) + + void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC ) + { + zend_class_entry* zend_class = php_pdo_get_dbh_ce(); + + SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_LONG: php_pdo_get_dbh_ce failed"); + int zr = zend_declare_class_constant_long( zend_class, const_cast( name ), strlen( name ), value TSRMLS_CC ); + if( zr == FAILURE ) { + throw core::CoreException(); + } + } + + void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC ) + { + zend_class_entry* zend_class = php_pdo_get_dbh_ce(); + + SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_STRING: php_pdo_get_dbh_ce failed"); + int zr = zend_declare_class_constant_string( zend_class, const_cast( name ), strlen( name ), const_cast( value ) TSRMLS_CC ); + if( zr == FAILURE ) { + + throw core::CoreException(); + } + } + + // array of pdo constants. + sqlsrv_attr_pdo_constant pdo_attr_constants[] = { + + // driver specific attributes + { "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING }, + { "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT }, + { "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY }, + { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, + { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, + { "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE }, + + // used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int, + // PDO::PARAM_STR uses the size of the string in the variable + { "SQLSRV_PARAM_OUT_DEFAULT_SIZE" , -1 }, + + // encoding attributes + { "SQLSRV_ENCODING_DEFAULT" , SQLSRV_ENCODING_DEFAULT }, + { "SQLSRV_ENCODING_SYSTEM" , SQLSRV_ENCODING_SYSTEM }, + { "SQLSRV_ENCODING_BINARY" , SQLSRV_ENCODING_BINARY }, + { "SQLSRV_ENCODING_UTF8" , SQLSRV_ENCODING_UTF8 }, + + // cursor types (can be assigned to SQLSRV_ATTR_CURSOR_SCROLL_TYPE + { "SQLSRV_CURSOR_STATIC" , SQL_CURSOR_STATIC }, + { "SQLSRV_CURSOR_DYNAMIC" , SQL_CURSOR_DYNAMIC }, + { "SQLSRV_CURSOR_KEYSET" , SQL_CURSOR_KEYSET_DRIVEN }, + { "SQLSRV_CURSOR_BUFFERED" , static_cast(SQLSRV_CURSOR_BUFFERED) }, + + { NULL , 0 } // terminate the table + }; +} diff --git a/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp similarity index 96% rename from pdo_sqlsrv/pdo_parser.cpp rename to source/pdo_sqlsrv/pdo_parser.cpp index f055ea8e..74c1a222 100644 --- a/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -1,359 +1,362 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: pdo_parser.cpp -// -// Contents: Implements a parser to parse the PDO DSN. -// -// Copyright Microsoft Corporation -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "pdo_sqlsrv.h" - -// Constructor -conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ) -{ - this->conn_str = dsn; - this->len = len; - this->conn_options_ht = conn_options_ht; - this->pos = -1; - this->ctx = &ctx; -} - -// Move to the next character -inline bool conn_string_parser::next( void ) -{ - // if already at the end then return false - if( this->is_eos() ) { - - return false; - } - - SQLSRV_ASSERT( this->pos < len, "Unexpected cursor position in conn_string_parser::next" ); - - this->pos++; - - if ( this->is_eos() ) { - - return false; - } - - return true; -} - -// Check for end of string. -inline bool conn_string_parser::is_eos( void ) -{ - if( this->pos == len ) - { - return true; // EOS - } - - SQLSRV_ASSERT(this->pos < len, "Unexpected cursor position in conn_string_parser::is_eos" ); - - return false; -} - -// Check for white space. -inline bool conn_string_parser::is_white_space( char c ) -{ - if( c == ' ' || c == '\r' || c == '\n' || c == '\t' ) { - return true; - } - return false; -} - -// Discard any trailing white spaces. -int conn_string_parser::discard_trailing_white_spaces( const char* str, int len ) -{ - const char* end = str + ( len - 1 ); - - while(( this->is_white_space( *end ) ) && (len > 0) ) { - - len--; - end--; - } - - return len; -} - -// Discard white spaces. -bool conn_string_parser::discard_white_spaces() -{ - if( this->is_eos() ) { - - return false; - } - - while( this->is_white_space( this->conn_str[ pos ] )) { - - if( !next() ) - return false; - } - - return true; -} - -// Add a key-value pair to the hashtable of connection options. -void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC ) -{ - zval value_z; - ZVAL_UNDEF( &value_z ); - - if( len == 0 ) { - - ZVAL_STRINGL( &value_z, "", 0); - } - else { - - ZVAL_STRINGL( &value_z, const_cast( value ), len ); - } - - core::sqlsrv_zend_hash_index_update( *ctx, this->conn_options_ht, this->current_key, &value_z TSRMLS_CC ); -} - -// Validate a given DSN keyword. -void conn_string_parser::validate_key(const char *key, int key_len TSRMLS_DC ) -{ - int new_len = discard_trailing_white_spaces( key, key_len ); - - for( int i=0; PDO_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) - { - // discard the null terminator. - if( new_len == ( PDO_CONN_OPTS[ i ].sqlsrv_len - 1 ) && !_strnicmp( key, PDO_CONN_OPTS[ i ].sqlsrv_name, new_len )) { - - this->current_key = PDO_CONN_OPTS[ i ].conn_option_key; - this->current_key_name = PDO_CONN_OPTS[ i ].sqlsrv_name; - return; - } - } - - // encountered an invalid key, throw error. - sqlsrv_malloc_auto_ptr key_name; - key_name = static_cast( sqlsrv_malloc( new_len + 1 )); - memcpy_s( key_name, new_len + 1 ,key, new_len ); - key_name[ new_len ] = '\0'; - - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, key_name ); -} - -// Primary function which parses the connection string/DSN. -void conn_string_parser:: parse_conn_string( TSRMLS_D ) -{ - States state = FirstKeyValuePair; // starting state - int start_pos = -1; - - try { - - while( !this->is_eos() ) { - - switch( state ) { - - case FirstKeyValuePair: - { - // discard leading spaces - if( !next() || !discard_white_spaces() ) { - - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING ); //EOS - } - - state = Key; - break; - } - - case Key: - { - start_pos = this->pos; - - // read the key name - while( this->conn_str[ pos ] != '=' ) { - - if( !next() ) { - - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY ); //EOS - } - } - - this->validate_key( &( this->conn_str[ start_pos ] ), ( pos - start_pos ) TSRMLS_CC ); - - state = Value; - - break; - } - - case Value: - { - SQLSRV_ASSERT(( this->conn_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->conn_str[ pos ] == ';' ) { - - add_key_value_pair( NULL, 0 TSRMLS_CC ); - - if( this->is_eos() ) { - - break; // EOS - } - else { - - // this->conn_str[ pos ] == ';' - state = NextKeyValuePair; - } - } - - // if LCB - else if( this->conn_str[ pos ] == '{' ) { - - start_pos = this->pos; // starting character is LCB - state = ValueContent1; - } - - // If NonSP-LCB-SC - else { - - start_pos = this->pos; - state = ValueContent2; - } - - break; - } - - case ValueContent1: - { - while ( this->conn_str[ pos ] != '}' ) { - - if ( ! next() ) { - - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, this->current_key_name ); - } - } - - // If we reached here than RCB encountered - state = RCBEncountered; - - break; - } - - case ValueContent2: - { - while( this->conn_str[ pos ] != ';' ) { - - if( ! next() ) { - - break; //EOS - } - } - - if( !this->is_eos() && this->conn_str[ pos ] == ';' ) { - - // semi-colon encountered, so go to next key-value pair - state = NextKeyValuePair; - } - - add_key_value_pair( &( this->conn_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 " ); - - break; - } - - case RCBEncountered: - { - - // Read the next character after RCB. - if( !next() ) { - - // EOS - add_key_value_pair( &( this->conn_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->conn_str[ pos ] == '}' ) { - - if( !next() ) { - - // EOS after a second RCB is error - THROW_PDO_ERROR( this->ctx, SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, this->current_key_name ); - } - - state = ValueContent1; - break; - } - - int end_pos = this->pos; - - // discard any trailing white-spaces. - if( this->is_white_space( this->conn_str[ pos ] )) { - - if( ! this->discard_white_spaces() ) { - - //EOS - add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); - break; - } - } - - // if semi-colon than go to next key-value pair - if ( this->conn_str[ pos ] == ';' ) { - - add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); - state = NextKeyValuePair; - break; - } - - // Non - (RCB, SP*, SC, EOS) character. Any other character after an RCB is an error. - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, this->current_key_name ); - break; - } - case NextKeyValuePair: - { - SQLSRV_ASSERT(( this->conn_str[ pos ] == ';' ), - "conn_string_parser::parse_conn_string: semi-colon was expected." ); - - // Call next() to skip the semi-colon. - if( !next() || !this->discard_white_spaces() ) { - - // EOS - break; - } - - if( this->conn_str[ pos ] == ';' ) { - - // a second semi-colon is error case. - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos ); - } - - else { - - // any other character leads to the next key - state = Key; - break; - } - } //case NextKeyValuePair - } // switch - } //while - } - catch( pdo::PDOException& ) { - - throw; - } -} - +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_parser.cpp +// +// Contents: Implements a parser to parse the PDO DSN. +// +// Copyright Microsoft Corporation +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_pdo_sqlsrv.h" + +// Constructor +conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ) +{ + this->conn_str = dsn; + this->len = len; + this->conn_options_ht = conn_options_ht; + this->pos = -1; + this->ctx = &ctx; + this->current_key = 0; + this->current_key_name = NULL; +} + +// Move to the next character +inline bool conn_string_parser::next( void ) +{ + // if already at the end then return false + if( this->is_eos() ) { + + return false; + } + + SQLSRV_ASSERT( this->pos < len, "Unexpected cursor position in conn_string_parser::next" ); + + this->pos++; + + if ( this->is_eos() ) { + + return false; + } + + return true; +} + +// Check for end of string. +inline bool conn_string_parser::is_eos( void ) +{ + if( this->pos == len ) + { + return true; // EOS + } + + SQLSRV_ASSERT(this->pos < len, "Unexpected cursor position in conn_string_parser::is_eos" ); + + return false; +} + +// Check for white space. +inline bool conn_string_parser::is_white_space( char c ) +{ + if( c == ' ' || c == '\r' || c == '\n' || c == '\t' ) { + return true; + } + return false; +} + +// Discard any trailing white spaces. +int conn_string_parser::discard_trailing_white_spaces( const char* str, int len ) +{ + const char* end = str + ( len - 1 ); + + while(( this->is_white_space( *end ) ) && (len > 0) ) { + + len--; + end--; + } + + return len; +} + +// Discard white spaces. +bool conn_string_parser::discard_white_spaces() +{ + if( this->is_eos() ) { + + return false; + } + + while( this->is_white_space( this->conn_str[ pos ] )) { + + if( !next() ) + return false; + } + + return true; +} + +// Add a key-value pair to the hashtable of connection options. +void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC ) +{ + zval value_z; + ZVAL_UNDEF( &value_z ); + + if( len == 0 ) { + + ZVAL_STRINGL( &value_z, "", 0); + } + else { + + ZVAL_STRINGL( &value_z, const_cast( value ), len ); + } + + core::sqlsrv_zend_hash_index_update( *ctx, this->conn_options_ht, this->current_key, &value_z TSRMLS_CC ); +} + +// Validate a given DSN keyword. +void conn_string_parser::validate_key(const char *key, int key_len TSRMLS_DC ) +{ + int new_len = discard_trailing_white_spaces( key, key_len ); + + for( int i=0; PDO_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) + { + // discard the null terminator. + if( new_len == ( PDO_CONN_OPTS[ i ].sqlsrv_len - 1 ) && !strncasecmp( key, PDO_CONN_OPTS[ i ].sqlsrv_name, new_len )) { + + this->current_key = PDO_CONN_OPTS[ i ].conn_option_key; + this->current_key_name = PDO_CONN_OPTS[ i ].sqlsrv_name; + return; + } + } + + // encountered an invalid key, throw error. + sqlsrv_malloc_auto_ptr key_name; + key_name = static_cast( sqlsrv_malloc( new_len + 1 )); + memcpy_s( key_name, new_len + 1 ,key, new_len ); + + key_name[ new_len ] = '\0'; + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast( key_name ) ); +} + +// Primary function which parses the connection string/DSN. +void conn_string_parser:: parse_conn_string( TSRMLS_D ) +{ + States state = FirstKeyValuePair; // starting state + int start_pos = -1; + + try { + + while( !this->is_eos() ) { + + switch( state ) { + + case FirstKeyValuePair: + { + // discard leading spaces + if( !next() || !discard_white_spaces() ) { + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING ); //EOS + } + + state = Key; + break; + } + + case Key: + { + start_pos = this->pos; + + // read the key name + while( this->conn_str[ pos ] != '=' ) { + + if( !next() ) { + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY ); //EOS + } + } + + this->validate_key( &( this->conn_str[ start_pos ] ), ( pos - start_pos ) TSRMLS_CC ); + + state = Value; + + break; + } + + case Value: + { + SQLSRV_ASSERT(( this->conn_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->conn_str[ pos ] == ';' ) { + + add_key_value_pair( NULL, 0 TSRMLS_CC ); + + if( this->is_eos() ) { + + break; // EOS + } + else { + + // this->conn_str[ pos ] == ';' + state = NextKeyValuePair; + } + } + + // if LCB + else if( this->conn_str[ pos ] == '{' ) { + + start_pos = this->pos; // starting character is LCB + state = ValueContent1; + } + + // If NonSP-LCB-SC + else { + + start_pos = this->pos; + state = ValueContent2; + } + + break; + } + + case ValueContent1: + { + while ( this->conn_str[ pos ] != '}' ) { + + if ( ! next() ) { + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, this->current_key_name ); + } + } + + // If we reached here than RCB encountered + state = RCBEncountered; + + break; + } + + case ValueContent2: + { + while( this->conn_str[ pos ] != ';' ) { + + if( ! next() ) { + + break; //EOS + } + } + + if( !this->is_eos() && this->conn_str[ pos ] == ';' ) { + + // semi-colon encountered, so go to next key-value pair + state = NextKeyValuePair; + } + + add_key_value_pair( &( this->conn_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 " ); + + break; + } + + case RCBEncountered: + { + + // Read the next character after RCB. + if( !next() ) { + + // EOS + add_key_value_pair( &( this->conn_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->conn_str[ pos ] == '}' ) { + + if( !next() ) { + + // EOS after a second RCB is error + THROW_PDO_ERROR( this->ctx, SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, this->current_key_name ); + } + + state = ValueContent1; + break; + } + + int end_pos = this->pos; + + // discard any trailing white-spaces. + if( this->is_white_space( this->conn_str[ pos ] )) { + + if( ! this->discard_white_spaces() ) { + + //EOS + add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); + break; + } + } + + // if semi-colon than go to next key-value pair + if ( this->conn_str[ pos ] == ';' ) { + + add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); + state = NextKeyValuePair; + break; + } + + // Non - (RCB, SP*, SC, EOS) character. Any other character after an RCB is an error. + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, this->current_key_name ); + break; + } + case NextKeyValuePair: + { + SQLSRV_ASSERT(( this->conn_str[ pos ] == ';' ), + "conn_string_parser::parse_conn_string: semi-colon was expected." ); + + // Call next() to skip the semi-colon. + if( !next() || !this->discard_white_spaces() ) { + + // EOS + break; + } + + if( this->conn_str[ pos ] == ';' ) { + + // a second semi-colon is error case. + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos ); + } + + else { + + // any other character leads to the next key + state = Key; + break; + } + } //case NextKeyValuePair + } // switch + } //while + } + catch( pdo::PDOException& ) { + + throw; + } +} + diff --git a/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp similarity index 93% rename from pdo_sqlsrv/pdo_stmt.cpp rename to source/pdo_sqlsrv/pdo_stmt.cpp index 324afebc..ff92c6d4 100644 --- a/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1,1340 +1,1358 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: pdo_stmt.cpp -// -// Contents: Implements the PDOStatement object for the PDO_SQLSRV -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "pdo_sqlsrv.h" - -// *** internal variables and constants *** - -namespace { - -// Maps to the list of PDO::FETCH_ORI_* constants -SQLSMALLINT odbc_fetch_orientation[] = -{ - SQL_FETCH_NEXT, // PDO_FETCH_ORI_NEXT - SQL_FETCH_PRIOR, // PDO_FETCH_ORI_PRIOR - SQL_FETCH_FIRST, // PDO_FETCH_ORI_FIRST - SQL_FETCH_LAST, // PDO_FETCH_ORI_LAST - SQL_FETCH_ABSOLUTE, // PDO_FETCH_ORI_ABS - SQL_FETCH_RELATIVE // PDO_FETCH_ORI_REL -}; - -// max length of a field type -const int SQL_SERVER_IDENT_SIZE_MAX = 128; - -inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori (enum pdo_fetch_orientation ori) -{ - SQLSRV_ASSERT( ori >= PDO_FETCH_ORI_NEXT && ori <= PDO_FETCH_ORI_REL, "Fetch orientation out of range." ); - OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); - OACR_WARNING_SUPPRESS( 26000, "Buffer length verified above" ); - return odbc_fetch_orientation[ori]; -} - -// Returns SQLSRV data type for a given PDO type. See pdo_param_type -// for list of supported pdo types. -SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( sqlsrv_stmt* driver_stmt, enum pdo_param_type pdo_type TSRMLS_DC ) -{ - switch( pdo_type ) { - - case PDO_PARAM_BOOL: - case PDO_PARAM_INT: - return SQLSRV_PHPTYPE_INT; - - case PDO_PARAM_STR: - return SQLSRV_PHPTYPE_STRING; - - case PDO_PARAM_NULL: - return SQLSRV_PHPTYPE_NULL; - - case PDO_PARAM_LOB: - // TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented. - return SQLSRV_PHPTYPE_STRING; - - case PDO_PARAM_STMT: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED ); - break; - - default: - DIE( "pdo_type_to_sqlsrv_php_type: Unexpected pdo_param_type encountered" ); - } - - return SQLSRV_PHPTYPE_INVALID; // to prevent compiler warning -} - -// Returns a pdo type for a given SQL type. See pdo_param_type -// for list of supported pdo types. -inline pdo_param_type sql_type_to_pdo_type( SQLSMALLINT sql_type ) -{ - pdo_param_type return_type = PDO_PARAM_STR; - - switch( sql_type ) { - - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - case SQL_BIGINT: - case SQL_BINARY: - case SQL_CHAR: - case SQL_DECIMAL: - case SQL_DOUBLE: - case SQL_FLOAT: - case SQL_GUID: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_NUMERIC: - case SQL_REAL: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_UDT: - case SQL_SS_VARIANT: - case SQL_SS_XML: - case SQL_TYPE_DATE: - case SQL_TYPE_TIMESTAMP: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_WCHAR: - case SQL_WLONGVARCHAR: - case SQL_WVARCHAR: - return_type = PDO_PARAM_STR; - break; - - default: { - DIE( "sql_type_to_pdo_type: Invalid SQL type provided." ); - break; - } - } - - return return_type; -} - -// Calls core_sqlsrv_set_scrollable function to set cursor. -// PDO supports two cursor types: PDO_CURSOR_FWDONLY, PDO_CURSOR_SCROLL. -void set_stmt_cursors( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - if( Z_TYPE_P( value_z ) != IS_LONG ) { - - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); - } - - zend_long pdo_cursor_type = Z_LVAL_P( value_z ); - long odbc_cursor_type = -1; - - switch( pdo_cursor_type ) { - - case PDO_CURSOR_FWDONLY: - odbc_cursor_type = SQL_CURSOR_FORWARD_ONLY; - break; - - case PDO_CURSOR_SCROLL: - odbc_cursor_type = SQL_CURSOR_STATIC; - break; - - default: - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); - } - - core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); -} - -void set_stmt_cursor_scroll_type( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - if( Z_TYPE_P( value_z ) != IS_LONG ) { - - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); - } - - if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { - - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE ); - } - - long odbc_cursor_type = static_cast( Z_LVAL_P( value_z ) ); - - core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); - - return; -} - -// Sets the statement encoding. Default encoding on the statement -// implies use the connection's encoding. -void set_stmt_encoding( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - // validate the value - if( Z_TYPE_P( value_z ) != IS_LONG ) { - - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING ); - } - - zend_long attr_value = Z_LVAL_P( value_z ); - - switch( attr_value ) { - - // when the default encoding is applied to a statement, it means use the creating connection's encoding - case SQLSRV_ENCODING_DEFAULT: - case SQLSRV_ENCODING_BINARY: - case SQLSRV_ENCODING_SYSTEM: - case SQLSRV_ENCODING_UTF8: - stmt->set_encoding( static_cast( attr_value )); - break; - - default: - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING ); - break; - } -} - -// internal helper function to free meta data structures allocated -void meta_data_free( field_meta_data* meta ) -{ - if( meta->field_name ) { - meta->field_name.reset(); - } - sqlsrv_free( meta ); -} - -zval convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len ) -{ - zval 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 { - 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; - } - - case SQLSRV_PHPTYPE_DATETIME: - DIE( "Unsupported php type" ); - out_zval = *( reinterpret_cast( *in_val )); - break; - - case SQLSRV_PHPTYPE_NULL: - ZVAL_NULL( &out_zval ); - break; - - default: - DIE( "Unknown php type" ); - break; - } - - return out_zval; -} - -} // namespace - -int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC); -int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC); -int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, - zend_long offset TSRMLS_DC); -int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, - struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC); -int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC); -int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, - char **ptr, size_t *len, int *caller_frees TSRMLS_DC); -int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC); -int pdo_sqlsrv_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC); -int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC); -int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC); -int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC); - -struct pdo_stmt_methods pdo_sqlsrv_stmt_methods = { - - pdo_sqlsrv_stmt_dtor, - pdo_sqlsrv_stmt_execute, - pdo_sqlsrv_stmt_fetch, - pdo_sqlsrv_stmt_describe_col, - pdo_sqlsrv_stmt_get_col_data, - pdo_sqlsrv_stmt_param_hook, - pdo_sqlsrv_stmt_set_attr, - pdo_sqlsrv_stmt_get_attr, - pdo_sqlsrv_stmt_get_col_meta, - pdo_sqlsrv_stmt_next_rowset, - pdo_sqlsrv_stmt_close_cursor - -}; - -void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - set_stmt_cursors( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_encoding:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - set_stmt_encoding( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_direct_query:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); - pdo_stmt->direct_query = ( zend_is_true( value_z )) ? true : false; -} - -void stmt_option_cursor_scroll_type:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - set_stmt_cursor_scroll_type( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_emulate_prepares:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - pdo_stmt_t *pdo_stmt = static_cast( stmt->driver() ); - pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; -} - -void stmt_option_fetch_numeric:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); - pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false; -} - - -// log a function entry point -#define PDO_LOG_STMT_ENTRY \ -{ \ - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ - driver_stmt->set_func( __FUNCTION__ ); \ - LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ -} - -// PDO SQLSRV statement destructor -pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void ) -{ - std::for_each( current_meta_data.begin(), current_meta_data.end(), meta_data_free ); - current_meta_data.clear(); - - if( bound_column_param_types ) { - sqlsrv_free( bound_column_param_types ); - bound_column_param_types = NULL; - } - - if( direct_query_subst_string ) { - // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme - // and we use estrdup to allocate this string, which uses emalloc - efree( reinterpret_cast( const_cast( direct_query_subst_string ))); - } -} - - -// pdo_sqlsrv_stmt_close_cursor -// Close any open cursors on the statement. Maps to PDO function PDOStatement::closeCursor. -// Parameters: -// *stmt - Pointer to current statement -// Return: -// Returns 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" ); - - sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); - - // to "close the cursor" means we make the statement ready for execution again. To do this, we - // skip all the result sets on the current statement. - while( driver_stmt->past_next_result_end == false ) { - - core_sqlsrv_next_result( driver_stmt TSRMLS_CC ); - } - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advanding to the next result set." ); - } - - return 1; -} - -// pdo_sqlsrv_stmt_describe_col -// Gets the metadata for a column based on the column number. -// Calls the core_sqlsrv_field_metadata function present in the core layer. -// Parameters: -// *stmt - pointer to current statement -// colno - Index of the column which requires description. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - SQLSRV_ASSERT(( colno >= 0 ), "pdo_sqlsrv_stmt_describe_col: Column number should be >= 0." ); - - sqlsrv_malloc_auto_ptr core_meta_data; - - try { - - core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast( stmt->driver_data ), colno TSRMLS_CC ); - } - - catch( core::CoreException& ) { - return 0; - } - - catch(...) { - DIE( "pdo_sqlsrv_stmt_describe_col: Unexpected exception occurred." ); - } - - pdo_column_data* column_data = &(stmt->columns[colno]); - - SQLSRV_ASSERT( column_data != NULL, "pdo_sqsrv_stmt_describe_col: pdo_column_data was null" ); - - // Set the name - column_data->name = zend_string_init( (const char*)core_meta_data->field_name.get(), core_meta_data->field_name_len, 0 ); - core_meta_data->field_name.reset(); - - // Set the maxlen - column_data->maxlen = ( core_meta_data->field_precision > 0 ) ? core_meta_data->field_precision : core_meta_data->field_size; - - // Set the precision - column_data->precision = core_meta_data->field_scale; - - // Set the param_type - column_data->param_type = PDO_PARAM_ZVAL; - - // store the field data for use by pdo_sqlsrv_stmt_get_col_data - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - SQLSRV_ASSERT( driver_stmt != NULL, "Invalid driver statement in pdo_sqlsrv_stmt_describe_col" ); - driver_stmt->current_meta_data.push_back( core_meta_data.get() ); - SQLSRV_ASSERT( driver_stmt->current_meta_data.size() == colno + 1, "Meta data vector out of sync with column numbers" ); - core_meta_data.transferred(); - - return 1; -} - - -// pdo_sqlsrv_stmt_dtor -// Maps to PDOStatement::__destruct. Destructor for the PDO Statement. -// Parameters: -// *stmt - pointer to current statement -// Return: -// 1 for success. -int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) -{ - sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - - LOG( SEV_NOTICE, "pdo_sqlsrv_stmt_dtor: entering" ); - - // if a PDO statement didn't complete preparation, its driver_data can be NULL - if( driver_stmt == NULL ) { - - return 1; - } - - driver_stmt->~sqlsrv_stmt(); - - sqlsrv_free( driver_stmt ); - - stmt->driver_data = NULL; - - return 1; -} - -// pdo_sqlsrv_stmt_execute -// Maps to PDOStatement::Execute. Executes the prepared statement. -// Parameters: -// *stmt - pointer to the current statement. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_execute: driver_data object was null" ); - - // prepare for execution by flushing anything remaining in the result set if it wasn't already - // done before binding parameters - if( driver_stmt->executed && !driver_stmt->past_next_result_end ) { - - while( driver_stmt->past_next_result_end == false ) { - - core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false ); - } - } - - const char* query = NULL; - unsigned int query_len = 0; - - // if the user is doing a direct query (PDO::SQLSRV_ATTR_DIRECT_QUERY), set the query here - if( driver_stmt->direct_query ) { - - query = driver_stmt->direct_query_subst_string; - query_len = static_cast( driver_stmt->direct_query_subst_string_len ); - } - - // if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the - // subtituted query provided by PDO - if( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { - - query = stmt->active_query_string; - query_len = static_cast( stmt->active_query_stringlen ); - } - - core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len ); - - stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); - - // return the row count regardless if there are any rows or not - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); - - // workaround for a bug in the PDO driver manager. It is fairly simple to crash the PDO driver manager with - // the following sequence: - // 1) Prepare and execute a statement (that has some results with it) - // 2) call PDOStatement::nextRowset until there are no more results - // 3) execute the statement again - // 4) call PDOStatement::getColumnMeta - // It crashes from what I can tell because there is no metadata because there was no call to - // pdo_stmt_sqlsrv_describe_col and stmt->columns is NULL on the second call to - // PDO::execute. My guess is that because stmt->executed is true, it optimizes away a necessary call to - // pdo_sqlsrv_stmt_describe_col. By setting the stmt->executed flag to 0, this call is not optimized away - // and the crash disappears. - if( stmt->columns == NULL ) { - stmt->executed = 0; - } - } - catch( core::CoreException& /*e*/ ) { - - return 0; - - } - catch( ... ) { - - DIE( "pdo_sqlsrv_stmt_execute: Unexpected exception occurred." ); - - } - - // success - return 1; -} - - - -// pdo_sqlsrv_stmt_fetch -// Maps to PDOStatement::fetch -// Move the cursor to the record indicated. If the cursor is moved off the end, -// or before the beginning if a scrollable cursor is created, then FAILURE is returned. -// Parameters: -// *stmt - pointer to current statement for which the cursor should be moved. -// ori - cursor orientation. Maps to the list of PDO::FETCH_ORI_* constants -// offset - For orientations that use it, offset to move to. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, - zend_long offset TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_fetch: pdo_stmt object was null" ); - - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_fetch: driver_data object was null" ); - - // set the types for bound columns to zval so that PDO does no conversion when the value - // is returned by pdo_sqlsrv_get_col_data. Remember the types that were bound by the user - // and use it to manually convert data types - if( stmt->bound_columns ) { - - pdo_bound_param_data* bind_data = NULL; - - if( !driver_stmt->bound_column_param_types ) { - driver_stmt->bound_column_param_types = - reinterpret_cast( sqlsrv_malloc( stmt->column_count, sizeof( pdo_param_type ), 0 )); - std::fill( driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count, - PDO_PARAM_ZVAL ); - } - - for( long i = 0; i < stmt->column_count; ++i ) { - - 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; - continue; - } - - if( bind_data->param_type != PDO_PARAM_ZVAL ) { - - driver_stmt->bound_column_param_types[ i ] = bind_data->param_type; - bind_data->param_type = PDO_PARAM_ZVAL; - } - } - } - - SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori ); - bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset TSRMLS_CC ); - - // support for the PDO rowCount method. Since rowCount doesn't call a method, PDO relies on us to fill the - // pdo_stmt_t::row_count member - if( driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { - - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); - - // a row_count of -1 means no rows, but we change it to 0 - if( stmt->row_count == -1 ) { - - stmt->row_count = 0; - } - } - - // if no data was returned, then return false so data isn't retrieved - if( !data ) { - return 0; - } - - return 1; - } - - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE ("pdo_sqlsrv_stmt_fetch: Unexpected exception occurred."); - } -} - -// pdo_sqlsrv_stmt_get_col_data -// Called by the set of PDO Fetch functions. -// Retrieves a single column. PDO driver manager is responsible for freeing the -// returned buffer. Because PDO can request fields out of order and ODBC does not -// support out of order field requests, this function should also cache fields. -// Parameters: -// stmt - Statement to retrive the column for. -// colno - Index of the column that needs to be retrieved. Starts with 0. -// ptr - Returns the buffer containing the column data. -// len - Length of the buffer returned. -// caller_frees - Flag to let the PDO driver manager know that it is responsible for -// freeing the memory. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, - char **ptr, size_t *len, int *caller_frees TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: pdo_stmt object was null" ); - - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: driver_data object was null" ); - - CHECK_CUSTOM_ERROR((colno < 0), driver_stmt, PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX ) { - return 0; - } - - // Let PDO free the memory after use. - *caller_frees = 1; - - // set the metadata for the current column - pdo_column_data* column_data = &(stmt->columns[colno]); - - // translate the pdo type to a type the core layer understands - sqlsrv_phptype sqlsrv_php_type; - SQLSRV_ASSERT( colno >= 0 && colno < static_cast( driver_stmt->current_meta_data.size()), - "Invalid column number in pdo_sqlsrv_stmt_get_col_data" ); - sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast( driver_stmt->current_meta_data[ colno ]->field_type ), - static_cast( driver_stmt->current_meta_data[ colno ]->field_size ), - true, driver_stmt->fetch_numeric ); - - // set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding - sqlsrv_php_type.typeinfo.encoding = driver_stmt->encoding(); - - // 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 ) { - - sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type( driver_stmt, - driver_stmt->bound_column_param_types[ colno ] - TSRMLS_CC ); - - pdo_bound_param_data* bind_data = NULL; - bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, colno)); - - if( bind_data != NULL && !Z_ISUNDEF(bind_data->driver_params) ) { - - CHECK_CUSTOM_ERROR( Z_TYPE( bind_data->driver_params ) != IS_LONG, driver_stmt, - PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, colno + 1 ) { - throw pdo::PDOException(); - } - - CHECK_CUSTOM_ERROR( driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_STR - && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_LOB, driver_stmt, - PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, colno + 1 ) { - - throw pdo::PDOException(); - } - - sqlsrv_php_type.typeinfo.encoding = Z_LVAL( bind_data->driver_params ); - - switch( sqlsrv_php_type.typeinfo.encoding ) { - case SQLSRV_ENCODING_SYSTEM: - case SQLSRV_ENCODING_BINARY: - case SQLSRV_ENCODING_UTF8: - break; - default: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, colno ); - break; - } - } - } - - SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID; - core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast(ptr)), - reinterpret_cast( len ), true, &sqlsrv_phptype_out TSRMLS_CC ); - - zval* zval_ptr = ( zval* )( sqlsrv_malloc( sizeof( zval ))); - *zval_ptr = convert_to_zval( sqlsrv_phptype_out, reinterpret_cast( ptr ), *len ); - *ptr = reinterpret_cast( zval_ptr ); - - *len = sizeof( zval ); - - return 1; - } - - catch ( core::CoreException& ) { - return 0; - } - catch ( ... ) { - DIE ("pdo_sqlsrv_stmt_get_col_data: Unexpected exception occurred."); - } -} - -// pdo_sqlsrv_stmt_set_attr -// Maps to the PDOStatement::setAttribute. Sets the attribute on a statement. -// Parameters: -// stmt - Current statement on which the attribute should be set. -// attr - Represents any valid set of attribute constants supported by this driver. -// val - Attribute value. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - pdo_sqlsrv_stmt* pdo_stmt = static_cast( stmt->driver_data ); - sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); - - try { - - switch( attr ) { - - case SQLSRV_ATTR_DIRECT_QUERY: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY ); - break; - - case SQLSRV_ATTR_ENCODING: - set_stmt_encoding( driver_stmt, val TSRMLS_CC ); - break; - - case PDO_ATTR_CURSOR: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY ); - break; - - case SQLSRV_ATTR_QUERY_TIMEOUT: - core_sqlsrv_set_query_timeout( driver_stmt, val TSRMLS_CC ); - break; - - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY ); - break; - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - core_sqlsrv_set_buffered_query_limit( driver_stmt, val TSRMLS_CC ); - break; - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - pdo_stmt->fetch_numeric = ( zend_is_true( val )) ? true : false; - break; - - default: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); - break; - } - } - catch( core::CoreException& ) { - return 0; - } - - catch ( ... ) { - DIE ("pdo_sqlsrv_stmt_set_attr: Unexpected exception occurred."); - } - - return 1; -} - - -// pdo_sqlsrv_stmt_get_attr -// Maps to the PDOStatement::getAttribute. Gets the value of a given attribute on a statement. -// Parameters: -// stmt - Current statement for which the attribute value is requested. -// attr - Represents any valid set of attribute constants supported by this driver. -// return_value - Attribute value. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_get_attr( pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC ) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - pdo_sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); - SQLSRV_ASSERT(( driver_stmt != NULL ), "pdo_sqlsrv_stmt_get_attr: stmt->driver_data was null" ); - - try { - - switch( attr ) { - - case SQLSRV_ATTR_DIRECT_QUERY: - { - ZVAL_BOOL( return_value, driver_stmt->direct_query ); - break; - } - - case SQLSRV_ATTR_ENCODING: - { - ZVAL_LONG( return_value, driver_stmt->encoding() ); - break; - } - - case PDO_ATTR_CURSOR: - { - ZVAL_LONG( return_value, ( driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ? - PDO_CURSOR_SCROLL : PDO_CURSOR_FWDONLY )); - break; - } - - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - { - ZVAL_LONG( return_value, driver_stmt->cursor_type ); - break; - } - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - { - ZVAL_LONG( return_value, driver_stmt->buffered_query_limit ); - break; - } - - case SQLSRV_ATTR_QUERY_TIMEOUT: - { - ZVAL_LONG( return_value, ( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_stmt->query_timeout )); - break; - } - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - { - ZVAL_BOOL( return_value, driver_stmt->fetch_numeric ); - break; - } - - default: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); - break; - } - } - catch( core::CoreException& ) { - - return 0; - } - catch ( ... ) { - - DIE ("pdo_sqlsrv_stmt_get_attr: Unexpected exception occurred."); - } - - return 1; -} - -// pdo_sqlsrv_stmt_get_col_meta -// Maps to PDOStatement::getColumnMeta. Return extra metadata. -// Though we don't return any extra metadata, PDO relies on us to -// create the associative array that holds the standard information, -// so we create one and return it for PDO's use. -// Parameters: -// stmt - Current statement. -// colno - The index of the field for which to return the metadata. -// return_value - zval* consisting of the metadata. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." ); - - sqlsrv_malloc_auto_ptr core_meta_data; - - sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); - - SQLSRV_ASSERT( colno >= 0 && colno < SHRT_MAX, "pdo_sqlsrv_stmt_get_col_meta: tried to overflow a short" ); - core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); - // initialize the array to nothing, as PDO requires us to create it - core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC ); - - // add the following fields: flags, native_type, driver:decl_type, table - add_assoc_long( return_value, "flags", 0 ); - - // get the name of the data type - char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ]; - SQLSMALLINT out_buff_len; - SQLLEN not_used; - core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name, - sizeof( field_type_name ), &out_buff_len, ¬_used TSRMLS_CC ); - add_assoc_string( return_value, "sqlsrv:decl_type", field_type_name ); - - // get the PHP type of the column. The types returned here mirror the types returned by debug_zval_dump when - // given a variable of the same type. However, debug_zval_dump also gives the length of a string, and we only - // say string, since the length is given in another field of the metadata array. - long pdo_type = sql_type_to_pdo_type( core_meta_data->field_type ); - switch( pdo_type ) { - case PDO_PARAM_STR: - add_assoc_string( return_value, "native_type", "string" ); - break; - default: - DIE( "pdo_sqlsrv_stmt_get_col_data: Unknown PDO type returned" ); - break; - } - - // add the table name of the field. All the tests so far show this to always be "", but we adhere to the PDO spec - char table_name[ SQL_SERVER_IDENT_SIZE_MAX ]; - 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[ colno ].param_type == PDO_PARAM_ZVAL ) { - add_assoc_long( return_value, "pdo_type", pdo_type ); - } - - // this will ensure that the field_name field, which is an auto pointer gets freed. - (*core_meta_data).~field_meta_data(); - } - catch( core::CoreException& ) { - - return 0; - } - catch(...) { - - DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." ); - } - - return 1; -} - - -// pdo_sqlsrv_stmt_next_rowset -// Maps to PDOStatement::nextRowset. -// Move the cursor to the beginning of the next rowset in a multi-rowset result. -// Clears the field cache from the last row retrieved using pdo_sqlsrv_stmt_get_col_data. -// Calls core_sqlsrv_next_result using the core_stmt found within stmt->driver_data. -// If another result set is available, this function returns 1. Otherwise it returns 0. -// Parameters: -// stmt - PDOStatement object containing the result set. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" ); - - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); - - core_sqlsrv_next_result( static_cast( stmt->driver_data ) TSRMLS_CC ); - - // clear the current meta data since the new result will generate new meta data - std::for_each( driver_stmt->current_meta_data.begin(), driver_stmt->current_meta_data.end(), meta_data_free ); - driver_stmt->current_meta_data.clear(); - - // if there are no more result sets, return that it failed. - if( driver_stmt->past_next_result_end == true ) { - return 0; - } - - stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); - - // return the row count regardless if there are any rows or not - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advancing to the next result set." ); - } - - return 1; -} - -// pdo_sqlsrv_stmt_param_hook -// Maps to PDOStatement::bindColumn. -// Called by PDO driver manager to bind a parameter or column. -// This function pulls several duties for binding parameters and columns. -// It takes an event as a parameter that explains what the function should do on -// behalf of a parameter or column. We only use one of these events, -// PDO_PARAM_EVT_EXEC_PRE, the remainder simply return. -// Paramters: -// stmt - PDO Statement object to bind a parameter. -// param - paramter to bind. -// event_type - Event to bind a parameter -// Return: -// Returns 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, - struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - - try { - - switch( event_type ) { - - // since the param isn't reliable, we don't do anything here - case PDO_PARAM_EVT_ALLOC: - // if emulate prepare is on, set the bind_param_encoding so it can be used in PDO::quote when binding parameters on the client side - if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( stmt->dbh->driver_data ); - driver_dbh->bind_param_encoding = static_cast( Z_LVAL( param->driver_params )); - } - break; - case PDO_PARAM_EVT_FREE: - break; - // bind the parameter in the core layer - case PDO_PARAM_EVT_EXEC_PRE: - { - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - // skip column bindings - if( !param->is_param ) { - break; - } - - sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_param_hook: driver_data object was null" ); - - // prepare for binding parameters by flushing anything remaining in the result set - if( driver_stmt->executed && !driver_stmt->past_next_result_end ) { - - while( driver_stmt->past_next_result_end == false ) { - - core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false ); - } - } - - int direction = SQL_PARAM_INPUT; - SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE; - SQLULEN column_size = SQLSRV_UNKNOWN_SIZE; - SQLSMALLINT decimal_digits = 0; - // determine the direction of the parameter. By default it's input, but if the user specifies a size - // that means they want output, and if they include the flag, then it's input/output. - // It's invalid to specify the input/output flag but not specify a length - CHECK_CUSTOM_ERROR( (param->param_type & PDO_PARAM_INPUT_OUTPUT) && (param->max_value_len == 0), - driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) { - throw pdo::PDOException(); - } - if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) { - if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) { - direction = SQL_PARAM_INPUT_OUTPUT; - } - else { - direction = SQL_PARAM_OUTPUT; - } - } - // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant - // and the SQLSRV_PHPTYPE_* constant - int pdo_type = param->param_type; - SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; - switch( pdo_type & ~PDO_PARAM_INPUT_OUTPUT ) { - case PDO_PARAM_BOOL: - case PDO_PARAM_INT: - php_out_type = SQLSRV_PHPTYPE_INT; - break; - case PDO_PARAM_STR: - php_out_type = SQLSRV_PHPTYPE_STRING; - break; - // when the user states PDO::PARAM_NULL, they mean send a null no matter what the variable is - // since the core layer keys off the zval type, we substitute a null for what they gave us - case PDO_PARAM_NULL: - { - zval null_zval; - php_out_type = SQLSRV_PHPTYPE_NULL; - - ZVAL_NULL( &null_zval ); - zval_ptr_dtor( ¶m->parameter ); - param->parameter = null_zval; - break; - } - case PDO_PARAM_LOB: - php_out_type = SQLSRV_PHPTYPE_STREAM; - break; - case PDO_PARAM_STMT: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED ); - break; - default: - SQLSRV_ASSERT( false, "Unknown PDO::PARAM_* constant given." ); - break; - } - // set the column size parameter for bind_param if we are expecting something back - if( direction != SQL_PARAM_INPUT ) { - switch( php_out_type ) { - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_STREAM: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE ); - break; - case SQLSRV_PHPTYPE_INT: - column_size = SQLSRV_UNKNOWN_SIZE; - break; - case SQLSRV_PHPTYPE_STRING: - { - CHECK_CUSTOM_ERROR( param->max_value_len <= 0, driver_stmt, - PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, param->paramno + 1 ) { - throw pdo::PDOException(); - } - column_size = param->max_value_len; - break; - } - default: - SQLSRV_ASSERT( false, "Invalid PHP type for output parameter. Should have been caught already." ); - break; - } - } - // block all objects from being bound as input or input/output parameters since there is a - // weird case: - // $obj = date_create(); - // $s->bindParam( n, $obj, PDO::PARAM_INT ); // anything different than PDO::PARAM_STR - // that succeeds since the core layer implements DateTime object handling for the sqlsrv - // 2.0 driver. To be consistent and avoid surprises of one object type working and others - // not, we block all objects here. - CHECK_CUSTOM_ERROR( direction != SQL_PARAM_OUTPUT && Z_TYPE( param->parameter ) == IS_OBJECT, - driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) { - throw pdo::PDOException(); - } - // the encoding by default is that set on the statement - SQLSRV_ENCODING encoding = driver_stmt->encoding(); - // if the statement's encoding is the default, then use the one on the connection - if( encoding == SQLSRV_ENCODING_DEFAULT ) { - encoding = driver_stmt->conn->encoding(); - } - // if the user provided an encoding, use it instead - if( !Z_ISUNDEF(param->driver_params) ) { - CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) { - throw pdo::PDOException(); - } - CHECK_CUSTOM_ERROR( pdo_type != PDO_PARAM_STR && pdo_type != PDO_PARAM_LOB, driver_stmt, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, param->paramno + 1 ) { - throw pdo::PDOException(); - } - encoding = static_cast( Z_LVAL( param->driver_params )); - - switch( encoding ) { - case SQLSRV_ENCODING_SYSTEM: - case SQLSRV_ENCODING_BINARY: - case SQLSRV_ENCODING_UTF8: - break; - default: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, - param->paramno + 1 ); - break; - } - } - // and bind the parameter - core_sqlsrv_bind_param( driver_stmt, static_cast( param->paramno ), direction, &(param->parameter) , php_out_type, encoding, - sql_type, column_size, decimal_digits TSRMLS_CC ); - } - break; - // undo any work done by the core layer after the statement is executed - case PDO_PARAM_EVT_EXEC_POST: - { - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - // skip column bindings - if( !param->is_param ) { - break; - } - - core_sqlsrv_post_param( reinterpret_cast( stmt->driver_data ), param->paramno, - &(param->parameter) TSRMLS_CC ); - } - break; - case PDO_PARAM_EVT_FETCH_PRE: - break; - case PDO_PARAM_EVT_FETCH_POST: - break; - case PDO_PARAM_EVT_NORMALIZE: - break; - default: - DIE( "pdo_sqlsrv_stmt_param_hook: Unknown event type" ); - break; - } - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE( "pdo_sqlsrv_stmt_param_hook: Unknown exception" ); - } - - return 1; -} - - -// Returns a sqlsrv_phptype for a given SQL Server data type. -sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_over_stream, bool prefer_number_to_string ) -{ - sqlsrv_phptype sqlsrv_phptype; - int local_encoding = this->encoding(); - // if the encoding on the connection changed - if( this->encoding() == SQLSRV_ENCODING_DEFAULT ) { - local_encoding = conn->encoding(); - SQLSRV_ASSERT( conn->encoding() != SQLSRV_ENCODING_DEFAULT || conn->encoding() == SQLSRV_ENCODING_INVALID, - "Invalid encoding on the connection. Must not be invalid or default." ); - } - - switch( sql_type ) { - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - if ( prefer_number_to_string ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = local_encoding; - } - break; - case SQL_FLOAT: - case SQL_REAL: - if ( prefer_number_to_string ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = local_encoding; - } - break; - case SQL_BIGINT: - case SQL_CHAR: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - 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: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = local_encoding; - break; - case SQL_BINARY: - case SQL_LONGVARBINARY: - case SQL_VARBINARY: - case SQL_SS_UDT: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; - break; - default: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; - break; - } - - return sqlsrv_phptype; -} - +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_stmt.cpp +// +// Contents: Implements the PDOStatement object for the PDO_SQLSRV +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_pdo_sqlsrv.h" + +// *** internal variables and constants *** + +namespace { + +// Maps to the list of PDO::FETCH_ORI_* constants +SQLSMALLINT odbc_fetch_orientation[] = +{ + SQL_FETCH_NEXT, // PDO_FETCH_ORI_NEXT + SQL_FETCH_PRIOR, // PDO_FETCH_ORI_PRIOR + SQL_FETCH_FIRST, // PDO_FETCH_ORI_FIRST + SQL_FETCH_LAST, // PDO_FETCH_ORI_LAST + SQL_FETCH_ABSOLUTE, // PDO_FETCH_ORI_ABS + SQL_FETCH_RELATIVE // PDO_FETCH_ORI_REL +}; + +// max length of a field type +const int SQL_SERVER_IDENT_SIZE_MAX = 128; + +inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori (enum pdo_fetch_orientation ori) +{ + SQLSRV_ASSERT( ori >= PDO_FETCH_ORI_NEXT && ori <= PDO_FETCH_ORI_REL, "Fetch orientation out of range."); +#ifdef _WIN32 + OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); + OACR_WARNING_SUPPRESS( 26000, "Buffer length verified above" ); +#endif + return odbc_fetch_orientation[ori]; +} + +// Returns SQLSRV data type for a given PDO type. See pdo_param_type +// for list of supported pdo types. +SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( sqlsrv_stmt* driver_stmt, enum pdo_param_type pdo_type TSRMLS_DC ) +{ + switch( pdo_type ) { + + case PDO_PARAM_BOOL: + case PDO_PARAM_INT: + return SQLSRV_PHPTYPE_INT; + + case PDO_PARAM_STR: + return SQLSRV_PHPTYPE_STRING; + + case PDO_PARAM_NULL: + return SQLSRV_PHPTYPE_NULL; + + case PDO_PARAM_LOB: + // TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented. + return SQLSRV_PHPTYPE_STRING; + + case PDO_PARAM_STMT: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED ); + break; + + default: + DIE( "pdo_type_to_sqlsrv_php_type: Unexpected pdo_param_type encountered" ); + } + + return SQLSRV_PHPTYPE_INVALID; // to prevent compiler warning +} + +// Returns a pdo type for a given SQL type. See pdo_param_type +// for list of supported pdo types. +inline pdo_param_type sql_type_to_pdo_type( SQLSMALLINT sql_type ) +{ + pdo_param_type return_type = PDO_PARAM_STR; + + switch( sql_type ) { + + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + case SQL_BIGINT: + case SQL_BINARY: + case SQL_CHAR: + case SQL_DECIMAL: + case SQL_DOUBLE: + case SQL_FLOAT: + case SQL_GUID: + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_NUMERIC: + case SQL_REAL: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_UDT: + case SQL_SS_VARIANT: + case SQL_SS_XML: + case SQL_TYPE_DATE: + case SQL_TYPE_TIMESTAMP: + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_WCHAR: + case SQL_WLONGVARCHAR: + case SQL_WVARCHAR: + return_type = PDO_PARAM_STR; + break; + + default: { + DIE( "sql_type_to_pdo_type: Invalid SQL type provided." ); + break; + } + } + + return return_type; +} + +// Calls core_sqlsrv_set_scrollable function to set cursor. +// PDO supports two cursor types: PDO_CURSOR_FWDONLY, PDO_CURSOR_SCROLL. +void set_stmt_cursors( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + if( Z_TYPE_P( value_z ) != IS_LONG ) { + + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); + } + + zend_long pdo_cursor_type = Z_LVAL_P( value_z ); + long odbc_cursor_type = -1; + + switch( pdo_cursor_type ) { + + case PDO_CURSOR_FWDONLY: + odbc_cursor_type = SQL_CURSOR_FORWARD_ONLY; + break; + + case PDO_CURSOR_SCROLL: + odbc_cursor_type = SQL_CURSOR_STATIC; + break; + + default: + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); + } + + core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); +} + +void set_stmt_cursor_scroll_type( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + if( Z_TYPE_P( value_z ) != IS_LONG ) { + + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); + } + + if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { + + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE ); + } + + long odbc_cursor_type = static_cast( Z_LVAL_P( value_z ) ); + + core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); + + return; +} + +// Sets the statement encoding. Default encoding on the statement +// implies use the connection's encoding. +void set_stmt_encoding( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + // validate the value + if( Z_TYPE_P( value_z ) != IS_LONG ) { + + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + } + + zend_long attr_value = Z_LVAL_P( value_z ); + + switch( attr_value ) { + + // when the default encoding is applied to a statement, it means use the creating connection's encoding + case SQLSRV_ENCODING_DEFAULT: + case SQLSRV_ENCODING_BINARY: + case SQLSRV_ENCODING_SYSTEM: + case SQLSRV_ENCODING_UTF8: + stmt->set_encoding( static_cast( attr_value )); + break; + + default: + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + break; + } +} + +// internal helper function to free meta data structures allocated +void meta_data_free( field_meta_data* meta ) +{ + if( meta->field_name ) { + meta->field_name.reset(); + } + sqlsrv_free( meta ); +} + +zval convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len ) +{ + zval 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 { + 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; + } + + case SQLSRV_PHPTYPE_DATETIME: + DIE( "Unsupported php type" ); + out_zval = *( reinterpret_cast( *in_val )); + break; + + case SQLSRV_PHPTYPE_NULL: + ZVAL_NULL( &out_zval ); + break; + + default: + DIE( "Unknown php type" ); + break; + } + + return out_zval; +} + +} // namespace + +int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC); +int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC); +int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, + zend_long offset TSRMLS_DC); +int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC); +int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC); +int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, + char **ptr, size_t *len, int *caller_frees TSRMLS_DC); +int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC); +int pdo_sqlsrv_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC); +int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC); +int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC); +int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC); + +struct pdo_stmt_methods pdo_sqlsrv_stmt_methods = { + + pdo_sqlsrv_stmt_dtor, + pdo_sqlsrv_stmt_execute, + pdo_sqlsrv_stmt_fetch, + pdo_sqlsrv_stmt_describe_col, + pdo_sqlsrv_stmt_get_col_data, + pdo_sqlsrv_stmt_param_hook, + pdo_sqlsrv_stmt_set_attr, + pdo_sqlsrv_stmt_get_attr, + pdo_sqlsrv_stmt_get_col_meta, + pdo_sqlsrv_stmt_next_rowset, + pdo_sqlsrv_stmt_close_cursor + +}; + +void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + set_stmt_cursors( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_encoding:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + set_stmt_encoding( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_direct_query:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); + pdo_stmt->direct_query = ( zend_is_true( value_z )) ? true : false; +} + +void stmt_option_cursor_scroll_type:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + set_stmt_cursor_scroll_type( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_emulate_prepares:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + pdo_stmt_t *pdo_stmt = static_cast( stmt->driver() ); + pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; +} + +void stmt_option_fetch_numeric:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); + pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false; +} + + +// log a function entry point +#ifndef _WIN32 +#define PDO_LOG_STMT_ENTRY \ +{ \ + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ + driver_stmt->set_func( __FUNCTION__ ); \ + int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ + char func[length+1]; \ + strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ + strcat_s( func, length+1, ": entering" ); \ + LOG( SEV_NOTICE, func ); \ +} +#else +#define PDO_LOG_STMT_ENTRY \ +{ \ + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ + driver_stmt->set_func( __FUNCTION__ ); \ + LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ +} +#endif + +// PDO SQLSRV statement destructor +pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void ) +{ + std::for_each( current_meta_data.begin(), current_meta_data.end(), meta_data_free ); + current_meta_data.clear(); + + if( bound_column_param_types ) { + sqlsrv_free( bound_column_param_types ); + bound_column_param_types = NULL; + } + + if( direct_query_subst_string ) { + // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme + // and we use estrdup to allocate this string, which uses emalloc + efree( reinterpret_cast( const_cast( direct_query_subst_string ))); + } +} + + +// pdo_sqlsrv_stmt_close_cursor +// Close any open cursors on the statement. Maps to PDO function PDOStatement::closeCursor. +// Parameters: +// *stmt - Pointer to current statement +// Return: +// Returns 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" ); + + sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); + + // to "close the cursor" means we make the statement ready for execution again. To do this, we + // skip all the result sets on the current statement. + while( driver_stmt->past_next_result_end == false ) { + + core_sqlsrv_next_result( driver_stmt TSRMLS_CC ); + } + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advanding to the next result set." ); + } + + return 1; +} + +// pdo_sqlsrv_stmt_describe_col +// Gets the metadata for a column based on the column number. +// Calls the core_sqlsrv_field_metadata function present in the core layer. +// Parameters: +// *stmt - pointer to current statement +// colno - Index of the column which requires description. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + SQLSRV_ASSERT(( colno >= 0 ), "pdo_sqlsrv_stmt_describe_col: Column number should be >= 0." ); + + sqlsrv_malloc_auto_ptr core_meta_data; + + try { + + core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast( stmt->driver_data ), colno TSRMLS_CC ); + } + + catch( core::CoreException& ) { + return 0; + } + + catch(...) { + DIE( "pdo_sqlsrv_stmt_describe_col: Unexpected exception occurred." ); + } + + pdo_column_data* column_data = &(stmt->columns[colno]); + + SQLSRV_ASSERT( column_data != NULL, "pdo_sqsrv_stmt_describe_col: pdo_column_data was null" ); + + // Set the name + column_data->name = zend_string_init( (const char*)core_meta_data->field_name.get(), core_meta_data->field_name_len, 0 ); + core_meta_data->field_name.reset(); + + // Set the maxlen + column_data->maxlen = ( core_meta_data->field_precision > 0 ) ? core_meta_data->field_precision : core_meta_data->field_size; + + // Set the precision + column_data->precision = core_meta_data->field_scale; + + // Set the param_type + column_data->param_type = PDO_PARAM_ZVAL; + + // store the field data for use by pdo_sqlsrv_stmt_get_col_data + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + SQLSRV_ASSERT( driver_stmt != NULL, "Invalid driver statement in pdo_sqlsrv_stmt_describe_col" ); + driver_stmt->current_meta_data.push_back( core_meta_data.get() ); + SQLSRV_ASSERT( driver_stmt->current_meta_data.size() == colno + 1, "Meta data vector out of sync with column numbers" ); + core_meta_data.transferred(); + + return 1; +} + + +// pdo_sqlsrv_stmt_dtor +// Maps to PDOStatement::__destruct. Destructor for the PDO Statement. +// Parameters: +// *stmt - pointer to current statement +// Return: +// 1 for success. +int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) +{ + sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + LOG( SEV_NOTICE, "pdo_sqlsrv_stmt_dtor: entering" ); + + // if a PDO statement didn't complete preparation, its driver_data can be NULL + if( driver_stmt == NULL ) { + + return 1; + } + + driver_stmt->~sqlsrv_stmt(); + + sqlsrv_free( driver_stmt ); + + stmt->driver_data = NULL; + + return 1; +} + +// pdo_sqlsrv_stmt_execute +// Maps to PDOStatement::Execute. Executes the prepared statement. +// Parameters: +// *stmt - pointer to the current statement. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_execute: driver_data object was null" ); + + // prepare for execution by flushing anything remaining in the result set if it wasn't already + // done before binding parameters + if( driver_stmt->executed && !driver_stmt->past_next_result_end ) { + + while( driver_stmt->past_next_result_end == false ) { + + core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false ); + } + } + + const char* query = NULL; + unsigned int query_len = 0; + + // if the user is doing a direct query (PDO::SQLSRV_ATTR_DIRECT_QUERY), set the query here + if( driver_stmt->direct_query ) { + + query = driver_stmt->direct_query_subst_string; + query_len = static_cast( driver_stmt->direct_query_subst_string_len ); + } + + // if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the + // subtituted query provided by PDO + if( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { + + query = stmt->active_query_string; + query_len = static_cast( stmt->active_query_stringlen ); + } + + core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len ); + + stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + + // return the row count regardless if there are any rows or not + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + // workaround for a bug in the PDO driver manager. It is fairly simple to crash the PDO driver manager with + // the following sequence: + // 1) Prepare and execute a statement (that has some results with it) + // 2) call PDOStatement::nextRowset until there are no more results + // 3) execute the statement again + // 4) call PDOStatement::getColumnMeta + // It crashes from what I can tell because there is no metadata because there was no call to + // pdo_stmt_sqlsrv_describe_col and stmt->columns is NULL on the second call to + // PDO::execute. My guess is that because stmt->executed is true, it optimizes away a necessary call to + // pdo_sqlsrv_stmt_describe_col. By setting the stmt->executed flag to 0, this call is not optimized away + // and the crash disappears. + if( stmt->columns == NULL ) { + stmt->executed = 0; + } + } + catch( core::CoreException& /*e*/ ) { + + return 0; + + } + catch( ... ) { + + DIE( "pdo_sqlsrv_stmt_execute: Unexpected exception occurred." ); + + } + + // success + return 1; +} + + + +// pdo_sqlsrv_stmt_fetch +// Maps to PDOStatement::fetch +// Move the cursor to the record indicated. If the cursor is moved off the end, +// or before the beginning if a scrollable cursor is created, then FAILURE is returned. +// Parameters: +// *stmt - pointer to current statement for which the cursor should be moved. +// ori - cursor orientation. Maps to the list of PDO::FETCH_ORI_* constants +// offset - For orientations that use it, offset to move to. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, + zend_long offset TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_fetch: pdo_stmt object was null" ); + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_fetch: driver_data object was null" ); + + // set the types for bound columns to zval so that PDO does no conversion when the value + // is returned by pdo_sqlsrv_get_col_data. Remember the types that were bound by the user + // and use it to manually convert data types + if( stmt->bound_columns ) { + + pdo_bound_param_data* bind_data = NULL; + + if( !driver_stmt->bound_column_param_types ) { + driver_stmt->bound_column_param_types = + reinterpret_cast( sqlsrv_malloc( stmt->column_count, sizeof( pdo_param_type ), 0 )); + std::fill( driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count, + PDO_PARAM_ZVAL ); + } + + for( long i = 0; i < stmt->column_count; ++i ) { + + 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; + continue; + } + + if( bind_data->param_type != PDO_PARAM_ZVAL ) { + + driver_stmt->bound_column_param_types[ i ] = bind_data->param_type; + bind_data->param_type = PDO_PARAM_ZVAL; + } + } + } + + SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori ); + bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset TSRMLS_CC ); + + // support for the PDO rowCount method. Since rowCount doesn't call a method, PDO relies on us to fill the + // pdo_stmt_t::row_count member + if( driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { + + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + // a row_count of -1 means no rows, but we change it to 0 + if( stmt->row_count == -1 ) { + + stmt->row_count = 0; + } + } + + // if no data was returned, then return false so data isn't retrieved + if( !data ) { + return 0; + } + + return 1; + } + + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_stmt_fetch: Unexpected exception occurred."); + } +} + +// pdo_sqlsrv_stmt_get_col_data +// Called by the set of PDO Fetch functions. +// Retrieves a single column. PDO driver manager is responsible for freeing the +// returned buffer. Because PDO can request fields out of order and ODBC does not +// support out of order field requests, this function should also cache fields. +// Parameters: +// stmt - Statement to retrive the column for. +// colno - Index of the column that needs to be retrieved. Starts with 0. +// ptr - Returns the buffer containing the column data. +// len - Length of the buffer returned. +// caller_frees - Flag to let the PDO driver manager know that it is responsible for +// freeing the memory. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, + char **ptr, size_t *len, int *caller_frees TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: pdo_stmt object was null" ); + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: driver_data object was null" ); + + CHECK_CUSTOM_ERROR((colno < 0), driver_stmt, PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX ) { + return 0; + } + + // Let PDO free the memory after use. + *caller_frees = 1; + + // translate the pdo type to a type the core layer understands + sqlsrv_phptype sqlsrv_php_type; + SQLSRV_ASSERT( colno >= 0 && colno < static_cast( driver_stmt->current_meta_data.size()), + "Invalid column number in pdo_sqlsrv_stmt_get_col_data" ); + sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast( driver_stmt->current_meta_data[ colno ]->field_type ), + static_cast( driver_stmt->current_meta_data[ colno ]->field_size ), + true ); + + // set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding + sqlsrv_php_type.typeinfo.encoding = driver_stmt->encoding(); + + // 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 ) { + + sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type( driver_stmt, + driver_stmt->bound_column_param_types[ colno ] + TSRMLS_CC ); + + pdo_bound_param_data* bind_data = NULL; + bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, colno)); + + if( bind_data != NULL && !Z_ISUNDEF(bind_data->driver_params) ) { + + CHECK_CUSTOM_ERROR( Z_TYPE( bind_data->driver_params ) != IS_LONG, driver_stmt, + PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, colno + 1 ) { + throw pdo::PDOException(); + } + + CHECK_CUSTOM_ERROR( driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_STR + && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_LOB, driver_stmt, + PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, colno + 1 ) { + + throw pdo::PDOException(); + } + + sqlsrv_php_type.typeinfo.encoding = Z_LVAL( bind_data->driver_params ); + + switch( sqlsrv_php_type.typeinfo.encoding ) { + case SQLSRV_ENCODING_SYSTEM: + case SQLSRV_ENCODING_BINARY: + case SQLSRV_ENCODING_UTF8: + break; + default: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, colno ); + break; + } + } + } + + SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID; + core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast(ptr)), + reinterpret_cast( len ), true, &sqlsrv_phptype_out TSRMLS_CC ); + + zval* zval_ptr = reinterpret_cast( sqlsrv_malloc( sizeof( zval ))); + *zval_ptr = convert_to_zval( sqlsrv_phptype_out, reinterpret_cast( ptr ), *len ); + *ptr = reinterpret_cast( zval_ptr ); + + *len = sizeof( zval ); + + return 1; + } + + catch ( core::CoreException& ) { + return 0; + } + catch ( ... ) { + DIE ("pdo_sqlsrv_stmt_get_col_data: Unexpected exception occurred."); + } +} + +// pdo_sqlsrv_stmt_set_attr +// Maps to the PDOStatement::setAttribute. Sets the attribute on a statement. +// Parameters: +// stmt - Current statement on which the attribute should be set. +// attr - Represents any valid set of attribute constants supported by this driver. +// val - Attribute value. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + pdo_sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); + + try { + + switch( attr ) { + + case SQLSRV_ATTR_DIRECT_QUERY: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY ); + break; + + case SQLSRV_ATTR_ENCODING: + set_stmt_encoding( driver_stmt, val TSRMLS_CC ); + break; + + case PDO_ATTR_CURSOR: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY ); + break; + + case SQLSRV_ATTR_QUERY_TIMEOUT: + core_sqlsrv_set_query_timeout( driver_stmt, val TSRMLS_CC ); + break; + + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY ); + break; + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + core_sqlsrv_set_buffered_query_limit( driver_stmt, val TSRMLS_CC ); + break; + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + driver_stmt->fetch_numeric = ( zend_is_true( val )) ? true : false; + break; + + default: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); + break; + } + } + catch( core::CoreException& ) { + return 0; + } + + catch ( ... ) { + DIE ("pdo_sqlsrv_stmt_set_attr: Unexpected exception occurred."); + } + + return 1; +} + + +// pdo_sqlsrv_stmt_get_attr +// Maps to the PDOStatement::getAttribute. Gets the value of a given attribute on a statement. +// Parameters: +// stmt - Current statement for which the attribute value is requested. +// attr - Represents any valid set of attribute constants supported by this driver. +// return_value - Attribute value. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_get_attr( pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC ) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + pdo_sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); + SQLSRV_ASSERT(( driver_stmt != NULL ), "pdo_sqlsrv_stmt_get_attr: stmt->driver_data was null" ); + + try { + + switch( attr ) { + + case SQLSRV_ATTR_DIRECT_QUERY: + { + ZVAL_BOOL( return_value, driver_stmt->direct_query ); + break; + } + + case SQLSRV_ATTR_ENCODING: + { + ZVAL_LONG( return_value, driver_stmt->encoding() ); + break; + } + + case PDO_ATTR_CURSOR: + { + ZVAL_LONG( return_value, ( driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ? + PDO_CURSOR_SCROLL : PDO_CURSOR_FWDONLY )); + break; + } + + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + { + ZVAL_LONG( return_value, driver_stmt->cursor_type ); + break; + } + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + { + ZVAL_LONG( return_value, driver_stmt->buffered_query_limit ); + break; + } + + case SQLSRV_ATTR_QUERY_TIMEOUT: + { + ZVAL_LONG( return_value, ( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_stmt->query_timeout )); + break; + } + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + { + ZVAL_BOOL( return_value, driver_stmt->fetch_numeric ); + break; + } + + default: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); + break; + } + } + catch( core::CoreException& ) { + + return 0; + } + catch ( ... ) { + + DIE ("pdo_sqlsrv_stmt_get_attr: Unexpected exception occurred."); + } + + return 1; +} + +// pdo_sqlsrv_stmt_get_col_meta +// Maps to PDOStatement::getColumnMeta. Return extra metadata. +// Though we don't return any extra metadata, PDO relies on us to +// create the associative array that holds the standard information, +// so we create one and return it for PDO's use. +// Parameters: +// stmt - Current statement. +// colno - The index of the field for which to return the metadata. +// return_value - zval* consisting of the metadata. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: pdo_stmt object was null" ); + SQLSRV_ASSERT( stmt->columns != NULL, "pdo_sqlsrv_stmt_get_col_meta: columns are not available." ); + SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." ); + + sqlsrv_malloc_auto_ptr core_meta_data; + + sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); + + SQLSRV_ASSERT( colno >= 0 && colno < stmt->column_count, "pdo_sqlsrv_stmt_get_col_meta: invalid column number." ); + + core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); + // initialize the array to nothing, as PDO requires us to create it + core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC ); + + // add the following fields: flags, native_type, driver:decl_type, table + add_assoc_long( return_value, "flags", 0 ); + + // get the name of the data type + char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ]; + SQLSMALLINT out_buff_len; + SQLLEN not_used; + core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name, + sizeof( field_type_name ), &out_buff_len, ¬_used TSRMLS_CC ); + add_assoc_string( return_value, "sqlsrv:decl_type", field_type_name ); + + // get the PHP type of the column. The types returned here mirror the types returned by debug_zval_dump when + // given a variable of the same type. However, debug_zval_dump also gives the length of a string, and we only + // say string, since the length is given in another field of the metadata array. + long pdo_type = sql_type_to_pdo_type( core_meta_data->field_type ); + switch( pdo_type ) { + case PDO_PARAM_STR: + { + //Declarations eliminate compiler warnings about string constant to char* conversions + std::string key = "native_type"; + std::string str = "string"; + add_assoc_string( return_value, &key[0], &str[0] ); + } + break; + default: + DIE( "pdo_sqlsrv_stmt_get_col_data: Unknown PDO type returned" ); + break; + } + + // add the table name of the field. All the tests so far show this to always be "", but we adhere to the PDO spec + char table_name[ SQL_SERVER_IDENT_SIZE_MAX ]; + 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[ colno ].param_type == PDO_PARAM_ZVAL ) { + add_assoc_long( return_value, "pdo_type", pdo_type ); + } + + // this will ensure that the field_name field, which is an auto pointer gets freed. + (*core_meta_data).~field_meta_data(); + } + catch( core::CoreException& ) { + + return 0; + } + catch(...) { + + DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." ); + } + + return 1; +} + + +// pdo_sqlsrv_stmt_next_rowset +// Maps to PDOStatement::nextRowset. +// Move the cursor to the beginning of the next rowset in a multi-rowset result. +// Clears the field cache from the last row retrieved using pdo_sqlsrv_stmt_get_col_data. +// Calls core_sqlsrv_next_result using the core_stmt found within stmt->driver_data. +// If another result set is available, this function returns 1. Otherwise it returns 0. +// Parameters: +// stmt - PDOStatement object containing the result set. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" ); + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); + + core_sqlsrv_next_result( static_cast( stmt->driver_data ) TSRMLS_CC ); + + // clear the current meta data since the new result will generate new meta data + std::for_each( driver_stmt->current_meta_data.begin(), driver_stmt->current_meta_data.end(), meta_data_free ); + driver_stmt->current_meta_data.clear(); + + // if there are no more result sets, return that it failed. + if( driver_stmt->past_next_result_end == true ) { + return 0; + } + + stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + + // return the row count regardless if there are any rows or not + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advancing to the next result set." ); + } + + return 1; +} + +// pdo_sqlsrv_stmt_param_hook +// Maps to PDOStatement::bindColumn. +// Called by PDO driver manager to bind a parameter or column. +// This function pulls several duties for binding parameters and columns. +// It takes an event as a parameter that explains what the function should do on +// behalf of a parameter or column. We only use one of these events, +// PDO_PARAM_EVT_EXEC_PRE, the remainder simply return. +// Paramters: +// stmt - PDO Statement object to bind a parameter. +// param - paramter to bind. +// event_type - Event to bind a parameter +// Return: +// Returns 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + + try { + + switch( event_type ) { + + // since the param isn't reliable, we don't do anything here + case PDO_PARAM_EVT_ALLOC: + // if emulate prepare is on, set the bind_param_encoding so it can be used in PDO::quote when binding parameters on the client side + if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( stmt->dbh->driver_data ); + driver_dbh->bind_param_encoding = static_cast( Z_LVAL( param->driver_params )); + } + break; + case PDO_PARAM_EVT_FREE: + break; + // bind the parameter in the core layer + case PDO_PARAM_EVT_EXEC_PRE: + { + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + // skip column bindings + if( !param->is_param ) { + break; + } + + sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_param_hook: driver_data object was null" ); + + // prepare for binding parameters by flushing anything remaining in the result set + if( driver_stmt->executed && !driver_stmt->past_next_result_end ) { + + while( driver_stmt->past_next_result_end == false ) { + + core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false ); + } + } + + int direction = SQL_PARAM_INPUT; + SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE; + SQLULEN column_size = SQLSRV_UNKNOWN_SIZE; + SQLSMALLINT decimal_digits = 0; + // determine the direction of the parameter. By default it's input, but if the user specifies a size + // that means they want output, and if they include the flag, then it's input/output. + // It's invalid to specify the input/output flag but not specify a length + CHECK_CUSTOM_ERROR( (param->param_type & PDO_PARAM_INPUT_OUTPUT) && (param->max_value_len == 0), + driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) { + throw pdo::PDOException(); + } + if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) { + if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) { + direction = SQL_PARAM_INPUT_OUTPUT; + } + else { + direction = SQL_PARAM_OUTPUT; + } + } + // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant + // and the SQLSRV_PHPTYPE_* constant + int pdo_type = param->param_type; + SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; + switch( pdo_type & ~PDO_PARAM_INPUT_OUTPUT ) { + case PDO_PARAM_BOOL: + case PDO_PARAM_INT: + php_out_type = SQLSRV_PHPTYPE_INT; + break; + case PDO_PARAM_STR: + php_out_type = SQLSRV_PHPTYPE_STRING; + break; + // when the user states PDO::PARAM_NULL, they mean send a null no matter what the variable is + // since the core layer keys off the zval type, we substitute a null for what they gave us + case PDO_PARAM_NULL: + { + zval null_zval; + php_out_type = SQLSRV_PHPTYPE_NULL; + + ZVAL_NULL( &null_zval ); + zval_ptr_dtor( ¶m->parameter ); + param->parameter = null_zval; + break; + } + case PDO_PARAM_LOB: + php_out_type = SQLSRV_PHPTYPE_STREAM; + break; + case PDO_PARAM_STMT: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED ); + break; + default: + SQLSRV_ASSERT( false, "Unknown PDO::PARAM_* constant given." ); + break; + } + // set the column size parameter for bind_param if we are expecting something back + if( direction != SQL_PARAM_INPUT ) { + switch( php_out_type ) { + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_STREAM: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE ); + break; + case SQLSRV_PHPTYPE_INT: + column_size = SQLSRV_UNKNOWN_SIZE; + break; + case SQLSRV_PHPTYPE_STRING: + { + CHECK_CUSTOM_ERROR( param->max_value_len <= 0, driver_stmt, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, param->paramno + 1 ) { + throw pdo::PDOException(); + } + column_size = param->max_value_len; + break; + } + default: + SQLSRV_ASSERT( false, "Invalid PHP type for output parameter. Should have been caught already." ); + break; + } + } + // block all objects from being bound as input or input/output parameters since there is a + // weird case: + // $obj = date_create(); + // $s->bindParam( n, $obj, PDO::PARAM_INT ); // anything different than PDO::PARAM_STR + // that succeeds since the core layer implements DateTime object handling for the sqlsrv + // 2.0 driver. To be consistent and avoid surprises of one object type working and others + // not, we block all objects here. + CHECK_CUSTOM_ERROR( direction != SQL_PARAM_OUTPUT && Z_TYPE( param->parameter ) == IS_OBJECT, + driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) { + throw pdo::PDOException(); + } + // the encoding by default is that set on the statement + SQLSRV_ENCODING encoding = driver_stmt->encoding(); + // if the statement's encoding is the default, then use the one on the connection + if( encoding == SQLSRV_ENCODING_DEFAULT ) { + encoding = driver_stmt->conn->encoding(); + } + // if the user provided an encoding, use it instead + if( !Z_ISUNDEF(param->driver_params) ) { + CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) { + throw pdo::PDOException(); + } + CHECK_CUSTOM_ERROR( pdo_type != PDO_PARAM_STR && pdo_type != PDO_PARAM_LOB, driver_stmt, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, param->paramno + 1 ) { + throw pdo::PDOException(); + } + encoding = static_cast( Z_LVAL( param->driver_params )); + + switch( encoding ) { + case SQLSRV_ENCODING_SYSTEM: + case SQLSRV_ENCODING_BINARY: + case SQLSRV_ENCODING_UTF8: + break; + default: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, + param->paramno + 1 ); + break; + } + } + // and bind the parameter + core_sqlsrv_bind_param( driver_stmt, static_cast( param->paramno ), direction, &(param->parameter) , php_out_type, encoding, + sql_type, column_size, decimal_digits TSRMLS_CC ); + } + break; + // undo any work done by the core layer after the statement is executed + case PDO_PARAM_EVT_EXEC_POST: + { + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + // skip column bindings + if( !param->is_param ) { + break; + } + + core_sqlsrv_post_param( reinterpret_cast( stmt->driver_data ), param->paramno, + &(param->parameter) TSRMLS_CC ); + } + break; + case PDO_PARAM_EVT_FETCH_PRE: + break; + case PDO_PARAM_EVT_FETCH_POST: + break; + case PDO_PARAM_EVT_NORMALIZE: + break; + default: + DIE( "pdo_sqlsrv_stmt_param_hook: Unknown event type" ); + break; + } + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_stmt_param_hook: Unknown exception" ); + } + + return 1; +} + + +// Returns a sqlsrv_phptype for a given SQL Server data type. +sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_over_stream ) +{ + sqlsrv_phptype sqlsrv_phptype; + int local_encoding = this->encoding(); + // if the encoding on the connection changed + if( this->encoding() == SQLSRV_ENCODING_DEFAULT ) { + local_encoding = conn->encoding(); + SQLSRV_ASSERT( conn->encoding() != SQLSRV_ENCODING_DEFAULT || conn->encoding() == SQLSRV_ENCODING_INVALID, + "Invalid encoding on the connection. Must not be invalid or default." ); + } + + switch( sql_type ) { + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + if ( this->fetch_numeric ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + } + break; + case SQL_FLOAT: + case SQL_REAL: + if ( this->fetch_numeric ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + } + break; + case SQL_BIGINT: + case SQL_CHAR: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + 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: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + break; + case SQL_BINARY: + case SQL_LONGVARBINARY: + case SQL_VARBINARY: + case SQL_SS_UDT: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + break; + default: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + break; + } + + return sqlsrv_phptype; +} + diff --git a/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp similarity index 90% rename from pdo_sqlsrv/pdo_util.cpp rename to source/pdo_sqlsrv/pdo_util.cpp index fe25efbb..c3f0fb36 100644 --- a/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -1,607 +1,602 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: pdo_util.cpp -// -// Contents: Utility functions used by both connection or statement functions -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "pdo_sqlsrv.h" - -#include "zend_exceptions.h" - - -// *** internal constants *** -namespace { - -const char WARNING_TEMPLATE[] = "SQLSTATE: %1!s!\nError Code: %2!d!\nMError Message: %3!s!\n"; -const char EXCEPTION_MSG_TEMPLATE[] = "SQLSTATE[%s]: %s"; -char EXCEPTION_PROPERTY_MSG[] = "message"; -char EXCEPTION_PROPERTY_CODE[] = "code"; -char EXCEPTION_PROPERTY_ERRORINFO[] = "errorInfo"; -const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negative - -// 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 ]; - -// internal error that says that FormatMessage failed -SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; - -// build the object and throw the PDO exception -void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ); - -} - -// pdo driver error messages -// errors have 3 components, the SQLSTATE (always 'IMSSP'), the error message, and an error code, which for us is always < 0 -pdo_error PDO_ERRORS[] = { - - { - SQLSRV_ERROR_DRIVER_NOT_INSTALLED, - { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 for SQL Server to " - "communicate with SQL Server. Access the following URL to download the ODBC Driver 11 for SQL Server " - "for %1!s!: " - "http://go.microsoft.com/fwlink/?LinkId=163712", -1, true } - }, - { - SQLSRV_ERROR_ZEND_HASH, - { IMSSP, (SQLCHAR*) "An error occurred creating or accessing a Zend hash table.", -2, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - { IMSSP, (SQLCHAR*) "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams " - "cannot be specified as output parameters.", -3, false } - }, - { - SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, - { IMSSP, (SQLCHAR*) "An invalid type for parameter %1!d! was specified. Only booleans, integers, floating point " - "numbers, strings, and streams may be used as parameters.", -4, true } - }, - { - SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, - { IMSSP, (SQLCHAR*) "An invalid SQL Server type for parameter %1!d! was specified.", -5, true } - }, - { - SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, - { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -6, true } - }, - { - SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -7, true } - }, - { - SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating string for an output param to UTF-8: %1!s!", -8, true } - }, - { - SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -9, true } - }, - { - SQLSRV_ERROR_ZEND_STREAM, - { IMSSP, (SQLCHAR*) "An error occurred reading from a PHP stream.", -10, false } - }, - { - SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -11, true } - }, - { - SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, - { IMSSP, (SQLCHAR*) "Failed to retrieve the server version. Unable to continue.", -12, false } - }, - { - SQLSRV_ERROR_FETCH_PAST_END, - { IMSSP, (SQLCHAR*) "There are no more rows in the active result set. Since this result set is not scrollable, " - "no more data may be retrieved.", -13, false } - }, - { - SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, - { IMSSP, (SQLCHAR*) "The statement must be executed before results can be retrieved.", -14, false } - }, - { - SQLSRV_ERROR_NO_FIELDS, - { IMSSP, (SQLCHAR*) "The active result for the query contains no fields.", -15, false } - }, - - { - SQLSRV_ERROR_FETCH_NOT_CALLED, - { IMSSP, (SQLCHAR*) "Internal pdo_sqlsrv error: Tried to retrieve a field before one of the PDOStatement::fetch " - "functions was called.", -16, false } - }, - { - SQLSRV_ERROR_NO_DATA, - { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -17, true } - }, - { - SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -18, true } - }, - { - SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, - { IMSSP, (SQLCHAR*) "Zend returned an error when creating an associative array.", -19, false } - }, - { - SQLSRV_ERROR_NEXT_RESULT_PAST_END, - { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -20, false } - }, - { - SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, - { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be" - " escaped with another right brace (}}).", -21, false } - }, - { - SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, - { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in the DSN string for keyword '%1!s!'. All right braces " - "must be escaped with another right brace (}}).", -22, true } - }, - { - SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -23, true } - }, - { - SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -24, true } - }, - { - SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, - { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of " - "key/value pairs.", -25, false } - }, - { - SQLSRV_ERROR_INVALID_CONNECTION_KEY, - { IMSSP, (SQLCHAR*) "An invalid connection option key type was received. Option key types must be strings.", -26, false } - }, - - { - SQLSRV_ERROR_INVALID_TYPE, - { IMSSP, (SQLCHAR*) "Invalid type.", -27, false } - }, - - { - PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, - {IMSSP, (SQLCHAR*)"An invalid column number was specified.", -28, false } - }, - - { - SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, - { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -29, true } - }, - { - SQLSRV_ERROR_INVALID_OPTION_KEY, - { IMSSP, (SQLCHAR*) "Invalid option key %1!s! specified.", -30, true } - }, - { - SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, - { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option PDO::SQLSRV_ATTR_QUERY_TIMEOUT.", -31, true } - }, - { - SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, - { IMSSP, (SQLCHAR*) "The value passed for the 'Scrollable' statement option is invalid.", -32, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_DBH_ATTR, - { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDO object.", -33, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, - { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDOStatement object.", -34, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_ENCODING, - { IMSSP, (SQLCHAR*) "An invalid encoding was specified for SQLSRV_ATTR_ENCODING.", -35, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, - { IMSSP, (SQLCHAR*) "An invalid type or value was given for the parameter driver data. Only encoding constants " - "such as PDO::SQLSRV_ENCODING_UTF8 may be used as parameter driver options.", -36, false } - }, - { - PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, - { IMSSP, (SQLCHAR*) "PDO::PARAM_STMT is not a supported parameter type.", -37, false } - }, - { - PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, - { IMSSP, (SQLCHAR*) "An unsupported attribute was designated on the PDO object.", -38, false } - }, - { - PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, - { IMSSP, (SQLCHAR*) "The given attribute is only supported on the PDOStatement object.", -39, false } - }, - { - PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, - { IMSSP, (SQLCHAR*) "A read-only attribute was designated on the PDO object.", -40, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_DSN_STRING, - {IMSSP, (SQLCHAR*)"An invalid DSN string was specified.", -41, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_DSN_KEY, - { IMSSP, (SQLCHAR*) "An invalid keyword '%1!s!' was specified in the DSN string.", -42, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, - { IMSSP, (SQLCHAR*) "An invalid statement option was specified.", -43, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, - { IMSSP, (SQLCHAR*) "An invalid cursor type was specified for either PDO::ATTR_CURSOR or " - "PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE", -44, false } - }, - { - PDO_SQLSRV_ERROR_PARAM_PARSE, - { IMSSP, (SQLCHAR*) "An error occurred substituting the named parameters.", -45, false } - }, - { - PDO_SQLSRV_ERROR_LAST_INSERT_ID, - { IMSSP, (SQLCHAR*) "An error occurred retrieving the last insert id.", -46, false } - }, - { - SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -47, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, - { IMSSP, (SQLCHAR*) "An invalid type or value was given as bound column driver data for column %1!d!. Only " - "encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.", -48, true } - }, - { - PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, - { IMSSP, (SQLCHAR*) "An encoding was specified for column %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR column types " - "can take an encoding option.", -49, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, - { IMSSP, (SQLCHAR*) "Invalid encoding specified for column %1!d!.", -50, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, - { IMSSP, (SQLCHAR*) "An encoding was specified for parameter %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an " - "encoding option.", -51, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, - { IMSSP, (SQLCHAR*) "Invalid encoding specified for parameter %1!d!.", -52, true } - }, - { - PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, - { IMSSP, (SQLCHAR*) "The PDO::ATTR_CURSOR and PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attributes may only be set in the " - "$driver_options array of PDO::prepare.", -53, false } - }, - { - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, - { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -54, true } - }, - { - SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, - { IMSSP, (SQLCHAR*) "Types for parameter value and PDO::PARAM_* constant must be compatible for input/output " - "parameter %1!d!.", -55, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, - { IMSSP, (SQLCHAR*) "Invalid direction specified for parameter %1!d!. Input/output parameters must have a length.", - -56, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, - { IMSSP, (SQLCHAR*) "Invalid size for output string parameter %1!d!. Input/output string parameters must have an " - "explicit length.", -57, true } - }, - { - PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, - { IMSSP, (SQLCHAR*) "This function is not implemented by this driver.", -58, false } - }, - { - /* The stream related errors are not currently used in PDO, but the core layer can throw the stream related - errors so having a mapping here */ - - SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, - { IMSSP, (SQLCHAR*) "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using " - "streams.", -59, false} - }, - { - SQLSRV_ERROR_STREAM_CREATE, - { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -60, false } - }, - { - SQLSRV_ERROR_MARS_OFF, - { IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. " - "To make the connection available for other queries, either fetch all results or cancel or free the statement. " - "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -61, false } - }, - { - SQLSRV_ERROR_FIELD_INDEX_ERROR, - { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. Cannot retrieve field %1!d! because its " - "index is less than the index of a field that has already been retrieved (%2!d!).", -62, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, - { IMSSP, (SQLCHAR*) "An invalid value was specified for the keyword '%1!s!' in the DSN string.", -63, true } - }, - { - PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, - { IMSSP, (SQLCHAR*) "Server keyword was not specified in the DSN string.", -64, false } - }, - { - PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, - { IMSSP, (SQLCHAR*) "The DSN string ended unexpectedly.", -65, false } - }, - { - PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, - { IMSSP, (SQLCHAR*) "An extra semi-colon was encountered in the DSN string at character (byte-count) position '%1!d!' .", - -66, true } - }, - { - PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, - { IMSSP, (SQLCHAR*) "An expected right brace (}) was not found in the DSN string for the value of the keyword '%1!s!'.", - -67, true } - }, - { - PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, - { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_DIRECT_QUERY attribute may only be set in the $driver_options array of " - "PDO::prepare.", -68, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, - { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attribute may only be set when PDO::ATTR_CURSOR is set to " - "PDO::CURSOR_SCROLL in the $driver_options array of PDO::prepare.", -69, false } - }, - { - SQLSRV_ERROR_INVALID_BUFFER_LIMIT, - { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not " - "positive. Only positive numbers are valid for this attribute.", -70, false } - }, - { - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, - { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -71, true } - }, - { UINT_MAX, {} } -}; - -// Returns a sqlsrv_error for a given error code. -sqlsrv_error_const* get_error_message(unsigned int sqlsrv_error_code) { - - sqlsrv_error_const *error_message = NULL; - int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr(g_pdo_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; - if( zr == FAILURE ) { - DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); - } - - SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null"); - - return error_message; -} - -// PDO error handler for the environment context. -bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ) -{ - SQLSRV_ASSERT(( ctx != NULL ), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null" ); - pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); - SQLSRV_ASSERT(( dbh != NULL ), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null" ); - - sqlsrv_error_auto_ptr error; - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); - } - else { - - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); - } - - strcpy_s( dbh->error_code, sizeof( pdo_error_type ), reinterpret_cast( error->sqlstate )); - - switch( dbh->error_mode ) { - - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error TSRMLS_CC ); - } - ctx.set_last_error( error ); - break; - - default: - DIE( "pdo_sqlsrv_handle_env_error: Unexpected error mode. %1!d!", dbh->error_mode ); - break; - } - - // we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count - // return error ignored = true for warnings. - return ( warning ? true : false ); - -} - -// pdo error handler for the dbh context. -bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ) -{ - pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); - SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" ); - - sqlsrv_error_auto_ptr error; - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); - } - else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); - } - - SQLSRV_ASSERT(strlen(reinterpret_cast(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow"); - strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast(error->sqlstate)); - - switch( dbh->error_mode ) { - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error TSRMLS_CC ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_WARNING: - if( !warning ) { - size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE - + MAX_DIGITS + 1; - char* 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 ); - sqlsrv_free( msg ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_SILENT: - ctx.set_last_error( error ); - break; - default: - DIE( "Unknown error mode. %1!d!", dbh->error_mode ); - break; - } - - // return error ignored = true for warnings. - return ( warning ? true : false ); -} - -// PDO error handler for the statement context. -bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ) -{ - pdo_stmt_t* pdo_stmt = reinterpret_cast( ctx.driver()); - SQLSRV_ASSERT( pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed" ); - - sqlsrv_error_auto_ptr error; - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); - } - else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); - } - - SQLSRV_ASSERT( strlen( reinterpret_cast( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow"); - strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast( error->sqlstate )); - - switch( pdo_stmt->dbh->error_mode ) { - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error TSRMLS_CC ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_WARNING: - if( !warning ) { - size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE - + MAX_DIGITS + 1; - char* msg = static_cast( sqlsrv_malloc(SQL_MAX_MESSAGE_LENGTH+1)); - core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, - error->native_message ); - php_error( E_WARNING, msg ); - sqlsrv_free( msg ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_SILENT: - ctx.set_last_error( error ); - break; - default: - DIE( "Unknown error mode. %1!d!", pdo_stmt->dbh->error_mode ); - break; - } - - // return error ignored = true for warnings. - return ( warning ? true : false ); -} - - -// Transfer a sqlsrv_context's error to a PDO zval. The standard format for a zval error is 3 elements: -// 0, native code -// 1, native message -// 2, SQLSTATE of the error (driver specific error messages are 'IMSSP') - -void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ) -{ - if( last_error ) { - - // SQLSTATE is already present in the zval. - add_next_index_long( pdo_zval, last_error->native_code ); - add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message )); - } -} - -// Formats the error message and writes to the php error log. -void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ) -{ - if( (severity & PDO_SQLSRV_G( log_severity )) == 0 ) { - return; - } - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); - // if an error occurs for FormatMessage, we just output an internal error occurred. - if( rc == 0 ) { - SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); - std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); - } - - php_log_err( log_msg TSRMLS_CC ); -} - -namespace { - -void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ) -{ - zval ex_obj; - ZVAL_UNDEF( &ex_obj ); - zend_class_entry* ex_class = pdo_get_exception_class(); - - int zr = object_init_ex( &ex_obj, ex_class ); - SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" ); - - sqlsrv_malloc_auto_ptr ex_msg; - size_t ex_msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + - 12 + 1; // 12 = "SQLSTATE[]: " - ex_msg = reinterpret_cast( sqlsrv_malloc( ex_msg_len )); - snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message ); - zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, - ex_msg TSRMLS_CC ); - zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, - reinterpret_cast( error->sqlstate ) TSRMLS_CC ); - - zval ex_error_info; - ZVAL_UNDEF( &ex_error_info ); - array_init( &ex_error_info ); - add_next_index_string( &ex_error_info, reinterpret_cast( error->sqlstate )); - add_next_index_long( &ex_error_info, error->native_code ); - add_next_index_string( &ex_error_info, reinterpret_cast( error->native_message )); - //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) - //and the refcount of the zend_array is incremented by 1 - zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, - &ex_error_info TSRMLS_CC ); - - //DELREF ex_error_info here to decrement the refcount of the zend_array is 1 - //the global hashtable EG(exception) then points to the zend_object in ex_obj in zend_throw_exception_object; - //this ensure when EG(exception) cleans itself at php shutdown, the zend_array allocated is properly destroyed - Z_DELREF( ex_error_info ); - zend_throw_exception_object( &ex_obj TSRMLS_CC ); -} - -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_util.cpp +// +// Contents: Utility functions used by both connection or statement functions +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_pdo_sqlsrv.h" + +#include "zend_exceptions.h" + + +// *** internal constants *** +namespace { + +const char WARNING_TEMPLATE[] = "SQLSTATE: %1!s!\nError Code: %2!d!\nError Message: %3!s!\n"; +const char EXCEPTION_MSG_TEMPLATE[] = "SQLSTATE[%s]: %s"; +char EXCEPTION_PROPERTY_MSG[] = "message"; +char EXCEPTION_PROPERTY_CODE[] = "code"; +char EXCEPTION_PROPERTY_ERRORINFO[] = "errorInfo"; +const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negative + +// the warning message is not the error message alone; it must take WARNING_TEMPLATE above into consideration without the formats +const int WARNING_MIN_LENGTH = strlen(WARNING_TEMPLATE) - strlen("%1!s!%2!d!%3!s!"); + +// 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 ]; + +// internal error that says that FormatMessage failed +SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; + +// build the object and throw the PDO exception +void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ); + +} + +// pdo driver error messages +// errors have 3 components, the SQLSTATE (always 'IMSSP'), the error message, and an error code, which for us is always < 0 +pdo_error PDO_ERRORS[] = { + + { + SQLSRV_ERROR_DRIVER_NOT_INSTALLED, + { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 13 for SQL Server to " + "communicate with SQL Server. Access the following URL to download the ODBC Driver 13 for SQL Server " + "for %1!s!: " + "http://go.microsoft.com/fwlink/?LinkId=163712", -1, true } + }, + { + SQLSRV_ERROR_ZEND_HASH, + { IMSSP, (SQLCHAR*) "An error occurred creating or accessing a Zend hash table.", -2, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + { IMSSP, (SQLCHAR*) "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams " + "cannot be specified as output parameters.", -3, false } + }, + { + SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + { IMSSP, (SQLCHAR*) "An invalid type for parameter %1!d! was specified. Only booleans, integers, floating point " + "numbers, strings, and streams may be used as parameters.", -4, true } + }, + { + SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + { IMSSP, (SQLCHAR*) "An invalid SQL Server type for parameter %1!d! was specified.", -5, true } + }, + { + SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, + { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -6, true } + }, + { + SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -7, true } + }, + { + SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating string for an output param to UTF-8: %1!s!", -8, true } + }, + { + SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -9, true } + }, + { + SQLSRV_ERROR_ZEND_STREAM, + { IMSSP, (SQLCHAR*) "An error occurred reading from a PHP stream.", -10, false } + }, + { + SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -11, true } + }, + { + SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, + { IMSSP, (SQLCHAR*) "Failed to retrieve the server version. Unable to continue.", -12, false } + }, + { + SQLSRV_ERROR_FETCH_PAST_END, + { IMSSP, (SQLCHAR*) "There are no more rows in the active result set. Since this result set is not scrollable, " + "no more data may be retrieved.", -13, false } + }, + { + SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, + { IMSSP, (SQLCHAR*) "The statement must be executed before results can be retrieved.", -14, false } + }, + { + SQLSRV_ERROR_NO_FIELDS, + { IMSSP, (SQLCHAR*) "The active result for the query contains no fields.", -15, false } + }, + + { + SQLSRV_ERROR_FETCH_NOT_CALLED, + { IMSSP, (SQLCHAR*) "Internal pdo_sqlsrv error: Tried to retrieve a field before one of the PDOStatement::fetch " + "functions was called.", -16, false } + }, + { + SQLSRV_ERROR_NO_DATA, + { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -17, true } + }, + { + SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -18, true } + }, + { + SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, + { IMSSP, (SQLCHAR*) "Zend returned an error when creating an associative array.", -19, false } + }, + { + SQLSRV_ERROR_NEXT_RESULT_PAST_END, + { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -20, false } + }, + { + SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, + { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be" + " escaped with another right brace (}}).", -21, false } + }, + { + SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, + { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in the DSN string for keyword '%1!s!'. All right braces " + "must be escaped with another right brace (}}).", -22, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, + { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -23, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, + { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -24, true } + }, + { + SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, + { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of " + "key/value pairs.", -25, false } + }, + { + SQLSRV_ERROR_INVALID_CONNECTION_KEY, + { IMSSP, (SQLCHAR*) "An invalid connection option key type was received. Option key types must be strings.", -26, false } + }, + + { + SQLSRV_ERROR_INVALID_TYPE, + { IMSSP, (SQLCHAR*) "Invalid type.", -27, false } + }, + + { + PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, + {IMSSP, (SQLCHAR*)"An invalid column number was specified.", -28, false } + }, + + { + SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, + { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -29, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_KEY, + { IMSSP, (SQLCHAR*) "Invalid option key %1!s! specified.", -30, true } + }, + { + SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, + { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option PDO::SQLSRV_ATTR_QUERY_TIMEOUT.", -31, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, + { IMSSP, (SQLCHAR*) "The value passed for the 'Scrollable' statement option is invalid.", -32, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DBH_ATTR, + { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDO object.", -33, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, + { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDOStatement object.", -34, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_ENCODING, + { IMSSP, (SQLCHAR*) "An invalid encoding was specified for SQLSRV_ATTR_ENCODING.", -35, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, + { IMSSP, (SQLCHAR*) "An invalid type or value was given for the parameter driver data. Only encoding constants " + "such as PDO::SQLSRV_ENCODING_UTF8 may be used as parameter driver options.", -36, false } + }, + { + PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, + { IMSSP, (SQLCHAR*) "PDO::PARAM_STMT is not a supported parameter type.", -37, false } + }, + { + PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, + { IMSSP, (SQLCHAR*) "An unsupported attribute was designated on the PDO object.", -38, false } + }, + { + PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, + { IMSSP, (SQLCHAR*) "The given attribute is only supported on the PDOStatement object.", -39, false } + }, + { + PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, + { IMSSP, (SQLCHAR*) "A read-only attribute was designated on the PDO object.", -40, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DSN_STRING, + {IMSSP, (SQLCHAR*)"An invalid DSN string was specified.", -41, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DSN_KEY, + { IMSSP, (SQLCHAR*) "An invalid keyword '%1!s!' was specified in the DSN string.", -42, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, + { IMSSP, (SQLCHAR*) "An invalid statement option was specified.", -43, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, + { IMSSP, (SQLCHAR*) "An invalid cursor type was specified for either PDO::ATTR_CURSOR or " + "PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE", -44, false } + }, + { + PDO_SQLSRV_ERROR_PARAM_PARSE, + { IMSSP, (SQLCHAR*) "An error occurred substituting the named parameters.", -45, false } + }, + { + PDO_SQLSRV_ERROR_LAST_INSERT_ID, + { IMSSP, (SQLCHAR*) "An error occurred retrieving the last insert id.", -46, false } + }, + { + SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -47, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, + { IMSSP, (SQLCHAR*) "An invalid type or value was given as bound column driver data for column %1!d!. Only " + "encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.", -48, true } + }, + { + PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, + { IMSSP, (SQLCHAR*) "An encoding was specified for column %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR column types " + "can take an encoding option.", -49, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, + { IMSSP, (SQLCHAR*) "Invalid encoding specified for column %1!d!.", -50, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, + { IMSSP, (SQLCHAR*) "An encoding was specified for parameter %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an " + "encoding option.", -51, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, + { IMSSP, (SQLCHAR*) "Invalid encoding specified for parameter %1!d!.", -52, true } + }, + { + PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, + { IMSSP, (SQLCHAR*) "The PDO::ATTR_CURSOR and PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attributes may only be set in the " + "$driver_options array of PDO::prepare.", -53, false } + }, + { + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, + { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -54, true } + }, + { + SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, + { IMSSP, (SQLCHAR*) "Types for parameter value and PDO::PARAM_* constant must be compatible for input/output " + "parameter %1!d!.", -55, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, + { IMSSP, (SQLCHAR*) "Invalid direction specified for parameter %1!d!. Input/output parameters must have a length.", + -56, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, + { IMSSP, (SQLCHAR*) "Invalid size for output string parameter %1!d!. Input/output string parameters must have an " + "explicit length.", -57, true } + }, + { + PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, + { IMSSP, (SQLCHAR*) "This function is not implemented by this driver.", -58, false } + }, + { + /* The stream related errors are not currently used in PDO, but the core layer can throw the stream related + errors so having a mapping here */ + + SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, + { IMSSP, (SQLCHAR*) "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using " + "streams.", -59, false} + }, + { + SQLSRV_ERROR_STREAM_CREATE, + { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -60, false } + }, + { + SQLSRV_ERROR_MARS_OFF, + { IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. " + "To make the connection available for other queries, either fetch all results or cancel or free the statement. " + "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -61, false } + }, + { + SQLSRV_ERROR_FIELD_INDEX_ERROR, + { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. Cannot retrieve field %1!d! because its " + "index is less than the index of a field that has already been retrieved (%2!d!).", -62, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, + { IMSSP, (SQLCHAR*) "An invalid value was specified for the keyword '%1!s!' in the DSN string.", -63, true } + }, + { + PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, + { IMSSP, (SQLCHAR*) "Server keyword was not specified in the DSN string.", -64, false } + }, + { + PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, + { IMSSP, (SQLCHAR*) "The DSN string ended unexpectedly.", -65, false } + }, + { + PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, + { IMSSP, (SQLCHAR*) "An extra semi-colon was encountered in the DSN string at character (byte-count) position '%1!d!' .", + -66, true } + }, + { + PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, + { IMSSP, (SQLCHAR*) "An expected right brace (}) was not found in the DSN string for the value of the keyword '%1!s!'.", + -67, true } + }, + { + PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, + { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_DIRECT_QUERY attribute may only be set in the $driver_options array of " + "PDO::prepare.", -68, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, + { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attribute may only be set when PDO::ATTR_CURSOR is set to " + "PDO::CURSOR_SCROLL in the $driver_options array of PDO::prepare.", -69, false } + }, + { + SQLSRV_ERROR_INVALID_BUFFER_LIMIT, + { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not " + "positive. Only positive numbers are valid for this attribute.", -70, false } + }, + { + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, + { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -71, true } + }, + { UINT_MAX, {} } +}; + +// Returns a sqlsrv_error for a given error code. +sqlsrv_error_const* get_error_message(unsigned int sqlsrv_error_code) { + + sqlsrv_error_const *error_message = NULL; + int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr(g_pdo_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; + if( zr == FAILURE ) { + DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); + } + + SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null"); + + return error_message; +} + +// PDO error handler for the environment context. +bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ) +{ + SQLSRV_ASSERT(( ctx != NULL ), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null" ); + pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); + SQLSRV_ASSERT(( dbh != NULL ), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null" ); + + sqlsrv_error_auto_ptr error; + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + } + else { + + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + } + + strcpy_s( dbh->error_code, sizeof( pdo_error_type ), reinterpret_cast( error->sqlstate )); + + switch( dbh->error_mode ) { + + case PDO_ERRMODE_EXCEPTION: + if( !warning ) { + + pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + } + ctx.set_last_error( error ); + break; + + default: + DIE( "pdo_sqlsrv_handle_env_error: Unexpected error mode. %1!d!", dbh->error_mode ); + break; + } + + // we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count + // return error ignored = true for warnings. + return ( warning ? true : false ); + +} + +// pdo error handler for the dbh context. +bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ) +{ + pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" ); + + sqlsrv_error_auto_ptr error; + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + } + else { + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + } + + SQLSRV_ASSERT(strlen(reinterpret_cast(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow"); + strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast(error->sqlstate)); + + switch( dbh->error_mode ) { + case PDO_ERRMODE_EXCEPTION: + if( !warning ) { + + pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + } + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_WARNING: + if( !warning ) { + size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + + MAX_DIGITS + WARNING_MIN_LENGTH + 1; + sqlsrv_malloc_auto_ptr msg; + 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 ); + } + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_SILENT: + ctx.set_last_error( error ); + break; + default: + DIE( "Unknown error mode. %1!d!", dbh->error_mode ); + break; + } + + // return error ignored = true for warnings. + return ( warning ? true : false ); +} + +// PDO error handler for the statement context. +bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ) +{ + pdo_stmt_t* pdo_stmt = reinterpret_cast( ctx.driver()); + SQLSRV_ASSERT( pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed" ); + + sqlsrv_error_auto_ptr error; + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + } + else { + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + } + + SQLSRV_ASSERT( strlen( reinterpret_cast( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow"); + strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast( error->sqlstate )); + + switch( pdo_stmt->dbh->error_mode ) { + case PDO_ERRMODE_EXCEPTION: + if( !warning ) { + + pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + } + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_WARNING: + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_SILENT: + ctx.set_last_error( error ); + break; + default: + DIE( "Unknown error mode. %1!d!", pdo_stmt->dbh->error_mode ); + break; + } + + // return error ignored = true for warnings. + return ( warning ? true : false ); +} + + +// Transfer a sqlsrv_context's error to a PDO zval. The standard format for a zval error is 3 elements: +// 0, native code +// 1, native message +// 2, SQLSTATE of the error (driver specific error messages are 'IMSSP') + +void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ) +{ + if( last_error ) { + // SQLSTATE is already present in the zval. + add_next_index_long( pdo_zval, last_error->native_code ); + add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message )); + } +} + +// Formats the error message and writes to the php error log. +void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ) +{ + if( (severity & PDO_SQLSRV_G( log_severity )) == 0 ) { + return; + } + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); + + // if an error occurs for FormatMessage, we just output an internal error occurred. + if( rc == 0 ) { + SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); + std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); + } + + php_log_err( log_msg TSRMLS_CC ); +} + +namespace { + +void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ) +{ + zval ex_obj; + ZVAL_UNDEF( &ex_obj ); + + zend_class_entry* ex_class = php_pdo_get_exception(); + + int zr = object_init_ex( &ex_obj, ex_class ); + SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" ); + + sqlsrv_malloc_auto_ptr ex_msg; + size_t ex_msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + + 12 + 1; // 12 = "SQLSTATE[]: " + ex_msg = reinterpret_cast( sqlsrv_malloc( ex_msg_len )); + snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message ); + zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, + ex_msg TSRMLS_CC ); + zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, + reinterpret_cast( error->sqlstate ) TSRMLS_CC ); + + zval ex_error_info; + ZVAL_UNDEF( &ex_error_info ); + array_init( &ex_error_info ); + add_next_index_string( &ex_error_info, reinterpret_cast( error->sqlstate )); + add_next_index_long( &ex_error_info, error->native_code ); + add_next_index_string( &ex_error_info, reinterpret_cast( error->native_message )); + //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) + //and the refcount of the zend_array is incremented by 1 + zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, + &ex_error_info TSRMLS_CC ); + + //DELREF ex_error_info here to decrement the refcount of the zend_array is 1 + //the global hashtable EG(exception) then points to the zend_object in ex_obj in zend_throw_exception_object; + //this ensure when EG(exception) cleans itself at php shutdown, the zend_array allocated is properly destroyed + Z_DELREF( ex_error_info ); + zend_throw_exception_object( &ex_obj TSRMLS_CC ); +} + +} diff --git a/pdo_sqlsrv/pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h similarity index 90% rename from pdo_sqlsrv/pdo_sqlsrv.h rename to source/pdo_sqlsrv/php_pdo_sqlsrv.h index 959f6696..348bf7d2 100644 --- a/pdo_sqlsrv/pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -1,403 +1,395 @@ -#ifndef PDO_SQLSRV_H -#define PDO_SQLSRV_H - -//--------------------------------------------------------------------------------------------------------------------------------- -// File: pdo_sqlsrv.h -// -// Contents: Declarations for the extension -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" -#include "version.h" - -extern "C" { - -#include "pdo/php_pdo.h" -#include "pdo/php_pdo_driver.h" -#include "pdo/php_pdo_int.h" - -} - -#include -#include - - -//********************************************************************************************************************************* -// Constants and Types -//********************************************************************************************************************************* - -// sqlsrv driver specific PDO attributes -enum PDO_SQLSRV_ATTR { - - // Currently there are only three custom attributes for this driver. - SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC, - SQLSRV_ATTR_QUERY_TIMEOUT, - SQLSRV_ATTR_DIRECT_QUERY, - SQLSRV_ATTR_CURSOR_SCROLL_TYPE, - SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, - SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, -}; - -// valid set of values for TransactionIsolation connection option -namespace PDOTxnIsolationValues { - - const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED"; - const char READ_COMMITTED[] = "READ_COMMITTED"; - const char REPEATABLE_READ[] = "REPEATABLE_READ"; - const char SERIALIZABLE[] = "SERIALIZABLE"; - const char SNAPSHOT[] = "SNAPSHOT"; -} - -//********************************************************************************************************************************* -// Global variables -//********************************************************************************************************************************* - -extern "C" { - -// request level variables -ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) - -unsigned int log_severity; -zend_long client_buffer_max_size; - -ZEND_END_MODULE_GLOBALS(pdo_sqlsrv) - -ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv); - -} - -// macros used to access the global variables. Use these to make global variable access agnostic to threads -#ifdef ZTS -#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v) -#else -#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v -#endif - -// INI settings and constants -// (these are defined as macros to allow concatenation as we do below) -#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" -#define INI_PDO_SQLSRV_LOG "log_severity" -#define INI_PREFIX "pdo_sqlsrv." - -PHP_INI_BEGIN() - STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, - zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, - client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) -PHP_INI_END() - -// henv context for creating connections -extern sqlsrv_context* g_henv_cp; -extern sqlsrv_context* g_henv_ncp; - - -//********************************************************************************************************************************* -// Initialization -//********************************************************************************************************************************* - -// module global variables (initialized in minit and freed in mshutdown) -extern HashTable* g_pdo_errors_ht; - -// module initialization -PHP_MINIT_FUNCTION(pdo_sqlsrv); -// module shutdown function -PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv); -// request initialization function -PHP_RINIT_FUNCTION(pdo_sqlsrv); -// request shutdown function -PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv); -// module info function (info returned by phpinfo()) -PHP_MINFO_FUNCTION(pdo_sqlsrv); - -extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP - -//********************************************************************************************************************************* -// PDO DSN Parser -//********************************************************************************************************************************* - -// Parser class used to parse DSN connection string. -class conn_string_parser -{ - enum States - { - FirstKeyValuePair, - Key, - Value, - ValueContent1, - ValueContent2, - RCBEncountered, - NextKeyValuePair, - }; - - private: - const char* conn_str; - sqlsrv_context* ctx; - int len; - int pos; - unsigned int current_key; - const char* current_key_name; - HashTable* conn_options_ht; - inline bool next( void ); - inline bool is_eos( void ); - inline bool is_white_space( char c ); - bool discard_white_spaces( void ); - int discard_trailing_white_spaces( const char* str, int len ); - void conn_string_parser::validate_key( const char *key, int key_len TSRMLS_DC ); - void add_key_value_pair( const char* value, int len TSRMLS_DC ); - - public: - conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ); - void parse_conn_string( TSRMLS_D ); -}; - -//********************************************************************************************************************************* -// Connection -//********************************************************************************************************************************* -extern const connection_option PDO_CONN_OPTS[]; - -int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC); - -// a core layer pdo dbh object. This object inherits and overrides the statement factory -struct pdo_sqlsrv_dbh : public sqlsrv_conn { - - zval* stmts; - bool direct_query; - long query_timeout; - zend_long client_buffer_max_size; - SQLSRV_ENCODING bind_param_encoding; - bool fetch_numeric; - - pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ); -}; - - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -struct stmt_option_encoding : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_scrollable : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_direct_query : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_cursor_scroll_type : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_emulate_prepares : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_fetch_numeric : public stmt_option_functor { - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; - -// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary -struct pdo_sqlsrv_stmt : public sqlsrv_stmt { - - pdo_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : - sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), - direct_query( false ), - direct_query_subst_string( NULL ), - direct_query_subst_string_len( 0 ), - bound_column_param_types( NULL ), - fetch_numeric( false ) - { - pdo_sqlsrv_dbh* db = static_cast( c ); - direct_query = db->direct_query; - fetch_numeric = db->fetch_numeric; - } - - virtual ~pdo_sqlsrv_stmt( void ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ); - - bool direct_query; // flag set if the query should be executed directly or prepared - const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters - size_t direct_query_subst_string_len; // length of query string used for direct queries - - // meta data for current result set - std::vector > current_meta_data; - pdo_param_type* bound_column_param_types; - bool fetch_numeric; -}; - - -//********************************************************************************************************************************* -// Error Handling Functions -//********************************************************************************************************************************* - -// represents the mapping between an error_code and the corresponding error message. -struct pdo_error { - - unsigned int error_code; - sqlsrv_error_const sqlsrv_error; -}; - -// called when an error occurs in the core layer. These routines are set as the error_callback in a -// context. The context is passed to this function since it contains the function - -bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ); -bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ); -bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ); - -// pointer to the function to return the class entry for the PDO exception Set in MINIT -extern zend_class_entry* (*pdo_get_exception_class)( void ); - -// common routine to transfer a sqlsrv_context's error to a PDO zval -void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ); - -// reset the errors from the last operation -inline void pdo_reset_dbh_error( pdo_dbh_t* dbh TSRMLS_DC ) -{ - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error - - // release the last statement from the dbh so that error handling won't have a statement passed to it - if( dbh->query_stmt ) { - dbh->query_stmt = NULL; - zend_objects_store_del( Z_OBJ_P(&dbh->query_stmt_zval TSRMLS_CC) ); - } - - // if the driver isn't valid, just return (PDO calls close sometimes more than once?) - if( dbh->driver_data == NULL ) { - return; - } - - // reset the last error on the sqlsrv_context - sqlsrv_context* ctx = static_cast( dbh->driver_data ); - - if( ctx->last_error() ) { - ctx->last_error().reset(); - } -} - -#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); - -inline void pdo_reset_stmt_error( pdo_stmt_t* stmt ) -{ - strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error - - // if the driver isn't valid, just return (PDO calls close sometimes more than once?) - if( stmt->driver_data == NULL ) { - return; - } - - // reset the last error on the sqlsrv_context - sqlsrv_context* ctx = static_cast( stmt->driver_data ); - - if( ctx->last_error() ) { - ctx->last_error().reset(); - } -} - -#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt ); - -// validate the driver objects -#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); } -#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); } - - -//********************************************************************************************************************************* -// Utility Functions -//********************************************************************************************************************************* - -// List of PDO specific error messages. -enum PDO_ERROR_CODES { - - PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC, - PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, - PDO_SQLSRV_ERROR_INVALID_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, - PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, - PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, - PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, - PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, - PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, - PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, - PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, - PDO_SQLSRV_ERROR_PARAM_PARSE, - PDO_SQLSRV_ERROR_LAST_INSERT_ID, - PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, - PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, - PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, - PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, - PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, - PDO_SQLSRV_ERROR_INVALID_DSN_STRING, - PDO_SQLSRV_ERROR_INVALID_DSN_KEY, - PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, - PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, - PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, - PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, - SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, - PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, - PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, - PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, - PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, - -}; - -extern pdo_error PDO_ERRORS[]; - -#define THROW_PDO_ERROR( ctx, custom, ... ) \ - call_error_handler( ctx, custom TSRMLS_CC, false, __VA_ARGS__ ); \ - throw pdo::PDOException(); - -namespace pdo { - - // an error which occurred in our PDO driver, NOT an exception thrown by PDO - struct PDOException : public core::CoreException { - - PDOException() : CoreException() - { - } - }; - -} // namespace pdo - -// called pdo_parse_params in php_pdo_driver.h -// we renamed it for 2 reasons: 1) we can't have the same name since it would conflict with our dynamic linking, and -// 2) this is a more precise name -extern int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, size_t inquery_len, - char **outquery, size_t *outquery_len TSRMLS_DC); - -// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro -void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); - -#endif /* PDO_SQLSRV_H */ - +#ifndef PHP_PDO_SQLSRV_H +#define PHP_PDO_SQLSRV_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: php_pdo_sqlsrv.h +// +// Contents: Declarations for the extension +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" +#include "version.h" + +extern "C" { + +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" + +} + +#include +#include + + +//********************************************************************************************************************************* +// Constants and Types +//********************************************************************************************************************************* + +// sqlsrv driver specific PDO attributes +enum PDO_SQLSRV_ATTR { + + // Currently there are only three custom attributes for this driver. + SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC, + SQLSRV_ATTR_QUERY_TIMEOUT, + SQLSRV_ATTR_DIRECT_QUERY, + SQLSRV_ATTR_CURSOR_SCROLL_TYPE, + SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, + SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, +}; + +// valid set of values for TransactionIsolation connection option +namespace PDOTxnIsolationValues { + + const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED"; + const char READ_COMMITTED[] = "READ_COMMITTED"; + const char REPEATABLE_READ[] = "REPEATABLE_READ"; + const char SERIALIZABLE[] = "SERIALIZABLE"; + const char SNAPSHOT[] = "SNAPSHOT"; +} + +//********************************************************************************************************************************* +// Global variables +//********************************************************************************************************************************* + +extern "C" { + +// request level variables +ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) + +unsigned int log_severity; +zend_long client_buffer_max_size; + +ZEND_END_MODULE_GLOBALS(pdo_sqlsrv) + +ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv); + +} + +// macros used to access the global variables. Use these to make global variable access agnostic to threads +#ifdef ZTS +#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v) +#else +#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v +#endif + +// INI settings and constants +// (these are defined as macros to allow concatenation as we do below) +#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" +#define INI_PDO_SQLSRV_LOG "log_severity" +#define INI_PREFIX "pdo_sqlsrv." + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, + zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, + client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) +PHP_INI_END() + +// henv context for creating connections +extern sqlsrv_context* g_henv_cp; +extern sqlsrv_context* g_henv_ncp; + + +//********************************************************************************************************************************* +// Initialization +//********************************************************************************************************************************* + +// module global variables (initialized in minit and freed in mshutdown) +extern HashTable* g_pdo_errors_ht; + +#define phpext_pdo_sqlsrv_ptr &g_pdo_sqlsrv_module_entry + +// module initialization +PHP_MINIT_FUNCTION(pdo_sqlsrv); +// module shutdown function +PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv); +// request initialization function +PHP_RINIT_FUNCTION(pdo_sqlsrv); +// request shutdown function +PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv); +// module info function (info returned by phpinfo()) +PHP_MINFO_FUNCTION(pdo_sqlsrv); + +extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP + +//********************************************************************************************************************************* +// PDO DSN Parser +//********************************************************************************************************************************* + +// Parser class used to parse DSN connection string. +class conn_string_parser +{ + enum States + { + FirstKeyValuePair, + Key, + Value, + ValueContent1, + ValueContent2, + RCBEncountered, + NextKeyValuePair, + }; + + private: + const char* conn_str; + sqlsrv_context* ctx; + int len; + int pos; + unsigned int current_key; + const char* current_key_name; + HashTable* conn_options_ht; + inline bool next( void ); + inline bool is_eos( void ); + inline bool is_white_space( char c ); + bool discard_white_spaces( void ); + int discard_trailing_white_spaces( const char* str, int len ); + void validate_key( const char *key, int key_len TSRMLS_DC ); + void add_key_value_pair( const char* value, int len TSRMLS_DC ); + + public: + conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ); + void parse_conn_string( TSRMLS_D ); +}; + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* +extern const connection_option PDO_CONN_OPTS[]; + +int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC); + +// a core layer pdo dbh object. This object inherits and overrides the statement factory +struct pdo_sqlsrv_dbh : public sqlsrv_conn { + + zval* stmts; + bool direct_query; + long query_timeout; + zend_long client_buffer_max_size; + SQLSRV_ENCODING bind_param_encoding; + bool fetch_numeric; + + pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ); +}; + + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +struct stmt_option_encoding : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_scrollable : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_direct_query : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_cursor_scroll_type : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_emulate_prepares : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_fetch_numeric : public stmt_option_functor { + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; + +// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary +struct pdo_sqlsrv_stmt : public sqlsrv_stmt { + + pdo_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : + sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), + direct_query( false ), + direct_query_subst_string( NULL ), + direct_query_subst_string_len( 0 ), + bound_column_param_types( NULL ), + fetch_numeric( false ) + { + pdo_sqlsrv_dbh* db = static_cast( c ); + direct_query = db->direct_query; + fetch_numeric = db->fetch_numeric; + } + + virtual ~pdo_sqlsrv_stmt( void ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ); + + bool direct_query; // flag set if the query should be executed directly or prepared + const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters + size_t direct_query_subst_string_len; // length of query string used for direct queries + + // meta data for current result set + std::vector > current_meta_data; + pdo_param_type* bound_column_param_types; + bool fetch_numeric; +}; + + +//********************************************************************************************************************************* +// Error Handling Functions +//********************************************************************************************************************************* + +// represents the mapping between an error_code and the corresponding error message. +struct pdo_error { + + unsigned int error_code; + sqlsrv_error_const sqlsrv_error; +}; + +// called when an error occurs in the core layer. These routines are set as the error_callback in a +// context. The context is passed to this function since it contains the function + +bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ); +bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ); +bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ); + +// common routine to transfer a sqlsrv_context's error to a PDO zval +void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ); + +// reset the errors from the last operation +inline void pdo_reset_dbh_error( pdo_dbh_t* dbh TSRMLS_DC ) +{ + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error + + // release the last statement from the dbh so that error handling won't have a statement passed to it + if( dbh->query_stmt ) { + dbh->query_stmt = NULL; + zend_objects_store_del( Z_OBJ_P(&dbh->query_stmt_zval TSRMLS_CC) ); + } + + // if the driver isn't valid, just return (PDO calls close sometimes more than once?) + if( dbh->driver_data == NULL ) { + return; + } + + // reset the last error on the sqlsrv_context + sqlsrv_context* ctx = static_cast( dbh->driver_data ); + + if( ctx->last_error() ) { + ctx->last_error().reset(); + } +} + +#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); + +inline void pdo_reset_stmt_error( pdo_stmt_t* stmt ) +{ + strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error + + // if the driver isn't valid, just return (PDO calls close sometimes more than once?) + if( stmt->driver_data == NULL ) { + return; + } + + // reset the last error on the sqlsrv_context + sqlsrv_context* ctx = static_cast( stmt->driver_data ); + + if( ctx->last_error() ) { + ctx->last_error().reset(); + } +} + +#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt ); + +// validate the driver objects +#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); } +#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); } + + +//********************************************************************************************************************************* +// Utility Functions +//********************************************************************************************************************************* + +// List of PDO specific error messages. +enum PDO_ERROR_CODES { + + PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC, + PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, + PDO_SQLSRV_ERROR_INVALID_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, + PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, + PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, + PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, + PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, + PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, + PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, + PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, + PDO_SQLSRV_ERROR_PARAM_PARSE, + PDO_SQLSRV_ERROR_LAST_INSERT_ID, + PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, + PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, + PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, + PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, + PDO_SQLSRV_ERROR_INVALID_DSN_STRING, + PDO_SQLSRV_ERROR_INVALID_DSN_KEY, + PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, + PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, + PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, + PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, + SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, + PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, + PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, + PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, + +}; + +extern pdo_error PDO_ERRORS[]; + +#define THROW_PDO_ERROR( ctx, custom, ... ) \ + call_error_handler( ctx, custom TSRMLS_CC, false, ## __VA_ARGS__ ); \ + throw pdo::PDOException(); + +namespace pdo { + + // an error which occurred in our PDO driver, NOT an exception thrown by PDO + struct PDOException : public core::CoreException { + + PDOException() : CoreException() + { + } + }; + +} // namespace pdo + +// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro +void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); + +#endif /* PHP_PDO_SQLSRV_H */ + diff --git a/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc similarity index 87% rename from pdo_sqlsrv/template.rc rename to source/pdo_sqlsrv/template.rc index c6a4f82a..d36cd82f 100644 --- a/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -1,83 +1,83 @@ -//---------------------------------------------------------------------------------------------------------------------------------- -// File: template.rc -// -// Contents: Version resource -// -// Microsoft Drivers 4.0 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#ifdef APSTUDIO_INVOKED -# error dont edit with MSVC -#endif - -#include "winresrc.h" -#include "main/php_version.h" -#include "version.h" - -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -#ifndef THANKS_GUYS -# define THANKS_GUYS "" -#endif - -#ifdef WANT_LOGO -0 ICON win32\build\php.ico -#endif - -#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build -#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel -#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build) -#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel) - -//Version -VS_VERSION_INFO VERSIONINFO - FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION - PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_MMDD,0 - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_DLL - FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright 2001-2016 The PHP Group. All rights reserved.\0" - VALUE "CompanyName", "Microsoft Corp.\0" - VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0" - VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION) - VALUE "InternalName", FILE_NAME "\0" - VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" - VALUE "OriginalFilename", FILE_NAME "\0" - VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" - VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD) - VALUE "URL", "http://www.microsoft.com\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END - -#ifdef MC_INCLUDE -#include MC_INCLUDE -#endif - +//---------------------------------------------------------------------------------------------------------------------------------- +// File: template.rc +// +// Contents: Version resource +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifdef APSTUDIO_INVOKED +# error dont edit with MSVC +#endif + +#include "winresrc.h" +#include "main/php_version.h" +#include "shared/version.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifndef THANKS_GUYS +# define THANKS_GUYS "" +#endif + +#ifdef WANT_LOGO +0 ICON win32\build\php.ico +#endif + +#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build +#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel +#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build) +#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel) + +//Version +VS_VERSION_INFO VERSIONINFO + FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD + PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_RELEASE,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright © 2001-2016 The PHP Group. All rights reserved.\0" + VALUE "CompanyName", "Microsoft Corp.\0" + VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0" + VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD) + VALUE "InternalName", FILE_NAME "\0" + VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" + VALUE "OriginalFilename", FILE_NAME "\0" + VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" + VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE) + VALUE "URL", "http://www.microsoft.com\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#ifdef MC_INCLUDE +#include MC_INCLUDE +#endif + diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp new file mode 100644 index 00000000..b3aa3e7a --- /dev/null +++ b/source/shared/FormattedPrint.cpp @@ -0,0 +1,2805 @@ + +//----------------------------------------------------------------------------- +// File: FormattedPrint.cpp +// +// +// Contents: Contains functions for handling Windows format strings +// and UTF-16 on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include +#include + +#if !defined(_MSC_VER) +#include +#endif + +#include "StringFunctions.h" + +// XPLAT_ODBC_TODO VSTS 819733 - MPlat: Reconcile std c++ usage between platforms +#ifdef MPLAT_UNIX +// #include +#elif defined(MPLAT_WWOWH) +# define _ASSERTE assert +# include +# undef _M_IX86 +# undef min +# undef max +#endif +#include +#include +#ifdef MPLAT_UNIX + #include "sal_def.h" +#endif + +#ifndef _WIN32 +#define PTR_IS_INT64 1 +#else +#define PTR_IS_INT64 0 +#endif // !_WIN32 + +#ifndef _MSC_VER +// SQL Server does not have a long double type +#define LONGDOUBLE_IS_DOUBLE 1 +typedef double _LONGDOUBLE; +#endif + +// XPLAT_ODBC_TODO VSTS VSTS 718708 Localization +#define _SAFECRT_IMPL + +#if !defined(_countof) +#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0])) +#endif // _countof + +#ifndef _VALIDATE_RETURN +#define _VALIDATE_RETURN( expr, errorcode, retexpr ) \ + { \ + int _Expr_val=!!(expr); \ + if ( !( _Expr_val ) ) \ + { \ + assert(false); \ + errno = errorcode; \ + return ( retexpr ); \ + } \ + } +#endif /* _VALIDATE_RETURN */ + + +static const char *__nullstring = "(null)"; /* string to print on null ptr */ +static const wchar_t *__wnullstring = L"(null)";/* string to print on null ptr */ + +#define BUFFERSIZE 512 +#define MAXPRECISION BUFFERSIZE +#define _CVTBUFSIZE (309+40) /* # of digits in max. dp value + slop */ + + +/* flag definitions */ +#define FL_SIGN 0x00001 /* put plus or minus in front */ +#define FL_SIGNSP 0x00002 /* put space or minus in front */ +#define FL_LEFT 0x00004 /* left justify */ +#define FL_LEADZERO 0x00008 /* pad with leading zeros */ +#define FL_LONG 0x00010 /* long value given */ +#define FL_SHORT 0x00020 /* short value given */ +#define FL_SIGNED 0x00040 /* signed data given */ +#define FL_ALTERNATE 0x00080 /* alternate form requested */ +#define FL_NEGATIVE 0x00100 /* value is negative */ +#define FL_FORCEOCTAL 0x00200 /* force leading '0' for octals */ +#define FL_LONGDOUBLE 0x00400 /* long double value given */ +#define FL_WIDECHAR 0x00800 /* wide characters */ +#define FL_LONGLONG 0x01000 /* long long value given */ +#define FL_I64 0x08000 /* __int64 value given */ + + +/* state definitions */ +enum STATE { + ST_NORMAL, /* normal state; outputting literal chars */ + ST_PERCENT, /* just read '%' */ + ST_FLAG, /* just read flag character */ + ST_WIDTH, /* just read width specifier */ + ST_DOT, /* just read '.' */ + ST_PRECIS, /* just read precision specifier */ + ST_SIZE, /* just read size specifier */ + ST_TYPE /* just read type specifier */ + ,ST_INVALID /* Invalid format */ + +}; + +#define NUMSTATES (ST_INVALID + 1) + +/* character type values */ +enum CHARTYPE { + CH_OTHER, /* character with no special meaning */ + CH_PERCENT, /* '%' */ + CH_DOT, /* '.' */ + CH_STAR, /* '*' */ + CH_ZERO, /* '0' */ + CH_DIGIT, /* '1'..'9' */ + CH_FLAG, /* ' ', '+', '-', '#' */ + CH_SIZE, /* 'h', 'l', 'L', 'N', 'F', 'w' */ + CH_TYPE /* type specifying character */ +}; + + +static const unsigned char __lookuptable_s[] = { + /* ' ' */ 0x06, + /* '!' */ 0x80, + /* '"' */ 0x80, + /* '#' */ 0x86, + /* '$' */ 0x80, + /* '%' */ 0x81, + /* '&' */ 0x80, + /* ''' */ 0x00, + /* '(' */ 0x00, + /* ')' */ 0x10, + /* '*' */ 0x03, + /* '+' */ 0x86, + /* ',' */ 0x80, + /* '-' */ 0x86, + /* '.' */ 0x82, + /* '/' */ 0x80, + /* '0' */ 0x14, + /* '1' */ 0x05, + /* '2' */ 0x05, + /* '3' */ 0x45, + /* '4' */ 0x45, + /* '5' */ 0x45, + /* '6' */ 0x85, + /* '7' */ 0x85, + /* '8' */ 0x85, + /* '9' */ 0x05, + /* ':' */ 0x00, + /* ';' */ 0x00, + /* '<' */ 0x30, + /* '=' */ 0x30, + /* '>' */ 0x80, + /* '?' */ 0x50, + /* '@' */ 0x80, +#if defined (_SAFECRT_IMPL) + /* 'A' */ 0x80, // Disable %A format +#else /* defined (_SAFECRT_IMPL) */ + /* 'A' */ 0x88, +#endif /* defined (_SAFECRT_IMPL) */ + /* 'B' */ 0x00, + /* 'C' */ 0x08, + /* 'D' */ 0x00, + /* 'E' */ 0x28, + /* 'F' */ 0x27, + /* 'G' */ 0x38, + /* 'H' */ 0x50, + /* 'I' */ 0x57, + /* 'J' */ 0x80, + /* 'K' */ 0x00, + /* 'L' */ 0x07, + /* 'M' */ 0x00, + /* 'N' */ 0x37, + /* 'O' */ 0x30, + /* 'P' */ 0x30, + /* 'Q' */ 0x50, + /* 'R' */ 0x50, + /* 'S' */ 0x88, + /* 'T' */ 0x00, + /* 'U' */ 0x00, + /* 'V' */ 0x00, + /* 'W' */ 0x20, + /* 'X' */ 0x28, + /* 'Y' */ 0x80, + /* 'Z' */ 0x88, + /* '[' */ 0x80, + /* '\' */ 0x80, + /* ']' */ 0x00, + /* '^' */ 0x00, + /* '_' */ 0x00, + /* '`' */ 0x60, +#if defined (_SAFECRT_IMPL) + /* 'a' */ 0x60, // Disable %a format +#else /* defined (_SAFECRT_IMPL) */ + /* 'a' */ 0x68, +#endif /* defined (_SAFECRT_IMPL) */ + /* 'b' */ 0x60, + /* 'c' */ 0x68, + /* 'd' */ 0x68, + /* 'e' */ 0x68, + /* 'f' */ 0x08, + /* 'g' */ 0x08, + /* 'h' */ 0x07, + /* 'i' */ 0x78, + /* 'j' */ 0x70, + /* 'k' */ 0x70, + /* 'l' */ 0x77, + /* 'm' */ 0x70, + /* 'n' */ 0x70, + /* 'o' */ 0x08, + /* 'p' */ 0x08, + /* 'q' */ 0x00, + /* 'r' */ 0x00, + /* 's' */ 0x08, + /* 't' */ 0x00, + /* 'u' */ 0x08, + /* 'v' */ 0x00, + /* 'w' */ 0x07, + /* 'x' */ 0x08 +}; + +static inline CHARTYPE GetCharType( WCHAR wch ) +{ + return ((wch < (L' ') || wch > (L'x')) ? CH_OTHER : (enum CHARTYPE)(__lookuptable_s[wch - (L' ')] & 0xF)); +} +static inline CHARTYPE GetCharType( char ch ) +{ + return ((ch < (' ') || ch > ('x')) ? CH_OTHER : (enum CHARTYPE)(__lookuptable_s[ch - (' ')] & 0xF)); +} +static inline STATE GetState( CHARTYPE type, STATE oldState ) +{ + return (enum STATE)(__lookuptable_s[type * NUMSTATES + oldState] >> 4); +} + + +static bool isleadbyte(unsigned char ch) +{ + return (FALSE != IsDBCSLeadByte(ch) ); +} +static bool isleadwchar(WCHAR ch) +{ + return ((ch & 0xFC00) == 0xD800); +} +static bool _isleadbyte_l(unsigned char ch, _locale_t loc) +{ + // XPLAT_ODBC_TODO VSTS 718708 Localization + return ( FALSE != IsDBCSLeadByte(ch) ); +} + + + +#define _WCTOMB_S mplat_wctomb_s +errno_t mplat_wctomb_s( + int *pRetValue, + char *mbchar, + size_t sizeInBytes, + WCHAR wchar +) +{ + DWORD rc; + size_t cch = SystemLocale::FromUtf16( CP_ACP, &wchar, 1, mbchar, sizeInBytes, NULL, &rc ); + *pRetValue = (int)cch; + return (ERROR_SUCCESS == rc ? 0 : -1); +} + +#define _MBTOWC mplat_mbtowc +int mplat_mbtowc( + WCHAR * pwchar, + const char * pmb, + size_t n +) +{ + size_t cch = SystemLocale::NextChar( CP_ACP, pmb ) - pmb; + if ( 0 < cch ) + { + size_t cchActual = SystemLocale::ToUtf16( CP_ACP, pmb, cch, pwchar, 1, NULL ); + assert( cch == cchActual ); + } + return (int)cch; +} + +// Floating point print routines +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]; + + if ( 0 != caps ) + { + fmt -= ('a') - ('A'); /* convert format char to upper */ + } + int chars_printed = snprintf( local_fmt, local_bufsize, "%%.%d%c", precision-1, fmt ); + assert( 0 < chars_printed && (size_t)chars_printed < local_bufsize ); + + // We want to use the platform version of snprintf so temporarily undef. + // Formatting of floating pt values is complex so we didn't implement it here. + // Even porting the CRT code would've been difficult. Instead, we can use the + // platform's snprintf for just floating pt values. We have to undef to prevent + // recursing right back to here. +# undef snprintf +# if defined(MPLAT_WWOWH) +# undef _snprintf +# define snprintf _snprintf +# endif + chars_printed = snprintf( buf, bufSize, local_fmt, *dbl ); + assert( 0 < chars_printed && chars_printed < bufSize ); +# if defined(MPLAT_WWOWH) +# undef snprintf +# define _snprintf mplat_snprintf +# endif +# define snprintf mplat_snprintf +} + +#if !LONGDOUBLE_IS_DOUBLE +// SQL Server does not support the long double data type so this should never be called. +// It will be compiled out on Linux. +void _CLDCVT( _LONGDOUBLE * dbl, char * buf, int bufSize, char fmt, int precision, int caps ) +{ + assert(false); +} +#endif + +static enum STATE ProcessSizeA( char sizeCh, char fmt_ch, char next_fmt_ch, int * advance, int * flags ) +{ + *advance = 0; + switch (sizeCh) + { + case 'l': + /* + * In order to handle the ll case, we depart from the + * simple deterministic state machine. + */ + if ( 'l' == fmt_ch ) + { + *advance = 1; + *flags |= FL_LONGLONG; + } + else + { + *flags |= FL_LONG; + } + break; + + case 'I': + /* + * In order to handle the I, I32, and I64 size modifiers, we + * depart from the simple deterministic state machine. The + * code below scans for characters following the 'I', + * and defaults to 64 bit on WIN64 and 32 bit on WIN32 + */ +#if PTR_IS_INT64 + *flags |= FL_I64; /* 'I' => __int64 on WIN64 systems */ +#endif /* PTR_IS_INT64 */ + if ( '6' == fmt_ch && '4' == next_fmt_ch ) + { + *advance = 2; + *flags |= FL_I64; /* I64 => __int64 */ + } + else if ( '3' == fmt_ch && '2' == next_fmt_ch ) + { + *advance = 2; + *flags &= ~FL_I64; /* I32 => __int32 */ + } + else if ( + (fmt_ch == 'd') || + (fmt_ch == 'i') || + (fmt_ch == 'o') || + (fmt_ch == 'u') || + (fmt_ch == 'x') || + (fmt_ch == 'X') ) + { + /* + * Nothing further needed. %Id (et al) is + * handled just like %d, except that it defaults to 64 bits + * on WIN64. Fall through to the next iteration. + */ + } + else + { + return ST_NORMAL; + } + break; + + case 'h': + *flags |= FL_SHORT; + break; + + case 'w': + *flags |= FL_WIDECHAR; + } + + return ST_SIZE; +} + +static enum STATE ProcessSizeW( WCHAR sizeCh, WCHAR fmt_ch, WCHAR next_fmt_ch, int * advance, int * flags ) +{ + *advance = 0; + switch (sizeCh) + { + case L'l': + /* + * In order to handle the ll case, we depart from the + * simple deterministic state machine. + */ + if ( L'l' == fmt_ch ) + { + *advance = 1; + *flags |= FL_LONGLONG; + } + else + { + *flags |= FL_LONG; + } + break; + + case L'I': + /* + * In order to handle the I, I32, and I64 size modifiers, we + * depart from the simple deterministic state machine. The + * code below scans for characters following the 'I', + * and defaults to 64 bit on WIN64 and 32 bit on WIN32 + */ +#if PTR_IS_INT64 + *flags |= FL_I64; /* 'I' => __int64 on WIN64 systems */ +#endif /* PTR_IS_INT64 */ + if ( L'6' == fmt_ch && L'4' == next_fmt_ch ) + { + *advance = 2; + *flags |= FL_I64; /* I64 => __int64 */ + } + else if ( L'3' == fmt_ch && L'2' == next_fmt_ch ) + { + *advance = 2; + *flags &= ~FL_I64; /* I32 => __int32 */ + } + else if ( + (fmt_ch == L'd') || + (fmt_ch == L'i') || + (fmt_ch == L'o') || + (fmt_ch == L'u') || + (fmt_ch == L'x') || + (fmt_ch == L'X') ) + { + /* + * Nothing further needed. %Id (et al) is + * handled just like %d, except that it defaults to 64 bits + * on WIN64. Fall through to the next iteration. + */ + } + else + { + return ST_NORMAL; + } + break; + + case L'h': + *flags |= FL_SHORT; + break; + + case L'w': + *flags |= FL_WIDECHAR; + } + + return ST_SIZE; +} + +STATE ProcessSize( WCHAR sizeCh, const WCHAR * format, int * advance, int * flags ) +{ + WCHAR formatCh = *format; + WCHAR next_formatCh = (L'\0' == formatCh ? L'\0' : *(format+1)); + return ProcessSizeW( sizeCh, formatCh, next_formatCh, advance, flags ); +} + +STATE ProcessSize( char sizeCh, const char * format, int * advance, int * flags ) +{ + char formatCh = *format; + char next_formatCh = ('\0' == formatCh ? '\0' : *(format+1)); + return ProcessSizeA( sizeCh, formatCh, next_formatCh, advance, flags ); +} + +// Tools\vc\src\crt\amd64\output.c +int FormattedPrintA( IFormattedPrintOutput * output, const char *format, va_list argptr ) +{ + int hexadd=0; /* offset to add to number to get 'a'..'f' */ + char ch; /* character just read */ + int flags=0; /* flag word -- see #defines above for flag values */ + enum STATE state; /* current state */ + enum CHARTYPE chclass; /* class of current character */ + int radix; /* current conversion radix */ + int charsout; /* characters currently written so far, -1 = IO error */ + int fldwidth = 0; /* selected field width -- 0 means default */ + int precision = 0; /* selected precision -- -1 means default */ + char prefix[2]; /* numeric prefix -- up to two characters */ + int prefixlen=0; /* length of prefix -- 0 means no prefix */ + int capexp=0; /* non-zero = 'E' exponent signifient, zero = 'e' or unused */ + int no_output=0; /* non-zero = prodcue no output for this specifier */ + union { + char *sz; /* pointer text to be printed, not zero terminated */ + WCHAR *wz; + } text; + + int textlen; /* length of the text in bytes/wchars to be printed. + textlen is in multibyte or wide chars if _UNICODE */ + union { + char sz[BUFFERSIZE]; + } buffer; + 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 */ + +#ifndef _SAFECRT_IMPL + _LocaleUpdate _loc_update(plocinfo); +#endif /* _SAFECRT_IMPL */ + + char *heapbuf = NULL; /* non-zero = test.sz using heap buffer to be freed */ + + int advance; /* count of how much helper fxns need format ptr incremented */ + + _VALIDATE_RETURN( ((output != NULL) && (format != NULL)), EINVAL, -1); + + charsout = 0; /* no characters written yet */ + textlen = 0; /* no text yet */ + state = ST_NORMAL; /* starting state */ + heapbuf = NULL; /* not using heap-allocated buffer */ + buffersize = 0; + + /* main loop -- loop while format character exist and no I/O errors */ + while ((ch = *format++) != '\0' && charsout >= 0) { + // Find char class and next state + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + /* execute code for each state */ + switch (state) { + + case ST_INVALID: + // "Incorrect format specifier" + assert( false ); + errno = EINVAL; + return -1; + + case ST_NORMAL: + + NORMAL_STATE: + + /* normal state -- just write character */ + bufferiswide = 0; +#ifdef _SAFECRT_IMPL + if (isleadbyte((unsigned char)ch)) { +#else /* _SAFECRT_IMPL */ + if (_isleadbyte_l((unsigned char)ch, _loc_update.GetLocaleT())) { +#endif /* _SAFECRT_IMPL */ + // XPLAT_ODBC_TODO VSTS 718708 Localization + // Deal with more than 2 storage units per character + output->WRITE_CHAR(ch, &charsout); + ch = *format++; + /* don't fall off format string */ + _VALIDATE_RETURN( (ch != '\0'), EINVAL, -1); + } + output->WRITE_CHAR(ch, &charsout); + break; + + case ST_PERCENT: + /* set default value of conversion parameters */ + prefixlen = fldwidth = no_output = capexp = 0; + flags = 0; + precision = -1; + bufferiswide = 0; /* default */ + break; + + case ST_FLAG: + /* set flag based on which flag character */ + switch (ch) { + case ('-'): + flags |= FL_LEFT; /* '-' => left justify */ + break; + case ('+'): + flags |= FL_SIGN; /* '+' => force sign indicator */ + break; + case (' '): + flags |= FL_SIGNSP; /* ' ' => force sign or space */ + break; + case ('#'): + flags |= FL_ALTERNATE; /* '#' => alternate form */ + break; + case ('0'): + flags |= FL_LEADZERO; /* '0' => pad with leading zeros */ + break; + } + break; + + case ST_WIDTH: + /* update width value */ + if (ch == ('*')) { + /* get width from arg list */ + fldwidth = va_arg(argptr, int); + if (fldwidth < 0) { + /* ANSI says neg fld width means '-' flag and pos width */ + flags |= FL_LEFT; + fldwidth = -fldwidth; + } + } + else { + /* add digit to current field width */ + fldwidth = fldwidth * 10 + (ch - ('0')); + } + break; + + case ST_DOT: + /* zero the precision, since dot with no number means 0 + not default, according to ANSI */ + precision = 0; + break; + + case ST_PRECIS: + /* update precison value */ + if (ch == ('*')) { + /* get precision from arg list */ + precision = va_arg(argptr, int); + if (precision < 0) + precision = -1; /* neg precision means default */ + } + else { + /* add digit to current precision */ + precision = precision * 10 + (ch - ('0')); + } + break; + + case ST_SIZE: + /* just read a size specifier, set the flags based on it */ + state = ProcessSize( ch, format, &advance, &flags ); + format += advance; + if ( ST_NORMAL == state ) + { + goto NORMAL_STATE; + } + break; + + case ST_TYPE: + /* we have finally read the actual type character, so we */ + /* now format and "print" the output. We use a big switch */ + /* statement that sets 'text' to point to the text that should */ + /* be printed, and 'textlen' to the length of this text. */ + /* Common code later on takes care of justifying it and */ + /* other miscellaneous chores. Note that cases share code, */ + /* in particular, all integer formatting is done in one place. */ + /* Look at those funky goto statements! */ + + switch (ch) { + + case ('C'): /* ISO wide character */ + if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR))) + flags |= FL_WIDECHAR; /* ISO std. */ + /* fall into 'c' case */ + + case ('c'): { + /* print a single character specified by int argument */ + if (flags & (FL_LONG|FL_WIDECHAR)) { + errno_t e = 0; + wchar = (WCHAR) va_arg(argptr, int); + /* convert to multibyte character */ + e = _WCTOMB_S(&textlen, buffer.sz, _countof(buffer.sz), wchar); + + /* check that conversion was successful */ + if (e != 0) + no_output = 1; + } else { + /* format multibyte character */ + /* this is an extension of ANSI */ + unsigned short temp; + temp = (unsigned short) va_arg(argptr, int); + { + buffer.sz[0] = (char) temp; + textlen = 1; + } + } + text.sz = buffer.sz; + } + break; + + case ('Z'): { + // 'Z' format specifier disabled + _VALIDATE_RETURN(0, EINVAL, -1); + } + break; + + case ('S'): /* ISO wide character string */ + if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR))) + flags |= FL_WIDECHAR; + + case ('s'): { + /* print a string -- */ + /* ANSI rules on how much of string to print: */ + /* all if precision is default, */ + /* min(precision, length) if precision given. */ + /* prints '(null)' if a null string is passed */ + + int i; + char *p; /* temps */ + WCHAR *pwch; + + /* At this point it is tempting to use strlen(), but */ + /* if a precision is specified, we're not allowed to */ + /* scan past there, because there might be no null */ + /* at all. Thus, we must do our own scan. */ + + i = (precision == -1) ? INT_MAX : precision; + text.sz = (char *)va_arg(argptr, void *); + + /* scan for null upto i characters */ + if (flags & (FL_LONG|FL_WIDECHAR)) { + if (text.wz == NULL) /* NULL passed, use special string */ + text.wz = (WCHAR *)__wnullstring; + bufferiswide = 1; + pwch = text.wz; + while ( i-- && *pwch ) + ++pwch; + textlen = (int)(pwch - text.wz); + /* textlen now contains length in wide chars */ + } else { + if (text.sz == NULL) /* NULL passed, use special string */ + text.sz = (char*) __nullstring; + p = text.sz; + while (i-- && *p) + ++p; + textlen = (int)(p - text.sz); /* length of the string */ + } + } + break; + + + case ('n'): { + // We will not support %n + _VALIDATE_RETURN(0, EINVAL, -1); + } + break; + + case ('E'): + case ('G'): + case ('A'): + capexp = 1; /* capitalize exponent */ + ch += ('a') - ('A'); /* convert format char to lower */ + /* DROP THROUGH */ + case ('e'): + case ('f'): + case ('g'): + case ('a'): { + /* floating point conversion -- we call cfltcvt routines */ + /* to do the work for us. */ + flags |= FL_SIGNED; /* floating point is signed conversion */ + text.sz = buffer.sz; /* put result in buffer */ + buffersize = BUFFERSIZE; + + /* compute the precision value */ + if (precision < 0) + precision = 6; /* default precision: 6 */ + else if (precision == 0 && ch == ('g')) + precision = 1; /* ANSI specified */ + else if (precision > MAXPRECISION) + precision = MAXPRECISION; + + if (precision > BUFFERSIZE - _CVTBUFSIZE) { + /* conversion will potentially overflow local buffer */ + /* so we need to use a heap-allocated buffer. */ + heapbuf = (char *)malloc(_CVTBUFSIZE + precision); + if (heapbuf != NULL) + { + text.sz = heapbuf; + buffersize = _CVTBUFSIZE + precision; + } + else + /* malloc failed, cap precision further */ + precision = BUFFERSIZE - _CVTBUFSIZE; + } + +#ifdef _SAFECRT_IMPL + /* for safecrt, we pass along the FL_ALTERNATE flag to _safecrt_cfltcvt */ + if (flags & FL_ALTERNATE) + { + capexp |= FL_ALTERNATE; + } +#endif /* _SAFECRT_IMPL */ + +#if !LONGDOUBLE_IS_DOUBLE + /* do the conversion */ + if (flags & FL_LONGDOUBLE) { + _LONGDOUBLE tmp; + tmp=va_arg(argptr, _LONGDOUBLE); + /* Note: assumes ch is in ASCII range */ + _CLDCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp); + } else +#endif /* !LONGDOUBLE_IS_DOUBLE */ + { + double tmp; + tmp=va_arg(argptr, double); + /* Note: assumes ch is in ASCII range */ + /* In safecrt, we provide a special version of _cfltcvt which internally calls printf (see safecrt_output_s.c) */ +#ifndef _SAFECRT_IMPL + _cfltcvt_l(&tmp, text.sz, buffersize, (char)ch, precision, capexp, _loc_update.GetLocaleT()); +#else /* _SAFECRT_IMPL */ + _CFLTCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp); +#endif /* _SAFECRT_IMPL */ + } + +#ifndef _SAFECRT_IMPL + /* For safecrt, this is done already in _safecrt_cfltcvt */ + + /* '#' and precision == 0 means force a decimal point */ + if ((flags & FL_ALTERNATE) && precision == 0) + { + _forcdecpt_l(text.sz, _loc_update.GetLocaleT()); + } + + /* 'g' format means crop zero unless '#' given */ + if (ch == ('g') && !(flags & FL_ALTERNATE)) + { + _cropzeros_l(text.sz, _loc_update.GetLocaleT()); + } +#endif /* _SAFECRT_IMPL */ + + /* check if result was negative, save '-' for later */ + /* and point to positive part (this is for '0' padding) */ + if (*text.sz == '-') { + flags |= FL_NEGATIVE; + ++text.sz; + } + + textlen = (int)strlen(text.sz); /* compute length of text */ + } + break; + + case ('d'): + case ('i'): + /* signed decimal output */ + flags |= FL_SIGNED; + radix = 10; + goto COMMON_INT; + + case ('u'): + radix = 10; + goto COMMON_INT; + + case ('p'): + /* write a pointer -- this is like an integer or long */ + /* except we force precision to pad with zeros and */ + /* output in big hex. */ + + precision = 2 * sizeof(void *); /* number of hex digits needed */ +#if PTR_IS_INT64 + flags |= FL_I64; /* assume we're converting an int64 */ +#endif /* !PTR_IS_INT */ + /* DROP THROUGH to hex formatting */ + + case ('X'): + /* unsigned upper hex output */ + hexadd = ('A') - ('9') - 1; /* set hexadd for uppercase hex */ + goto COMMON_HEX; + + case ('x'): + /* unsigned lower hex output */ + hexadd = ('a') - ('9') - 1; /* set hexadd for lowercase hex */ + /* DROP THROUGH TO COMMON_HEX */ + + COMMON_HEX: + radix = 16; + if (flags & FL_ALTERNATE) { + /* alternate form means '0x' prefix */ + prefix[0] = ('0'); + prefix[1] = (char)(('x') - ('a') + ('9') + 1 + hexadd); /* 'x' or 'X' */ + prefixlen = 2; + } + goto COMMON_INT; + + case ('o'): + /* unsigned octal output */ + radix = 8; + if (flags & FL_ALTERNATE) { + /* alternate form means force a leading 0 */ + flags |= FL_FORCEOCTAL; + } + /* DROP THROUGH to COMMON_INT */ + + COMMON_INT: { + /* This is the general integer formatting routine. */ + /* Basically, we get an argument, make it positive */ + /* if necessary, and convert it according to the */ + /* correct radix, setting text and textlen */ + /* appropriately. */ + + ULONGLONG number; /* number to convert */ + int digit; /* ascii value of digit */ + LONGLONG l; /* temp long value */ + + /* 1. read argument into l, sign extend as needed */ + if (flags & FL_I64) + l = va_arg(argptr, LONGLONG); + else if (flags & FL_LONGLONG) + l = va_arg(argptr, LONGLONG); + else + + if (flags & FL_SHORT) { + if (flags & FL_SIGNED) + l = (short) va_arg(argptr, int); /* sign extend */ + else + l = (unsigned short) va_arg(argptr, int); /* zero-extend*/ + + } else + { + if (flags & FL_SIGNED) + l = (int)va_arg(argptr, int); /* sign extend */ + else + l = (unsigned int) va_arg(argptr, int); /* zero-extend*/ + } + + /* 2. check for negative; copy into number */ + if ( (flags & FL_SIGNED) && l < 0) { + number = -l; + flags |= FL_NEGATIVE; /* remember negative sign */ + } else { + number = l; + } + + if ( (flags & FL_I64) == 0 && (flags & FL_LONGLONG) == 0 ) { + /* + * Unless printing a full 64-bit value, insure values + * here are not in cananical longword format to prevent + * the sign extended upper 32-bits from being printed. + */ + number &= 0xffffffff; + } + + /* 3. check precision value for default; non-default */ + /* turns off 0 flag, according to ANSI. */ + if (precision < 0) + precision = 1; /* default precision */ + else { + flags &= ~FL_LEADZERO; + if (precision > MAXPRECISION) + precision = MAXPRECISION; + } + + /* 4. Check if data is 0; if so, turn off hex prefix */ + if (number == 0) + prefixlen = 0; + + /* 5. Convert data to ASCII -- note if precision is zero */ + /* and number is zero, we get no digits at all. */ + + text.sz = &buffer.sz[BUFFERSIZE-1]; /* last digit at end of buffer */ + + while (precision-- > 0 || number != 0) { + digit = (int)(number % radix) + '0'; + number /= radix; /* reduce number */ + if (digit > '9') { + /* a hex digit, make it a letter */ + digit += hexadd; + } + *text.sz-- = (char)digit; /* store the digit */ + } + + textlen = (int)((char *)&buffer.sz[BUFFERSIZE-1] - text.sz); /* compute length of number */ + ++text.sz; /* text points to first digit now */ + + + /* 6. Force a leading zero if FORCEOCTAL flag set */ + if ((flags & FL_FORCEOCTAL) && (textlen == 0 || text.sz[0] != '0')) { + *--text.sz = '0'; + ++textlen; /* add a zero */ + } + } + break; + } + + /* At this point, we have done the specific conversion, and */ + /* 'text' points to text to print; 'textlen' is length. Now we */ + /* justify it, put on prefixes, leading zeros, and then */ + /* print it. */ + + if (!no_output) { + int padding; /* amount of padding, negative means zero */ + + if (flags & FL_SIGNED) { + if (flags & FL_NEGATIVE) { + /* prefix is a '-' */ + prefix[0] = ('-'); + prefixlen = 1; + } + else if (flags & FL_SIGN) { + /* prefix is '+' */ + prefix[0] = ('+'); + prefixlen = 1; + } + else if (flags & FL_SIGNSP) { + /* prefix is ' ' */ + prefix[0] = (' '); + prefixlen = 1; + } + } + + /* calculate amount of padding -- might be negative, */ + /* but this will just mean zero */ + padding = fldwidth - textlen - prefixlen; + + /* put out the padding, prefix, and text, in the correct order */ + + if (!(flags & (FL_LEFT | FL_LEADZERO))) { + /* pad on left with blanks */ + output->WRITE_MULTI_CHAR((' '), padding, &charsout); + } + + /* write prefix */ + output->WRITE_STRING(prefix, prefixlen, &charsout); + + if ((flags & FL_LEADZERO) && !(flags & FL_LEFT)) { + /* write leading zeros */ + output->WRITE_MULTI_CHAR(('0'), padding, &charsout); + } + + /* write text */ + if (bufferiswide && (textlen > 0)) { + WCHAR *p; + int retval, count; + errno_t e = 0; + char L_buffer[MB_LEN_MAX+1]; + + p = text.wz; + count = textlen; + while (count--) { + e = _WCTOMB_S(&retval, L_buffer, _countof(L_buffer), *p++); + if (e != 0 || retval == 0) { + charsout = -1; + break; + } + output->WRITE_STRING(L_buffer, retval, &charsout); + } + } else { + output->WRITE_STRING(text.sz, textlen, &charsout); + } + + if (charsout >= 0 && (flags & FL_LEFT)) { + /* pad on right with blanks */ + output->WRITE_MULTI_CHAR((' '), padding, &charsout); + } + + /* we're done! */ + } + if (heapbuf) { + free(heapbuf); + heapbuf = NULL; + } + break; + } + } + + /* The format string shouldn't be incomplete - i.e. when we are finished + with the format string, the last thing we should have encountered + should have been a regular char to be output or a type specifier. Else + the format string was incomplete */ + _VALIDATE_RETURN(((state == ST_NORMAL) || (state == ST_TYPE)), EINVAL, -1); + + return charsout; /* return value = number of characters written */ +} + +// Tools\vc\src\crt\amd64\output.c +int FormattedPrintW( IFormattedPrintOutput * output, const WCHAR *format, va_list argptr ) +{ + int hexadd=0; /* offset to add to number to get 'a'..'f' */ + WCHAR ch; /* character just read */ + int flags=0; /* flag word -- see #defines above for flag values */ + enum STATE state; /* current state */ + enum CHARTYPE chclass; /* class of current character */ + int radix; /* current conversion radix */ + int charsout; /* characters currently written so far, -1 = IO error */ + int fldwidth = 0; /* selected field width -- 0 means default */ + int precision = 0; /* selected precision -- -1 means default */ + WCHAR prefix[2]; /* numeric prefix -- up to two characters */ + int prefixlen=0; /* length of prefix -- 0 means no prefix */ + int capexp=0; /* non-zero = 'E' exponent signifient, zero = 'e' or unused */ + int no_output=0; /* non-zero = prodcue no output for this specifier */ + union { + char *sz; /* pointer text to be printed, not zero terminated */ + WCHAR *wz; + } text; + + int textlen; /* length of the text in bytes/wchars to be printed. + textlen is in multibyte or wide chars if _UNICODE */ + union { + char sz[BUFFERSIZE]; + WCHAR wz[BUFFERSIZE]; + } buffer; + 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 */ + +#ifndef _SAFECRT_IMPL + _LocaleUpdate _loc_update(plocinfo); +#endif /* _SAFECRT_IMPL */ + + char *heapbuf = NULL; /* non-zero = test.sz using heap buffer to be freed */ + + int advance; /* count of how much helper fxns need format ptr incremented */ + + _VALIDATE_RETURN( ((output != NULL) && (format != NULL)), EINVAL, -1); + + charsout = 0; /* no characters written yet */ + textlen = 0; /* no text yet */ + state = ST_NORMAL; /* starting state */ + heapbuf = NULL; /* not using heap-allocated buffer */ + buffersize = 0; + + /* main loop -- loop while format character exist and no I/O errors */ + while ((ch = *format++) != L'\0' && charsout >= 0) { + // Find char class and next state + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + /* execute code for each state */ + switch (state) { + + case ST_INVALID: + // "Incorrect format specifier" + assert( false ); + errno = EINVAL; + return -1; + + case ST_NORMAL: + + NORMAL_STATE: + + /* normal state -- just write character */ + bufferiswide = 1; + if (isleadwchar(ch)) { + // Deal with more than 2 storage units per character + output->WRITE_CHAR(ch, &charsout); + ch = *format++; + /* don't fall off format string */ + _VALIDATE_RETURN( (ch != L'\0'), EINVAL, -1); + } + output->WRITE_CHAR(ch, &charsout); + break; + + case ST_PERCENT: + /* set default value of conversion parameters */ + prefixlen = fldwidth = no_output = capexp = 0; + flags = 0; + precision = -1; + bufferiswide = 0; /* default */ + break; + + case ST_FLAG: + /* set flag based on which flag character */ + switch (ch) { + case L'-': + flags |= FL_LEFT; /* '-' => left justify */ + break; + case L'+': + flags |= FL_SIGN; /* '+' => force sign indicator */ + break; + case L' ': + flags |= FL_SIGNSP; /* ' ' => force sign or space */ + break; + case L'#': + flags |= FL_ALTERNATE; /* '#' => alternate form */ + break; + case L'0': + flags |= FL_LEADZERO; /* '0' => pad with leading zeros */ + break; + } + break; + + case ST_WIDTH: + /* update width value */ + if (ch == L'*') { + /* get width from arg list */ + fldwidth = va_arg(argptr, int); + if (fldwidth < 0) { + /* ANSI says neg fld width means '-' flag and pos width */ + flags |= FL_LEFT; + fldwidth = -fldwidth; + } + } + else { + /* add digit to current field width */ + fldwidth = fldwidth * 10 + (ch - L'0'); + } + break; + + case ST_DOT: + /* zero the precision, since dot with no number means 0 + not default, according to ANSI */ + precision = 0; + break; + + case ST_PRECIS: + /* update precison value */ + if (ch == L'*') { + /* get precision from arg list */ + precision = va_arg(argptr, int); + if (precision < 0) + precision = -1; /* neg precision means default */ + } + else { + /* add digit to current precision */ + precision = precision * 10 + (ch - L'0'); + } + break; + + case ST_SIZE: + /* just read a size specifier, set the flags based on it */ + state = ProcessSize( ch, format, &advance, &flags ); + format += advance; + if ( ST_NORMAL == state ) + { + goto NORMAL_STATE; + } + break; + + case ST_TYPE: + /* we have finally read the actual type character, so we */ + /* now format and "print" the output. We use a big switch */ + /* statement that sets 'text' to point to the text that should */ + /* be printed, and 'textlen' to the length of this text. */ + /* Common code later on takes care of justifying it and */ + /* other miscellaneous chores. Note that cases share code, */ + /* in particular, all integer formatting is done in one place. */ + /* Look at those funky goto statements! */ + + switch (ch) { + + case L'C': /* ISO wide character */ + if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR))) + flags |= FL_SHORT; + /* fall into 'c' case */ + + case L'c': { + /* print a single character specified by int argument */ + bufferiswide = 1; + wchar = (WCHAR) va_arg(argptr, int); + if (flags & FL_SHORT) { + /* format multibyte character */ + /* this is an extension of ANSI */ + char tempchar[2]; + { + tempchar[0] = (char)(wchar & 0x00ff); + tempchar[1] = '\0'; + } + +#ifdef _SAFECRT_IMPL + if (_MBTOWC(buffer.wz,tempchar, MB_CUR_MAX) < 0) +#else /* _SAFECRT_IMPL */ + if (_mbtowc_l(buffer.wz, + tempchar, + _loc_update.GetLocaleT()->locinfo->mb_cur_max, + _loc_update.GetLocaleT()) < 0) +#endif /* _SAFECRT_IMPL */ + { + /* ignore if conversion was unsuccessful */ + no_output = 1; + } + } else { + buffer.wz[0] = wchar; + } + text.wz = buffer.wz; + textlen = 1; /* print just a single character */ + } + break; + + case L'Z': { + // 'Z' format specifier disabled + _VALIDATE_RETURN(0, EINVAL, -1); + } + break; + + case L'S': /* ISO wide character string */ + if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR))) + flags |= FL_SHORT; + + case L's': { + /* print a string -- */ + /* ANSI rules on how much of string to print: */ + /* all if precision is default, */ + /* min(precision, length) if precision given. */ + /* prints '(null)' if a null string is passed */ + + int i; + char *p; /* temps */ + WCHAR *pwch; + + /* At this point it is tempting to use strlen(), but */ + /* if a precision is specified, we're not allowed to */ + /* scan past there, because there might be no null */ + /* at all. Thus, we must do our own scan. */ + + i = (precision == -1) ? INT_MAX : precision; + text.sz = (char *)va_arg(argptr, void *); + + /* scan for null upto i characters */ + if (flags & FL_SHORT) { + if (text.sz == NULL) /* NULL passed, use special string */ + text.sz = (char*) __nullstring; + p = text.sz; + for (textlen=0; textlen MAXPRECISION) + precision = MAXPRECISION; + + if (precision > BUFFERSIZE - _CVTBUFSIZE) { + /* conversion will potentially overflow local buffer */ + /* so we need to use a heap-allocated buffer. */ + heapbuf = (char *)malloc(_CVTBUFSIZE + precision); + if (heapbuf != NULL) + { + text.sz = heapbuf; + buffersize = _CVTBUFSIZE + precision; + } + else + /* malloc failed, cap precision further */ + precision = BUFFERSIZE - _CVTBUFSIZE; + } + +#ifdef _SAFECRT_IMPL + /* for safecrt, we pass along the FL_ALTERNATE flag to _safecrt_cfltcvt */ + if (flags & FL_ALTERNATE) + { + capexp |= FL_ALTERNATE; + } +#endif /* _SAFECRT_IMPL */ + +#if !LONGDOUBLE_IS_DOUBLE + /* do the conversion */ + if (flags & FL_LONGDOUBLE) { + _LONGDOUBLE tmp; + tmp=va_arg(argptr, _LONGDOUBLE); + /* Note: assumes ch is in ASCII range */ + _CLDCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp); + } else +#endif /* !LONGDOUBLE_IS_DOUBLE */ + { + double tmp; + tmp=va_arg(argptr, double); + /* Note: assumes ch is in ASCII range */ + /* In safecrt, we provide a special version of _cfltcvt which internally calls printf (see safecrt_output_s.c) */ +#ifndef _SAFECRT_IMPL + _cfltcvt_l(&tmp, text.sz, buffersize, (char)ch, precision, capexp, _loc_update.GetLocaleT()); +#else /* _SAFECRT_IMPL */ + _CFLTCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp); +#endif /* _SAFECRT_IMPL */ + } + +#ifndef _SAFECRT_IMPL + /* For safecrt, this is done already in _safecrt_cfltcvt */ + + /* '#' and precision == 0 means force a decimal point */ + if ((flags & FL_ALTERNATE) && precision == 0) + { + _forcdecpt_l(text.sz, _loc_update.GetLocaleT()); + } + + /* 'g' format means crop zero unless '#' given */ + if (ch == L'g' && !(flags & FL_ALTERNATE)) + { + _cropzeros_l(text.sz, _loc_update.GetLocaleT()); + } +#endif /* _SAFECRT_IMPL */ + + /* check if result was negative, save '-' for later */ + /* and point to positive part (this is for '0' padding) */ + if (*text.sz == '-') { + flags |= FL_NEGATIVE; + ++text.sz; + } + + textlen = (int)strlen(text.sz); /* compute length of text */ + } + break; + + case L'd': + case L'i': + /* signed decimal output */ + flags |= FL_SIGNED; + radix = 10; + goto COMMON_INT; + + case L'u': + radix = 10; + goto COMMON_INT; + + case L'p': + /* write a pointer -- this is like an integer or long */ + /* except we force precision to pad with zeros and */ + /* output in big hex. */ + + precision = 2 * sizeof(void *); /* number of hex digits needed */ +#if PTR_IS_INT64 + flags |= FL_I64; /* assume we're converting an int64 */ +#endif /* !PTR_IS_INT */ + /* DROP THROUGH to hex formatting */ + + case L'X': + /* unsigned upper hex output */ + hexadd = L'A' - L'9' - 1; /* set hexadd for uppercase hex */ + goto COMMON_HEX; + + case L'x': + /* unsigned lower hex output */ + hexadd = L'a' - L'9' - 1; /* set hexadd for lowercase hex */ + /* DROP THROUGH TO COMMON_HEX */ + + COMMON_HEX: + radix = 16; + if (flags & FL_ALTERNATE) { + /* alternate form means '0x' prefix */ + prefix[0] = L'0'; + prefix[1] = (WCHAR)(L'x' - L'a' + L'9' + 1 + hexadd); /* 'x' or 'X' */ + prefixlen = 2; + } + goto COMMON_INT; + + case L'o': + /* unsigned octal output */ + radix = 8; + if (flags & FL_ALTERNATE) { + /* alternate form means force a leading 0 */ + flags |= FL_FORCEOCTAL; + } + /* DROP THROUGH to COMMON_INT */ + + COMMON_INT: { + /* This is the general integer formatting routine. */ + /* Basically, we get an argument, make it positive */ + /* if necessary, and convert it according to the */ + /* correct radix, setting text and textlen */ + /* appropriately. */ + + ULONGLONG number; /* number to convert */ + int digit; /* ascii value of digit */ + LONGLONG l; /* temp long value */ + + /* 1. read argument into l, sign extend as needed */ + if (flags & FL_I64) + l = va_arg(argptr, LONGLONG); + else if (flags & FL_LONGLONG) + l = va_arg(argptr, LONGLONG); + else + + if (flags & FL_SHORT) { + if (flags & FL_SIGNED) + l = (short) va_arg(argptr, int); /* sign extend */ + else + l = (unsigned short) va_arg(argptr, int); /* zero-extend*/ + + } else + { + if (flags & FL_SIGNED) + l = (int)va_arg(argptr, int); /* sign extend */ + else + l = (unsigned int) va_arg(argptr, int); /* zero-extend*/ + } + + /* 2. check for negative; copy into number */ + if ( (flags & FL_SIGNED) && l < 0) { + number = -l; + flags |= FL_NEGATIVE; /* remember negative sign */ + } else { + number = l; + } + + if ( (flags & FL_I64) == 0 && (flags & FL_LONGLONG) == 0 ) { + /* + * Unless printing a full 64-bit value, insure values + * here are not in cananical longword format to prevent + * the sign extended upper 32-bits from being printed. + */ + number &= 0xffffffff; + } + + /* 3. check precision value for default; non-default */ + /* turns off 0 flag, according to ANSI. */ + if (precision < 0) + precision = 1; /* default precision */ + else { + flags &= ~FL_LEADZERO; + if (precision > MAXPRECISION) + precision = MAXPRECISION; + } + + /* 4. Check if data is 0; if so, turn off hex prefix */ + if (number == 0) + prefixlen = 0; + + /* 5. Convert data to ASCII -- note if precision is zero */ + /* and number is zero, we get no digits at all. */ + + text.sz = &buffer.sz[BUFFERSIZE-1]; /* last digit at end of buffer */ + + while (precision-- > 0 || number != 0) { + digit = (int)(number % radix) + '0'; + number /= radix; /* reduce number */ + if (digit > '9') { + /* a hex digit, make it a letter */ + digit += hexadd; + } + *text.sz-- = (char)digit; /* store the digit */ + } + + textlen = (int)((char *)&buffer.sz[BUFFERSIZE-1] - text.sz); /* compute length of number */ + ++text.sz; /* text points to first digit now */ + + + /* 6. Force a leading zero if FORCEOCTAL flag set */ + if ((flags & FL_FORCEOCTAL) && (textlen == 0 || text.sz[0] != '0')) { + *--text.sz = '0'; + ++textlen; /* add a zero */ + } + } + break; + } + + /* At this point, we have done the specific conversion, and */ + /* 'text' points to text to print; 'textlen' is length. Now we */ + /* justify it, put on prefixes, leading zeros, and then */ + /* print it. */ + + if (!no_output) { + int padding; /* amount of padding, negative means zero */ + + if (flags & FL_SIGNED) { + if (flags & FL_NEGATIVE) { + /* prefix is a '-' */ + prefix[0] = L'-'; + prefixlen = 1; + } + else if (flags & FL_SIGN) { + /* prefix is '+' */ + prefix[0] = L'+'; + prefixlen = 1; + } + else if (flags & FL_SIGNSP) { + /* prefix is ' ' */ + prefix[0] = L' '; + prefixlen = 1; + } + } + + /* calculate amount of padding -- might be negative, */ + /* but this will just mean zero */ + padding = fldwidth - textlen - prefixlen; + + /* put out the padding, prefix, and text, in the correct order */ + + if (!(flags & (FL_LEFT | FL_LEADZERO))) { + /* pad on left with blanks */ + output->WRITE_MULTI_CHAR(L' ', padding, &charsout); + } + + /* write prefix */ + output->WRITE_STRING(prefix, prefixlen, &charsout); + + if ((flags & FL_LEADZERO) && !(flags & FL_LEFT)) { + /* write leading zeros */ + output->WRITE_MULTI_CHAR(L'0', padding, &charsout); + } + + /* write text */ + if (!bufferiswide && textlen > 0) { + char *p; + int retval, count; + + p = text.sz; + count = textlen; + while (count-- > 0) { +#ifdef _SAFECRT_IMPL + retval = _MBTOWC(&wchar, p, MB_CUR_MAX); +#else /* _SAFECRT_IMPL */ + retval = _mbtowc_l(&wchar, + p, + _loc_update.GetLocaleT()->locinfo->mb_cur_max, + _loc_update.GetLocaleT()); +#endif /* _SAFECRT_IMPL */ + if (retval <= 0) { + charsout = -1; + break; + } + output->WRITE_CHAR(wchar, &charsout); + p += retval; + } + } else { + output->WRITE_STRING(text.wz, textlen, &charsout); + } + + if (charsout >= 0 && (flags & FL_LEFT)) { + /* pad on right with blanks */ + output->WRITE_MULTI_CHAR(L' ', padding, &charsout); + } + + /* we're done! */ + } + if (heapbuf) { + free(heapbuf); + heapbuf = NULL; + } + break; + } + } + + /* The format string shouldn't be incomplete - i.e. when we are finished + with the format string, the last thing we should have encountered + should have been a regular char to be output or a type specifier. Else + the format string was incomplete */ + _VALIDATE_RETURN(((state == ST_NORMAL) || (state == ST_TYPE)), EINVAL, -1); + + return charsout; /* return value = number of characters written */ +} + + +// Used for holding the size and value of a variable argument. +// Uses INT and LONGLONG to hold all possible values. Each is just a buffer to hold the right number of bits. +struct vararg_t +{ + enum ArgType_e + { + Unknown, + Int32, + Int64, + ShouldBeInt32, + ShouldBeInt64 + }; + + vararg_t() : int64Val(0), int32Val(0), argType(vararg_t::Unknown) {} + vararg_t( INT val ) : int64Val(0), int32Val(val), argType(vararg_t::Int32) {} + vararg_t( LONGLONG ptr ) : int64Val(ptr), int32Val(0), argType(vararg_t::Int64) {} + + ArgType_e Type() const { return argType; } + INT Int32Value() const { return int32Val; } + LONGLONG Int64Value() const { return int64Val; } + void * PtrValue() const + { +#if PTR_IS_INT64 + return reinterpret_cast(int64Val); +#else + return reinterpret_cast(int32Val); +#endif + } + + void SetForInt32() + { + assert( vararg_t::Unknown == argType ); + argType = vararg_t::ShouldBeInt32; + } + void SetForInt64() + { + assert( vararg_t::Unknown == argType ); + argType = vararg_t::ShouldBeInt64; + } + void SetForPtr() + { +#if PTR_IS_INT64 + SetForInt64(); +#else + SetForInt32(); +#endif + } + + void Int32Value( INT val ) + { + assert( vararg_t::Unknown == argType || vararg_t::ShouldBeInt32 == argType ); + assert( 0 == int64Val ); + argType = vararg_t::Int32; + int32Val = val; + } + void Int64Value( LONGLONG val ) + { + assert( vararg_t::Unknown == argType || vararg_t::ShouldBeInt64 == argType ); + assert( 0 == int32Val ); + argType = vararg_t::Int64; + int64Val = val; + } + +private: + LONGLONG int64Val; + INT int32Val; + ArgType_e argType; +}; + +// Caches the var arg values in the supplied vector. Types are determined by inspecting the format string. +// On error, sets errno and returns false +static bool GetFormatMessageArgsA( const char * format, std::vector< vararg_t > * argcache, va_list * Arguments ) +{ + if ( NULL == format ) + { + errno = EINVAL; + return false; + } + + const char *p = format; + char fmt_ch; + + while( '\0' != (fmt_ch = *p++) ) + { + if ( '%' != fmt_ch ) + { + // continue to next format spec + } + else if ( '0' == *p || '\0' == *p ) + { + // %0 or null term means end formatting + break; + } + else if ( *p < '1' || '9' < *p ) + { + // Escaped char, skip and keep going + ++p; + } + else + { + // Integer must be [1..99] + size_t argPos = *p++ - '0'; + if ( '0' <= *p && *p <= '9' ) + { + argPos *= 10; + argPos += *p++ - '0'; + } + assert( 0 < argPos && argPos < 100 ); + + if ( argcache->size() < argPos ) + { + // Haven't processed this arg, yet + argcache->resize( argPos ); + } + + if ( vararg_t::Unknown == argcache->at(argPos-1).Type() ) + { + if ( '!' != *p ) + { + // Assume %s as per spec + argcache->at(argPos-1).SetForPtr(); + } + else + { + // Step over the initial '!' and process format specification + ++p; + + char ch; + int flags = 0; + int advance = 0; + enum CHARTYPE chclass; + enum STATE state = ST_PERCENT; + bool found_terminator = false; + while ( !found_terminator && ('\0' != (ch = *p++)) ) + { + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + switch ( state ) + { + case ST_DOT: + case ST_FLAG: + break; + + case ST_WIDTH: + case ST_PRECIS: + if ( '*' == ch ) + { + argcache->at(argPos-1).SetForInt32(); + ++argPos; + if ( argcache->size() < argPos ) + { + argcache->resize( argPos ); + } + } + break; + + case ST_SIZE: + state = ProcessSize( ch, p, &advance, &flags ); + p += advance; + if ( ST_SIZE != state ) + { + // Size and type flags were inconsistent + errno = EINVAL; + return false; + } + break; + + case ST_TYPE: + // Group into 32-bit and 64-bit sized args + assert( vararg_t::Unknown == argcache->at(argPos-1).Type() ); + switch ( ch ) + { + case 'C': // chars + case 'c': + argcache->at(argPos-1).SetForInt32(); + break; + + case 'd': // ints + case 'i': + case 'u': + case 'X': + case 'x': + case 'o': + // INT args + if ( (flags & FL_I64) || (flags & FL_LONGLONG) ) + argcache->at(argPos-1).SetForInt64(); + else + argcache->at(argPos-1).SetForInt32(); + break; + + case 'S': // strings + case 's': + case 'p': // pointer + argcache->at(argPos-1).SetForPtr(); + break; + + case 'E': // doubles (not supported as per spec) + case 'e': + case 'G': + case 'g': + case 'A': + case 'a': + case 'f': + default: + errno = EINVAL; + return false; + } + break; + + case ST_NORMAL: + if ( '!' == ch ) + { + found_terminator = true; + break; + } + // Fall thru to error, missing terminating '!' + + default: + errno = EINVAL; + return false; + } + } + + if ( !found_terminator ) + { + // End of string before trailing '!' was found + errno = EINVAL; + return false; + } + } + } + } + } + + if ( 0 < argcache->size() && NULL == Arguments ) + { + errno = EINVAL; + return false; + } + + // Cache var arg values now that we know the number and sizes + for ( std::vector< vararg_t >::iterator arg = argcache->begin(); arg != argcache->end(); ++arg ) + { + if ( vararg_t::Unknown == arg->Type() ) + { + // Arg not referenced in format string so assume ptr sized. + // This is a decent assumption since every arg gets ptr-size bytes to ensure alignment + // of later arg values. Verified this behavior with both Windows and Linux. + arg->SetForPtr(); + } + + vararg_t::ArgType_e argtype = arg->Type(); + assert( vararg_t::ShouldBeInt32 == argtype || vararg_t::ShouldBeInt64 == argtype ); + + if ( vararg_t::ShouldBeInt32 == argtype ) + { + arg->Int32Value( (INT)va_arg(*Arguments, INT) ); + } + else + { + arg->Int64Value( (LONGLONG)va_arg(*Arguments, LONGLONG) ); + } + } + + return true; +} + +// Caches the var arg values in the supplied vector. Types are determined by inspecting the format string. +// On error, sets errno and returns false +static bool GetFormatMessageArgsW( const WCHAR * format, std::vector< vararg_t > * argcache, va_list * Arguments ) +{ + if ( NULL == format ) + { + errno = EINVAL; + return false; + } + + const WCHAR *p = format; + WCHAR fmt_ch; + + while( L'\0' != (fmt_ch = *p++) ) + { + if ( L'%' != fmt_ch ) + { + // continue to next format spec + } + else if ( L'0' == *p || L'\0' == *p ) + { + // %0 or null term means end formatting + break; + } + else if ( *p < L'1' || L'9' < *p ) + { + // Escaped char, skip and keep going + ++p; + } + else + { + // Integer must be [1..99] + size_t argPos = *p++ - L'0'; + if ( L'0' <= *p && *p <= L'9' ) + { + argPos *= 10; + argPos += *p++ - L'0'; + } + assert( 0 < argPos && argPos < 100 ); + + if ( argcache->size() < argPos ) + { + // Haven't processed this arg, yet + argcache->resize( argPos ); + } + + if ( vararg_t::Unknown == argcache->at(argPos-1).Type() ) + { + if ( L'!' != *p ) + { + // Assume %s as per spec + argcache->at(argPos-1).SetForPtr(); + } + else + { + // Step over the initial '!' and process format specification + ++p; + + WCHAR ch; + int flags = 0; + int advance = 0; + enum CHARTYPE chclass; + enum STATE state = ST_PERCENT; + bool found_terminator = false; + while ( !found_terminator && (L'\0' != (ch = *p++)) ) + { + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + switch ( state ) + { + case ST_DOT: + case ST_FLAG: + break; + + case ST_WIDTH: + case ST_PRECIS: + if ( L'*' == ch ) + { + argcache->at(argPos-1).SetForInt32(); + ++argPos; + if ( argcache->size() < argPos ) + { + argcache->resize( argPos ); + } + } + break; + + case ST_SIZE: + state = ProcessSize( ch, p, &advance, &flags ); + p += advance; + if ( ST_SIZE != state ) + { + // Size and type flags were inconsistent + errno = EINVAL; + return false; + } + break; + + case ST_TYPE: + // Group into 32-bit and 64-bit sized args + assert( vararg_t::Unknown == argcache->at(argPos-1).Type() ); + switch ( ch ) + { + case L'C': // chars + case L'c': + argcache->at(argPos-1).SetForInt32(); + break; + + case L'd': // ints + case L'i': + case L'u': + case L'X': + case L'x': + case L'o': + // INT args + if ( (flags & FL_I64) || (flags & FL_LONGLONG) ) + argcache->at(argPos-1).SetForInt64(); + else + argcache->at(argPos-1).SetForInt32(); + break; + + case L'S': // strings + case L's': + case L'p': // pointer + argcache->at(argPos-1).SetForPtr(); + break; + + case L'E': // doubles (not supported as per spec) + case L'e': + case L'G': + case L'g': + case L'A': + case L'a': + case L'f': + default: + errno = EINVAL; + return false; + } + break; + + case ST_NORMAL: + if ( L'!' == ch ) + { + found_terminator = true; + break; + } + // Fall thru to error, missing terminating '!' + + default: + errno = EINVAL; + return false; + } + } + + if ( !found_terminator ) + { + // End of string before trailing '!' was found + errno = EINVAL; + return false; + } + } + } + } + } + + if ( 0 < argcache->size() && NULL == Arguments ) + { + errno = EINVAL; + return false; + } + + // Cache var arg values now that we know the number and sizes + for ( std::vector< vararg_t >::iterator arg = argcache->begin(); arg != argcache->end(); ++arg ) + { + if ( vararg_t::Unknown == arg->Type() ) + { + // Arg not referenced in format string so assume ptr sized. + // This is a decent assumption since every arg gets ptr-size bytes to ensure alignment + // of later arg values. Verified this behavior with both Windows and Linux. + arg->SetForPtr(); + } + + vararg_t::ArgType_e argtype = arg->Type(); + assert( vararg_t::ShouldBeInt32 == argtype || vararg_t::ShouldBeInt64 == argtype ); + + if ( vararg_t::ShouldBeInt32 == argtype ) + { + arg->Int32Value( (INT)va_arg(*Arguments, INT) ); + } + else + { + arg->Int64Value( (LONGLONG)va_arg(*Arguments, LONGLONG) ); + } + } + + return true; +} + +// On success, returns the number of chars written into the buffer excluding null terminator. +// On error, sets errno and returns zero. +static DWORD FormatMessageToBufferA( const char * format, char * buffer, DWORD bufferWCharSize, const std::vector< vararg_t > & args ) +{ + char * msg = buffer; + DWORD bufsize = std::min(bufferWCharSize, (DWORD)64000); + DWORD msg_pos = 0; + const DWORD fmtsize = 32; + char fmt[fmtsize]; + DWORD fmt_pos; + char fmt_ch; + + const char * p = format; + while( msg_pos < bufsize && '\0' != (fmt_ch = *p++) ) + { + if ( '%' != fmt_ch ) + { + msg[msg_pos++] = fmt_ch; + } + else if ( '0' == *p || '\0' == *p ) + { + // %0 or null term means end formatting + break; + } + else if ( *p < '1' || '9' < *p ) + { + // Escaped char, print and keep going + // Eg. "%n" == '\n' + switch ( *p ) + { + case 'a': msg[msg_pos++] = '\a'; break; + case 'b': msg[msg_pos++] = '\b'; break; + case 'f': msg[msg_pos++] = '\f'; break; + case 'n': msg[msg_pos++] = '\n'; break; + case 'r': msg[msg_pos++] = '\r'; break; + case 't': msg[msg_pos++] = '\t'; break; + case 'v': msg[msg_pos++] = '\v'; break; + default: msg[msg_pos++] = *p; break; + } + ++p; + } + else + { + // Integer must be [1..99] + size_t argPos = *p++ - '0'; + if ( '0' <= *p && *p <= '9' ) + { + argPos *= 10; + argPos += *p++ - '0'; + } + assert( 0 < argPos && argPos < 100 ); + + fmt_pos = 0; + fmt[fmt_pos++] = '%'; + + if ( '!' != *p ) + { + // Assume %s as per spec + fmt[fmt_pos++] = 's'; + fmt[fmt_pos] = '\0'; + int chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].PtrValue() ); + if ( chars_printed < 0 ) + { + errno = EINVAL; + return 0; + } + msg_pos += chars_printed; + } + else + { + // Skip over '!' and build format string + ++p; + char ch; + int flags = 0; + int advance = 0; + enum CHARTYPE chclass; + enum STATE state = ST_PERCENT; + bool found_terminator = false; + while ( fmt_pos < fmtsize && !found_terminator && ('\0' != (ch = *p++)) ) + { + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + switch ( state ) + { + case ST_SIZE: + state = ProcessSize( ch, p, &advance, &flags ); + fmt[fmt_pos++] = ch; + while ( fmt_pos < fmtsize && 0 < advance-- ) + { + fmt[fmt_pos++] = *p++; + } + break; + + case ST_NORMAL: + assert( '!' == ch ); + found_terminator = true; + break; + + case ST_INVALID: + case ST_PERCENT: + errno = EINVAL; + return 0; + + default: + fmt[fmt_pos++] = ch; + break; + } + } + + if ( fmtsize <= fmt_pos ) + { + // Should not have a format string longer than 31 chars + // It can happen but shouldn't (eg. a bunch of size mods like %llllllllllllllld) + errno = EINVAL; + return 0; + } + + fmt[fmt_pos] = '\0'; + + // Format string might need up to 3 args (eg. %*.*d ) + // If more than one arg, then the first ones must be 32-bit ints + // Hence, first 64-bit arg tells us the last arg we need to send. + int chars_printed = 0; + if ( vararg_t::Int64 == args[argPos-1].Type() ) + { + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int64Value() ); + } + else if ( args.size() == argPos ) + { + // No more args so send the one Int + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value() ); + } + else if ( vararg_t::Int64 == args[argPos].Type() ) + { + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int64Value() ); + } + else if ( args.size() == (argPos+1) ) + { + // No more args so send the two Ints + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos-1].Int32Value() ); + } + else if ( vararg_t::Int64 == args[argPos+1].Type() ) + { + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int32Value(), args[argPos+1].Int64Value() ); + } + else + { + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int32Value(), args[argPos+1].Int32Value() ); + } + + if ( chars_printed < 0 ) + { + errno = EINVAL; + return 0; + } + msg_pos += chars_printed; + } + } + } + + if ( bufsize <= msg_pos ) + { + errno = ERANGE; + return 0; + } + + msg[msg_pos] = '\0'; + return msg_pos; +} + +// On success, returns the number of chars written into the buffer excluding null terminator. +// On error, sets errno and returns zero. +static DWORD FormatMessageToBufferW( const WCHAR * format, WCHAR * buffer, DWORD bufferWCharSize, const std::vector< vararg_t > & args ) +{ + WCHAR * msg = buffer; + DWORD bufsize = std::min(bufferWCharSize, (DWORD)64000); + DWORD msg_pos = 0; + const DWORD fmtsize = 32; + WCHAR fmt[fmtsize]; + DWORD fmt_pos; + WCHAR fmt_ch; + + const WCHAR * p = format; + while( msg_pos < bufsize && L'\0' != (fmt_ch = *p++) ) + { + if ( L'%' != fmt_ch ) + { + msg[msg_pos++] = fmt_ch; + } + else if ( L'0' == *p || L'\0' == *p ) + { + // %0 or null term means end formatting + break; + } + else if ( *p < L'1' || L'9' < *p ) + { + // Escaped char, print and keep going + // Eg. "%n" == '\n' + switch ( *p ) + { + case L'a': msg[msg_pos++] = L'\a'; break; + case L'b': msg[msg_pos++] = L'\b'; break; + case L'f': msg[msg_pos++] = L'\f'; break; + case L'n': msg[msg_pos++] = L'\n'; break; + case L'r': msg[msg_pos++] = L'\r'; break; + case L't': msg[msg_pos++] = L'\t'; break; + case L'v': msg[msg_pos++] = L'\v'; break; + default: msg[msg_pos++] = *p; break; + } + ++p; + } + else + { + // Integer must be [1..99] + size_t argPos = *p++ - L'0'; + if ( L'0' <= *p && *p <= L'9' ) + { + argPos *= 10; + argPos += *p++ - L'0'; + } + assert( 0 < argPos && argPos < 100 ); + + fmt_pos = 0; + fmt[fmt_pos++] = L'%'; + + if ( L'!' != *p ) + { + // Assume %s as per spec + fmt[fmt_pos++] = L's'; + fmt[fmt_pos] = L'\0'; + int chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].PtrValue() ); + if ( chars_printed < 0 ) + { + errno = EINVAL; + return 0; + } + msg_pos += chars_printed; + } + else + { + // Skip over '!' and build format string + ++p; + WCHAR ch; + int flags = 0; + int advance = 0; + enum CHARTYPE chclass; + enum STATE state = ST_PERCENT; + bool found_terminator = false; + while ( fmt_pos < fmtsize && !found_terminator && (L'\0' != (ch = *p++)) ) + { + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + switch ( state ) + { + case ST_SIZE: + state = ProcessSize( ch, p, &advance, &flags ); + fmt[fmt_pos++] = ch; + while ( fmt_pos < fmtsize && 0 < advance-- ) + { + fmt[fmt_pos++] = *p++; + } + break; + + case ST_NORMAL: + assert( L'!' == ch ); + found_terminator = true; + break; + + case ST_INVALID: + case ST_PERCENT: + errno = EINVAL; + return 0; + + default: + fmt[fmt_pos++] = ch; + break; + } + } + + if ( fmtsize <= fmt_pos ) + { + // Should not have a format string longer than 31 chars + // It can happen but shouldn't (eg. a bunch of size mods like %llllllllllllllld) + errno = EINVAL; + return 0; + } + + fmt[fmt_pos] = L'\0'; + + // Format string might need up to 3 args (eg. %*.*d ) + // If more than one arg, then the first ones must be 32-bit ints + // Hence, first 64-bit arg tells us the last arg we need to send. + int chars_printed = 0; + if ( vararg_t::Int64 == args[argPos-1].Type() ) + { + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int64Value() ); + } + else if ( args.size() == argPos ) + { + // No more args so send the one Int + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value() ); + } + else if ( vararg_t::Int64 == args[argPos].Type() ) + { + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int64Value() ); + } + else if ( args.size() == (argPos+1) ) + { + // No more args so send the two Ints + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos-1].Int32Value() ); + } + else if ( vararg_t::Int64 == args[argPos+1].Type() ) + { + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int32Value(), args[argPos+1].Int64Value() ); + } + else + { + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int32Value(), args[argPos+1].Int32Value() ); + } + + if ( chars_printed < 0 ) + { + errno = EINVAL; + return 0; + } + msg_pos += chars_printed; + } + } + } + + if ( bufsize <= msg_pos ) + { + errno = ERANGE; + return 0; + } + + msg[msg_pos] = L'\0'; + return msg_pos; +} + + +DWORD FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPTSTR lpBuffer, DWORD nSize, va_list *Arguments) +{ + DWORD chars_printed = 0; + + // XPLAT_ODBC_TODO VSTS 718708 Localization by handling FORMAT_MESSAGE_FROM_HMODULE and dwLanguageId param + if ( dwFlags & FORMAT_MESSAGE_FROM_STRING ) + { + // Format specification allows for reordering of insertions relative to var arg position + // This means we need to walk thru the format specification to find the types of the var args in var arg order + // We extract the var args in order based on the identified types + // Finally, we re-walk the format specfication and perform the insertions + + // First pass thru the format string to determine all args and their types + // This first pass also validates the format string and will return an error + // if it is invalid. This allows FormatMessageToBuffer to have less error + // checking. + std::vector< vararg_t > args; + // Based on quick scan of RC files, the largest arg count was 7 so reserve 8 slots to reduce allocations + args.reserve(8); + if ( GetFormatMessageArgsA( reinterpret_cast(lpSource), &args, Arguments ) ) + { + if ( dwFlags == (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING) ) + { + *((char**)lpBuffer) = NULL; + + const DWORD max_size = 64000; + char local_buf[max_size]; + chars_printed = FormatMessageToBufferA( reinterpret_cast(lpSource), local_buf, max_size, args ); + if ( 0 < chars_printed ) + { + size_t buf_size = std::min( max_size, std::max(nSize, (chars_printed+1)) ); + char * return_buf = (char *)LocalAlloc(0, buf_size * sizeof(char)); + if ( NULL == return_buf ) + { + errno = ENOMEM; + } + else + { + mplat_cscpy(return_buf, local_buf); + *((char**)lpBuffer) = return_buf; + } + } + } + else if ( dwFlags == FORMAT_MESSAGE_FROM_STRING ) + { + chars_printed = FormatMessageToBufferA( reinterpret_cast(lpSource), lpBuffer, std::min(nSize, (DWORD)64000), args ); + } + } + } + else if ( dwFlags & FORMAT_MESSAGE_FROM_SYSTEM ) + { + // Since we don't have the Windows system error messages available use a fixed message + // Can not use a message ID for this since this same code is used by driver and tools, + // each having their own RLL file. Don't think we should be reserving an ID across all RLLs. + const char systemMsg[] = "Error code 0x%X"; + if ( dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER ) + { + *((char**)lpBuffer) = NULL; + + // Add 9 for up to 8 hex digits plus null term (ignore removal of format specs) + const size_t msgsize = (9 + sizeof(systemMsg)/sizeof(systemMsg[0])); + char * return_buf = (char *)LocalAlloc(0, msgsize * sizeof(char)); + if ( NULL == return_buf ) + { + errno = ENOMEM; + } + else + { + chars_printed = mplat_snprintf_s( return_buf, msgsize, msgsize, systemMsg, dwMessageId ); + // Assert that we did our buffer size math right + assert( chars_printed < msgsize ); + if ( 0 < chars_printed ) + { + *((char**)lpBuffer) = return_buf; + } + else + { + LocalFree( return_buf ); + errno = EINVAL; + } + } + } + else + { + chars_printed = mplat_snprintf_s( lpBuffer, nSize, nSize, systemMsg, dwMessageId ); + } + } + + return chars_printed; +} + + +// FormatMessage implementation details (see MSDN for more info) +// +// The Windows FormatMessage API is very rich, complex. This is not an exact duplication of that function. +// Instead, the most important aspects of this function have been implemented here along with constraints to +// match how we use it within SNAC, BCP, and SQLCMD. +// +// Only these combinations of dwFlags are supported: +// FORMAT_MESSAGE_FROM_STRING +// Writes formatted message into supplied buffer +// FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING +// Allocates a buffer, writes formatted message into that buffer, returns buffer in lpBufffer +// FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS +// Writes fixed, English message into the supplied buffer (do not have Windows resources to get real message) +// FORMAT_MESSAGE_FROM_HMODULE +// SQLCMD uses this to read strings from the RLL that have not been translated to the current lang +// +// dwLanguageId is ignored for FORMAT_MESSAGE_FROM_STRING as per spec +// For FORMAT_MESSAGE_FROM_SYSTEM, we don't have Windows resources so language is irrelevant +DWORD FormatMessageW(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPWSTR lpBuffer, DWORD nSize, va_list *Arguments) +{ + DWORD chars_printed = 0; + + // XPLAT_ODBC_TODO VSTS 718708 Localization by handling FORMAT_MESSAGE_FROM_HMODULE and dwLanguageId param + if ( dwFlags & FORMAT_MESSAGE_FROM_STRING ) + { + // Format specification allows for reordering of insertions relative to var arg position + // This means we need to walk thru the format specification to find the types of the var args in var arg order + // We extract the var args in order based on the identified types + // Finally, we re-walk the format specfication and perform the insertions + + // First pass thru the format string to determine all args and their types + // This first pass also validates the format string and will return an error + // if it is invalid. This allows FormatMessageToBuffer to have less error + // checking. + std::vector< vararg_t > args; + // Based on quick scan of RC files, the largest arg count was 7 so reserve 8 slots to reduce allocations + args.reserve(8); + if ( GetFormatMessageArgsW( reinterpret_cast(lpSource), &args, Arguments ) ) + { + if ( dwFlags == (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING) ) + { + *((WCHAR**)lpBuffer) = NULL; + + const DWORD max_size = 64000; + WCHAR local_buf[max_size]; + chars_printed = FormatMessageToBufferW( reinterpret_cast(lpSource), local_buf, max_size, args ); + if ( 0 < chars_printed ) + { + size_t buf_size = std::min( max_size, std::max(nSize, (chars_printed+1)) ); + WCHAR * return_buf = (WCHAR *)LocalAlloc(0, buf_size * sizeof(WCHAR)); + if ( NULL == return_buf ) + { + errno = ENOMEM; + } + else + { + mplat_wcscpy(return_buf, local_buf); + *((WCHAR**)lpBuffer) = return_buf; + } + } + } + else if ( dwFlags == FORMAT_MESSAGE_FROM_STRING ) + { + chars_printed = FormatMessageToBufferW( reinterpret_cast(lpSource), lpBuffer, std::min(nSize, (DWORD)64000), args ); + } + } + } + else if ( dwFlags & FORMAT_MESSAGE_FROM_SYSTEM ) + { + // Since we don't have the Windows system error messages available use a fixed message + // Can not use a message ID for this since this same code is used by driver and tools, + // each having their own RLL file. Don't think we should be reserving an ID across all RLLs. + const wchar_t systemMsg[] = L"Error code 0x%X"; + if ( dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER ) + { + *((WCHAR**)lpBuffer) = NULL; + + // Add 9 for up to 8 hex digits plus null term (ignore removal of format specs) + const size_t msgsize = (9 + sizeof(systemMsg)/sizeof(systemMsg[0])); + WCHAR * return_buf = (WCHAR *)LocalAlloc(0, msgsize * sizeof(WCHAR)); + if ( NULL == return_buf ) + { + errno = ENOMEM; + } + else + { + chars_printed = mplat_snwprintf_s( return_buf, msgsize, msgsize, (WCHAR *)systemMsg, dwMessageId ); + // Assert that we did our buffer size math right + assert( chars_printed < msgsize ); + if ( 0 < chars_printed ) + { + *((WCHAR**)lpBuffer) = return_buf; + } + else + { + LocalFree( return_buf ); + errno = EINVAL; + } + } + } + else + { + chars_printed = mplat_snwprintf_s( lpBuffer, nSize, nSize, (WCHAR *)systemMsg, dwMessageId ); + } + } + + return chars_printed; +} + +//--------Other definitions from xplat stub sources-------------- + +BOOL IsDBCSLeadByte(__inn BYTE TestChar) +{ + // XPLAT_ODBC_TODO: This is to allow BatchParser to function + // BatchParser will single step thru utf8 code points + // BatchParser needs to become utf8-aware + // VSTS 718708 Localization + if ( CP_UTF8 == SystemLocale::Singleton().AnsiCP() ) + return FALSE; + // XPLAT_ODBC_TODO + + return IsDBCSLeadByteEx(SystemLocale::Singleton().AnsiCP(), TestChar); +} + +BOOL IsDBCSLeadByteEx( + __inn UINT CodePage, + __inn BYTE TestChar) +{ + if ( 1 == SystemLocale::MaxCharCchSize(CodePage) ) + return FALSE; + + // Lead byte ranges for code pages, inclusive: + // CP932 + // 0x81-0x9f, 0xe0-0xfc + // CP936, CP949, CP950 + // 0x81-0xfe + assert( 932 == CodePage || 936 == CodePage || 949 == CodePage || 950 == CodePage ); + if ( 932 == CodePage ) + { + if ( TestChar < (unsigned char)0x81 + || (unsigned char)0xfc < TestChar + || ((unsigned char)0x9f < TestChar && TestChar < (unsigned char)0xe0) ) + { + return FALSE; + } + } + else if ( TestChar < (unsigned char)0x81 || TestChar == (unsigned char)0xff ) + return FALSE; + + return TRUE; +} + +int mplat_vsnwprintf( WCHAR * buffer, size_t count, const WCHAR * format, va_list args ) +{ + BufferOutput output( buffer, count ); + return FormattedPrintW( &output, format, args ); +} + +int mplat_snwprintf_s( WCHAR *buffer, size_t bufsize, size_t count, const WCHAR *format, ... ) +{ + va_list args; + va_start( args, format ); + int retcode = mplat_vsnwprintf( buffer, std::min(bufsize, count), format, args ); + va_end( args ); + return retcode; +} + +int mplat_vsnprintf( char * buffer, size_t count, const char * format, va_list args ) +{ + BufferOutput output( buffer, count ); + return FormattedPrintA( &output, format, args ); +} + +int mplat_snprintf_s( char *buffer, size_t bufsize, size_t count, const char *format, ... ) +{ + va_list args; + va_start( args, format ); + int retcode = mplat_vsnprintf( buffer, std::min(bufsize, count), format, args ); + va_end( args ); + return retcode; +} + +// Tools\vc\src\crt\amd64\wcscat.c +WCHAR * mplat_wcscpy( WCHAR * dst, const WCHAR * src ) +{ + WCHAR * cp = dst; + + while( (*cp++ = *src++) ) + ; /* Copy src over dst */ + + return( dst ); +} + +char * mplat_cscpy( char * dst, const char * src ) +{ + char * cp = dst; + + while( (*cp++ = *src++) ) + ; /* Copy src over dst */ + + return( dst ); +} + +size_t mplat_wcslen( const WCHAR * str ) +{ + const WCHAR * eos = str; + while( *eos++ ) + { + } + return( (size_t)(eos - str- 1) ); +} + +HLOCAL LocalAlloc(UINT uFlags, SIZE_T uBytes) +{ + assert(uFlags == 0); // For now + return malloc(uBytes); +} + +HLOCAL LocalFree(HLOCAL hMem) +{ + assert(hMem != NULL); + + free(hMem); + return NULL; +} diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h new file mode 100644 index 00000000..f143a2a4 --- /dev/null +++ b/source/shared/FormattedPrint.h @@ -0,0 +1,231 @@ +//----------------------------------------------------------------------------- +// File: FormattedPrint.h +// +// Contents: Contains functions for handling Windows format strings +// and UTF-16 on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef _FORMATTEDPRINT_H_ +#define _FORMATTEDPRINT_H_ + +#include "xplat_winnls.h" +#include "localization.hpp" + +inline bool __ascii_iswalpha(WCHAR c) { return( ('A' <= (c) && (c) <= 'Z') || ( 'a' <= (c) && (c) <= 'z') ); } +inline WCHAR __ascii_towupper(WCHAR c) { return( (((c) >= L'a') && ((c) <= L'z')) ? (WCHAR)((c) - L'a' + L'A') : (c) ); } +inline char __ascii_toupper(char c) { return( (((c) >= 'a') && ((c) <= 'z')) ? ((c) - 'a' + 'A') : (c) ); } + + + + +template< typename T > +struct IFormattedPrintOutput +{ + /* + Method names are all CAPS to match the original code for formatted print from the Windows CRT. + + pCumulativeOutputCount + Used to track running total of output storage units. + Note that the count is the memory size in sizeof(TCHAR) and not the display char count. + For example, a UTF-8 char+diacritical mark is two chars in memory but only one for display + so pCumulativeOutputCount will be incremented by 2 after output. + If an error is encountered, then set this to -1. + If the value is -1 upon entry of any callback, simply return and don't output anything. + */ + + // Writes a single character to the output. + virtual void WRITE_CHAR( T ch , int * pCumulativeOutputCount ) = 0; + + // Repeatedly writes a single character to the output. If there isn't enough room, writes to end of buffer. + // If repeatCount is <=0, then don't output anything and leave pCumulativeOutputCount as is. + virtual void WRITE_MULTI_CHAR( T ch, int repeatCount, int * pCumulativeOutputCount ) = 0; + + // Writes the supplied string to the output. If there isn't enough room, writes to end of buffer. + // If count is <=0, then don't output anything and leave pCumulativeOutputCount as is. + virtual void WRITE_STRING( const T * pch, int count, int * pCumulativeOutputCount ) = 0; + + // Ensure dtors are virtual + virtual ~IFormattedPrintOutput() { } +}; + +template< typename T > +class FormattedOutput : public IFormattedPrintOutput +{ +protected: + bool ShouldOutput( const int * pCumulativeOutputCount, int count ) const + { + assert( NULL != pCumulativeOutputCount ); + return ( (0 <= *pCumulativeOutputCount) && (0 < count) ); + } +}; + +int FormattedPrintA( IFormattedPrintOutput * output, const char *format, va_list argptr ); +int FormattedPrintW( IFormattedPrintOutput * output, const WCHAR *format, va_list argptr ); + +template< typename T > +class BufferOutput : public FormattedOutput +{ + T * m_buffer; + size_t m_countRemainingInBuffer; + + bool CanOutput() const + { + return ( 0 < m_countRemainingInBuffer ); + } + + // Stop these from being available + BufferOutput(); + BufferOutput( const BufferOutput & ); + BufferOutput & operator=( const BufferOutput & ); + +public: + BufferOutput( T * pcb, size_t bufsize ) + : m_buffer( pcb ), + m_countRemainingInBuffer( bufsize ) + { + assert( NULL != m_buffer ); + if ( m_countRemainingInBuffer < INT_MAX ) + { + memset( m_buffer, 0, m_countRemainingInBuffer * sizeof(T) ); + } + } + + virtual void WRITE_CHAR(T ch, int * pCumulativeOutputCount) + { + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, 1 ) ) + { + if ( CanOutput() ) + { + ++(*pCumulativeOutputCount); + --m_countRemainingInBuffer; + *m_buffer++ = ch; + } + else + { + *pCumulativeOutputCount = -1; + } + } + } + virtual void WRITE_MULTI_CHAR(T ch, int repeatCount, int * pCumulativeOutputCount) + { + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, repeatCount ) ) + { + if ( CanOutput() ) + { + while ( 0 != m_countRemainingInBuffer && 0 != repeatCount ) + { + *m_buffer++ = ch; + --m_countRemainingInBuffer; + --repeatCount; + ++(*pCumulativeOutputCount); + } + if ( 0 != repeatCount ) + { + // Not enough room in buffer + *pCumulativeOutputCount = -1; + } + } + else + { + *pCumulativeOutputCount = -1; + } + } + } + virtual void WRITE_STRING(const T * pch, int count, int * pCumulativeOutputCount) + { + assert( NULL != pch ); + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, count ) ) + { + if ( CanOutput() ) + { + while ( 0 != m_countRemainingInBuffer && 0 != count ) + { + *m_buffer++ = *pch++; + --m_countRemainingInBuffer; + --count; + ++(*pCumulativeOutputCount); + } + if ( 0 != count ) + { + // Not enough room in buffer + *pCumulativeOutputCount = -1; + } + } + else + { + *pCumulativeOutputCount = -1; + } + } + } +}; + + +template< typename T > +class FileOutput : public FormattedOutput +{ + FILE * m_file; + + // Stop these from being available + FileOutput(); + FileOutput( const FileOutput & ); + FileOutput & operator=( const FileOutput & ); + +public: + FileOutput( FILE * file ) + : m_file( file ) + { + assert( NULL != m_file ); + } + + virtual void WRITE_CHAR(T ch, int * pCumulativeOutputCount) + { + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, 1 ) ) + { + ++(*pCumulativeOutputCount); + if ( fputc( ch, m_file ) != ch ) + *pCumulativeOutputCount = -1; + } + } + virtual void WRITE_MULTI_CHAR(T ch, int repeatCount, int * pCumulativeOutputCount) + { + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, repeatCount ) ) + { + *pCumulativeOutputCount += repeatCount; + while ( 0 < repeatCount-- ) + { + if ( fputc( ch, m_file ) != ch ) + { + *pCumulativeOutputCount = -1; + return; + } + } + } + } + virtual void WRITE_STRING(const T * pch, int count, int * pCumulativeOutputCount) + { + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, count ) ) + { + assert( NULL != pch ); + *pCumulativeOutputCount += count; + if ( (size_t)count != fwrite( pch, sizeof(T), count, m_file ) ) + *pCumulativeOutputCount = -1; + } + } +}; + + + +#endif // _FORMATTEDPRINT_H_ diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp new file mode 100644 index 00000000..086b9184 --- /dev/null +++ b/source/shared/StringFunctions.cpp @@ -0,0 +1,145 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: StringFunctions.cpp +// +// Contents: Contains functions for handling UTF-16 on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "StringFunctions.h" + +// Tools\vc\src\crt\amd64\memcpy_s.c +int mplat_memcpy_s( void * dest, size_t destSize, const void * src, size_t count ) +{ + if ( 0 == count ) + { + // nothing to do + return 0; + } + + // validation section + if ( NULL == dest ) + { + errno = EINVAL; + return EINVAL; + } + + if ( src == NULL || destSize < count ) + { + // zeroes the destination buffer + memset(dest, 0, destSize*sizeof(char)); + + if ( NULL == src ) + { + errno = EINVAL; + return EINVAL; + } + if ( destSize < count ) + { + errno = ERANGE; + return ERANGE; + } + + return EINVAL; + } + + memcpy(dest, src, count*sizeof(char)); + return 0; +} + +// Tools\vc\src\crt\amd64\strcpy_s.c +int mplat_strcpy_s( char * dest, size_t destSize, const char * src ) +{ + char * p; + size_t available; + + // validation section + if ( NULL == dest || 0 == destSize ) + { + errno = EINVAL; + return EINVAL; + } + if ( NULL == src ) + { + *dest = 0; + errno = EINVAL; + return EINVAL; + } + + p = dest; + available = destSize; + while ( (*p++ = *src++) != 0 && --available > 0 ) + { + } + + if ( 0 == available ) + { + *dest = 0; + errno = ERANGE; + return ERANGE; + } + return 0; +} + +// Tools\vc\src\crt\amd64\strcat_s.c +int mplat_strcat_s( char * dest, size_t destSize, const char * src ) +{ + char *p; + size_t available; + + // validation section + if ( NULL == dest || 0 == destSize ) + { + errno = EINVAL; + return EINVAL; + } + if ( NULL == src ) + { + *dest = 0; + errno = EINVAL; + return EINVAL; + } + + p = dest; + available = destSize; + while (available > 0 && *p != 0) + { + p++; + available--; + } + + if (available == 0) + { + *dest = 0; + errno = EINVAL; + return EINVAL; + } + + while ((*p++ = *src++) != 0 && --available > 0) + { + } + + if (available == 0) + { + *dest = 0; + errno = ERANGE; + return ERANGE; + } + *p = 0; + return 0; +} +// +// End copy functions +//---------------------------------------------------------------------------- + diff --git a/pdo_sqlsrv/version.h b/source/shared/StringFunctions.h similarity index 64% rename from pdo_sqlsrv/version.h rename to source/shared/StringFunctions.h index 93bb6d47..bb9d5bc0 100644 --- a/pdo_sqlsrv/version.h +++ b/source/shared/StringFunctions.h @@ -1,27 +1,39 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: version.h -// Contents: Version number constants -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#define VER_FILEVERSION_STR "4.1.0.0" -#define _FILEVERSION 4,1,0,0 -#define SQLVERSION_MAJOR 4 -#define SQLVERSION_MINOR 1 -#define SQLVERSION_MMDD 0 -#define SQLVERSION_REVISION 0 - - - +//--------------------------------------------------------------------------------------------------------------------------------- +// File: StringFunctions.h +// +// Contents: Contains functions for handling UTF-16 on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#if !defined(_STRINGFUNCTIONS_H_) +#define _STRINGFUNCTIONS_H_ + +#include "xplat_winnls.h" + +// --------------------------------------------------------------------------- +// Declare internal versions of string handling functions +// Only the functions implemented are declared here + +// Copy +int mplat_memcpy_s(void *_S1, size_t _N1, const void *_S2, size_t _N); +int mplat_strcat_s( char *strDestination, size_t numberOfElements, const char *strSource ); +int mplat_strcpy_s(char * _Dst, size_t _SizeInBytes, const char * _Src); + +// Copy +#define memcpy_s mplat_memcpy_s +#define strcat_s mplat_strcat_s +#define strcpy_s mplat_strcpy_s + +#endif // _STRINGFUNCTIONS_H_ diff --git a/pdo_sqlsrv/core_conn.cpp b/source/shared/core_conn.cpp similarity index 77% rename from pdo_sqlsrv/core_conn.cpp rename to source/shared/core_conn.cpp index 97566d3c..60040fd8 100644 --- a/pdo_sqlsrv/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -1,774 +1,833 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_conn.cpp -// -// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include -#include -#include -#include - -#include -#include - -// *** internal variables and constants *** - -namespace { - -// *** internal constants *** -// an arbitrary figure that should be large enough for most connection strings. -const int DEFAULT_CONN_STR_LEN = 2048; - -// length of buffer used to retrieve information for client and server info buffers -const int INFO_BUFFER_LEN = 256; - -// processor architectures -const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" }; - -// ODBC driver name. -const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"}; - -// default options if only the server is specified -const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}"; - -// connection option appended when no user name or password is given -const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};"; - -// connection option appended for MARS when MARS isn't explicitly mentioned -const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};"; - -// *** internal function prototypes *** - -void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, - HashTable* options_ht, const connection_option valid_conn_opts[], - void* driver,_Inout_ std::string& connection_string TSRMLS_DC ); -void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ); -const char* get_processor_arch( void ); -void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ); -connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, SQLULEN key_len TSRMLS_DC ); -void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ); - -} - -// core_sqlsrv_connect -// opens a connection and returns a sqlsrv_conn structure. -// Parameters: -// henv_cp - connection pooled env context -// henv_ncp - non connection pooled env context -// server - name of the server we're connecting to -// uid - username -// pwd - password -// options_ht - zend_hash list of options -// err - error callback to put into the connection's context -// valid_conn_opts[] - array of valid driver supported connection options. -// driver - reference to caller -// Return -// A sqlsrv_conn structure. An exception is thrown if an error occurs - -sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, - const char* server, const char* uid, const char* pwd, - HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[], - void* driver, const char* driver_func TSRMLS_DC ) - -{ - SQLRETURN r; - std::string conn_str; - conn_str.reserve( DEFAULT_CONN_STR_LEN ); - sqlsrv_malloc_auto_ptr conn; - sqlsrv_malloc_auto_ptr wconn_string; - unsigned int wconn_len = 0; - - try { - - sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv - - // check the connection pooling setting to determine which henv to use to allocate the connection handle - // we do this earlier because we have to allocate the connection handle prior to setting attributes on - // it in build_connection_string_and_set_conn_attr. - - if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - - zval* option_z = NULL; - int zr = SUCCESS; - - option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING); - if (option_z) { - - // if the option was found and it's not true, then use the non pooled environment handle - if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) { - - henv = &henv_ncp; - } - } - } - - SQLHANDLE temp_conn_h; - core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC ); - - conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); - conn->set_func( driver_func ); - - for ( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) { - conn_str = CONNECTION_STRING_DRIVER_NAME[i]; - build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, - conn_str TSRMLS_CC ); - - // We only support UTF-8 encoding for connection string. - // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW - wconn_len = static_cast( conn_str.length() + 1 ) * sizeof( wchar_t ); - - wconn_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast(conn_str.length()), &wconn_len); - CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) - { - throw core::CoreException(); - } - - SQLSMALLINT output_conn_size; - r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get()), - static_cast( wconn_len ), NULL, - 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); - // clear the connection string from memory to remove sensitive data (such as a password). - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn_str.clear(); - - if( !SQL_SUCCEEDED( r )) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; - SQLSMALLINT len; - SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); - bool missing_driver_error = ( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && - state[4] == '2' ); - // if it's a IM002, meaning that the correct ODBC driver is not installed - CHECK_CUSTOM_ERROR( missing_driver_error && ( i == DRIVER_VERSION::MAX ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { - throw core::CoreException(); - } - if ( !missing_driver_error ) { - break; - } - } else { - conn->driver_version = static_cast( i ); - break; - } - - } - CHECK_SQL_ERROR( r, conn ) { - throw core::CoreException(); - } - - CHECK_SQL_WARNING_AS_ERROR( r, conn ) { - throw core::CoreException(); - } - - // determine the version of the server we're connected to. The server version is left in the - // connection upon return. - determine_server_version( conn TSRMLS_CC ); - - } - catch( std::bad_alloc& ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn->invalidate(); - DIE( "C++ memory allocation failure building the connection string." ); - } - catch( std::out_of_range const& ex ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); - conn->invalidate(); - throw; - } - catch( std::length_error const& ex ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); - conn->invalidate(); - throw; - } - catch( core::CoreException& ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn->invalidate(); - throw; - } - - sqlsrv_conn* return_conn = conn; - conn.transferred(); - - return return_conn; -} - - - -// core_sqlsrv_begin_transaction -// Begins a transaction on a specified connection. The current transaction -// includes all statements on the specified connection that were executed after -// the call to core_sqlsrv_begin_transaction and before any calls to -// core_sqlsrv_rollback or core_sqlsrv_commit. -// The default transaction mode is auto-commit. This means that all queries -// are automatically committed upon success unless they have been designated -// as part of an explicit transaction by using core_sqlsrv_begin_transaction. -// Parameters: -// sqlsrv_conn*: The connection with which the transaction is associated. - -void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_OFF ), - SQL_IS_UINTEGER TSRMLS_CC ); - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_commit -// Commits the current transaction on the specified connection and returns the -// connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or -// core_sqlsrv_commit. -// Parameters: -// sqlsrv_conn*: The connection on which the transaction is active. - -void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." ); - - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), - SQL_IS_UINTEGER TSRMLS_CC ); - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_rollback -// Rolls back the current transaction on the specified connection and returns -// the connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or -// core_sqlsrv_commit. -// Parameters: -// sqlsrv_conn*: The connection on which the transaction is active. - -void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." ); - - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), - SQL_IS_UINTEGER TSRMLS_CC ); - - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_close -// Called when a connection resource is destroyed by the Zend engine. -// Parameters: -// conn - The current active connection. -void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ) -{ - // if the connection wasn't successful, just return. - if( conn == NULL ) - return; - - try { - - // rollback any transaction in progress (we don't care about the return result) - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); - } - catch( core::CoreException& ) { - LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." ); - } - - // disconnect from the server - SQLRETURN r = SQLDisconnect( conn->handle() ); - if( !SQL_SUCCEEDED( r )) { - LOG( SEV_ERROR, "Disconnect failed when closing the connection." ); - } - - // free the connection handle - conn->invalidate(); - - sqlsrv_free( conn ); -} - -// core_sqlsrv_prepare -// Create a statement object and prepare the SQL query passed in for execution at a later time. -// Parameters: -// stmt - statement to be prepared -// sql - T-SQL command to prepare -// sql_len - length of the T-SQL string - -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ) -{ - try { - - // convert the string from its encoding to UTf-16 - // if the string is empty, we initialize the fields and skip since an empty string is a - // failure case for utf16_string_from_mbcs_string - sqlsrv_malloc_auto_ptr wsql_string; - unsigned int wsql_len = 0; - if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { - wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t ))); - wsql_string[0] = L'\0'; - wsql_len = 0; - } - else { - - if (sql_len > INT_MAX) - { - LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); - throw core::CoreException(); - } - - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : - stmt->encoding() ); - wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - static_cast( sql_len ), &wsql_len ); - CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - get_last_error_message() ) { - throw core::CoreException(); - } - } - - // prepare our wide char query string - core::SQLPrepareW( stmt, reinterpret_cast( wsql_string.get() ), wsql_len TSRMLS_CC ); - } - catch( core::CoreException& ) { - - throw; - } -} - -// core_sqlsrv_get_server_version -// Determines the vesrion of the SQL Server we are connected to. Calls a helper function -// get_server_version to get the version of SQL Server. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// *server_version - zval for returning results. - -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); - if (NULL != buffer) { - sqlsrv_free( buffer ); - } - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_get_server_info -// Returns the Database name, the name of the SQL Server we are connected to -// and the version of the SQL Server. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// *server_info - zval for returning results. - -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval *server_info TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - // initialize the array - core::sqlsrv_array_init( *conn, server_info TSRMLS_CC ); - - // Get the database name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the server version - get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the server name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_get_client_info -// Returns the ODBC driver's dll name, version and the ODBC version. -// Parameters -// conn - The connection resource by which the client and server are connected. -// *client_info - zval for returning the results. - -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - // initialize the array - core::sqlsrv_array_init( *conn, client_info TSRMLS_CC ); - - // Get the ODBC driver's dll name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the ODBC driver's ODBC version - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the OBDC driver's version - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - } - - catch( core::CoreException& ) { - throw; - } -} - - -// core_is_conn_opt_value_escaped -// determine if connection string value is properly escaped. -// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that -// the value will be surrounded by { and } by the caller after it has been validated - -bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ) -{ - // 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 ] == '}' ) { - ++value; - value_len -= 2; - } - // check to make sure that all right braces are escaped - size_t i = 0; - while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) { - // skip both braces - if( value[i] == '}' ) - ++i; - ++i; - } - if( i < value_len && value[i] == '}' ) { - return false; - } - - return true; -} - - -// *** internal connection functions and classes *** - -namespace { - -connection_option const* get_connection_option( sqlsrv_conn* conn, SQLULEN key, - 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 ) { - - if( key == conn_opts[ opt_idx ].conn_option_key ) { - - return &conn_opts[ opt_idx ]; - } - } - - SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." ); - return NULL; // avoid a compiler warning -} - -// says what it does, and does what it says -// rather than have attributes and connection strings as ODBC does, we unify them into a hash table -// passed to the connection, and then break them out ourselves and either set attributes or put the -// option in the connection string. - -void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, - HashTable* options, const connection_option valid_conn_opts[], - void* driver,_Inout_ std::string& connection_string TSRMLS_DC ) -{ - bool credentials_mentioned = false; - bool mars_mentioned = false; - connection_option const* conn_opt; - int zr = SUCCESS; - - try { - - // Add the server name - common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC ); - - // if uid is not present then we use trusted connection. - if(uid == NULL || strlen( uid ) == 0 ) { - - connection_string += "Trusted_Connection={Yes};"; - } - else { - - bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { - throw core::CoreException(); - } - - common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( 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, strlen( pwd )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { - - throw core::CoreException(); - } - - common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC ); - } - } - - // if no options were given, then we set MARS the defaults and return immediately. - if( options == NULL || zend_hash_num_elements( options ) == 0 ) { - connection_string += CONNECTION_STRING_DEFAULT_OPTIONS; - return; - } - - // workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file - // if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn" - // flag is set to false. - if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) { - - zval* trace_value = NULL; - trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); - - if (trace_value == NULL || !zend_is_true(trace_value)) { - - zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE ); - } - } - - zend_string *key = NULL; - zend_ulong index = -1; - zval* data = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); - - conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); - - if( index == SQLSRV_CONN_OPTION_MARS ) { - mars_mentioned = true; - } - - conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - - // MARS on if not explicitly turned off - if( !mars_mentioned ) { - connection_string += CONNECTION_OPTION_MARS_ON; - } - - } - catch( core::CoreException& ) { - throw; - } -} - - -// get_server_version -// Helper function which returns the version of the SQL Server we are connected to. - -void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - *server_version = buffer; - len = buffer_len; - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - - -// get_processor_arch -// Calls GetSystemInfo to verify the what architecture of the processor is supported -// and return the string of the processor name. -const char* get_processor_arch( void ) -{ - SYSTEM_INFO sys_info; - GetSystemInfo( &sys_info); - switch( sys_info.wProcessorArchitecture ) { - - case PROCESSOR_ARCHITECTURE_INTEL: - return PROCESSOR_ARCH[0]; - - case PROCESSOR_ARCHITECTURE_AMD64: - return PROCESSOR_ARCH[1]; - - case PROCESSOR_ARCHITECTURE_IA64: - return PROCESSOR_ARCH[2]; - - default: - DIE( "Unknown Windows processor architecture." ); - return NULL; - } -} - - -// some features require a server of a certain version or later -// this function determines the version of the server we're connected to -// and stores it in the connection. Any errors are logged before return. -// Exception is thrown when the server version is either undetermined -// or is invalid (< 2000). - -void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) -{ - SQLSMALLINT info_len; - char p[ INFO_BUFFER_LEN ]; - core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); - - errno = 0; - char version_major_str[ 3 ]; - SERVER_VERSION version_major; - memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); - 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 ) - { - throw core::CoreException(); - } - - // SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least - // that high - conn->server_version = version_major; -} - -void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) -{ - // wrap a connection option in a quote. It is presumed that any character that need to be escaped will - // be escaped, such as a closing }. - TSRMLS_C; - - if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) { - ++val; - val_len -= 2; - } - conn_str += odbc_name; - conn_str += "={"; - conn_str.append( val, val_len ); - conn_str += "};"; -} - -} // namespace - -// simply add the parsed value to the connection string -void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str - TSRMLS_DC ) -{ - const char* val_str = Z_STRVAL_P( value ); - size_t val_len = Z_STRLEN_P( value ); - common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC ); -} - -// do nothing for connection pooling since we handled it earlier when -// deciding which environment handle to use. -void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ - TSRMLS_DC ) -{ - TSRMLS_C; -} - -// helper function to evaluate whether a string value is true or false. -// Values = ("true" or "1") are treated as true values. Everything else is treated as false. -// Returns 1 for true and 0 for false. - -size_t core_str_zval_is_true( zval* value_z ) -{ - SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); - - char* value_in = Z_STRVAL_P( value_z ); - size_t val_len = Z_STRLEN_P( 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( value_in[ last_char ] )) { - value_in[ last_char ] = '\0'; - val_len = last_char; - --last_char; - } - - // save adjustments to the value made by stripping whitespace at the end - Z_STRLEN_P( value_z ) = val_len; - - const char VALID_TRUE_VALUE_1[] = "true"; - const char VALID_TRUE_VALUE_2[] = "1"; - - if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) || - ( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len )) - ) { - - return 1; // true - } - - return 0; // false -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_conn.cpp +// +// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +#include + +#ifdef _WIN32 +#include +#include +#include +#endif // _WIN32 + +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +// *** internal variables and constants *** + +namespace { + +// *** internal constants *** +// an arbitrary figure that should be large enough for most connection strings. +const int DEFAULT_CONN_STR_LEN = 2048; + +// length of buffer used to retrieve information for client and server info buffers +const int INFO_BUFFER_LEN = 256; + +// processor architectures +const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" }; + +// ODBC driver name. +const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"}; + +// default options if only the server is specified +const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}"; + +// connection option appended when no user name or password is given +const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};"; + +// connection option appended for MARS when MARS isn't explicitly mentioned +const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};"; + +// *** internal function prototypes *** + +void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, + HashTable* options_ht, const connection_option valid_conn_opts[], + void* driver,_Inout_ std::string& connection_string TSRMLS_DC ); +void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ); +const char* get_processor_arch( void ); +void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ); +connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, SQLULEN key_len TSRMLS_DC ); +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ); + +} + +// core_sqlsrv_connect +// opens a connection and returns a sqlsrv_conn structure. +// Parameters: +// henv_cp - connection pooled env context +// henv_ncp - non connection pooled env context +// server - name of the server we're connecting to +// uid - username +// pwd - password +// options_ht - zend_hash list of options +// err - error callback to put into the connection's context +// valid_conn_opts[] - array of valid driver supported connection options. +// driver - reference to caller +// Return +// A sqlsrv_conn structure. An exception is thrown if an error occurs + +sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, + const char* server, const char* uid, const char* pwd, + HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[], + void* driver, const char* driver_func TSRMLS_DC ) + +{ + SQLRETURN r; + std::string conn_str; + conn_str.reserve( DEFAULT_CONN_STR_LEN ); + sqlsrv_malloc_auto_ptr conn; + sqlsrv_malloc_auto_ptr wconn_string; + unsigned int wconn_len = 0; + +#ifdef _WIN32 + sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv +#else + sqlsrv_context* henv = &henv_ncp; // by default do not use the connection pooling henv +#endif // _WIN32 + + try { + // Due to the limitations on connection pooling in unixODBC 2.3.1 driver manager, we do not consider + // the connection string attributes to set (enable/disable) connection pooling. + // Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section. + +#ifndef _WIN32 + 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' )) + { + henv = &henv_cp; + } +#else + // check the connection pooling setting to determine which henv to use to allocate the connection handle + // we do this earlier because we have to allocate the connection handle prior to setting attributes on + // it in build_connection_string_and_set_conn_attr. + + if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { + + zval* option_z = NULL; + option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING); + if ( option_z ) { + // if the option was found and it's not true, then use the non pooled environment handle + if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) { + henv = &henv_ncp; + } + } + } +#endif // !_WIN32 + + SQLHANDLE temp_conn_h; + core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC ); + + conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); + conn->set_func( driver_func ); + + for( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) { + conn_str = CONNECTION_STRING_DRIVER_NAME[i]; + build_connection_string_and_set_conn_attr(conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str TSRMLS_CC); + + // We only support UTF-8 encoding for connection string. + // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW + wconn_len = static_cast( conn_str.length() + 1 ) * sizeof( SQLWCHAR ); + + wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast(conn_str.length()), &wconn_len ); + + CHECK_CUSTOM_ERROR( wconn_string == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) + { + throw core::CoreException(); + } + + SQLSMALLINT output_conn_size; +#ifndef _WIN32 + // unixODBC 2.3.1 requires a non-wide SQLDriverConnect call while pooling enabled. + // connection handle has been allocated using henv_cp, means pooling enabled in a PHP script + if ( henv == &henv_cp ) + { + r = SQLDriverConnect( conn->handle(), NULL, (SQLCHAR*)conn_str.c_str(), SQL_NTS, NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); + } + else + { + r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), static_cast( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); + } +#else + r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), static_cast( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); +#endif // !_WIN32 + + // clear the connection string from memory to remove sensitive data (such as a password). + memset( const_cast(conn_str.c_str()), 0, conn_str.size()); + memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes + conn_str.clear(); + if (!SQL_SUCCEEDED(r)) { + SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; + SQLSMALLINT len; + SQLRETURN r = SQLGetDiagField(SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len); + bool missing_driver_error = (SQL_SUCCEEDED(r) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && state[4] == '2'); + // if it's a IM002, meaning that the correct ODBC driver is not installed + CHECK_CUSTOM_ERROR(missing_driver_error && (i == DRIVER_VERSION::MAX), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { + throw core::CoreException(); + } + if (!missing_driver_error) { + break; + } + } + else { + conn->driver_version = static_cast( i ); + break; + } + } + CHECK_SQL_ERROR( r, conn ) { + throw core::CoreException(); + } + + CHECK_SQL_WARNING_AS_ERROR( r, conn ) { + throw core::CoreException(); + } + + // determine the version of the server we're connected to. The server version is left in the + // connection upon return. + // + // unixODBC 2.3.1: + // SQLGetInfo works when r = SQL_SUCCESS_WITH_INFO (non-pooled connection) + // but fails if the connection is using a pool, i.e. r= SQL_SUCCESS. + // Thus, in Linux, we don't call determine_server_version() for a connection that uses pool. +#ifndef _WIN32 + if ( r == SQL_SUCCESS_WITH_INFO ) { +#endif // !_WIN32 + determine_server_version( conn TSRMLS_CC ); +#ifndef _WIN32 + } +#endif // !_WIN32 + } + catch( std::bad_alloc& ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes + conn->invalidate(); + DIE( "C++ memory allocation failure building the connection string." ); + } + catch( std::out_of_range const& ex ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes + LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); + conn->invalidate(); + throw; + } + catch( std::length_error const& ex ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes + LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); + conn->invalidate(); + throw; + } + catch( core::CoreException& ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes + conn->invalidate(); + throw; + } + + sqlsrv_conn* return_conn = conn; + conn.transferred(); + + return return_conn; +} + + + +// core_sqlsrv_begin_transaction +// Begins a transaction on a specified connection. The current transaction +// includes all statements on the specified connection that were executed after +// the call to core_sqlsrv_begin_transaction and before any calls to +// core_sqlsrv_rollback or core_sqlsrv_commit. +// The default transaction mode is auto-commit. This means that all queries +// are automatically committed upon success unless they have been designated +// as part of an explicit transaction by using core_sqlsrv_begin_transaction. +// Parameters: +// sqlsrv_conn*: The connection with which the transaction is associated. + +void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." ); + + core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_OFF ), + SQL_IS_UINTEGER TSRMLS_CC ); + } + catch ( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_commit +// Commits the current transaction on the specified connection and returns the +// connection to the auto-commit mode. The current transaction includes all +// statements on the specified connection that were executed after the call to +// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or +// core_sqlsrv_commit. +// Parameters: +// sqlsrv_conn*: The connection on which the transaction is active. + +void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." ); + + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC ); + + core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), + SQL_IS_UINTEGER TSRMLS_CC ); + } + catch ( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_rollback +// Rolls back the current transaction on the specified connection and returns +// the connection to the auto-commit mode. The current transaction includes all +// statements on the specified connection that were executed after the call to +// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or +// core_sqlsrv_commit. +// Parameters: +// sqlsrv_conn*: The connection on which the transaction is active. + +void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." ); + + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); + + core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), + SQL_IS_UINTEGER TSRMLS_CC ); + + } + catch ( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_close +// Called when a connection resource is destroyed by the Zend engine. +// Parameters: +// conn - The current active connection. +void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ) +{ + // if the connection wasn't successful, just return. + if( conn == NULL ) + return; + + try { + + // rollback any transaction in progress (we don't care about the return result) + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); + } + catch( core::CoreException& ) { + LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." ); + } + + // disconnect from the server + SQLRETURN r = SQLDisconnect( conn->handle() ); + if( !SQL_SUCCEEDED( r )) { + LOG( SEV_ERROR, "Disconnect failed when closing the connection." ); + } + + // free the connection handle + conn->invalidate(); + + sqlsrv_free( conn ); +} + +// core_sqlsrv_prepare +// Create a statement object and prepare the SQL query passed in for execution at a later time. +// Parameters: +// stmt - statement to be prepared +// sql - T-SQL command to prepare +// sql_len - length of the T-SQL string + +void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ) +{ + try { + + // convert the string from its encoding to UTf-16 + // if the string is empty, we initialize the fields and skip since an empty string is a + // failure case for utf16_string_from_mbcs_string + sqlsrv_malloc_auto_ptr wsql_string; + unsigned int wsql_len = 0; + if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { + wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( SQLWCHAR ))); + wsql_string[0] = L'\0'; + wsql_len = 0; + } + else { + if( sql_len > INT_MAX ) { + LOG( SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } + + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding() ); + wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), static_cast( sql_len ), &wsql_len ); + CHECK_CUSTOM_ERROR( wsql_string == 0, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) { + throw core::CoreException(); + } + } + + // prepare our wide char query string + core::SQLPrepareW( stmt, reinterpret_cast( wsql_string.get() ), wsql_len TSRMLS_CC ); + } + catch( core::CoreException& ) { + + throw; + } +} + +// core_sqlsrv_get_server_version +// Determines the vesrion of the SQL Server we are connected to. Calls a helper function +// get_server_version to get the version of SQL Server. +// Parameters: +// conn - The connection resource by which the client and server are connected. +// *server_version - zval for returning results. + +void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); + core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); + if ( buffer != 0 ) { + sqlsrv_free( buffer ); + } + buffer.transferred(); + } + + catch( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_get_server_info +// Returns the Database name, the name of the SQL Server we are connected to +// and the version of the SQL Server. +// Parameters: +// conn - The connection resource by which the client and server are connected. +// *server_info - zval for returning results. + +void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval *server_info TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + // Get the database name + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + + // initialize the array + core::sqlsrv_array_init( *conn, server_info TSRMLS_CC ); + + core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + // Get the server version + get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + // Get the server name + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + } + + catch( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_get_client_info +// Returns the ODBC driver's dll name, version and the ODBC version. +// Parameters +// conn - The connection resource by which the client and server are connected. +// *client_info - zval for returning the results. + +void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + // Get the ODBC driver's dll name + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + + // initialize the array + core::sqlsrv_array_init( *conn, client_info TSRMLS_CC ); + +#ifndef _WIN32 + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverName", buffer, 0 /*duplicate*/ TSRMLS_CC ); +#else + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC ); +#endif // !_WIN32 + buffer.transferred(); + + // Get the ODBC driver's ODBC version + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + // Get the OBDC driver's version + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + } + + catch( core::CoreException& ) { + throw; + } +} + + +// core_is_conn_opt_value_escaped +// determine if connection string value is properly escaped. +// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that +// the value will be surrounded by { and } by the caller after it has been validated + +bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ) +{ + // 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 ] == '}' ) { + ++value; + value_len -= 2; + } + // check to make sure that all right braces are escaped + size_t i = 0; + while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) { + // skip both braces + if( value[i] == '}' ) + ++i; + ++i; + } + if( i < value_len && value[i] == '}' ) { + return false; + } + + return true; +} + + +// *** internal connection functions and classes *** + +namespace { + +connection_option const* get_connection_option( sqlsrv_conn* conn, SQLULEN key, + 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 ) { + + if( key == conn_opts[ opt_idx ].conn_option_key ) { + + return &conn_opts[ opt_idx ]; + } + } + + SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." ); + return NULL; // avoid a compiler warning +} + +// says what it does, and does what it says +// rather than have attributes and connection strings as ODBC does, we unify them into a hash table +// passed to the connection, and then break them out ourselves and either set attributes or put the +// option in the connection string. + +void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, + HashTable* options, const connection_option valid_conn_opts[], + void* driver,_Inout_ std::string& connection_string TSRMLS_DC ) +{ + bool mars_mentioned = false; + connection_option const* conn_opt; + + try { + + // Add the server name + common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC ); + + // if uid is not present then we use trusted connection. + if(uid == NULL || strlen( uid ) == 0 ) { + + connection_string += "Trusted_Connection={Yes};"; + } + else { + + bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid )); + CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { + throw core::CoreException(); + } + + common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( 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, strlen( pwd )); + CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { + throw core::CoreException(); + } + + common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC ); + } + } + + // if no options were given, then we set MARS the defaults and return immediately. + if( options == NULL || zend_hash_num_elements( options ) == 0 ) { + connection_string += CONNECTION_STRING_DEFAULT_OPTIONS; + return; + } + + // workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file + // if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn" + // flag is set to false. + if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) { + + zval* trace_value = NULL; + trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); + + if (trace_value == NULL || !zend_is_true(trace_value)) { + + zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE ); + } + } + + zend_string *key = NULL; + zend_ulong index = -1; + zval* data = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); + + conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); + + if( index == SQLSRV_CONN_OPTION_MARS ) { + mars_mentioned = true; + } + + conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); + + // MARS on if not explicitly turned off + if( !mars_mentioned ) { + connection_string += CONNECTION_OPTION_MARS_ON; + } + + } + catch( core::CoreException& ) { + throw; + } +} + + +// get_server_version +// Helper function which returns the version of the SQL Server we are connected to. + +void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + *server_version = buffer; + len = buffer_len; + buffer.transferred(); + } + + catch( core::CoreException& ) { + throw; + } +} + + +// get_processor_arch +// Calls GetSystemInfo to verify the what architecture of the processor is supported +// and return the string of the processor name. +const char* get_processor_arch( void ) +{ +#ifndef _WIN32 + struct utsname sys_info; + if ( uname(&sys_info) == -1 ) + { + DIE( "Error retrieving system info" ); + } + if( strcmp(sys_info.machine, "x86") == 0 ) { + return PROCESSOR_ARCH[0]; + } else if ( strcmp(sys_info.machine, "x86_64") == 0) { + return PROCESSOR_ARCH[1]; + } else if ( strcmp(sys_info.machine, "ia64") == 0 ) { + return PROCESSOR_ARCH[2]; + } else { + DIE( "Unknown processor architecture." ); + } + return NULL; +#else + SYSTEM_INFO sys_info; + GetSystemInfo( &sys_info); + switch( sys_info.wProcessorArchitecture ) { + + case PROCESSOR_ARCHITECTURE_INTEL: + return PROCESSOR_ARCH[0]; + + case PROCESSOR_ARCHITECTURE_AMD64: + return PROCESSOR_ARCH[1]; + + case PROCESSOR_ARCHITECTURE_IA64: + return PROCESSOR_ARCH[2]; + + default: + DIE( "Unknown Windows processor architecture." ); + return NULL; + } + return NULL; +#endif // !_WIN32 +} + + +// some features require a server of a certain version or later +// this function determines the version of the server we're connected to +// and stores it in the connection. Any errors are logged before return. +// Exception is thrown when the server version is either undetermined +// or is invalid (< 2000). + +void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) +{ + SQLSMALLINT info_len; + char p[ INFO_BUFFER_LEN ]; + core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); + + errno = 0; + char version_major_str[ 3 ]; + SERVER_VERSION version_major; + memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); + + 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 ) + { + throw core::CoreException(); + } + + // SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least + // that high + conn->server_version = version_major; +} + +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) +{ + // wrap a connection option in a quote. It is presumed that any character that need to be escaped will + // be escaped, such as a closing }. + TSRMLS_C; + + if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) { + ++val; + val_len -= 2; + } + conn_str += odbc_name; + conn_str += "={"; + conn_str.append( val, val_len ); + conn_str += "};"; +} + +} // namespace + +// simply add the parsed value to the connection string +void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str + TSRMLS_DC ) +{ + const char* val_str = Z_STRVAL_P( value ); + size_t val_len = Z_STRLEN_P( value ); + common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC ); +} + +// do nothing for connection pooling since we handled it earlier when +// deciding which environment handle to use. +void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ + TSRMLS_DC ) +{ + TSRMLS_C; +} + +// helper function to evaluate whether a string value is true or false. +// Values = ("true" or "1") are treated as true values. Everything else is treated as false. +// Returns 1 for true and 0 for false. + +size_t core_str_zval_is_true( zval* value_z ) +{ + SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); + + char* value_in = Z_STRVAL_P( value_z ); + size_t val_len = Z_STRLEN_P( 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( value_in[ last_char ] )) { + value_in[ last_char ] = '\0'; + val_len = last_char; + --last_char; + } + + // save adjustments to the value made by stripping whitespace at the end + Z_STRLEN_P( value_z ) = val_len; + + const char VALID_TRUE_VALUE_1[] = "true"; + const char VALID_TRUE_VALUE_2[] = "1"; + + if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) || + ( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len )) + ) { + + return 1; // true + } + + return 0; // false +} diff --git a/pdo_sqlsrv/core_init.cpp b/source/shared/core_init.cpp similarity index 97% rename from pdo_sqlsrv/core_init.cpp rename to source/shared/core_init.cpp index 1b1e8e57..4bf80167 100644 --- a/pdo_sqlsrv/core_init.cpp +++ b/source/shared/core_init.cpp @@ -1,175 +1,178 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_init.cpp -// -// Contents: common initialization routines shared by PDO and sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - - -// module global variables (initialized in minit and freed in mshutdown) -HMODULE g_sqlsrv_hmodule = NULL; -OSVERSIONINFO g_osversion; - - -// core_sqlsrv_minit -// Module initialization -// This function is called once per execution by the driver layer's MINIT function. -// The primary responsibility of this function is to allocate the two environment -// handles used by core_sqlsrv_connect to allocate either a pooled or non pooled ODBC -// connection handle. -// Parameters: -// henv_cp - Environment handle for pooled connection. -// henv_ncp - Environment handle for non-pooled connection. -// err - Driver specific error handler which handles any errors during initialization. -void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ) -{ - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); - - *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL - - try { - - // get the version of the OS we're running on. For now this governs certain flags used by - // WideCharToMultiByte. It might be relevant to other things in the future. - g_osversion.dwOSVersionInfoSize = sizeof( g_osversion ); - BOOL ver_return = GetVersionEx( &g_osversion ); - if( !ver_return ) { - LOG( SEV_ERROR, "Failed to retrieve Windows version information." ); - throw core::CoreException(); - } - - SQLHANDLE henv = SQL_NULL_HANDLE; - SQLRETURN r; - - // allocate the non pooled environment handle - // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so - // we use the direct ODBC function. - r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); - if( !SQL_SUCCEEDED( r )) { - throw core::CoreException(); - } - - *henv_ncp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); - (*henv_ncp)->set_func( driver_func ); - - // set to ODBC 3 - core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER - TSRMLS_CC ); - - // disable connection pooling - core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_OFF ), - SQL_IS_UINTEGER TSRMLS_CC ); - - // allocate the pooled envrionment handle - // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so - // we use the direct ODBC function. - r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); - if( !SQL_SUCCEEDED( r )) { - throw core::CoreException(); - } - - *henv_cp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); - (*henv_cp)->set_func( driver_func ); - - // set to ODBC 3 - core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC); - - // enable connection pooling - core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_ONE_PER_HENV ), - SQL_IS_UINTEGER TSRMLS_CC ); - - } - catch( core::CoreException& e ) { - - LOG( SEV_ERROR, "core_sqlsrv_minit: Failed to allocate environment handles." ); - - if( *henv_ncp != NULL ) { - // free the ODBC env handle allocated just above - SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); - delete *henv_ncp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) - *henv_ncp = NULL; - } - if( *henv_cp != NULL ) { - // free the ODBC env handle allocated just above - SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); - delete *henv_cp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) - *henv_cp = NULL; - } - - throw e; // rethrow for the driver to catch - } - catch( std::bad_alloc& e ) { - - LOG( SEV_ERROR, "core_sqlsrv_minit: Failed memory allocation for environment handles." ); - - if( *henv_ncp != NULL ) { - SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); - delete *henv_ncp; - *henv_ncp = NULL; - } - if( *henv_cp ) { - SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); - delete *henv_cp; - *henv_cp = NULL; - } - - throw e; // rethrow for the driver to catch - } -} - -// core_sqlsrv_mshutdown -// Module shutdown function -// Free the environment handles allocated in MINIT and unregister our stream wrapper. -// Resource types and constants are automatically released since we don't flag them as -// persistent when they are registered. -// Parameters: -// henv_cp - Pooled environment handle. -// henv_ncp - Non-pooled environment handle. -void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ) -{ - if( henv_ncp != SQL_NULL_HANDLE ) { - - henv_ncp.invalidate(); - } - delete &henv_ncp; - - if( henv_cp != SQL_NULL_HANDLE ) { - - henv_cp.invalidate(); - } - delete &henv_cp; - - return; -} - - -// DllMain for the extension. - -BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID ) -{ - switch( fdwReason ) { - case DLL_PROCESS_ATTACH: - // store the module handle for use by client_info and server_info - g_sqlsrv_hmodule = hinstDLL; - break; - default: - break; - } - - return TRUE; -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_init.cpp +// +// Contents: common initialization routines shared by PDO and sqlsrv +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + + +// module global variables (initialized in minit and freed in mshutdown) +HMODULE g_sqlsrv_hmodule = NULL; +OSVERSIONINFO g_osversion; + + +// core_sqlsrv_minit +// Module initialization +// This function is called once per execution by the driver layer's MINIT function. +// The primary responsibility of this function is to allocate the two environment +// handles used by core_sqlsrv_connect to allocate either a pooled or non pooled ODBC +// connection handle. +// Parameters: +// henv_cp - Environment handle for pooled connection. +// henv_ncp - Environment handle for non-pooled connection. +// err - Driver specific error handler which handles any errors during initialization. +void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ) +{ + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); + + *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL + + try { + +#ifdef _WIN32 + // get the version of the OS we're running on. For now this governs certain flags used by + // WideCharToMultiByte. It might be relevant to other things in the future. + g_osversion.dwOSVersionInfoSize = sizeof( g_osversion ); + BOOL ver_return = GetVersionEx( &g_osversion ); + if( !ver_return ) { + LOG( SEV_ERROR, "Failed to retrieve Windows version information." ); + throw core::CoreException(); + } +#endif //_WIN32 + + SQLHANDLE henv = SQL_NULL_HANDLE; + SQLRETURN r; + + // allocate the non pooled environment handle + // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so + // we use the direct ODBC function. + r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); + if( !SQL_SUCCEEDED( r )) { + throw core::CoreException(); + } + + *henv_ncp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); + (*henv_ncp)->set_func( driver_func ); + + // set to ODBC 3 + core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER + TSRMLS_CC ); + + // disable connection pooling + core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_OFF ), + SQL_IS_UINTEGER TSRMLS_CC ); + + // allocate the pooled envrionment handle + // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so + // we use the direct ODBC function. + r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); + if( !SQL_SUCCEEDED( r )) { + throw core::CoreException(); + } + + *henv_cp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); + (*henv_cp)->set_func( driver_func ); + + // set to ODBC 3 + core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC); + + // enable connection pooling + core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_ONE_PER_HENV ), + SQL_IS_UINTEGER TSRMLS_CC ); + + } + catch( core::CoreException& e ) { + + LOG( SEV_ERROR, "core_sqlsrv_minit: Failed to allocate environment handles." ); + + if( *henv_ncp != NULL ) { + // free the ODBC env handle allocated just above + SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); + delete *henv_ncp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) + *henv_ncp = NULL; + } + if( *henv_cp != NULL ) { + // free the ODBC env handle allocated just above + SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); + delete *henv_cp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) + *henv_cp = NULL; + } + + throw e; // rethrow for the driver to catch + } + catch( std::bad_alloc& e ) { + + LOG( SEV_ERROR, "core_sqlsrv_minit: Failed memory allocation for environment handles." ); + + if( *henv_ncp != NULL ) { + SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); + delete *henv_ncp; + *henv_ncp = NULL; + } + if( *henv_cp ) { + SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); + delete *henv_cp; + *henv_cp = NULL; + } + + throw e; // rethrow for the driver to catch + } +} + +// core_sqlsrv_mshutdown +// Module shutdown function +// Free the environment handles allocated in MINIT and unregister our stream wrapper. +// Resource types and constants are automatically released since we don't flag them as +// persistent when they are registered. +// Parameters: +// henv_cp - Pooled environment handle. +// henv_ncp - Non-pooled environment handle. +void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ) +{ + if( henv_ncp != SQL_NULL_HANDLE ) { + + henv_ncp.invalidate(); + } + delete &henv_ncp; + + if( henv_cp != SQL_NULL_HANDLE ) { + + henv_cp.invalidate(); + } + delete &henv_cp; + + return; +} + + +// DllMain for the extension. +#ifdef _WIN32 +BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID ) +{ + switch( fdwReason ) { + case DLL_PROCESS_ATTACH: + // store the module handle for use by client_info and server_info + g_sqlsrv_hmodule = hinstDLL; + break; + default: + break; + } + + return TRUE; +} +#endif diff --git a/pdo_sqlsrv/core_results.cpp b/source/shared/core_results.cpp similarity index 81% rename from pdo_sqlsrv/core_results.cpp rename to source/shared/core_results.cpp index 8b2657ed..1f1593a2 100644 --- a/pdo_sqlsrv/core_results.cpp +++ b/source/shared/core_results.cpp @@ -1,1361 +1,1581 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_results.cpp -// -// Contents: Result sets -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include -#include -#include - -using namespace core; - -// conversion matrix -// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported -// this is initialized the first time the buffered result set is created. -sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix; - -namespace { - -// *** internal types *** - -#pragma warning(disable:4200) - -// *** internal constants *** - -const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field - -// *** internal functions *** - -// return an integral type rounded up to a certain number -template -T align_to( T number ) -{ - DEBUG_SQLSRV_ASSERT( (number + align) > number, "Number to align overflowed" ); - return ((number % align) == 0) ? number : (number + align - (number % align)); -} - -// return a pointer address aligned to a certain address boundary -template -T* align_to( T* ptr ) -{ - size_t p_value = (size_t) ptr; - return align_to( p_value ); -} - -// set the nth bit of the bitstream starting at ptr -void set_bit( void* ptr, unsigned int bit ) -{ - unsigned char* null_bits = reinterpret_cast( ptr ); - null_bits += bit >> 3; - *null_bits |= 1 << ( 7 - ( bit & 0x7 )); -} - -// retrieve the nth bit from the bitstream starting at ptr -bool get_bit( void* ptr, unsigned int bit ) -{ - unsigned char* null_bits = reinterpret_cast( ptr ); - null_bits += bit >> 3; - return ((*null_bits & (1 << ( 7 - ( bit & 0x07 )))) != 0); -} - -// read in LOB field during buffered result creation -SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - zend_long mem_used TSRMLS_DC ); - -// dtor for each row in the cache -void cache_row_dtor(zval* data); - -// convert a number to a string using locales -// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of -// subclassing a new streambuf just to avoid the copy, it's easier to do the copy -template -SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - sqlsrv_error_auto_ptr& last_error ) -{ - // get to display size by removing the null terminator from buffer length - size_t display_size = ( buffer_length - sizeof( Char )) / sizeof( Char ); - - std::basic_ostringstream os; - // use the display size to determine the sql type. And if it is a double, set the precision accordingly - // the display sizes are set by the ODBC driver based on the precision of the sql type - // otherwise we can just use the default precision as long will not be truncated - size_t real_display_size = 14; - size_t float_display_size = 24; - size_t real_precision = 7; - size_t float_precision = 15; - // this is the case of sql type float(24) or real - if ( display_size == real_display_size ) { - os.precision( real_precision ); - } - // this is the case of sql type float(53) - else if ( display_size == float_display_size ) { - os.precision( float_precision ); - } - std::locale loc; - os.imbue( loc ); - std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf() ), os, ' ', *number_data ); - std::basic_string& str_num = os.str(); - - if( os.fail() ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "IMSSP", (SQLCHAR*) "Failed to convert number to string", -1 ); - return SQL_ERROR; - } - - if( str_num.size() * sizeof(Char) > (size_t) buffer_length ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); - return SQL_ERROR; - } - - *out_buffer_length = str_num.size() * sizeof( Char ); // str_num.size() already include the NULL terminator - memcpy_s( buffer, buffer_length, str_num.c_str(), *out_buffer_length ); - - return SQL_SUCCESS; -} - -template -SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) -{ - Number* number_data = reinterpret_cast( buffer ); - std::locale loc; // default locale should match system - std::basic_istringstream is; - is.str( string_data ); - is.imbue( loc ); - std::ios_base::iostate st = 0; - - std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream::_Iter( is.rdbuf( ) ), - std::basic_istream::_Iter(0), is, st, *number_data ); - - if( st & std::ios_base::failbit ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103 ); - return SQL_ERROR; - } - - *out_buffer_length = sizeof( Number ); - - return SQL_SUCCESS; -} - -// "closure" for the hash table destructor -struct row_dtor_closure { - - sqlsrv_buffered_result_set* results; - BYTE* row_data; - - row_dtor_closure( sqlsrv_buffered_result_set* st, BYTE* row ) : - results( st ), row_data( row ) - { - } -}; - -sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number ) -{ - SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ]; - SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLINTEGER native_code; - SQLSMALLINT wnative_message_len = 0; - - SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message, - SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len ); - if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - return NULL; - } - - // convert the error into the encoding of the context - SQLSRV_ENCODING enc = odbc->encoding(); - if( enc == SQLSRV_ENCODING_DEFAULT ) { - enc = odbc->conn->encoding(); - } - - // convert the error into the encoding of the context - sqlsrv_malloc_auto_ptr sql_state; - SQLLEN sql_state_len = 0; - if (!convert_string_from_utf16( enc, wsql_state, sizeof(wsql_state), (char**)&sql_state, sql_state_len )) { - return NULL; - } - - sqlsrv_malloc_auto_ptr native_message; - SQLLEN native_message_len = 0; - if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) { - return NULL; - } - - return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) sql_state, (SQLCHAR*) native_message, - native_code ); -} - -} // namespace - -// base class result set - -sqlsrv_result_set::sqlsrv_result_set( sqlsrv_stmt* stmt ) : - odbc( stmt ) -{ -} - - -// ODBC result set -// This object simply wraps ODBC function calls - -sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( sqlsrv_stmt* stmt ) : - sqlsrv_result_set( stmt ) -{ -} - -sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void ) -{ -} - -SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC ); -} - -SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC ); -} - -SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length, - out_buffer_length TSRMLS_CC ); -} - -sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( SQLSMALLINT record_number ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return odbc_get_diag_rec( odbc, record_number ); -} - -SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLRowCount( odbc TSRMLS_CC ); -} - - -// Buffered result set -// This class holds a result set in memory - -sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS_DC ) : - sqlsrv_result_set( stmt ), - cache(NULL), - col_count(0), - meta(NULL), - current(0), - last_field_index(-1), - read_so_far(0) -{ - // 10 is an arbitrary number for now for the initial size of the cache - ALLOC_HASHTABLE( cache ); - core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); - col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); - // there is no result set to buffer - if( col_count == 0 ) { - return; - } - - SQLULEN null_bytes = ( col_count / 8 ) + 1; // number of bits to reserve at the beginning of each row for NULL flags - meta = static_cast( sqlsrv_malloc( col_count * - sizeof( sqlsrv_buffered_result_set::meta_data ))); - - // 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; - } - - // get the meta data and calculate the size of a row buffer - SQLULEN offset = null_bytes; - for( SQLSMALLINT i = 0; i < col_count; ++i ) { - - core::SQLDescribeCol( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC ); - - offset = align_to<4>( offset ); - meta[i].offset = offset; - - switch( meta[i].type ) { - - // these types are the display size - case SQL_BIGINT: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, - reinterpret_cast( &meta[i].length ) TSRMLS_CC ); - meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space - offset += meta[i].length; - break; - - // these types are the column size - case SQL_BINARY: - case SQL_CHAR: - case SQL_SS_UDT: - case SQL_VARBINARY: - case SQL_VARCHAR: - // var* field types are length prefixed - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - offset += sizeof( void* ); - } - else { - meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space - offset += meta[i].length; - } - break; - - case SQL_WCHAR: - case SQL_WVARCHAR: - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - offset += sizeof( void* ); - } - else { - meta[i].length *= sizeof( WCHAR ); - meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space - offset += meta[i].length; - } - break; - - // these types are LOBs - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - meta[i].length = sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN; - offset += sizeof( void* ); - break; - - // these types are the ISO date size - case SQL_DATETIME: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_TYPE_TIMESTAMP: - core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, - reinterpret_cast( &meta[i].length ) TSRMLS_CC ); - meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space - offset += meta[i].length; - break; - - // these types are the native size - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - meta[i].length = sizeof( long ); - offset += meta[i].length; - break; - - case SQL_REAL: - case SQL_FLOAT: - meta[i].length = sizeof( double ); - offset += meta[i].length; - break; - - default: - SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); - break; - } - - switch( meta[i].type ) { - - case SQL_BIGINT: - case SQL_CHAR: - case SQL_DATETIME: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - case SQL_LONGVARCHAR: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_XML: - case SQL_TYPE_TIMESTAMP: - case SQL_VARCHAR: - meta[i].c_type = SQL_C_CHAR; - break; - - case SQL_SS_UDT: - case SQL_LONGVARBINARY: - case SQL_BINARY: - case SQL_VARBINARY: - meta[i].c_type = SQL_C_BINARY; - break; - - case SQL_WLONGVARCHAR: - case SQL_WCHAR: - case SQL_WVARCHAR: - meta[i].c_type = SQL_C_WCHAR; - break; - - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - meta[i].c_type = SQL_C_LONG; - break; - - case SQL_REAL: - case SQL_FLOAT: - meta[i].c_type = SQL_C_DOUBLE; - break; - - default: - SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); - break; - } - - } - - // read the data into the cache - // (offset from the above loop has the size of the row buffer necessary) - zend_long mem_used = 0; - unsigned long row_count = 0; - - while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { - - // allocate the row buffer - unsigned char* row = static_cast( sqlsrv_malloc( offset )); - memset( row, 0, offset ); - - // read the fields into the row buffer - for( SQLSMALLINT i = 0; i < col_count; ++i ) { - - SQLLEN out_buffer_temp = SQL_NULL_DATA; - SQLPOINTER buffer; - SQLLEN* out_buffer_length = &out_buffer_temp; - - switch( meta[i].c_type ) { - - case SQL_C_CHAR: - case SQL_C_WCHAR: - case SQL_C_BINARY: - 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 ] ); - *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); - // a NULL pointer means NULL field - if( *lob_addr == NULL ) { - *out_buffer_length = SQL_NULL_DATA; - } - else { - *out_buffer_length = **reinterpret_cast( lob_addr ); - mem_used += *out_buffer_length; - } - } - else { - - mem_used += meta[i].length; - CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - - buffer = row + meta[i].offset + sizeof( SQLULEN ); - out_buffer_length = reinterpret_cast( row + meta[i].offset ); - core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); - } - break; - - case SQL_C_LONG: - case SQL_C_DOUBLE: - { - mem_used += meta[i].length; - CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - buffer = row + meta[i].offset; - out_buffer_length = &out_buffer_temp; - core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); - } - break; - - default: - SQLSRV_ASSERT( false, "Unknown C type" ); - break; - } - - if( *out_buffer_length == SQL_NULL_DATA ) { - unsigned char* null_bits = reinterpret_cast( row ); - set_bit( row, i ); - } - } - - SQLSRV_ASSERT( row_count < LONG_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); - - // add it to the cache - row_dtor_closure cl( this, row ); - sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); - } - -} - -sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void ) -{ - // free the rows - if( cache ) { - zend_hash_destroy( cache ); - FREE_HASHTABLE( cache ); - cache = NULL; - } - - // free the meta data - if( meta ) { - efree( meta ); - meta = NULL; - } -} - -SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) -{ - last_error = NULL; - last_field_index = -1; - read_so_far = 0; - - switch( orientation ) { - - case SQL_FETCH_NEXT: - offset = 1; - orientation = SQL_FETCH_RELATIVE; - break; - case SQL_FETCH_PRIOR: - offset = -1; - orientation = SQL_FETCH_RELATIVE; - break; - } - - switch( orientation ) { - - case SQL_FETCH_FIRST: - current = 1; - break; - case SQL_FETCH_LAST: - current = row_count( TSRMLS_C ); - break; - case SQL_FETCH_ABSOLUTE: - current = offset; - break; - case SQL_FETCH_RELATIVE: - current += offset; - break; - default: - SQLSRV_ASSERT( false, "Invalid fetch orientation. Should have been caught before here." ); - break; - } - - // check validity of current row - // the cursor can never get further away than just before the first row - if( current <= 0 && ( offset < 0 || orientation != SQL_FETCH_RELATIVE )) { - current = 0; - return SQL_NO_DATA; - } - - // the cursor can never get further away than just after the last row - if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) { - current = row_count( TSRMLS_C ) + 1; - return SQL_NO_DATA; - } - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) -{ - last_error = NULL; - field_index--; // convert from 1 based to 0 based - SQLSRV_ASSERT( field_index < column_count(), "Invalid field index requested" ); - - if( field_index != last_field_index ) { - last_field_index = field_index; - read_so_far = 0; - } - - unsigned char* row = get_row(); - - // if the field is null, then return SQL_NULL_DATA - if( get_bit( row, field_index )) { - *out_buffer_length = SQL_NULL_DATA; - return SQL_SUCCESS; - } - - // check to make sure the conversion type is valid - if( conv_matrix.find( meta[ field_index ].c_type ) == conv_matrix.end() || - conv_matrix.find( meta[ field_index ].c_type )->second.find( target_type ) == - conv_matrix.find( meta[ field_index ].c_type )->second.end() ) { - - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "07006", - (SQLCHAR*) "Restricted data type attribute violation", 0 ); - return SQL_ERROR; - } - - return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length, - out_buffer_length ); -} - -SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) -{ - SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); - SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE, - "Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); - SQLSRV_ASSERT( buffer_length >= SQL_SQLSTATE_BUFSIZE, - "Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field" ); - - if( last_error == NULL ) { - return SQL_NO_DATA; - } - - SQLSRV_ASSERT( last_error->sqlstate != NULL, - "Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" ); - - memcpy_s( diag_info_buffer, buffer_length, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE )); - - return SQL_SUCCESS; -} - -unsigned char* sqlsrv_buffered_result_set::get_row( void ) -{ - row_dtor_closure* cl_ptr; - cl_ptr = reinterpret_cast(zend_hash_index_find_ptr(cache, static_cast(current - 1))); - SQLSRV_ASSERT(cl_ptr != NULL, "Failed to find row %1!d! in the cache", current); - return cl_ptr->row_data; -} - -sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( SQLSMALLINT record_number ) -{ - // we only hold a single error if there is one, otherwise return the ODBC error(s) - if( last_error == NULL ) { - return odbc_get_diag_rec( odbc, record_number ); - } - if( record_number > 1 ) { - return NULL; - } - - return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code ); -} - -SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D ) -{ - last_error = NULL; - - return zend_hash_num_elements( cache ); -} - -// private functions -template -SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, _Out_ void* buffer, - SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - sqlsrv_error_auto_ptr& out_error ) -{ - // hex characters for the conversion loop below - static char hex_chars[] = "0123456789ABCDEF"; - - SQLSRV_ASSERT( out_error == NULL, "Pending error for sqlsrv_buffered_results_set::binary_to_string" ); - - SQLRETURN r = SQL_ERROR; - - // Set the amount of space necessary for null characters at the end of the data. - SQLSMALLINT extra = sizeof(Char); - - SQLSRV_ASSERT( ((buffer_length - extra) % (extra * 2)) == 0, "Must be multiple of 2 for binary to system string or " - "multiple of 4 for binary to wide string" ); - - // all fields will be treated as ODBC returns varchar(max) fields: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - // *2 is for each byte to hex conversion and * extra is for either system or wide string allocation - *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra; - - // copy as much as we can into the buffer - SQLLEN to_copy; - if( buffer_length < *out_buffer_length + extra ) { - to_copy = (buffer_length - extra); - out_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - r = SQL_SUCCESS; - to_copy = *out_buffer_length; - } - - // if there are bytes to copy as hex - if( to_copy > 0 ) { - // quick hex conversion routine - Char* h = reinterpret_cast( buffer ); - BYTE* b = reinterpret_cast( field_data ); - // to_copy contains the number of bytes to copy, so we divide the number in half (or quarter) - // to get the number of hex digits we can copy - SQLLEN to_copy_hex = to_copy / (2 * extra); - for( int i = 0; i < to_copy_hex; ++i ) { - *h = hex_chars[ (*b & 0xf0) >> 4 ]; - h++; - *h = hex_chars[ (*b++ & 0x0f) ]; - h++; - } - read_so_far += to_copy_hex; - *h = static_cast( 0 ); - } - else { - reinterpret_cast( buffer )[0] = '\0'; - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLCHAR* row = get_row(); - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - 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 ); -} - -SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLCHAR* row = get_row(); - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - 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 ); -} - - -SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - LONG* long_data = reinterpret_cast( buffer ); - - if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) { - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "22003", - (SQLCHAR*) "Numeric value out of range", 0 ); - return SQL_ERROR; - } - - if( *double_data != floor( *double_data )) { - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "01S07", - (SQLCHAR*) "Fractional truncation", 0 ); - return SQL_SUCCESS_WITH_INFO; - } - - *long_data = static_cast( *double_data ); - *out_buffer_length = sizeof( LONG ); - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - - return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - - return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - - *double_data = static_cast( *long_data ); - *out_buffer_length = sizeof( double ); - - return SQL_SUCCESS; -} -SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - - return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - - return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ); - - 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ); - - 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ); - - 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ); - - 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" ); - SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - SQLCHAR* field_data = NULL; - SQLULEN field_len = NULL; - - 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; - } - else { - - 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: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR); - - // to_copy is the number of characters to copy, not including the null terminator - // supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair. - SQLLEN to_copy; - - if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) { - - to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR); // to_copy is the number of characters - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - - r = SQL_SUCCESS; - to_copy = field_len - read_so_far; - } - - if( to_copy > 0 ) { - - bool tried_again = false; - do { - if (to_copy > INT_MAX ) { - LOG(SEV_ERROR, "MultiByteToWideChar: Buffer length exceeded."); - throw core::CoreException(); - } - - int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast(to_copy), - static_cast(buffer), static_cast(to_copy)); - if( ch_space == 0 ) { - - switch( GetLastError() ) { - - case ERROR_NO_UNICODE_TRANSLATION: - // the theory here is the conversion failed because the end of the buffer we provided contained only - // half a character at the end - if( !tried_again ) { - to_copy--; - tried_again = true; - continue; - } - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); - break; - default: - SQLSRV_ASSERT( false, "Severe error translating Unicode" ); - break; - } - - return SQL_ERROR; - } - - ((WCHAR*)buffer)[ to_copy ] = L'\0'; - read_so_far += to_copy; - break; - - } while( true ); - } - else { - reinterpret_cast( buffer )[0] = L'\0'; - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::to_same_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - // Set the amount of space necessary for null characters at the end of the data. - SQLSMALLINT extra = 0; - - switch( meta[ field_index ].c_type ) { - case SQL_C_WCHAR: - extra = sizeof( SQLWCHAR ); - break; - case SQL_C_BINARY: - extra = 0; - break; - case SQL_C_CHAR: - extra = sizeof( SQLCHAR ); - break; - default: - SQLSRV_ASSERT( false, "Invalid type in get_string_data" ); - break; - } - - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); - } - - // all fields will be treated as ODBC returns varchar(max) fields: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - *out_buffer_length = *reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far; - - // copy as much as we can into the buffer - SQLLEN to_copy; - if( buffer_length < *out_buffer_length + extra ) { - to_copy = buffer_length - extra; - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - r = SQL_SUCCESS; - to_copy = *out_buffer_length; - } - - SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" ); - - if( to_copy > 0 ) { - memcpy_s( buffer, buffer_length, field_data + read_so_far, to_copy ); - read_so_far += to_copy; - } - if( extra ) { - OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); - memcpy_s( reinterpret_cast( buffer ) + to_copy, buffer_length, L"\0", extra ); - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - SQLCHAR* field_data = NULL; - SQLLEN field_len = NULL; - - // if this is the first time called for this field, just convert the entire string to system first then - // use that to read from instead of converting chunk by chunk. This is because it's impossible to know - // the total length of the string for output_buffer_length without doing the conversion and returning - // SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and - // to_same_string). - - if( read_so_far == 0 ) { - - 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; - } - else { - - field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; - } - - BOOL default_char_used = FALSE; - char default_char = '?'; - - // allocate enough to handle WC -> DBCS conversion if it happens - temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof(char), sizeof(char))); - temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), - (LPSTR) temp_string.get(), static_cast(field_len), &default_char, &default_char_used ); - - if( temp_length == 0 ) { - - switch( GetLastError() ) { - - case ERROR_NO_UNICODE_TRANSLATION: - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); - break; - default: - SQLSRV_ASSERT( false, "Severe error translating Unicode" ); - break; - } - - return SQL_ERROR; - } - } - - *out_buffer_length = (temp_length - read_so_far); - - SQLLEN to_copy = 0; - - if( (size_t) buffer_length < (temp_length - read_so_far + sizeof(char))) { - - to_copy = buffer_length - sizeof(char); - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - - to_copy = (temp_length - read_so_far); - r = SQL_SUCCESS; - } - - if( to_copy > 0 ) { - - memcpy_s( buffer, buffer_length, temp_string.get() + read_so_far, to_copy ); - } - SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); - OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); - ((SQLCHAR*) buffer)[ to_copy ] = '\0'; - read_so_far += to_copy; - - return r; -} - - -SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - return to_same_string( field_index, buffer, buffer_length, out_buffer_length ); -} - -SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invlid 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 ] ); - - memcpy_s( buffer, buffer_length, long_data, sizeof( LONG )); - *out_buffer_length = sizeof( LONG ); - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invlid 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 ] ); - - memcpy_s( buffer, buffer_length, double_data, sizeof( double )); - *out_buffer_length = sizeof( double ); - - return SQL_SUCCESS; -} - -namespace { - -// called for each row in the cache when the cache is destroyed in the destructor -void cache_row_dtor( zval* data ) -{ - row_dtor_closure* cl = reinterpret_cast( Z_PTR_P( data ) ); - BYTE* row = cl->row_data; - // don't release this here, since this is called from the destructor of the result_set - sqlsrv_buffered_result_set* result_set = cl->results; - - for( SQLSMALLINT i = 0; i < result_set->column_count(); ++i ) { - - 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 ] ); - sqlsrv_free( out_of_row_data ); - } - } - - sqlsrv_free( row ); - sqlsrv_free( cl ); -} - -SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - zend_long mem_used TSRMLS_DC ) -{ - SQLSMALLINT extra = 0; - SQLULEN* output_buffer_len = NULL; - - // Set the amount of space necessary for null characters at the end of the data. - switch( meta.c_type ) { - case SQL_C_WCHAR: - extra = sizeof( SQLWCHAR ); - break; - case SQL_C_BINARY: - extra = 0; - break; - case SQL_C_CHAR: - extra = sizeof( SQLCHAR ); - break; - default: - SQLSRV_ASSERT( false, "Invalid type in read_lob_field" ); - break; - } - - SQLLEN already_read = 0; - SQLLEN to_read = INITIAL_FIELD_STRING_LEN; - sqlsrv_malloc_auto_ptr buffer; - buffer = static_cast( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN ))); - SQLRETURN r = SQL_SUCCESS; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLLEN last_field_len = 0; - bool full_length_returned = false; - - do { - - - output_buffer_len = reinterpret_cast( buffer.get() ); - r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ), - to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC ); - - // if the field is NULL, then return a NULL pointer - if( last_field_len == SQL_NULL_DATA ) { - return NULL; - } - - // if the last read was successful, we're done - if( r == SQL_SUCCESS ) { - // check to make sure we haven't overflown our memory limit - CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - break; - } - // else if it wasn't the truncated warning (01004) then we're done - else if( r == SQL_SUCCESS_WITH_INFO ) { - SQLSMALLINT len; - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - - if( !is_truncated_warning( state )) { - break; - } - } - - SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" ); - - // if the type of the field returns the total to be read, we use that and preallocate the buffer - if( last_field_len != SQL_NO_TOTAL ) { - - CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - - already_read += to_read - already_read; - to_read = last_field_len; - buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); - // record the size of the field since we have it available - *output_buffer_len = last_field_len; - full_length_returned = true; - } - // otherwise allocate another chunk of memory to read in - else { - already_read += to_read - already_read; - to_read *= 2; - CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); - } - - } while( true ); - - SQLSRV_ASSERT( output_buffer_len != NULL, "Output buffer not allocated properly" ); - - // most LOB field types return the total length in the last_field_len, but some field types such as XML - // only return the amount read on the last read - if( !full_length_returned ) { - *output_buffer_len = already_read + last_field_len; - } - - char* return_buffer = buffer; - buffer.transferred(); - return return_buffer; -} - -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_results.cpp +// +// Contents: Result sets +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +#include + +#include + +#ifndef _WIN32 +#include +#include +#endif // !_WIN32 + + +using namespace core; + +// conversion matrix +// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported +// this is initialized the first time the buffered result set is created. +sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix; + +namespace { + +// *** internal types *** + +#if defined(_MSC_VER) +#pragma warning(disable:4200) +#endif + +// *** internal constants *** + +const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field + +// *** internal functions *** + +// return an integral type rounded up to a certain number +template +T align_to( T number ) +{ + DEBUG_SQLSRV_ASSERT( (number + align) > number, "Number to align overflowed" ); + return ((number % align) == 0) ? number : (number + align - (number % align)); +} + +// return a pointer address aligned to a certain address boundary +template +T* align_to( T* ptr ) +{ + size_t p_value = (size_t) ptr; + return align_to( p_value ); +} + +// set the nth bit of the bitstream starting at ptr +void set_bit( void* ptr, unsigned int bit ) +{ + unsigned char* null_bits = reinterpret_cast( ptr ); + null_bits += bit >> 3; + *null_bits |= 1 << ( 7 - ( bit & 0x7 )); +} + +// retrieve the nth bit from the bitstream starting at ptr +bool get_bit( void* ptr, unsigned int bit ) +{ + unsigned char* null_bits = reinterpret_cast( ptr ); + null_bits += bit >> 3; + return ((*null_bits & (1 << ( 7 - ( bit & 0x07 )))) != 0); +} + +// read in LOB field during buffered result creation +SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, + zend_long mem_used TSRMLS_DC ); + +// dtor for each row in the cache +void cache_row_dtor(zval* data); + +size_t get_float_precision(SQLLEN buffer_length, size_t unitsize) +{ + SQLSRV_ASSERT(unitsize != 0, "Invalid unit size!"); + + // get to display size by removing the null terminator from buffer length + size_t display_size = (buffer_length - unitsize) / unitsize; + + // use the display size to determine the sql type. And if it is a double, set the precision accordingly + // the display sizes are set by the ODBC driver based on the precision of the sql type + // otherwise we can just use the default precision + size_t real_display_size = 14; + size_t float_display_size = 24; + size_t real_precision = 7; + size_t float_precision = 15; + + // For more information about display sizes for REAL vs FLOAT/DOUBLE: https://msdn.microsoft.com/en-us/library/ms713974(v=vs.85).aspx + // For more information about precision: https://msdn.microsoft.com/en-us/library/ms173773.aspx + + // this is the case of sql type float(24) or real + if ( display_size == real_display_size ) { + return real_precision; + } + // this is the case of sql type float(53) + else if ( display_size == float_display_size ) { + return float_precision; + } + + return 0; +} + +#ifndef _WIN32 +// copy the number into a char string using the num_put facet +template +SQLRETURN get_string_from_stream( Number number_data, std::basic_string &str_num, size_t precision, sqlsrv_error_auto_ptr& last_error) +{ + //std::locale loc( std::locale(""), new std::num_put ); // By default, SQL Server doesn't take user's locale into consideration + std::locale loc; + std::basic_ostringstream os; + + os.precision( precision ); + + os.imbue( loc ); + auto itert = std::ostreambuf_iterator( os.rdbuf() ); + std::use_facet< std::num_put>(loc).put( itert, os, ' ', number_data ); + str_num = os.str(); + + if ( os.fail()) { + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "IMSSP", ( SQLCHAR* ) "Failed to convert number to string", -1 ); + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + +// copy the Char string into the output buffer - check first that it will fit +template +SQLRETURN copy_buffer( _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, std::basic_string &str, sqlsrv_error_auto_ptr& last_error ) +{ + *out_buffer_length = str.size() * sizeof( Char ); // NULL terminator is provided subsequently + + if ( *out_buffer_length > buffer_length ) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error(( SQLCHAR* ) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); + return SQL_ERROR; + } + + memcpy_s(buffer, *out_buffer_length, str.c_str(), *out_buffer_length); + + return SQL_SUCCESS; +} +#endif // !_WIN32 + +// convert a number to a string using locales +// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of +// subclassing a new streambuf just to avoid the copy, it's easier to do the copy +template +SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) +{ + size_t precision = 0; + +#ifdef _WIN32 + std::basic_ostringstream os; + precision = get_float_precision( buffer_length, sizeof( Char )); + os.precision( precision ); + std::locale loc; + os.imbue(loc); + std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf()), os, ' ', *number_data ); + std::basic_string& str_num = os.str(); + + if ( os.fail() ) { + last_error = new ( sqlsrv_malloc(sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "IMSSP", (SQLCHAR*) "Failed to convert number to string", -1 ); + return SQL_ERROR; + } + + if ( str_num.size() * sizeof( Char ) > ( size_t )buffer_length ) { + last_error = new ( sqlsrv_malloc(sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "HY090", ( SQLCHAR* ) "Buffer length too small to hold number as string", -1 ); + return SQL_ERROR; + } + + *out_buffer_length = str_num.size() * sizeof( Char ); // str_num.size() already include the NULL terminator + memcpy_s( buffer, buffer_length, str_num.c_str(), *out_buffer_length ); + + return SQL_SUCCESS; +#else + std::basic_string str_num; + SQLRETURN r; + + if ( std::is_integral::value ) + { + long num_data = *number_data; + r = get_string_from_stream( num_data, str_num, precision, last_error ); + } + else + { + precision = get_float_precision( buffer_length, sizeof( Char )); + r = get_string_from_stream( *number_data, str_num, precision, last_error ); + } + + if ( r == SQL_ERROR ) return SQL_ERROR; + + if ( std::is_same::value ) + { + std::basic_string str; + char *str_num_ptr = &str_num[0], *str_num_end = &str_num[0] + str_num.size(); + + for ( const auto &mb : str_num ) + { + char16_t ch16; + std::mbstate_t mbs = std::mbstate_t(); + + int len = mbrtoc16( &ch16, &mb, str_num_end - str_num_ptr, &mbs ); + if ( len > 0 || len == -3 ) + { + str.push_back( ch16 ); + if ( len > 0 ) + { + str_num_ptr += len; + } + } + } + + return copy_buffer( buffer, buffer_length, out_buffer_length, str, last_error ); + } + + return copy_buffer( buffer, buffer_length, out_buffer_length, str_num, last_error ); + +#endif // _WIN32 + +} + +template +SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) +{ + Number* number_data = reinterpret_cast( buffer ); +#ifdef _WIN32 + std::locale loc; // default locale should match system + std::basic_istringstream is; + is.str( string_data ); + is.imbue( loc ); + std::ios_base::iostate st = 0; + + std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream::_Iter( is.rdbuf()), std::basic_istream::_Iter( 0 ), is, st, *number_data ); + + if ( st & std::ios_base::failbit ) { + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "22003", ( SQLCHAR* ) "Numeric value out of range", 103 ); + return SQL_ERROR; + } + + *out_buffer_length = sizeof( Number ); +#else + std::string str; + if ( std::is_same::value ) + { + // convert to regular character string first + char c_str[3] = ""; + mbstate_t mbs; + + SQLLEN i = 0; + while ( string_data[i] ) + { + memset( &mbs, 0, sizeof( mbs )); //set shift state to the initial state + memset( c_str, 0, sizeof( c_str )); + int len = c16rtomb( c_str, string_data[i++], &mbs ); // treat string_data as a char16_t string + str.append(std::string( c_str, len )); + } + } + else + { + str.append( std::string(( char * )string_data )); + } + + std::istringstream is( str ); + std::locale loc; // default locale should match system + is.imbue( loc ); + + auto& facet = std::use_facet>( is.getloc()); + std::istreambuf_iterator beg( is ), end; + std::ios_base::iostate err = std::ios_base::goodbit; + + if ( std::is_integral::value ) + { + long number; + facet.get( beg, end, is, err, number ); + + *number_data = number; + } + else + { + double number; + facet.get( beg, end, is, err, number ); + + *number_data = number; + } + + *out_buffer_length = sizeof( Number ); + + if ( is.fail() ) + { + last_error = new ( sqlsrv_malloc(sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "22003", ( SQLCHAR* ) "Numeric value out of range", 103 ); + return SQL_ERROR; + } +#endif // _WIN32 + return SQL_SUCCESS; + +} + +// "closure" for the hash table destructor +struct row_dtor_closure { + + sqlsrv_buffered_result_set* results; + BYTE* row_data; + + row_dtor_closure( sqlsrv_buffered_result_set* st, BYTE* row ) : + results( st ), row_data( row ) + { + } +}; + +sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number ) +{ + SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ]; + SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; + SQLINTEGER native_code; + SQLSMALLINT wnative_message_len = 0; + + SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message, + SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len ); + if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { + return NULL; + } + + // convert the error into the encoding of the context + SQLSRV_ENCODING enc = odbc->encoding(); + if( enc == SQLSRV_ENCODING_DEFAULT ) { + enc = odbc->conn->encoding(); + } + + // convert the error into the encoding of the context + sqlsrv_malloc_auto_ptr sql_state; + SQLLEN sql_state_len = 0; + if ( !convert_string_from_utf16( enc, wsql_state, SQL_SQLSTATE_BUFSIZE, (char**)&sql_state, sql_state_len )) { + return NULL; + } + + sqlsrv_malloc_auto_ptr native_message; + SQLLEN native_message_len = 0; + if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) { + return NULL; + } + + return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) sql_state, (SQLCHAR*) native_message, + native_code ); +} + +} // namespace + +// base class result set + +sqlsrv_result_set::sqlsrv_result_set( sqlsrv_stmt* stmt ) : + odbc( stmt ) +{ +} + + +// ODBC result set +// This object simply wraps ODBC function calls + +sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( sqlsrv_stmt* stmt ) : + sqlsrv_result_set( stmt ) +{ +} + +sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void ) +{ +} + +SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC ); +} + +SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC ); +} + +SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length, + out_buffer_length TSRMLS_CC ); +} + +sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( SQLSMALLINT record_number ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return odbc_get_diag_rec( odbc, record_number ); +} + +SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLRowCount( odbc TSRMLS_CC ); +} + + +// Buffered result set +// This class holds a result set in memory + +sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS_DC ) : + sqlsrv_result_set( stmt ), + cache(NULL), + col_count(0), + meta(NULL), + current(0), + last_field_index(-1), + read_so_far(0), + temp_length(0) +{ + // 10 is an arbitrary number for now for the initial size of the cache + ALLOC_HASHTABLE( cache ); + core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); + col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); + // there is no result set to buffer + if( col_count == 0 ) { + return; + } + + SQLULEN null_bytes = ( col_count / 8 ) + 1; // number of bits to reserve at the beginning of each row for NULL flags + meta = static_cast( sqlsrv_malloc( col_count * + sizeof( sqlsrv_buffered_result_set::meta_data ))); + + // 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; + } + + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : + stmt->encoding()); + + // get the meta data and calculate the size of a row buffer + SQLULEN offset = null_bytes; + for( SQLSMALLINT i = 0; i < col_count; ++i ) { + + core::SQLDescribeColW( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC ); + + offset = align_to( offset ); + meta[i].offset = offset; + + switch( meta[i].type ) { + + // these types are the display size + case SQL_BIGINT: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + core::SQLColAttributeW( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, + reinterpret_cast( &meta[i].length ) TSRMLS_CC ); + meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space + offset += meta[i].length; + break; + case SQL_CHAR: + case SQL_VARCHAR: + if ( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + offset += sizeof( void* ); + } + else { + // If encoding is set to UTF-8, the following types are not necessarily column size. + // We need to call SQLGetData with c_type SQL_C_WCHAR and set the size accordingly. + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + meta[i].length *= sizeof( WCHAR ); + meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space + offset += meta[i].length; + } + else { + meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space + offset += meta[i].length; + } + } + break; + + // these types are the column size + case SQL_BINARY: + case SQL_SS_UDT: + case SQL_VARBINARY: + // var* field types are length prefixed + if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + offset += sizeof( void* ); + } + else { + meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space + offset += meta[i].length; + } + break; + + case SQL_WCHAR: + case SQL_WVARCHAR: + if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + offset += sizeof( void* ); + } + else { + meta[i].length *= sizeof( WCHAR ); + meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space + offset += meta[i].length; + } + break; + + // these types are LOBs + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + meta[i].length = sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN; + offset += sizeof( void* ); + break; + + // these types are the ISO date size + case SQL_DATETIME: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_TYPE_TIMESTAMP: + core::SQLColAttributeW( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, + reinterpret_cast( &meta[i].length ) TSRMLS_CC ); + meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space + offset += meta[i].length; + break; + + // these types are the native size + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + meta[i].length = sizeof( long ); + offset += meta[i].length; + break; + + case SQL_REAL: + case SQL_FLOAT: + meta[i].length = sizeof( double ); + offset += meta[i].length; + break; + + default: + SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); + break; + } + + switch( meta[i].type ) { + + case SQL_BIGINT: + case SQL_DATETIME: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_XML: + case SQL_TYPE_TIMESTAMP: + meta[i].c_type = SQL_C_CHAR; + break; + + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + // If encoding is set to UTF-8, the following types are not necessarily column size. + // We need to call SQLGetData with c_type SQL_C_WCHAR and set the size accordingly. + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + meta[i].c_type = SQL_C_WCHAR; + } + else { + meta[i].c_type = SQL_C_CHAR; + } + break; + + case SQL_SS_UDT: + case SQL_LONGVARBINARY: + case SQL_BINARY: + case SQL_VARBINARY: + meta[i].c_type = SQL_C_BINARY; + break; + + case SQL_WLONGVARCHAR: + case SQL_WCHAR: + case SQL_WVARCHAR: + meta[i].c_type = SQL_C_WCHAR; + break; + + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + meta[i].c_type = SQL_C_LONG; + break; + + case SQL_REAL: + case SQL_FLOAT: + meta[i].c_type = SQL_C_DOUBLE; + break; + + default: + SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); + break; + } + + } + + // read the data into the cache + // (offset from the above loop has the size of the row buffer necessary) + zend_long mem_used = 0; + size_t row_count = 0; + + while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { + + // allocate the row buffer + unsigned char* row = static_cast( sqlsrv_malloc( offset )); + memset( row, 0, offset ); + + // read the fields into the row buffer + for( SQLSMALLINT i = 0; i < col_count; ++i ) { + + SQLLEN out_buffer_temp = SQL_NULL_DATA; + SQLPOINTER buffer; + SQLLEN* out_buffer_length = &out_buffer_temp; + + switch( meta[i].c_type ) { + + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_BINARY: + 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 ] ); + *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); + // a NULL pointer means NULL field + if( *lob_addr == NULL ) { + *out_buffer_length = SQL_NULL_DATA; + } + else { + *out_buffer_length = **reinterpret_cast( lob_addr ); + mem_used += *out_buffer_length; + } + } + else { + + mem_used += meta[i].length; + CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + + buffer = row + meta[i].offset + sizeof( SQLULEN ); + out_buffer_length = reinterpret_cast( row + meta[i].offset ); + core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, + false TSRMLS_CC ); + } + break; + + case SQL_C_LONG: + case SQL_C_DOUBLE: + { + mem_used += meta[i].length; + CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + buffer = row + meta[i].offset; + out_buffer_length = &out_buffer_temp; + core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, + false TSRMLS_CC ); + } + break; + + default: + SQLSRV_ASSERT( false, "Unknown C type" ); + break; + } + + row_count++; + if( *out_buffer_length == SQL_NULL_DATA ) { + set_bit( row, i ); + } + } + + SQLSRV_ASSERT( row_count < INT_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); + + // add it to the cache + row_dtor_closure cl( this, row ); + sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); + } + +} + +sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void ) +{ + // free the rows + if( cache ) { + zend_hash_destroy( cache ); + FREE_HASHTABLE( cache ); + cache = NULL; + } + + // free the meta data + if( meta ) { + efree( meta ); + meta = NULL; + } +} + +SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) +{ + last_error = NULL; + last_field_index = -1; + read_so_far = 0; + + switch( orientation ) { + + case SQL_FETCH_NEXT: + offset = 1; + orientation = SQL_FETCH_RELATIVE; + break; + case SQL_FETCH_PRIOR: + offset = -1; + orientation = SQL_FETCH_RELATIVE; + break; + } + + switch( orientation ) { + + case SQL_FETCH_FIRST: + current = 1; + break; + case SQL_FETCH_LAST: + current = row_count( TSRMLS_C ); + break; + case SQL_FETCH_ABSOLUTE: + current = offset; + break; + case SQL_FETCH_RELATIVE: + current += offset; + break; + default: + SQLSRV_ASSERT( false, "Invalid fetch orientation. Should have been caught before here." ); + break; + } + + // check validity of current row + // the cursor can never get further away than just before the first row + if( current <= 0 && ( offset < 0 || orientation != SQL_FETCH_RELATIVE )) { + current = 0; + return SQL_NO_DATA; + } + + // the cursor can never get further away than just after the last row + if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) { + current = row_count( TSRMLS_C ) + 1; + return SQL_NO_DATA; + } + + return SQL_SUCCESS; +} + +SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ) +{ + last_error = NULL; + field_index--; // convert from 1 based to 0 based + SQLSRV_ASSERT( field_index < column_count(), "Invalid field index requested" ); + + if( field_index != last_field_index ) { + last_field_index = field_index; + read_so_far = 0; + } + + unsigned char* row = get_row(); + + // if the field is null, then return SQL_NULL_DATA + if( get_bit( row, field_index )) { + *out_buffer_length = SQL_NULL_DATA; + return SQL_SUCCESS; + } + + + // check to make sure the conversion type is valid + conv_matrix_t::const_iterator conv_iter = conv_matrix.find( meta[field_index].c_type ); + if( conv_iter == conv_matrix.end() || conv_iter->second.find( target_type ) == conv_iter->second.end() ) { + last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0 ); + return SQL_ERROR; + } + + return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length, + out_buffer_length ); +} + +SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) +{ + SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); + SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE, + "Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); + SQLSRV_ASSERT( buffer_length >= SQL_SQLSTATE_BUFSIZE, + "Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field" ); + + if( last_error == 0 ) { + return SQL_NO_DATA; + } + + SQLSRV_ASSERT( last_error->sqlstate != NULL, + "Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" ); + + SQLSMALLINT bufsize = ( buffer_length < SQL_SQLSTATE_BUFSIZE ) ? buffer_length : SQL_SQLSTATE_BUFSIZE; + + memcpy_s( diag_info_buffer, buffer_length, last_error->sqlstate, bufsize); + + return SQL_SUCCESS; +} + +unsigned char* sqlsrv_buffered_result_set::get_row( void ) +{ + row_dtor_closure* cl_ptr; + cl_ptr = reinterpret_cast(zend_hash_index_find_ptr(cache, static_cast(current - 1))); + SQLSRV_ASSERT(cl_ptr != NULL, "Failed to find row %1!d! in the cache", current); + return cl_ptr->row_data; +} + +sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( SQLSMALLINT record_number ) +{ + // we only hold a single error if there is one, otherwise return the ODBC error(s) + if( last_error == 0 ) { + return odbc_get_diag_rec( odbc, record_number ); + } + if( record_number > 1 ) { + return NULL; + } + + return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code ); +} + +SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D ) +{ + last_error = NULL; + + return zend_hash_num_elements( cache ); +} + +// private functions +template +SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, _Out_ void* buffer, + SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + sqlsrv_error_auto_ptr& out_error ) +{ + // hex characters for the conversion loop below + static char hex_chars[] = "0123456789ABCDEF"; + + SQLSRV_ASSERT( out_error == 0, "Pending error for sqlsrv_buffered_results_set::binary_to_string" ); + + SQLRETURN r = SQL_ERROR; + + // Set the amount of space necessary for null characters at the end of the data. + SQLSMALLINT extra = sizeof(Char); + + SQLSRV_ASSERT( ((buffer_length - extra) % (extra * 2)) == 0, "Must be multiple of 2 for binary to system string or " + "multiple of 4 for binary to wide string" ); + + // all fields will be treated as ODBC returns varchar(max) fields: + // the entire length of the string is returned the first + // call in out_buffer_len. Successive calls return how much is + // left minus how much has already been read by previous reads + // *2 is for each byte to hex conversion and * extra is for either system or wide string allocation + *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra; + + // copy as much as we can into the buffer + SQLLEN to_copy; + if( buffer_length < *out_buffer_length + extra ) { + to_copy = (buffer_length - extra); + out_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + r = SQL_SUCCESS; + to_copy = *out_buffer_length; + } + + // if there are bytes to copy as hex + if( to_copy > 0 ) { + // quick hex conversion routine + Char* h = reinterpret_cast( buffer ); + BYTE* b = reinterpret_cast( field_data ); + // to_copy contains the number of bytes to copy, so we divide the number in half (or quarter) + // to get the number of hex digits we can copy + SQLLEN to_copy_hex = to_copy / (2 * extra); + for( int i = 0; i < to_copy_hex; ++i ) { + *h = hex_chars[ (*b & 0xf0) >> 4 ]; + h++; + *h = hex_chars[ (*b++ & 0x0f) ]; + h++; + } + read_so_far += to_copy_hex; + *h = static_cast( 0 ); + } + else { + reinterpret_cast( buffer )[0] = '\0'; + } + + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLCHAR* row = get_row(); + SQLCHAR* field_data = NULL; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + } + else { + + 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 ); +} + +SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLCHAR* row = get_row(); + SQLCHAR* field_data = NULL; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + } + else { + + 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 ); +} + + +SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ] ); + LONG* long_data = reinterpret_cast( buffer ); + + if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) { + last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "22003", + (SQLCHAR*) "Numeric value out of range", 0 ); + return SQL_ERROR; + } + + if( *double_data != floor( *double_data )) { + last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "01S07", + (SQLCHAR*) "Fractional truncation", 0 ); + return SQL_SUCCESS_WITH_INFO; + } + + *long_data = static_cast( *double_data ); + *out_buffer_length = sizeof( LONG ); + + return SQL_SUCCESS; +} + +SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ] ); + SQLRETURN r = SQL_SUCCESS; +#ifdef _WIN32 + r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); +#else + r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); +#endif // _WIN32 + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ] ); + SQLRETURN r = SQL_SUCCESS; +#ifdef _WIN32 + r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); +#else + r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); +#endif // _WIN32 + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ] ); + + *double_data = static_cast( *long_data ); + *out_buffer_length = sizeof( double ); + + return SQL_SUCCESS; +} +SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ] ); + SQLRETURN r = SQL_SUCCESS; +#ifdef _WIN32 + r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); +#else + r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); +#endif // _WIN32 + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ] ); + SQLRETURN r = SQL_SUCCESS; +#ifdef _WIN32 + r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); +#else + r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); +#endif // _WIN32 + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ); + + 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ); + + 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ); + + 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ); + + 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( last_error == 0, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" ); + SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" ); + + SQLRETURN r = SQL_ERROR; + unsigned char* row = get_row(); + + SQLCHAR* field_data = NULL; + SQLULEN field_len = 0; + + 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; + } + else { + + 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: + // the entire length of the string is returned the first + // call in out_buffer_len. Successive calls return how much is + // left minus how much has already been read by previous reads + *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR); + + // to_copy is the number of characters to copy, not including the null terminator + // supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair. + SQLLEN to_copy; + + if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) { + + to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR); // to_copy is the number of characters + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + + r = SQL_SUCCESS; + to_copy = field_len - read_so_far; + } + + if( to_copy > 0 ) { + + bool tried_again = false; + do { + if (to_copy > INT_MAX ) { + LOG(SEV_ERROR, "MultiByteToWideChar: Buffer length exceeded."); + throw core::CoreException(); + } + +#ifndef _WIN32 + int ch_space = SystemLocale::ToUtf16( CP_ACP, (LPCSTR) field_data, static_cast(to_copy), + static_cast(buffer), static_cast(to_copy)); + +#else + int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast(to_copy), + static_cast(buffer), static_cast(to_copy)); +#endif // !_WIN32 + + if( ch_space == 0 ) { + + switch( GetLastError() ) { + + case ERROR_NO_UNICODE_TRANSLATION: + // the theory here is the conversion failed because the end of the buffer we provided contained only + // half a character at the end + if( !tried_again ) { + to_copy--; + tried_again = true; + continue; + } + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); + break; + default: + SQLSRV_ASSERT( false, "Severe error translating Unicode" ); + break; + } + + return SQL_ERROR; + } + + ((WCHAR*)buffer)[ to_copy ] = L'\0'; + read_so_far += to_copy; + break; + + } while( true ); + } + else { + reinterpret_cast( buffer )[0] = L'\0'; + } + + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( last_error == 0, "Pending error for sqlsrv_buffered_results_set::to_same_string" ); + + SQLRETURN r = SQL_ERROR; + unsigned char* row = get_row(); + + // Set the amount of space necessary for null characters at the end of the data. + SQLSMALLINT extra = 0; + + switch( meta[ field_index ].c_type ) { + case SQL_C_WCHAR: + extra = sizeof( SQLWCHAR ); + break; + case SQL_C_BINARY: + extra = 0; + break; + case SQL_C_CHAR: + extra = sizeof( SQLCHAR ); + break; + default: + SQLSRV_ASSERT( false, "Invalid type in get_string_data" ); + break; + } + + SQLCHAR* field_data = NULL; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + } + else { + + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + } + + // all fields will be treated as ODBC returns varchar(max) fields: + // the entire length of the string is returned the first + // call in out_buffer_len. Successive calls return how much is + // left minus how much has already been read by previous reads + *out_buffer_length = *reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far; + + // copy as much as we can into the buffer + SQLLEN to_copy; + if( buffer_length < *out_buffer_length + extra ) { + to_copy = buffer_length - extra; + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + r = SQL_SUCCESS; + to_copy = *out_buffer_length; + } + + SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" ); + + if( to_copy > 0 ) { + memcpy_s( buffer, buffer_length, field_data + read_so_far, to_copy ); + read_so_far += to_copy; + } + if( extra ) { + OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); + memcpy_s( reinterpret_cast( buffer ) + to_copy, buffer_length, L"\0", extra ); + } + + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( last_error == 0, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" ); + + SQLRETURN r = SQL_ERROR; + unsigned char* row = get_row(); + + SQLCHAR* field_data = NULL; + SQLLEN field_len = 0; + + // if this is the first time called for this field, just convert the entire string to system first then + // use that to read from instead of converting chunk by chunk. This is because it's impossible to know + // the total length of the string for output_buffer_length without doing the conversion and returning + // SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and + // to_same_string). + + if( read_so_far == 0 ) { + + 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; + } + else { + + field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; + } + + // allocate enough to handle WC -> DBCS conversion if it happens + temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof(char), sizeof(char))); + +#ifndef _WIN32 + temp_length = SystemLocale::FromUtf16( CP_ACP, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), + (LPSTR) temp_string.get(), static_cast(field_len) ); +#else + BOOL default_char_used = FALSE; + char default_char = '?'; + + temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), + (LPSTR) temp_string.get(), static_cast(field_len), &default_char, &default_char_used ); +#endif // !_WIN32 + if( temp_length == 0 ) { + + switch( GetLastError() ) { + + case ERROR_NO_UNICODE_TRANSLATION: + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); + break; + default: + SQLSRV_ASSERT( false, "Severe error translating Unicode" ); + break; + } + + return SQL_ERROR; + } + } + + *out_buffer_length = (temp_length - read_so_far); + + SQLLEN to_copy = 0; + + if( (size_t) buffer_length < (temp_length - read_so_far + sizeof(char))) { + + to_copy = buffer_length - sizeof(char); + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + + to_copy = (temp_length - read_so_far); + r = SQL_SUCCESS; + } + + if( to_copy > 0 ) { + memcpy_s( buffer, buffer_length, temp_string.get() + read_so_far, to_copy ); + } + SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); + OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); + ((SQLCHAR*) buffer)[ to_copy ] = '\0'; + read_so_far += to_copy; + + return r; +} + + +SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + return to_same_string( field_index, buffer, buffer_length, out_buffer_length ); +} + +SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ] ); + memcpy_s( buffer, buffer_length, long_data, sizeof( LONG )); + *out_buffer_length = sizeof( LONG ); + + return SQL_SUCCESS; +} + +SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + 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 ] ); + memcpy_s( buffer, buffer_length, double_data, sizeof( double )); + *out_buffer_length = sizeof( double ); + + return SQL_SUCCESS; +} + +namespace { + +// called for each row in the cache when the cache is destroyed in the destructor +void cache_row_dtor( zval* data ) +{ + row_dtor_closure* cl = reinterpret_cast( Z_PTR_P( data ) ); + BYTE* row = cl->row_data; + // don't release this here, since this is called from the destructor of the result_set + sqlsrv_buffered_result_set* result_set = cl->results; + + for( SQLSMALLINT i = 0; i < result_set->column_count(); ++i ) { + + 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 ] ); + sqlsrv_free( out_of_row_data ); + } + } + + sqlsrv_free( row ); + sqlsrv_free( cl ); +} + +SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, + zend_long mem_used TSRMLS_DC ) +{ + SQLSMALLINT extra = 0; + SQLULEN* output_buffer_len = NULL; + + // Set the amount of space necessary for null characters at the end of the data. + switch( meta.c_type ) { + case SQL_C_WCHAR: + extra = sizeof( SQLWCHAR ); + break; + case SQL_C_BINARY: + extra = 0; + break; + case SQL_C_CHAR: + extra = sizeof( SQLCHAR ); + break; + default: + SQLSRV_ASSERT( false, "Invalid type in read_lob_field" ); + break; + } + + SQLLEN already_read = 0; + SQLLEN to_read = INITIAL_FIELD_STRING_LEN; + sqlsrv_malloc_auto_ptr buffer; + buffer = static_cast( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN ))); + SQLRETURN r = SQL_SUCCESS; + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLLEN last_field_len = 0; + bool full_length_returned = false; + + do { + + + output_buffer_len = reinterpret_cast( buffer.get() ); + r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ), + to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC ); + + // if the field is NULL, then return a NULL pointer + if( last_field_len == SQL_NULL_DATA ) { + return NULL; + } + + // if the last read was successful, we're done + if( r == SQL_SUCCESS ) { + // check to make sure we haven't overflown our memory limit + CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + break; + } + // else if it wasn't the truncated warning (01004) then we're done + else if( r == SQL_SUCCESS_WITH_INFO ) { + SQLSMALLINT len; + core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len + TSRMLS_CC ); + + if( !is_truncated_warning( state )) { + break; + } + } + + SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" ); + + // if the type of the field returns the total to be read, we use that and preallocate the buffer + if( last_field_len != SQL_NO_TOTAL ) { + + CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + + already_read += to_read - already_read; + to_read = last_field_len; + buffer.resize( to_read + extra + sizeof( SQLULEN )); + output_buffer_len = reinterpret_cast( buffer.get() ); + // record the size of the field since we have it available + *output_buffer_len = last_field_len; + full_length_returned = true; + } + // otherwise allocate another chunk of memory to read in + else { + already_read += to_read - already_read; + to_read *= 2; + CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + buffer.resize( to_read + extra + sizeof( SQLULEN )); + output_buffer_len = reinterpret_cast( buffer.get() ); + } + + } while( true ); + + SQLSRV_ASSERT( output_buffer_len != NULL, "Output buffer not allocated properly" ); + + // most LOB field types return the total length in the last_field_len, but some field types such as XML + // only return the amount read on the last read + if( !full_length_returned ) { + *output_buffer_len = already_read + last_field_len; + } + + char* return_buffer = buffer; + buffer.transferred(); + return return_buffer; +} + +} diff --git a/sqlsrv/core_sqlsrv.h b/source/shared/core_sqlsrv.h similarity index 94% rename from sqlsrv/core_sqlsrv.h rename to source/shared/core_sqlsrv.h index c511b057..9b6c2d2d 100644 --- a/sqlsrv/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1,2263 +1,2341 @@ -#ifndef CORE_SQLSRV_H -#define CORE_SQLSRV_H - -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_sqlsrv.h -// -// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -//********************************************************************************************************************************* -// Includes -//********************************************************************************************************************************* - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef PHP_WIN32 -#define PHP_SQLSRV_API __declspec(dllexport) -#else -#define PHP_SQLSRV_API -#endif - -// OACR is an internal Microsoft static code analysis tool -#if defined(OACR) -#include -OACR_WARNING_PUSH -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) -OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." ) -#else -// define to eliminate static analysis hints in the code -#define OACR_WARNING_SUPPRESS( warning, msg ) -#endif - -extern "C" { - -#pragma warning(push) -#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) - -#ifdef ZTS -#include "TSRM.h" -#endif - -#if _MSC_VER >= 1400 -// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. -// php.h defines this constant as unsigned int which causes a compile error -// in ws2tcpip.h. Fortunately php.h allows an override by defining -// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define -// socklen_t here and override the php.h version. -typedef int socklen_t; -#define HAVE_SOCKLEN_T -#endif - -#include "php.h" -#include "php_globals.h" -#include "php_ini.h" -#include "ext/standard/php_standard.h" -#include "ext/standard/info.h" - -#pragma warning(pop) - -#if ZEND_DEBUG -// debug build causes warning C4505 to pop up from the Zend header files -#pragma warning( disable: 4505 ) -#endif - -} // extern "C" - -#if defined(OACR) -OACR_WARNING_POP -#endif - -#include -#include - -#if !defined(WC_ERR_INVALID_CHARS) -// imported from winnls.h as it isn't included by 5.3.0 -#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars -#endif - -// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when -// we use std::copy, which causes compilation to fail since we compile with warnings as errors. -#if defined(ZEND_DEBUG) && defined(inline) -#undef inline -#endif - -#include -#include -#include -#include -#include -#include -#include -// included for SQL Server specific constants -#include "msodbcsql.h" - -//********************************************************************************************************************************* -// Constants and Types -//********************************************************************************************************************************* - -// constants for maximums in SQL Server -const int SS_MAXCOLNAMELEN = 128; -const int SQL_SERVER_MAX_FIELD_SIZE = 8000; -const int SQL_SERVER_MAX_PRECISION = 38; -const int SQL_SERVER_MAX_TYPE_SIZE = 0; -const int SQL_SERVER_MAX_PARAMS = 2100; - -// max size of a date time string when converting from a DateTime object to a string -const int MAX_DATETIME_STRING_LEN = 256; - -// precision and scale for the date time types between servers -const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; -const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; -const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; -const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; - -// types for conversions on output parameters (though they can be used for input parameters, they are ignored) -enum SQLSRV_PHPTYPE { - MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type - SQLSRV_PHPTYPE_NULL = 1, - SQLSRV_PHPTYPE_INT, - SQLSRV_PHPTYPE_FLOAT, - SQLSRV_PHPTYPE_STRING, - SQLSRV_PHPTYPE_DATETIME, - SQLSRV_PHPTYPE_STREAM, - MAX_SQLSRV_PHPTYPE, // highest value for a php type - SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid -}; - -// encodings supported by this extension. These basically translate into the use of SQL_C_CHAR or SQL_C_BINARY when getting -// information as a string or a stream. -enum SQLSRV_ENCODING { - SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables. - SQLSRV_ENCODING_DEFAULT, // use what is the connection's default for a statement, use system if a connection - SQLSRV_ENCODING_BINARY, // use SQL_C_BINARY when using SQLGetData - SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData - SQLSRV_ENCODING_SYSTEM = SQLSRV_ENCODING_CHAR, - SQLSRV_ENCODING_UTF8 = CP_UTF8, -}; - -// the array keys used when returning a row via sqlsrv_fetch_array and sqlsrv_fetch_object. -enum SQLSRV_FETCH_TYPE { - MIN_SQLSRV_FETCH = 1, // lowest value for fetch type - SQLSRV_FETCH_NUMERIC = 1, // return an array with only numeric indices - SQLSRV_FETCH_ASSOC = 2, // return an array with keys made from the field names - SQLSRV_FETCH_BOTH = 3, // return an array indexed with both numbers and keys - MAX_SQLSRV_FETCH = 3, // highest value for fetch type -}; - -// buffer size of a sql state (including the null character) -const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1; - -// buffer size allocated to retrieve data from a PHP stream. This number -// was chosen since PHP doesn't return more than 8k at a time even if -// the amount requested was more. -const int PHP_STREAM_BUFFER_SIZE = 8192; - -// SQL types for parameters encoded in an integer. The type corresponds to the SQL type ODBC constants. -// The size is the column size or precision, and scale is the decimal digits for precise numeric types. - -union sqlsrv_sqltype { - struct typeinfo_t { - int type:9; - int size:14; - int scale:8; - } typeinfo; - - zend_long value; -}; - - -// SQLSRV PHP types (as opposed to the Zend PHP type constants). Contains the type (see SQLSRV_PHPTYPE) -// and the encoding for strings and streams (see SQLSRV_ENCODING) - -union sqlsrv_phptype { - - struct typeinfo_t { - unsigned type:8; - unsigned encoding:16; - } typeinfo; - - zend_long value; -}; - -// static assert for enforcing compile time conditions -template -struct sqlsrv_static_assert; - -template <> -struct sqlsrv_static_assert { static const int value = 1; }; - -#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() ) - - -//********************************************************************************************************************************* -// Logging -//********************************************************************************************************************************* -// log_callback -// a driver specific callback for logging messages -// severity - severity of the message: notice, warning, or error -// msg - the message to log in a FormatMessage style formatting -// print_args - args to the message -typedef void (*log_callback)( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); - -// each driver must register a log callback. This should be the first thing a driver does. -void core_sqlsrv_register_logger( log_callback ); - -// a simple wrapper around a PHP error logging function. -void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ... ); - -// a macro to make it convenient to use the function. -#define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, __VA_ARGS__ ) - -// mask for filtering which severities are written to the log -enum logging_severity { - SEV_ERROR = 0x01, - SEV_WARNING = 0x02, - SEV_NOTICE = 0x04, - SEV_ALL = -1, -}; - -// Kill the PHP process and log the message to PHP -void die( const char* msg, ... ); -#define DIE( msg, ... ) { die( msg, __VA_ARGS__ ); } - - -//********************************************************************************************************************************* -// Resource/Memory Management -//********************************************************************************************************************************* - -// the macro max is defined and overrides the call to max in the allocator class -#pragma push_macro( "max" ) -#undef max - -// new memory allocation/free debugging facilities to help us verify that all allocations are being -// released in a timely manner and not just at the end of the script. -// Zend has memory logging and checking, but it can generate a lot of noise for just one extension. -// It's meant for internal use but might be useful for people adding features to our extension. -// To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases -// must be done with sqlsrv_malloc and sqlsrv_free. -// #define SQLSRV_MEM_DEBUG 1 -#if defined( PHP_DEBUG ) && !defined( ZTS ) && defined( SQLSRV_MEM_DEBUG ) - -inline void* sqlsrv_malloc_trace( size_t size, const char* file, int line ) -{ - void* ptr = emalloc( size ); - LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); - return ptr; -} - -inline void* sqlsrv_malloc_trace( size_t element_count, size_t element_size, size_t extra, const char* file, int line ) -{ - OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); - - if(( element_count > 0 && element_size > 0 ) && - ( element_count > element_size * element_count || element_size > element_size * element_count )) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count > element_size * element_count + extra ) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count + extra == 0 ) { - DIE( "Allocation size must be more than 0" ); - } - - void* ptr = emalloc( element_size * element_count + extra ); - LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); - return ptr; -} - -inline void* sqlsrv_realloc_trace( void* buffer, size_t size, const char* file, int line ) -{ - void* ptr = erealloc( original, size ); - LOG( SEV_NOTICE, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original ); - return ptr; -} - -inline void sqlsrv_free_trace( void* ptr, const char* file, int line ) -{ - LOG( SEV_NOTICE, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line ); - efree( ptr ); -} - -#define sqlsrv_malloc( size ) sqlsrv_malloc_trace( size, __FILE__, __LINE__ ) -#define sqlsrv_malloc( count, size, extra ) sqlsrv_malloc_trace( count, size, extra, __FILE__, __LINE__ ) -#define sqlsrv_realloc( buffer, size ) sqlsrv_realloc_trace( buffer, size, __FILE__, __LINE__ ) -#define sqlsrv_free( ptr ) sqlsrv_free_trace( ptr, __FILE__, __LINE__ ) - -#else - -inline void* sqlsrv_malloc( size_t size ) -{ - return emalloc( size ); -} - -inline void* sqlsrv_malloc( size_t element_count, size_t element_size, size_t extra ) -{ - OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); - - if(( element_count > 0 && element_size > 0 ) && - ( element_count > element_size * element_count || element_size > element_size * element_count )) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count > element_size * element_count + extra ) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count + extra == 0 ) { - DIE( "Allocation size must be more than 0" ); - } - - return emalloc( element_size * element_count + extra ); -} - -inline void* sqlsrv_realloc( void* buffer, size_t size ) -{ - return erealloc( buffer, size ); -} - -inline void sqlsrv_free( void* ptr ) -{ - efree( ptr ); -} - -#endif - -// trait class that allows us to assign const types to an auto_ptr -template -struct remove_const { - typedef T type; -}; - -template -struct remove_const { - typedef T* type; -}; - -// allocator that uses the zend memory manager to manage memory -// this allows us to use STL classes that still work with Zend objects -template -struct sqlsrv_allocator { - - // typedefs used by the STL classes - typedef T value_type; - typedef value_type* pointer; - typedef const value_type* const_pointer; - typedef value_type& reference; - typedef const value_type& const_reference; - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; - - // conversion typedef (used by list and other STL classes) - template - struct rebind { - typedef sqlsrv_allocator other; - }; - - inline sqlsrv_allocator() {} - inline ~sqlsrv_allocator() {} - inline sqlsrv_allocator( sqlsrv_allocator const& ) {} - template - inline sqlsrv_allocator( sqlsrv_allocator const& ) {} - - // address (doesn't work if the class defines operator&) - inline pointer address( reference r ) - { - return &r; - } - - inline const_pointer address( const_reference r ) - { - return &r; - } - - // memory allocation/deallocation - inline pointer allocate( size_type cnt, - typename std::allocator::const_pointer = 0 ) - { - return reinterpret_cast( sqlsrv_malloc(cnt, sizeof (T), 0)); - } - - inline void deallocate( pointer p, size_type ) - { - sqlsrv_free(p); - } - - // size - inline size_type max_size( void ) const - { - return std::numeric_limits::max() / sizeof(T); - } - - // object construction/destruction - inline void construct( pointer p, const T& t ) - { - new(p) T(t); - } - - inline void destroy(pointer p) - { - p->~T(); - } - - // equality operators - inline bool operator==( sqlsrv_allocator const& ) - { - return true; - } - - inline bool operator!=( sqlsrv_allocator const& a ) - { - return !operator==(a); - } -}; - - -// base class for auto_ptrs that we define below. It provides common operators and functions -// used by all the classes. -template -class sqlsrv_auto_ptr { - -public: - - sqlsrv_auto_ptr( void ) : _ptr( NULL ) - { - } - - ~sqlsrv_auto_ptr( void ) - { - static_cast(this)->reset( NULL ); - } - - // call when ownership is transferred - void transferred( void ) - { - _ptr = NULL; - } - - // explicit function to get the pointer. - T* get( void ) const - { - return _ptr; - } - - // cast operator to allow auto_ptr to be used where a normal const * can be. - operator const T* () const - { - return _ptr; - } - - // cast operator to allow auto_ptr to be used where a normal pointer can be. - operator typename remove_const::type () const - { - return _ptr; - } - - operator bool() const - { - return _ptr != NULL; - } - - // 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[]( int index ) const - { - 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[]( unsigned int index ) const - { - 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[]( long index ) const - { - return _ptr[ index ]; - } - - - #ifdef __WIN64 - // 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[](std::size_t index) const - { - return _ptr[index]; - } - #endif - - // 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[]( unsigned short index ) const - { - return _ptr[ index ]; - } - - // access elements of a structure through the auto ptr - T* const operator->( void ) const - { - return _ptr; - } - - // value from reference operator (i.e., i = *(&i); or *i = blah;) - T& operator*() const - { - return *_ptr; - } - - // allow the use of the address-of operator to simulate a **. - // Note: this operator conflicts with storing these within an STL container. If you need - // to do that, then redefine this as getpp and change instances of &auto_ptr to auto_ptr.getpp() - T** operator&( void ) - { - return &_ptr; - } - -protected: - - sqlsrv_auto_ptr( T* ptr ) : - _ptr( ptr ) - { - } - - sqlsrv_auto_ptr( sqlsrv_auto_ptr& src ) - { - if( _ptr ) { - static_cast(this)->reset( src._ptr ); - } - src.transferred(); - } - - // assign a new pointer to the auto_ptr. It will free the previous memory block - // because ownership is deemed finished. - T* operator=( T* ptr ) - { - static_cast( this )->reset( ptr ); - - return ptr; - } - - T* _ptr; -}; - -// an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer -// in a variable of sqlsrv_malloc_auto_ptr. sqlsrv_malloc_auto_ptr will "own" that block and assure that it is -// freed until the variable is destroyed (out of scope) or ownership is transferred using the function -// "transferred". -// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. - -template -class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr > { - -public: - - sqlsrv_malloc_auto_ptr( void ) : - sqlsrv_auto_ptr >( NULL ) - { - } - - sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) : - sqlsrv_auto_ptr >( src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( T* ptr = NULL ) - { - if( _ptr ) - sqlsrv_free( (void*) _ptr ); - _ptr = ptr; - } - - T* operator=( T* ptr ) - { - return sqlsrv_auto_ptr >::operator=( ptr ); - } - - void operator=( sqlsrv_malloc_auto_ptr& src ) - { - T* p = src.get(); - src.transferred(); - this->_ptr = p; - } - - // DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. - // has the same parameter list as sqlsrv_realloc: new_size is the size in bytes of the newly allocated buffer - void resize( size_t new_size ) - { - _ptr = reinterpret_cast( sqlsrv_realloc( _ptr, new_size )); - } -}; - - -// auto ptr for Zend hash tables. Used to clean up a hash table allocated when -// something caused an early exit from the function. This is used when the hash_table is -// allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr. - -class hash_auto_ptr : public sqlsrv_auto_ptr { - -public: - - hash_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( HashTable* ptr = NULL ) - { - if( _ptr ) { - zend_hash_destroy( _ptr ); - FREE_HASHTABLE( _ptr ); - } - _ptr = ptr; - } - - HashTable* operator=( HashTable* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - -private: - - hash_auto_ptr( HashTable const& hash ); - - hash_auto_ptr( hash_auto_ptr const& hash ); -}; - - -// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr. -// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed -// (out of scope) or ownership is transferred using the function "transferred". - -class zval_auto_ptr : public sqlsrv_auto_ptr { - -public: - - zval_auto_ptr( void ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( zval* ptr = NULL ) - { - if( _ptr ) - zval_ptr_dtor(_ptr ); - _ptr = ptr; - } - - zval* operator=( zval* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - - -private: - - zval_auto_ptr( const zval_auto_ptr& src ); -}; - -#pragma pop_macro( "max" ) - - -//********************************************************************************************************************************* -// sqlsrv_error -//********************************************************************************************************************************* - -// *** PHP specific errors *** -// sqlsrv errors are held in a structure of this type used by the driver handle_error functions -// format is a flag that tells the driver error handler functions if there are parameters to use with FormatMessage -// into the error message before returning it. - -// base class which can be instatiated with aggregates (see error constants) -struct sqlsrv_error_const { - - SQLCHAR* sqlstate; - SQLCHAR* native_message; - SQLINTEGER native_code; - bool format; -}; - -// subclass which is used by the core layer to instantiate ODBC errors -struct sqlsrv_error : public sqlsrv_error_const { - - sqlsrv_error( void ) - { - sqlstate = NULL; - native_message = NULL; - native_code = -1; - format = false; - } - - sqlsrv_error( SQLCHAR* sql_state, SQLCHAR* message, SQLINTEGER code, bool printf_format = false ) - { - sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); - strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); - strcpy_s( reinterpret_cast( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast( message )); - native_code = code; - format = printf_format; - } - - sqlsrv_error( sqlsrv_error_const const& prototype ) - { - sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format ); - } - - ~sqlsrv_error( void ) - { - if( sqlstate != NULL ) { - sqlsrv_free( sqlstate ); - } - if( native_message != NULL ) { - sqlsrv_free( native_message ); - } - } -}; - - -// an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete -class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr { - -public: - - sqlsrv_error_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - sqlsrv_error_auto_ptr( sqlsrv_error_auto_ptr const& src ) : - sqlsrv_auto_ptr( (sqlsrv_error_auto_ptr&) src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( sqlsrv_error* ptr = NULL ) - { - if( _ptr ) { - _ptr->~sqlsrv_error(); - sqlsrv_free( (void*) _ptr ); - } - _ptr = ptr; - } - - sqlsrv_error* operator=( sqlsrv_error* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - - // unlike traditional assignment operators, the chained assignment of an auto_ptr doesn't make much - // sense. Only the last one would have anything in it. - void operator=( sqlsrv_error_auto_ptr& src ) - { - sqlsrv_error* p = src.get(); - src.transferred(); - this->_ptr = p; - } -}; - - -//********************************************************************************************************************************* -// Context -//********************************************************************************************************************************* - -class sqlsrv_context; -struct sqlsrv_conn; - -// error_callback -// a driver specific callback for processing errors. -// ctx - the context holding the handles -// sqlsrv_error_code - specific error code to return. -typedef bool (*error_callback)( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool error TSRMLS_DC, va_list* print_args ); - -// sqlsrv_context -// a context holds relevant information to be passed with a connection and statement objects. - -class sqlsrv_context { - - public: - - sqlsrv_context( SQLSMALLINT type, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : - handle_( SQL_NULL_HANDLE ), - handle_type_( type ), - err_( e ), - name_( NULL ), - driver_( drv ), - last_error_(), - encoding_( encoding ) - { - } - - sqlsrv_context( SQLHANDLE h, SQLSMALLINT t, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : - handle_( h ), - handle_type_( t ), - err_( e ), - name_( NULL ), - driver_( drv ), - last_error_(), - encoding_( encoding ) - { - } - - sqlsrv_context( sqlsrv_context const& ctx ) : - handle_( ctx.handle_ ), - handle_type_( ctx.handle_type_ ), - err_( ctx.err_ ), - name_( ctx.name_ ), - driver_( ctx.driver_ ), - last_error_( ctx.last_error_ ) - { - } - - void set_func( const char* f ) - { - name_ = f; - } - - void set_last_error( sqlsrv_error_auto_ptr& last_error ) - { - last_error_ = last_error; - } - - sqlsrv_error_auto_ptr& last_error( void ) - { - return last_error_; - } - - // since the primary responsibility of a context is to hold an ODBC handle, we - // provide these convenience operators for using them interchangeably - operator SQLHANDLE ( void ) const - { - return handle_; - } - - error_callback error_handler( void ) const - { - return err_; - } - - SQLHANDLE handle( void ) const - { - return handle_; - } - - SQLSMALLINT handle_type( void ) const - { - return handle_type_; - } - - const char* func( void ) const - { - return name_; - } - - void* driver( void ) const - { - return driver_; - } - - void set_driver( void* driver ) - { - this->driver_ = driver; - } - - void invalidate( void ) - { - if( handle_ != SQL_NULL_HANDLE ) { - ::SQLFreeHandle( handle_type_, handle_ ); - - last_error_.reset(); - } - handle_ = SQL_NULL_HANDLE; - } - - bool valid( void ) - { - return handle_ != SQL_NULL_HANDLE; - } - - SQLSRV_ENCODING encoding( void ) const - { - return encoding_; - } - - void set_encoding( SQLSRV_ENCODING e ) - { - encoding_ = e; - } - - private: - SQLHANDLE handle_; // ODBC handle for this context - SQLSMALLINT handle_type_; // type of the ODBC handle - const char* name_; // function name currently executing this context - error_callback err_; // driver error callback if error occurs in core layer - void* driver_; // points back to the driver for PDO - sqlsrv_error_auto_ptr last_error_; // last error that happened on this object - SQLSRV_ENCODING encoding_; // encoding of the context -}; - -const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista - -// maps an IANA encoding to a code page -struct sqlsrv_encoding { - - const char* iana; - size_t iana_len; - unsigned int code_page; - bool not_for_connection; - - sqlsrv_encoding( const char* iana, unsigned int code_page, bool not_for_conn = false ): - iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn ) - { - } -}; - - -//********************************************************************************************************************************* -// Initialization -//********************************************************************************************************************************* - -// variables set during initialization -extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in -extern HashTable* g_encodings; // encodings supported by this driver - -void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ); -void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ); - -// environment context used by sqlsrv_connect for when a connection error occurs. -struct sqlsrv_henv { - - sqlsrv_context ctx; - - sqlsrv_henv( SQLHANDLE handle, error_callback e, void* drv ) : - ctx( handle, SQL_HANDLE_ENV, e, drv ) - { - } -}; - - -//********************************************************************************************************************************* -// Connection -//********************************************************************************************************************************* - -// supported server versions (determined at connection time) -enum SERVER_VERSION { - SERVER_VERSION_UNKNOWN = -1, - SERVER_VERSION_2000 = 8, - SERVER_VERSION_2005, - SERVER_VERSION_2008, // use this for anything 2008 or later -}; - -// supported driver versions. -enum DRIVER_VERSION : size_t { - MIN = 0, - ODBC_DRIVER_13 = MIN, - ODBC_DRIVER_11 = 1, - MAX = ODBC_DRIVER_11, -}; - -// forward decl -struct sqlsrv_stmt; -struct stmt_option; - -// *** connection resource structure *** -// this is the resource structure returned when a connection is made. -struct sqlsrv_conn : public sqlsrv_context { - - // instance variables - SERVER_VERSION server_version; // version of the server that we're connected to - - DRIVER_VERSION driver_version; - - // initialize with default values - sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) : - sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) - { - } - - // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be - // called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating - // the sqlsrv_conn with a connection. -}; - -enum SQLSRV_STMT_OPTIONS { - - SQLSRV_STMT_OPTION_INVALID, - SQLSRV_STMT_OPTION_QUERY_TIMEOUT, - SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, - SQLSRV_STMT_OPTION_SCROLLABLE, - SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, - - // Driver specific connection options - SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, - -}; - -namespace ODBCConnOptions { - -const char APP[] = "APP"; -const char ApplicationIntent[] = "ApplicationIntent"; -const char AttachDBFileName[] = "AttachDbFileName"; -const char CharacterSet[] = "CharacterSet"; -const char ConnectionPooling[] = "ConnectionPooling"; -const char Database[] = "Database"; -const char Encrypt[] = "Encrypt"; -const char Failover_Partner[] = "Failover_Partner"; -const char LoginTimeout[] = "LoginTimeout"; -const char MARS_ODBC[] = "MARS_Connection"; -const char MultiSubnetFailover[] = "MultiSubnetFailover"; -const char QuotedId[] = "QuotedId"; -const char TraceFile[] = "TraceFile"; -const char TraceOn[] = "TraceOn"; -const char TrustServerCertificate[] = "TrustServerCertificate"; -const char TransactionIsolation[] = "TransactionIsolation"; -const char WSID[] = "WSID"; -const char UID[] = "UID"; -const char PWD[] = "PWD"; -const char SERVER[] = "Server"; - -} - -enum SQLSRV_CONN_OPTIONS { - - SQLSRV_CONN_OPTION_INVALID, - SQLSRV_CONN_OPTION_APP, - SQLSRV_CONN_OPTION_CHARACTERSET, - SQLSRV_CONN_OPTION_CONN_POOLING, - SQLSRV_CONN_OPTION_DATABASE, - SQLSRV_CONN_OPTION_ENCRYPT, - SQLSRV_CONN_OPTION_FAILOVER_PARTNER, - SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, - SQLSRV_CONN_OPTION_MARS, - SQLSRV_CONN_OPTION_QUOTED_ID, - SQLSRV_CONN_OPTION_TRACE_FILE, - SQLSRV_CONN_OPTION_TRACE_ON, - SQLSRV_CONN_OPTION_TRANS_ISOLATION, - SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, - SQLSRV_CONN_OPTION_WSID, - SQLSRV_CONN_OPTION_ATTACHDBFILENAME, - SQLSRV_CONN_OPTION_APPLICATION_INTENT, - SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, - - // Driver specific connection options - SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, - -}; - - -#define NO_ATTRIBUTE -1 - -// type of connection attributes -enum CONN_ATTR_TYPE { - CONN_ATTR_INT, - CONN_ATTR_BOOL, - CONN_ATTR_STRING, - CONN_ATTR_INVALID, -}; - -// a connection option that includes the callback function that handles that option (e.g., adds it to the connection string or -// sets an attribute) -struct connection_option { - // the name of the option as passed in by the user - const char * sqlsrv_name; - unsigned int sqlsrv_len; - - unsigned int conn_option_key; - // the name of the option in the ODBC connection string - const char * odbc_name; - unsigned int odbc_len; - enum CONN_ATTR_TYPE value_type; - - // process the connection type - // return whether or not the function was successful in processing the connection option - void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC ); -}; - -// connection attribute functions -template -struct str_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), - static_cast(Z_STRLEN_P( value )) TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } - } -}; - -// simply add the parsed value to the connection string -struct conn_str_append_func { - - static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); -}; - -struct conn_null_func { - - static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ - TSRMLS_DC ); -}; - -// factory to create a connection (since they are subclassed to instantiate statements) -typedef sqlsrv_conn* (*driver_conn_factory)( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); - -// *** connection functions *** -sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, - const char* server, const char* uid, const char* pwd, - HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[], - void* driver, const char* driver_func TSRMLS_DC ); -void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ); -void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC ); -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ); -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); -bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); -size_t core_str_zval_is_true( zval* str_zval ); - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -struct stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ); -}; - -struct stmt_option_query_timeout : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_send_at_exec : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_buffered_query_limit : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -// used to hold the table for statment options -struct stmt_option { - - const char * name; // name of the statement option - unsigned int name_len; // name length - unsigned int key; - std::unique_ptr func; // callback that actually handles the work of the option - -}; - -// holds the stream param and the encoding that it was assigned -struct sqlsrv_stream { - - zval* stream_z; - SQLSRV_ENCODING encoding; - SQLUSMALLINT field_index; - SQLSMALLINT sql_type; - sqlsrv_stmt* stmt; - std::size_t stmt_index; - - sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) : - stream_z( str_z ), encoding( enc ) - { - } - - sqlsrv_stream() : stream_z( NULL ), encoding( SQLSRV_ENCODING_INVALID ), stmt( NULL ) - { - } -}; - -// close any active stream -void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); - -extern php_stream_wrapper g_sqlsrv_stream_wrapper; - -// resource constants used when registering the stream type with PHP -#define SQLSRV_STREAM_WRAPPER "sqlsrv" -#define SQLSRV_STREAM "sqlsrv_stream" - -// holds the output parameter information. Strings also need the encoding and other information for -// after processing. Only integer, float, and strings are allowable output parameters. -struct sqlsrv_output_param { - - zval* param_z; - SQLSRV_ENCODING encoding; - SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement - SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer - bool is_bool; - - // string output param constructor - sqlsrv_output_param( zval* p_z, SQLSRV_ENCODING enc, int num, SQLUINTEGER buffer_len ) : - param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false ) - { - } - - // every other type output parameter constructor - sqlsrv_output_param( zval* p_z, int num, bool is_bool ) : - param_z( p_z ), - param_num( num ), - encoding( SQLSRV_ENCODING_INVALID ), - original_buffer_len( -1 ), - is_bool( is_bool ) - { - } -}; - -// forward decls -struct sqlsrv_result_set; - -// *** Statement resource structure *** -struct sqlsrv_stmt : public sqlsrv_context { - - void free_param_data( TSRMLS_D ); - virtual void new_result_set( TSRMLS_D ); - - sqlsrv_conn* conn; // Connection that created this statement - - bool executed; // Whether the statement has been executed yet (used for error messages) - bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row - sqlsrv_result_set* current_results; // Current result set - SQLULEN cursor_type; // Type of cursor for the current result set - bool has_rows; // Has_rows is set if there are actual rows in the row set - bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called - int last_field_index; // last field retrieved by core_sqlsrv_get_field - bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the - // 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) - - // 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 - // memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold - std::deque param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter - zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP - zval output_params; // hold all the output parameters - zval param_streams; // track which streams to send data to the server - zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects - bool send_streams_at_exec; // send all stream data right after execution before returning - sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter - unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string - // to the server) - zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals - zval active_stream; // the currently active stream reading data from the database - - sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); - virtual ~sqlsrv_stmt( void ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string = false ) = 0; - -}; - -// *** field metadata struct *** -struct field_meta_data { - - sqlsrv_malloc_auto_ptr field_name; - SQLSMALLINT field_name_len; - SQLSMALLINT field_type; - SQLULEN field_size; - SQLULEN field_precision; - SQLSMALLINT field_scale; - SQLSMALLINT field_is_nullable; - - field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0), - field_scale (0), field_is_nullable(0) - { - } - - ~field_meta_data() - { - } -}; - -// *** statement constants *** -// unknown column size used by core_sqlsrv_bind_param when the user doesn't supply a value -const SQLULEN SQLSRV_UNKNOWN_SIZE = 0xffffffff; -const int SQLSRV_DEFAULT_SIZE = -1; // size given for an output parameter that doesn't really need one (e.g., int) - -// uninitialized query timeout value -const unsigned int QUERY_TIMEOUT_INVALID = 0xffffffff; - -// special buffered query constant -const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant - -// factory to create a statement -typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); - -// *** statement functions *** -sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, - const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ); -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, - SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, - SQLSMALLINT decimal_digits TSRMLS_DC ); -void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 ); -field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ); -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ); -void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, - _Out_ void*& field_value, _Out_ SQLLEN* field_length, bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC); -bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ); -void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true ); -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC ); -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ); -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ); -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); - - -//********************************************************************************************************************************* -// Result Set -//********************************************************************************************************************************* - -// Abstract the result set so that a result set can either be used as is from ODBC or buffered. -// This is not a complete abstraction of a result set. Only enough is abstracted to allow for -// information and capabilities normally not available when a result set is not buffered -// (e.g., forward only vs buffered means row count is available and cursor movement is possible). -// Otherwise, normal ODBC calls are still valid and should be used to get information about the -// result set (e.g., SQLNumResultCols). - -struct sqlsrv_result_set { - - sqlsrv_stmt* odbc; - - explicit sqlsrv_result_set( sqlsrv_stmt* ); - virtual ~sqlsrv_result_set( void ) { } - - virtual bool cached( int field_index ) = 0; - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0; - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC )= 0; - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0; - virtual SQLLEN row_count( TSRMLS_D ) = 0; -}; - -struct sqlsrv_odbc_result_set : public sqlsrv_result_set { - - explicit sqlsrv_odbc_result_set( sqlsrv_stmt* ); - virtual ~sqlsrv_odbc_result_set( void ); - - virtual bool cached( int field_index ) { return false; } - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ); - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); - virtual SQLLEN row_count( TSRMLS_D ); - - private: - // prevent invalid instantiations and assignments - sqlsrv_odbc_result_set( void ); - sqlsrv_odbc_result_set( sqlsrv_odbc_result_set& ); - sqlsrv_odbc_result_set& operator=( sqlsrv_odbc_result_set& ); -}; - -struct sqlsrv_buffered_result_set : public sqlsrv_result_set { - - struct meta_data { - SQLSMALLINT type; - SQLSMALLINT c_type; // convenience - SQLULEN offset; // in bytes - SQLULEN length; // in bytes - SQLSMALLINT scale; - - static const SQLULEN SIZE_UNKNOWN = 0; - }; - - // default maximum amount of memory that a buffered query can consume - #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings - static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB - static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; - - explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC ); - virtual ~sqlsrv_buffered_result_set( void ); - - virtual bool cached( int field_index ) { return true; } - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ); - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); - virtual SQLLEN row_count( TSRMLS_D ); - - // buffered result set specific - SQLSMALLINT column_count( void ) - { - return col_count; - } - - struct meta_data& col_meta_data( SQLSMALLINT i ) - { - return meta[i]; - } - - private: - // prevent invalid instantiations and assignments - sqlsrv_buffered_result_set( void ); - sqlsrv_buffered_result_set( sqlsrv_buffered_result_set& ); - sqlsrv_buffered_result_set& operator=( sqlsrv_buffered_result_set& ); - - HashTable* cache; // rows of data kept in index based hash table - SQLSMALLINT col_count; // number of columns in the current result set - meta_data* meta; // metadata for fields in the cache - SQLLEN current; // 1 based, 0 means before first row - sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here - SQLUSMALLINT last_field_index; // the last field data retrieved from - SQLLEN read_so_far; // position within string to read from (for partial reads of strings) - sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use - SQLLEN temp_length; // number of bytes in the temp conversion buffer - - typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; - - // two dimentional sparse matrix that holds the [from][to] functions that do conversions - static conv_matrix_t conv_matrix; - - // string conversion functions - SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // long conversion functions - SQLRETURN to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // double conversion functions - SQLRETURN to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // string to number conversion functions - // Future: See if these can be converted directly to template member functions - SQLRETURN string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // utility functions for conversions - unsigned char* get_row( void ); -}; - -//********************************************************************************************************************************* -// Utility -//********************************************************************************************************************************* - -// Simple macro to alleviate unused variable warnings. These are optimized out by the compiler. -// We use this since the unused variables are buried in the PHP_FUNCTION macro. -#define SQLSRV_UNUSED( var ) var; - -// do a heap check in debug mode, but only print errors, not all of the allocations -#define MEMCHECK_SILENT 1 - -// utility functions shared by multiple callers across files -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len); -bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len); -bool validate_string(char* string, SQLLEN& len); -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ); -wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, - unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); - -//********************************************************************************************************************************* -// Error handling routines and Predefined Errors -//********************************************************************************************************************************* - -enum SQLSRV_ERROR_CODES { - - SQLSRV_ERROR_ODBC, - SQLSRV_ERROR_DRIVER_NOT_INSTALLED, - SQLSRV_ERROR_ZEND_HASH, - SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, - SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, - SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, - SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, - SQLSRV_ERROR_ZEND_STREAM, - SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, - SQLSRV_ERROR_FETCH_PAST_END, - SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, - SQLSRV_ERROR_NO_FIELDS, - SQLSRV_ERROR_INVALID_TYPE, - SQLSRV_ERROR_FETCH_NOT_CALLED, - SQLSRV_ERROR_NO_DATA, - SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, - SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, - SQLSRV_ERROR_NEXT_RESULT_PAST_END, - SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, - SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, - SQLSRV_ERROR_INVALID_CONNECTION_KEY, - SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, - SQLSRV_ERROR_INVALID_OPTION_KEY, - SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, - SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, - SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, - SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, - SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, - SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, - SQLSRV_ERROR_STREAM_CREATE, - SQLSRV_ERROR_MARS_OFF, - SQLSRV_ERROR_FIELD_INDEX_ERROR, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, - SQLSRV_ERROR_INVALID_BUFFER_LIMIT, - - // Driver specific error codes starts from here. - SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, - -}; - -// the message returned by ODBC Driver 11 for SQL Server -static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command", - "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" }; - -// SQLSTATE for all internal errors -extern SQLCHAR IMSSP[]; - -// SQLSTATE for all internal warnings -extern SQLCHAR SSPWARN[]; - -// flags passed to sqlsrv_errors to filter its return values -enum error_handling_flags { - SQLSRV_ERR_ERRORS, - SQLSRV_ERR_WARNINGS, - SQLSRV_ERR_ALL -}; - -// *** internal error macros and functions *** -// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the -// errno is 1 based. It returns it as an array with 3 members: -// 1/SQLSTATE) sqlstate -// 2/code) driver specific error code -// 3/message) driver specific error message -// The fetch type determines if the indices are numeric, associative, or both. -bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, _Out_ sqlsrv_error_auto_ptr& error, - logging_severity severity TSRMLS_DC ); - -// format and return a driver specfic error -void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, - sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ); - - -// return the message for the HRESULT returned by GetLastError. Some driver errors use this to -// return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails. -const char* get_last_error_message( DWORD last_error = 0 ); - -// a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer -DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ); - -// convenience functions that overload either a reference or a pointer so we can use -// either in the CHECK_* functions. -inline bool call_error_handler( sqlsrv_context& ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) -{ - va_list print_params; - va_start( print_params, warning ); - bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); - va_end( print_params ); - return ignored; -} - -inline bool call_error_handler( sqlsrv_context* ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) -{ - va_list print_params; - va_start( print_params, warning ); - bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); - va_end( print_params ); - return ignored; -} - -// PHP equivalent of ASSERT. C asserts cause a dialog to show and halt the process which -// we don't want on a web server - -#define SQLSRV_ASSERT( condition, msg, ...) if( !(condition)) DIE( msg, __VA_ARGS__ ); - -#if defined( PHP_DEBUG ) - -#define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) \ - if( !(condition)) { \ - DIE (msg, __VA_ARGS__ ); \ - } - -#else - - #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) ((void)0) - -#endif - -// check to see if the sqlstate is 01004, truncated field retrieved. Used for retrieving large fields. -inline bool is_truncated_warning( SQLCHAR* state ) -{ -#if defined(ZEND_DEBUG) - if( state == NULL || strlen( reinterpret_cast( state )) != 5 ) { \ - DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \ - } -#endif - return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4'); -} - -// Macros for handling errors. These macros are simplified if statements that take boilerplate -// code down to a single line to avoid distractions in the code. - -#define CHECK_ERROR_EX( unique, condition, context, ssphp, ... ) \ - bool flag##unique = (condition); \ - bool ignored##unique = true; \ - if (flag##unique) { \ - ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, __VA_ARGS__ ); \ - } \ - if( !ignored##unique ) - -#define CHECK_ERROR_UNIQUE( unique, condition, context, ssphp, ...) \ - CHECK_ERROR_EX( unique, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_ERROR( condition, context, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, NULL, __VA_ARGS__ ) - -#define CHECK_CUSTOM_ERROR( condition, context, ssphp, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_SQL_ERROR( result, context, ... ) \ - SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ - CHECK_ERROR( result == SQL_ERROR, context, __VA_ARGS__ ) - -#define CHECK_WARNING_AS_ERROR_UNIQUE( unique, condition, context, ssphp, ... ) \ - bool ignored##unique = true; \ - if( condition ) { \ - ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, __VA_ARGS__ ); \ - } \ - if( !ignored##unique ) - -#define CHECK_SQL_WARNING_AS_ERROR( result, context, ... ) \ - CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__,( result == SQL_SUCCESS_WITH_INFO ), context, SQLSRV_ERROR_ODBC, __VA_ARGS__ ) - -#define CHECK_SQL_WARNING( result, context, ... ) \ - if( result == SQL_SUCCESS_WITH_INFO ) { \ - (void)call_error_handler( context, NULL TSRMLS_CC, /*warning*/ true, __VA_ARGS__ ); \ - } - -#define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \ - CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_ZEND_ERROR( zr, ctx, error, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, ( zr == FAILURE ), ctx, error, __VA_ARGS__ ) \ - -#define CHECK_SQL_ERROR_OR_WARNING( result, context, ... ) \ - SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ - bool ignored = true; \ - if( result == SQL_ERROR ) { \ - ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, false, __VA_ARGS__ ); \ - } \ - else if( result == SQL_SUCCESS_WITH_INFO ) { \ - ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, true TSRMLS_CC, __VA_ARGS__ ); \ - } \ - if( !ignored ) - -// throw an exception after it has been hooked into the custom error handler -#define THROW_CORE_ERROR( ctx, custom, ... ) \ - (void)call_error_handler( ctx, custom TSRMLS_CC, /*warning*/ false, __VA_ARGS__ ); \ - throw core::CoreException(); - -//********************************************************************************************************************************* -// ODBC/Zend function wrappers -//********************************************************************************************************************************* - -namespace core { - - // base exception for the driver - struct CoreException : public std::exception { - - CoreException() - { - } - }; - - inline void check_for_mars_error( sqlsrv_stmt* stmt, SQLRETURN r TSRMLS_DC ) - { - // We check for the 'connection busy' error caused by having MultipleActiveResultSets off - // and return a more helpful message prepended to the ODBC errors if that error occurs - if( !SQL_SUCCEEDED( r )) { - - SQLCHAR err_msg[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLSMALLINT len = 0; - - SQLRETURN r = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, - err_msg, SQL_MAX_MESSAGE_LENGTH, &len ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - - throw CoreException(); - } - std::size_t driver_version = stmt->conn->driver_version; - if( !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); - } - } - } - - // *** ODBC wrappers *** - - // wrap the ODBC functions to throw exceptions rather than use the return value to signal errors - // some of the signatures have been altered to be more convenient since the return value is no longer - // required to return the status of the call (e.g., SQLNumResultCols). - // These functions take the sqlsrv_context type. However, since the error handling code can alter - // the context to hold the error, they are not passed as const. - - inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) - { - SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, - diag_info_buffer, buffer_length, out_buffer_length ); - - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - - return r; - } - - inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle, - _Out_writes_(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); - CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) { - throw CoreException(); - } - } - - inline void SQLBindParameter( sqlsrv_stmt* stmt, - SQLUSMALLINT ParameterNumber, - SQLSMALLINT InputOutputType, - SQLSMALLINT ValueType, - SQLSMALLINT ParameterType, - SQLULEN ColumnSize, - SQLSMALLINT DecimalDigits, - _Inout_ SQLPOINTER ParameterValuePtr, - SQLLEN BufferLength, - _Inout_ SQLLEN * StrLen_Or_IndPtr - TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, - DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, - _Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC ) - { - SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, - buffer_length, out_buffer_length, field_type_num ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLCHAR* col_name, SQLSMALLINT col_name_length, - _Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size, - _Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, - data_type, col_size, decimal_digits, nullable); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLEndTran( SQLSMALLINT handleType, sqlsrv_conn* conn, SQLSMALLINT completionType TSRMLS_DC ) - { - SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success - inline SQLRETURN SQLExecDirect( sqlsrv_stmt* stmt, char* sql TSRMLS_DC ) - { - SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast( sql ), SQL_NTS ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - - throw CoreException(); - } - return r; - } - - inline SQLRETURN SQLExecDirectW( sqlsrv_stmt* stmt, wchar_t* wsql TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast( wsql ), SQL_NTS ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - // SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success - inline SQLRETURN SQLExecute( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLExecute( stmt->handle() ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return r; - } - - inline SQLRETURN SQLFetchScroll( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) - { - SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - - // wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine - inline void SQLFreeHandle( sqlsrv_context& ctx TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() ); - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} - } - - inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) - { - SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); - - if( r == SQL_NO_DATA ) - return r; - - CHECK_SQL_ERROR( r, stmt ) { - throw CoreException(); - } - - if( handle_warning ) { - CHECK_SQL_WARNING_AS_ERROR( r, stmt ) { - throw CoreException(); - } - } - - return r; - } - - - inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, _Out_ SQLPOINTER info_value, SQLSMALLINT buffer_len, - _Out_ SQLSMALLINT* str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - - inline void SQLGetTypeInfo( sqlsrv_stmt* stmt, SQLUSMALLINT data_type TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLGetTypeInfo( stmt->handle(), data_type ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - // SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set. - inline SQLRETURN SQLMoreResults( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r = ::SQLMoreResults( stmt->handle() ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return r; - } - - inline SQLSMALLINT SQLNumResultCols( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - SQLSMALLINT num_cols; - r = ::SQLNumResultCols( stmt->handle(), &num_cols ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return num_cols; - } - - // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more - // parameters or when the parameters are all processed. - inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, _Out_ SQLPOINTER* value_ptr_ptr TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - inline void SQLPrepareW( sqlsrv_stmt* stmt, SQLWCHAR * sql, SQLINTEGER sql_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLPrepareW( stmt->handle(), sql, sql_len ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLPutData( sqlsrv_stmt* stmt, SQLPOINTER data_ptr, SQLLEN strlen_or_ind TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline SQLLEN SQLRowCount( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - SQLLEN rows_affected; - - r = ::SQLRowCount( stmt->handle(), &rows_affected ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return rows_affected; - } - - - inline void SQLSetConnectAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - } - - - inline void SQLSetEnvAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len ); - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - } - - inline void SQLSetConnectAttr( sqlsrv_conn* conn, SQLINTEGER attribute, SQLPOINTER value_ptr, SQLINTEGER value_len TSRMLS_DC ) - { - SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - inline void SQLSetStmtAttr( sqlsrv_stmt* stmt, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - // *** zend wrappers *** - - //zend_resource_dtor sets the type of destroyed resources to -1 - #define RSRC_INVALID_TYPE -1 - - // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string - // so allocated memory inside of value_z should be released before assigning it to the new string - inline void sqlsrv_zval_stringl(zval* value_z, const char* str, const std::size_t str_len) - { - if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { - zend_string* temp_zstr = zend_string_init(str, str_len, 0); - zend_string_release(Z_STR_P(value_z)); - ZVAL_NEW_STR(value_z, temp_zstr); - } - else { - ZVAL_STRINGL(value_z, str, str_len); - } - } - - - // exception thrown when a zend function wrapped here fails. - - // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw - // exceptions when an error occurs. They are prefaced with sqlsrv_ because many of the zend functions are - // actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system. - // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error - // that can be thrown from it. - - inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, zend_ulong index, zval* value TSRMLS_DC) - { - int zr = ::add_index_zval( array, index, value ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_next_index_zval( sqlsrv_context& ctx, zval* array, zval* value TSRMLS_DC) - { - int zr = ::add_next_index_zval( array, value ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, const char* key TSRMLS_DC ) - { - int zr = ::add_assoc_null( array_z, key ); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, const char* key, zend_long val TSRMLS_DC ) - { - int zr = ::add_assoc_long( array_z, key, val ); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, const char* key, char* val, bool duplicate TSRMLS_DC ) - { - int zr = ::add_assoc_string(array_z, key, val); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - if (duplicate == 0) { - sqlsrv_free(val); - } - } - - inline void sqlsrv_array_init( sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) - { - int zr = ::array_init(new_array); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval* stream_z TSRMLS_DC ) - { - // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment - php_stream_from_zval_no_verify( stream, stream_z ); - CHECK_CUSTOM_ERROR( stream == NULL, ctx, SQLSRV_ERROR_ZEND_STREAM ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_get_current_data(sqlsrv_context& ctx, HashTable* ht, _Out_ zval*& output_data TSRMLS_DC) - { - int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_get_current_data_ptr(sqlsrv_context& ctx, HashTable* ht, _Out_ void*& output_data TSRMLS_DC) - { - int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, zend_ulong index TSRMLS_DC ) - { - int zr = ::zend_hash_index_del( ht, index ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, zend_ulong index, zval* data_z TSRMLS_DC ) - { - int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_update_ptr(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData TSRMLS_DC) - { - int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - - inline void sqlsrv_zend_hash_index_update_mem(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData, std::size_t size TSRMLS_DC) - { - int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, zval* data TSRMLS_DC ) - { - int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert_mem(sqlsrv_context& ctx, HashTable* ht, void* data, uint data_size TSRMLS_DC) - { - int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert_ptr(sqlsrv_context& ctx, HashTable* ht, void* data TSRMLS_DC) - { - int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, HashTable* ht, uint32_t initial_size, - dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) - { - ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); - } - - inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, zend_string* key, unsigned int key_len, zval* data, - unsigned int data_size, zval* pDest TSRMLS_DC ) - { - int zr = (pDest = ::zend_hash_add(ht, key, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - -template -sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) -{ - return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC ); -} - -template -sqlsrv_conn* allocate_conn( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) -{ - return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC ); -} - -} // namespace core - -#endif // CORE_SQLSRV_H +#ifndef CORE_SQLSRV_H +#define CORE_SQLSRV_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_sqlsrv.h +// +// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +//********************************************************************************************************************************* +// Includes +//********************************************************************************************************************************* + +#ifdef SQL_WCHART_CONVERT +#undef SQL_WCHART_CONVERT +#endif +#ifndef _WCHART_DEFINED +#define _WCHART_DEFINED +#endif + +#include "php.h" +#include "php_globals.h" +#include "php_ini.h" +#include "ext/standard/php_standard.h" +#include "ext/standard/info.h" + +#ifndef _WIN32 +#include "FormattedPrint.h" +#include "StringFunctions.h" +#endif // !_WIN32 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef PHP_WIN32 +#define PHP_SQLSRV_API __declspec(dllexport) +#else +#define PHP_SQLSRV_API +#endif + +// #define MultiByteToWideChar SystemLocale::ToUtf16 + + + +#define stricmp strcasecmp +#define strnicmp strncasecmp + +#ifndef _WIN32 +#define GetLastError() errno +#define SetLastError(err) errno=err + +typedef struct _OSVERSIONINFOA { + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + CHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usage +} OSVERSIONINFOA, *POSVERSIONINFOA, *LPOSVERSIONINFOA; +typedef OSVERSIONINFOA OSVERSIONINFO; +#endif // !_WIN32 + + +// OACR is an internal Microsoft static code analysis tool +#if defined(OACR) +#include +OACR_WARNING_PUSH +OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) +OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) +OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) +OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) +OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) +OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." ) +#else +// define to eliminate static analysis hints in the code +#define OACR_WARNING_SUPPRESS( warning, msg ) +#endif + +extern "C" { + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#if _MSC_VER >= 1400 +// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. +// php.h defines this constant as unsigned int which causes a compile error +// in ws2tcpip.h. Fortunately php.h allows an override by defining +// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define +// socklen_t here and override the php.h version. +typedef int socklen_t; +#define HAVE_SOCKLEN_T +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if ZEND_DEBUG +// debug build causes warning C4505 to pop up from the Zend header files +#pragma warning( disable: 4505 ) +#endif + +} // extern "C" + +#if defined(OACR) +OACR_WARNING_POP +#endif + +#ifdef _WIN32 +#include +#include +#endif // _WIN32 + +#if !defined(WC_ERR_INVALID_CHARS) +// imported from winnls.h as it isn't included by 5.3.0 +#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars +#endif + +// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when +// we use std::copy, which causes compilation to fail since we compile with warnings as errors. +#if defined(ZEND_DEBUG) && defined(inline) +#undef inline +#endif + +#include +#include +#include +#include +#include +#include +// included for SQL Server specific constants +#include "msodbcsql.h" + +#ifdef _WIN32 +#include +#endif // _WIN32 + +//********************************************************************************************************************************* +// Constants and Types +//********************************************************************************************************************************* + +// constants for maximums in SQL Server +const int SS_MAXCOLNAMELEN = 128; +const int SQL_SERVER_MAX_FIELD_SIZE = 8000; +const int SQL_SERVER_MAX_PRECISION = 38; +const int SQL_SERVER_MAX_TYPE_SIZE = 0; +const int SQL_SERVER_MAX_PARAMS = 2100; + +// max size of a date time string when converting from a DateTime object to a string +const int MAX_DATETIME_STRING_LEN = 256; + +// precision and scale for the date time types between servers +const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; +const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; +const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; +const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; + +// types for conversions on output parameters (though they can be used for input parameters, they are ignored) +enum SQLSRV_PHPTYPE { + MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type + SQLSRV_PHPTYPE_NULL = 1, + SQLSRV_PHPTYPE_INT, + SQLSRV_PHPTYPE_FLOAT, + SQLSRV_PHPTYPE_STRING, + SQLSRV_PHPTYPE_DATETIME, + SQLSRV_PHPTYPE_STREAM, + MAX_SQLSRV_PHPTYPE, // highest value for a php type + SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid +}; + +// encodings supported by this extension. These basically translate into the use of SQL_C_CHAR or SQL_C_BINARY when getting +// information as a string or a stream. +enum SQLSRV_ENCODING { + SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables. + SQLSRV_ENCODING_DEFAULT, // use what is the connection's default for a statement, use system if a connection + SQLSRV_ENCODING_BINARY, // use SQL_C_BINARY when using SQLGetData + SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData + SQLSRV_ENCODING_SYSTEM = SQLSRV_ENCODING_CHAR, + SQLSRV_ENCODING_UTF8 = CP_UTF8, +}; + +// the array keys used when returning a row via sqlsrv_fetch_array and sqlsrv_fetch_object. +enum SQLSRV_FETCH_TYPE { + MIN_SQLSRV_FETCH = 1, // lowest value for fetch type + SQLSRV_FETCH_NUMERIC = 1, // return an array with only numeric indices + SQLSRV_FETCH_ASSOC = 2, // return an array with keys made from the field names + SQLSRV_FETCH_BOTH = 3, // return an array indexed with both numbers and keys + MAX_SQLSRV_FETCH = 3, // highest value for fetch type +}; + +// buffer size of a sql state (including the null character) +const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1; + +// buffer size allocated to retrieve data from a PHP stream. This number +// was chosen since PHP doesn't return more than 8k at a time even if +// the amount requested was more. +const int PHP_STREAM_BUFFER_SIZE = 8192; + +// SQL types for parameters encoded in an integer. The type corresponds to the SQL type ODBC constants. +// The size is the column size or precision, and scale is the decimal digits for precise numeric types. + +union sqlsrv_sqltype { + struct typeinfo_t { + int type:9; + int size:14; + int scale:8; + } typeinfo; + + zend_long value; +}; + + +// SQLSRV PHP types (as opposed to the Zend PHP type constants). Contains the type (see SQLSRV_PHPTYPE) +// and the encoding for strings and streams (see SQLSRV_ENCODING) + +union sqlsrv_phptype { + + struct typeinfo_t { + unsigned type:8; + unsigned encoding:16; + } typeinfo; + + zend_long value; +}; + +// static assert for enforcing compile time conditions +template +struct sqlsrv_static_assert; + +template <> +struct sqlsrv_static_assert { static const int value = 1; }; + +#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() ) + + +//********************************************************************************************************************************* +// Logging +//********************************************************************************************************************************* +// log_callback +// a driver specific callback for logging messages +// severity - severity of the message: notice, warning, or error +// msg - the message to log in a FormatMessage style formatting +// print_args - args to the message +typedef void (*log_callback)( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); + +// each driver must register a log callback. This should be the first thing a driver does. +void core_sqlsrv_register_logger( log_callback ); + +// a simple wrapper around a PHP error logging function. +void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ... ); + +// a macro to make it convenient to use the function. +#define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, ## __VA_ARGS__ ) + +// mask for filtering which severities are written to the log +enum logging_severity { + SEV_ERROR = 0x01, + SEV_WARNING = 0x02, + SEV_NOTICE = 0x04, + SEV_ALL = -1, +}; + +// Kill the PHP process and log the message to PHP +void die( const char* msg, ... ); +#define DIE( msg, ... ) { die( msg, ## __VA_ARGS__ ); } + + +//********************************************************************************************************************************* +// Resource/Memory Management +//********************************************************************************************************************************* + +// the macro max is defined and overrides the call to max in the allocator class +#pragma push_macro( "max" ) +#undef max + +// new memory allocation/free debugging facilities to help us verify that all allocations are being +// released in a timely manner and not just at the end of the script. +// Zend has memory logging and checking, but it can generate a lot of noise for just one extension. +// It's meant for internal use but might be useful for people adding features to our extension. +// To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases +// must be done with sqlsrv_malloc and sqlsrv_free. +// #define SQLSRV_MEM_DEBUG 1 +#if defined( PHP_DEBUG ) && !defined( ZTS ) && defined( SQLSRV_MEM_DEBUG ) + +inline void* sqlsrv_malloc_trace( size_t size, const char* file, int line ) +{ + void* ptr = emalloc( size ); + LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); + return ptr; +} + +inline void* sqlsrv_malloc_trace( size_t element_count, size_t element_size, size_t extra, const char* file, int line ) +{ + OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); + + if(( element_count > 0 && element_size > 0 ) && + ( element_count > element_size * element_count || element_size > element_size * element_count )) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count > element_size * element_count + extra ) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count + extra == 0 ) { + DIE( "Allocation size must be more than 0" ); + } + + void* ptr = emalloc( element_size * element_count + extra ); + LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); + return ptr; +} + +inline void* sqlsrv_realloc_trace( void* buffer, size_t size, const char* file, int line ) +{ + void* ptr = erealloc( original, size ); + LOG( SEV_NOTICE, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original ); + return ptr; +} + +inline void sqlsrv_free_trace( void* ptr, const char* file, int line ) +{ + LOG( SEV_NOTICE, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line ); + efree( ptr ); +} + +#define sqlsrv_malloc( size ) sqlsrv_malloc_trace( size, __FILE__, __LINE__ ) +#define sqlsrv_malloc( count, size, extra ) sqlsrv_malloc_trace( count, size, extra, __FILE__, __LINE__ ) +#define sqlsrv_realloc( buffer, size ) sqlsrv_realloc_trace( buffer, size, __FILE__, __LINE__ ) +#define sqlsrv_free( ptr ) sqlsrv_free_trace( ptr, __FILE__, __LINE__ ) + +#else + +inline void* sqlsrv_malloc( size_t size ) +{ + return emalloc( size ); +} + +inline void* sqlsrv_malloc( size_t element_count, size_t element_size, size_t extra ) +{ + OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); + + if(( element_count > 0 && element_size > 0 ) && + ( element_count > element_size * element_count || element_size > element_size * element_count )) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count > element_size * element_count + extra ) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count + extra == 0 ) { + DIE( "Allocation size must be more than 0" ); + } + + return emalloc( element_size * element_count + extra ); +} + +inline void* sqlsrv_realloc( void* buffer, size_t size ) +{ + return erealloc( buffer, size ); +} + +inline void sqlsrv_free( void* ptr ) +{ + efree( ptr ); +} + +#endif + +// trait class that allows us to assign const types to an auto_ptr +template +struct remove_const { + typedef T type; +}; + +template +struct remove_const { + typedef T* type; +}; + +// allocator that uses the zend memory manager to manage memory +// this allows us to use STL classes that still work with Zend objects +template +struct sqlsrv_allocator { + + // typedefs used by the STL classes + typedef T value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + // conversion typedef (used by list and other STL classes) + template + struct rebind { + typedef sqlsrv_allocator other; + }; + + inline sqlsrv_allocator() {} + inline ~sqlsrv_allocator() {} + inline sqlsrv_allocator( sqlsrv_allocator const& ) {} + template + inline sqlsrv_allocator( sqlsrv_allocator const& ) {} + + // address (doesn't work if the class defines operator&) + inline pointer address( reference r ) + { + return &r; + } + + inline const_pointer address( const_reference r ) + { + return &r; + } + + // memory allocation/deallocation + inline pointer allocate( size_type cnt, + typename std::allocator::const_pointer = 0 ) + { + return reinterpret_cast( sqlsrv_malloc(cnt, sizeof (T), 0)); + } + + inline void deallocate( pointer p, size_type ) + { + sqlsrv_free(p); + } + + // size + inline size_type max_size( void ) const + { + return std::numeric_limits::max() / sizeof(T); + } + + // object construction/destruction + inline void construct( pointer p, const T& t ) + { + new(p) T(t); + } + + inline void destroy(pointer p) + { + p->~T(); + } + + // equality operators + inline bool operator==( sqlsrv_allocator const& ) + { + return true; + } + + inline bool operator!=( sqlsrv_allocator const& a ) + { + return !operator==(a); + } +}; + + +// base class for auto_ptrs that we define below. It provides common operators and functions +// used by all the classes. +template +class sqlsrv_auto_ptr { + +public: + + sqlsrv_auto_ptr( void ) : _ptr( NULL ) + { + } + + ~sqlsrv_auto_ptr( void ) + { + static_cast(this)->reset( NULL ); + } + + // call when ownership is transferred + void transferred( void ) + { + _ptr = NULL; + } + + // explicit function to get the pointer. + T* get( void ) const + { + return _ptr; + } + + // cast operator to allow auto_ptr to be used where a normal const * can be. + operator const T* () const + { + return _ptr; + } + + // cast operator to allow auto_ptr to be used where a normal pointer can be. + operator typename remove_const::type () const + { + return _ptr; + } + + operator bool() const + { + return _ptr != NULL; + } + + // 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[]( int index ) const + { + 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[]( unsigned int index ) const + { + 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[]( long index ) const + { + return _ptr[ index ]; + } + + + #ifdef __WIN64 + // 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[](std::size_t index) const + { + return _ptr[index]; + } + #endif + + // 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[]( unsigned short index ) const + { + return _ptr[ index ]; + } + + // access elements of a structure through the auto ptr + T* const operator->( void ) const + { + return _ptr; + } + + // value from reference operator (i.e., i = *(&i); or *i = blah;) + T& operator*() const + { + return *_ptr; + } + + // allow the use of the address-of operator to simulate a **. + // Note: this operator conflicts with storing these within an STL container. If you need + // to do that, then redefine this as getpp and change instances of &auto_ptr to auto_ptr.getpp() + T** operator&( void ) + { + return &_ptr; + } + +protected: + + sqlsrv_auto_ptr( T* ptr ) : + _ptr( ptr ) + { + } + + sqlsrv_auto_ptr( sqlsrv_auto_ptr& src ) + { + if( _ptr ) { + static_cast(this)->reset( src._ptr ); + } + src.transferred(); + } + + // assign a new pointer to the auto_ptr. It will free the previous memory block + // because ownership is deemed finished. + T* operator=( T* ptr ) + { + static_cast( this )->reset( ptr ); + + return ptr; + } + + T* _ptr; +}; + +// an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer +// in a variable of sqlsrv_malloc_auto_ptr. sqlsrv_malloc_auto_ptr will "own" that block and assure that it is +// freed until the variable is destroyed (out of scope) or ownership is transferred using the function +// "transferred". +// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. + +template +class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr > { + +public: + + sqlsrv_malloc_auto_ptr( void ) : + sqlsrv_auto_ptr >( NULL ) + { + } + + sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) : + sqlsrv_auto_ptr >( src ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( T* ptr = NULL ) + { + if( sqlsrv_auto_ptr >::_ptr ) + sqlsrv_free( (void*) sqlsrv_auto_ptr >::_ptr ); + sqlsrv_auto_ptr >::_ptr = ptr; + } + + T* operator=( T* ptr ) + { + return sqlsrv_auto_ptr >::operator=( ptr ); + } + + void operator=( sqlsrv_malloc_auto_ptr& src ) + { + T* p = src.get(); + src.transferred(); + this->_ptr = p; + } + + // DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. + // has the same parameter list as sqlsrv_realloc: new_size is the size in bytes of the newly allocated buffer + void resize( size_t new_size ) + { + sqlsrv_auto_ptr >::_ptr = reinterpret_cast( sqlsrv_realloc( sqlsrv_auto_ptr >::_ptr, new_size )); + } +}; + + +// auto ptr for Zend hash tables. Used to clean up a hash table allocated when +// something caused an early exit from the function. This is used when the hash_table is +// allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr. + +class hash_auto_ptr : public sqlsrv_auto_ptr { + +public: + + hash_auto_ptr( void ) : + sqlsrv_auto_ptr( NULL ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( HashTable* ptr = NULL ) + { + if( _ptr ) { + zend_hash_destroy( _ptr ); + FREE_HASHTABLE( _ptr ); + } + _ptr = ptr; + } + + HashTable* operator=( HashTable* ptr ) + { + return sqlsrv_auto_ptr::operator=( ptr ); + } + +private: + + hash_auto_ptr( HashTable const& hash ); + + hash_auto_ptr( hash_auto_ptr const& hash ); +}; + + +// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr. +// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed +// (out of scope) or ownership is transferred using the function "transferred". + +class zval_auto_ptr : public sqlsrv_auto_ptr { + +public: + + zval_auto_ptr( void ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( zval* ptr = NULL ) + { + if( _ptr ) + zval_ptr_dtor(_ptr ); + _ptr = ptr; + } + + zval* operator=( zval* ptr ) + { + return sqlsrv_auto_ptr::operator=( ptr ); + } + + +private: + + zval_auto_ptr( const zval_auto_ptr& src ); +}; + +#pragma pop_macro( "max" ) + + +//********************************************************************************************************************************* +// sqlsrv_error +//********************************************************************************************************************************* + +// *** PHP specific errors *** +// sqlsrv errors are held in a structure of this type used by the driver handle_error functions +// format is a flag that tells the driver error handler functions if there are parameters to use with FormatMessage +// into the error message before returning it. + +// base class which can be instatiated with aggregates (see error constants) +struct sqlsrv_error_const { + + SQLCHAR* sqlstate; + SQLCHAR* native_message; + SQLINTEGER native_code; + bool format; +}; + +// subclass which is used by the core layer to instantiate ODBC errors +struct sqlsrv_error : public sqlsrv_error_const { + + sqlsrv_error( void ) + { + sqlstate = NULL; + native_message = NULL; + native_code = -1; + format = false; + } + + sqlsrv_error( SQLCHAR* sql_state, SQLCHAR* message, SQLINTEGER code, bool printf_format = false ) + { + sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); + native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); + strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); + strcpy_s( reinterpret_cast( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast( message )); + native_code = code; + format = printf_format; + } + + sqlsrv_error( sqlsrv_error_const const& prototype ) + { + sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format ); + } + + ~sqlsrv_error( void ) + { + if( sqlstate != NULL ) { + sqlsrv_free( sqlstate ); + } + if( native_message != NULL ) { + sqlsrv_free( native_message ); + } + } +}; + + +// an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete +class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr { + +public: + + sqlsrv_error_auto_ptr( void ) : + sqlsrv_auto_ptr( NULL ) + { + } + + sqlsrv_error_auto_ptr( sqlsrv_error_auto_ptr const& src ) : + sqlsrv_auto_ptr( (sqlsrv_error_auto_ptr&) src ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( sqlsrv_error* ptr = NULL ) + { + if( _ptr ) { + _ptr->~sqlsrv_error(); + sqlsrv_free( (void*) _ptr ); + } + _ptr = ptr; + } + + sqlsrv_error* operator=( sqlsrv_error* ptr ) + { + return sqlsrv_auto_ptr::operator=( ptr ); + } + + // unlike traditional assignment operators, the chained assignment of an auto_ptr doesn't make much + // sense. Only the last one would have anything in it. + void operator=( sqlsrv_error_auto_ptr& src ) + { + sqlsrv_error* p = src.get(); + src.transferred(); + reset( p ); + } +}; + + +//********************************************************************************************************************************* +// Context +//********************************************************************************************************************************* + +class sqlsrv_context; +struct sqlsrv_conn; + +// error_callback +// a driver specific callback for processing errors. +// ctx - the context holding the handles +// sqlsrv_error_code - specific error code to return. +typedef bool (*error_callback)( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool error TSRMLS_DC, va_list* print_args ); + +// sqlsrv_context +// a context holds relevant information to be passed with a connection and statement objects. + +class sqlsrv_context { + + public: + + sqlsrv_context( SQLSMALLINT type, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : + handle_( SQL_NULL_HANDLE ), + handle_type_( type ), + name_( NULL ), + err_( e ), + driver_( drv ), + last_error_(), + encoding_( encoding ) + { + } + + sqlsrv_context( SQLHANDLE h, SQLSMALLINT t, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : + handle_( h ), + handle_type_( t ), + name_( NULL ), + err_( e ), + driver_( drv ), + last_error_(), + encoding_( encoding ) + { + } + + sqlsrv_context( sqlsrv_context const& ctx ) : + handle_( ctx.handle_ ), + handle_type_( ctx.handle_type_ ), + name_( ctx.name_ ), + err_( ctx.err_ ), + driver_( ctx.driver_ ), + last_error_( ctx.last_error_ ) + { + } + + virtual ~sqlsrv_context() + { + } + + void set_func( const char* f ) + { + name_ = f; + } + + void set_last_error( sqlsrv_error_auto_ptr& last_error ) + { + last_error_ = last_error; + } + + sqlsrv_error_auto_ptr& last_error( void ) + { + return last_error_; + } + + // since the primary responsibility of a context is to hold an ODBC handle, we + // provide these convenience operators for using them interchangeably + operator SQLHANDLE ( void ) const + { + return handle_; + } + + error_callback error_handler( void ) const + { + return err_; + } + + SQLHANDLE handle( void ) const + { + return handle_; + } + + SQLSMALLINT handle_type( void ) const + { + return handle_type_; + } + + const char* func( void ) const + { + return name_; + } + + void* driver( void ) const + { + return driver_; + } + + void set_driver( void* driver ) + { + this->driver_ = driver; + } + + void invalidate( void ) + { + if( handle_ != SQL_NULL_HANDLE ) { + ::SQLFreeHandle( handle_type_, handle_ ); + + last_error_.reset(); + } + handle_ = SQL_NULL_HANDLE; + } + + bool valid( void ) + { + return handle_ != SQL_NULL_HANDLE; + } + + SQLSRV_ENCODING encoding( void ) const + { + return encoding_; + } + + void set_encoding( SQLSRV_ENCODING e ) + { + encoding_ = e; + } + + private: + SQLHANDLE handle_; // ODBC handle for this context + SQLSMALLINT handle_type_; // type of the ODBC handle + const char* name_; // function name currently executing this context + error_callback err_; // driver error callback if error occurs in core layer + void* driver_; // points back to the driver for PDO + sqlsrv_error_auto_ptr last_error_; // last error that happened on this object + SQLSRV_ENCODING encoding_; // encoding of the context +}; + +const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista + +// maps an IANA encoding to a code page +struct sqlsrv_encoding { + + const char* iana; + size_t iana_len; + unsigned int code_page; + bool not_for_connection; + + sqlsrv_encoding( const char* iana, unsigned int code_page, bool not_for_conn = false ): + iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn ) + { + } +}; + + +//********************************************************************************************************************************* +// Initialization +//********************************************************************************************************************************* + +// variables set during initialization +extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in +extern HashTable* g_encodings; // encodings supported by this driver + +void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ); +void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ); + +// environment context used by sqlsrv_connect for when a connection error occurs. +struct sqlsrv_henv { + + sqlsrv_context ctx; + + sqlsrv_henv( SQLHANDLE handle, error_callback e, void* drv ) : + ctx( handle, SQL_HANDLE_ENV, e, drv ) + { + } +}; + + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* + +// supported server versions (determined at connection time) +enum SERVER_VERSION { + SERVER_VERSION_UNKNOWN = -1, + SERVER_VERSION_2000 = 8, + SERVER_VERSION_2005, + SERVER_VERSION_2008, // use this for anything 2008 or later +}; + +// supported driver versions. +enum DRIVER_VERSION : size_t { + MIN = 0, + ODBC_DRIVER_13 = MIN, + ODBC_DRIVER_11 = 1, + MAX = ODBC_DRIVER_11, +}; + +// forward decl +struct sqlsrv_stmt; +struct stmt_option; + +// *** connection resource structure *** +// this is the resource structure returned when a connection is made. +struct sqlsrv_conn : public sqlsrv_context { + + // instance variables + SERVER_VERSION server_version; // version of the server that we're connected to + + DRIVER_VERSION driver_version; + + // initialize with default values + sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) : + sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) + { + server_version = SERVER_VERSION_UNKNOWN; + driver_version = ODBC_DRIVER_13; + } + + // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be + // called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating + // the sqlsrv_conn with a connection. +}; + +enum SQLSRV_STMT_OPTIONS { + + SQLSRV_STMT_OPTION_INVALID, + SQLSRV_STMT_OPTION_QUERY_TIMEOUT, + SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, + SQLSRV_STMT_OPTION_SCROLLABLE, + SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, + + // Driver specific connection options + SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, + +}; + +namespace ODBCConnOptions { + +const char APP[] = "APP"; +const char ApplicationIntent[] = "ApplicationIntent"; +const char AttachDBFileName[] = "AttachDbFileName"; +const char CharacterSet[] = "CharacterSet"; +const char ConnectionPooling[] = "ConnectionPooling"; +const char Database[] = "Database"; +const char Encrypt[] = "Encrypt"; +const char Failover_Partner[] = "Failover_Partner"; +const char LoginTimeout[] = "LoginTimeout"; +const char MARS_ODBC[] = "MARS_Connection"; +const char MultiSubnetFailover[] = "MultiSubnetFailover"; +const char QuotedId[] = "QuotedId"; +const char TraceFile[] = "TraceFile"; +const char TraceOn[] = "TraceOn"; +const char TrustServerCertificate[] = "TrustServerCertificate"; +const char TransactionIsolation[] = "TransactionIsolation"; +const char WSID[] = "WSID"; +const char UID[] = "UID"; +const char PWD[] = "PWD"; +const char SERVER[] = "Server"; + +} + +enum SQLSRV_CONN_OPTIONS { + + SQLSRV_CONN_OPTION_INVALID, + SQLSRV_CONN_OPTION_APP, + SQLSRV_CONN_OPTION_CHARACTERSET, + SQLSRV_CONN_OPTION_CONN_POOLING, + SQLSRV_CONN_OPTION_DATABASE, + SQLSRV_CONN_OPTION_ENCRYPT, + SQLSRV_CONN_OPTION_FAILOVER_PARTNER, + SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, + SQLSRV_CONN_OPTION_MARS, + SQLSRV_CONN_OPTION_QUOTED_ID, + SQLSRV_CONN_OPTION_TRACE_FILE, + SQLSRV_CONN_OPTION_TRACE_ON, + SQLSRV_CONN_OPTION_TRANS_ISOLATION, + SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, + SQLSRV_CONN_OPTION_WSID, + SQLSRV_CONN_OPTION_ATTACHDBFILENAME, + SQLSRV_CONN_OPTION_APPLICATION_INTENT, + SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, + + // Driver specific connection options + SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, + +}; + + +#define NO_ATTRIBUTE -1 + +// type of connection attributes +enum CONN_ATTR_TYPE { + CONN_ATTR_INT, + CONN_ATTR_BOOL, + CONN_ATTR_STRING, + CONN_ATTR_INVALID, +}; + +// a connection option that includes the callback function that handles that option (e.g., adds it to the connection string or +// sets an attribute) +struct connection_option { + // the name of the option as passed in by the user + const char * sqlsrv_name; + unsigned int sqlsrv_len; + + unsigned int conn_option_key; + // the name of the option in the ODBC connection string + const char * odbc_name; + unsigned int odbc_len; + enum CONN_ATTR_TYPE value_type; + + // process the connection type + // return whether or not the function was successful in processing the connection option + void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC ); +}; + +// simply add the parsed value to the connection string +struct conn_str_append_func { + + static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); +}; + +struct conn_null_func { + + static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ + TSRMLS_DC ); +}; + +// factory to create a connection (since they are subclassed to instantiate statements) +typedef sqlsrv_conn* (*driver_conn_factory)( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); + +// *** connection functions *** +sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, + const char* server, const char* uid, const char* pwd, + HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[], + void* driver, const char* driver_func TSRMLS_DC ); +void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ); +void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC ); +void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ); +void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); +bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); +size_t core_str_zval_is_true( zval* str_zval ); + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +struct stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ); +}; + +struct stmt_option_query_timeout : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_send_at_exec : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_buffered_query_limit : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); +}; + +// used to hold the table for statment options +struct stmt_option { + + const char * name; // name of the statement option + unsigned int name_len; // name length + unsigned int key; + std::unique_ptr func; // callback that actually handles the work of the option + +}; + +// holds the stream param and the encoding that it was assigned +struct sqlsrv_stream { + + zval* stream_z; + SQLSRV_ENCODING encoding; + SQLUSMALLINT field_index; + SQLSMALLINT sql_type; + sqlsrv_stmt* stmt; + + sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) : + stream_z( str_z ), encoding( enc ), field_index( 0 ), sql_type( SQL_UNKNOWN_TYPE ), stmt( NULL ) + { + } + + sqlsrv_stream() : stream_z( NULL ), encoding( SQLSRV_ENCODING_INVALID ), field_index( 0 ), sql_type( SQL_UNKNOWN_TYPE ), stmt( NULL ) + { + } +}; + +// close any active stream +void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); + +extern php_stream_wrapper g_sqlsrv_stream_wrapper; + +// resource constants used when registering the stream type with PHP +#define SQLSRV_STREAM_WRAPPER "sqlsrv" +#define SQLSRV_STREAM "sqlsrv_stream" + +// holds the output parameter information. Strings also need the encoding and other information for +// after processing. Only integer, float, and strings are allowable output parameters. +struct sqlsrv_output_param { + + zval* param_z; + SQLSRV_ENCODING encoding; + SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement + SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer + bool is_bool; + + // string output param constructor + sqlsrv_output_param( zval* p_z, SQLSRV_ENCODING enc, int num, SQLUINTEGER buffer_len ) : + param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false ) + { + } + + // every other type output parameter constructor + sqlsrv_output_param( zval* p_z, int num, bool is_bool ) : + param_z( p_z ), + encoding( SQLSRV_ENCODING_INVALID ), + param_num( num ), + original_buffer_len( -1 ), + is_bool( is_bool ) + { + } +}; + +// forward decls +struct sqlsrv_result_set; + +// *** Statement resource structure *** +struct sqlsrv_stmt : public sqlsrv_context { + + void free_param_data( TSRMLS_D ); + virtual void new_result_set( TSRMLS_D ); + + sqlsrv_conn* conn; // Connection that created this statement + + bool executed; // Whether the statement has been executed yet (used for error messages) + bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row + sqlsrv_result_set* current_results; // Current result set + SQLULEN cursor_type; // Type of cursor for the current result set + bool has_rows; // Has_rows is set if there are actual rows in the row set + bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called + int last_field_index; // last field retrieved by core_sqlsrv_get_field + bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the + // 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) + + // 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 + // memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold + std::deque param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter + zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP + zval output_params; // hold all the output parameters + zval param_streams; // track which streams to send data to the server + zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects + bool send_streams_at_exec; // send all stream data right after execution before returning + sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter + unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string + // to the server) + zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals + zval active_stream; // the currently active stream reading data from the database + + sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); + virtual ~sqlsrv_stmt( void ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) = 0; + +}; + +// *** field metadata struct *** +struct field_meta_data { + + sqlsrv_malloc_auto_ptr field_name; + SQLSMALLINT field_name_len; + SQLSMALLINT field_type; + SQLULEN field_size; + SQLULEN field_precision; + SQLSMALLINT field_scale; + SQLSMALLINT field_is_nullable; + + field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0), + field_scale (0), field_is_nullable(0) + { + } + + ~field_meta_data() + { + } +}; + +// *** statement constants *** +// unknown column size used by core_sqlsrv_bind_param when the user doesn't supply a value +const SQLULEN SQLSRV_UNKNOWN_SIZE = 0xffffffff; +const int SQLSRV_DEFAULT_SIZE = -1; // size given for an output parameter that doesn't really need one (e.g., int) + +// uninitialized query timeout value +const unsigned int QUERY_TIMEOUT_INVALID = 0xffffffff; + +// special buffered query constant +#ifndef _WIN32 +const size_t SQLSRV_CURSOR_BUFFERED = 42; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant +#else +const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant +#endif // !_WIN32 + +// factory to create a statement +typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); + +// *** statement functions *** +sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, + const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ); +void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, + SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, + SQLSMALLINT decimal_digits TSRMLS_DC ); +void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 ); +field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ); +bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ); +void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, + _Out_ void*& field_value, _Out_ SQLLEN* field_length, bool cache_field, + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC); +bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ); +void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true ); +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC ); +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ); +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ); +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); +void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); +bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); + + +//********************************************************************************************************************************* +// Result Set +//********************************************************************************************************************************* + +// Abstract the result set so that a result set can either be used as is from ODBC or buffered. +// This is not a complete abstraction of a result set. Only enough is abstracted to allow for +// information and capabilities normally not available when a result set is not buffered +// (e.g., forward only vs buffered means row count is available and cursor movement is possible). +// Otherwise, normal ODBC calls are still valid and should be used to get information about the +// result set (e.g., SQLNumResultCols). + +struct sqlsrv_result_set { + + sqlsrv_stmt* odbc; + + explicit sqlsrv_result_set( sqlsrv_stmt* ); + virtual ~sqlsrv_result_set( void ) { } + + virtual bool cached( int field_index ) = 0; + virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0; + virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC )= 0; + virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; + virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0; + virtual SQLLEN row_count( TSRMLS_D ) = 0; +}; + +struct sqlsrv_odbc_result_set : public sqlsrv_result_set { + + explicit sqlsrv_odbc_result_set( sqlsrv_stmt* ); + virtual ~sqlsrv_odbc_result_set( void ); + + virtual bool cached( int field_index ) { return false; } + virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); + virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ); + virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); + virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); + virtual SQLLEN row_count( TSRMLS_D ); + + private: + // prevent invalid instantiations and assignments + sqlsrv_odbc_result_set( void ); + sqlsrv_odbc_result_set( sqlsrv_odbc_result_set& ); + sqlsrv_odbc_result_set& operator=( sqlsrv_odbc_result_set& ); +}; + +struct sqlsrv_buffered_result_set : public sqlsrv_result_set { + + struct meta_data { + SQLSMALLINT type; + SQLSMALLINT c_type; // convenience + SQLULEN offset; // in bytes + SQLULEN length; // in bytes + SQLSMALLINT scale; + + static const SQLULEN SIZE_UNKNOWN = 0; + }; + + // default maximum amount of memory that a buffered query can consume + #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings + static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB + static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; + + explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC ); + virtual ~sqlsrv_buffered_result_set( void ); + + virtual bool cached( int field_index ) { return true; } + virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); + virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ); + virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); + virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); + virtual SQLLEN row_count( TSRMLS_D ); + + // buffered result set specific + SQLSMALLINT column_count( void ) + { + return col_count; + } + + struct meta_data& col_meta_data( SQLSMALLINT i ) + { + return meta[i]; + } + + private: + // prevent invalid instantiations and assignments + sqlsrv_buffered_result_set( void ); + sqlsrv_buffered_result_set( sqlsrv_buffered_result_set& ); + sqlsrv_buffered_result_set& operator=( sqlsrv_buffered_result_set& ); + + HashTable* cache; // rows of data kept in index based hash table + SQLSMALLINT col_count; // number of columns in the current result set + meta_data* meta; // metadata for fields in the cache + SQLLEN current; // 1 based, 0 means before first row + sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here + SQLUSMALLINT last_field_index; // the last field data retrieved from + SQLLEN read_so_far; // position within string to read from (for partial reads of strings) + sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use + SQLLEN temp_length; // number of bytes in the temp conversion buffer + + typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; + + // two dimentional sparse matrix that holds the [from][to] functions that do conversions + static conv_matrix_t conv_matrix; + + // string conversion functions + SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + + // long conversion functions + SQLRETURN to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + + // double conversion functions + SQLRETURN to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + + // string to number conversion functions + // Future: See if these can be converted directly to template member functions + SQLRETURN string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + + // utility functions for conversions + unsigned char* get_row( void ); +}; + +//********************************************************************************************************************************* +// Utility +//********************************************************************************************************************************* + +// Simple macro to alleviate unused variable warnings. These are optimized out by the compiler. +// We use this since the unused variables are buried in the PHP_FUNCTION macro. +#define SQLSRV_UNUSED( var ) var; + +// do a heap check in debug mode, but only print errors, not all of the allocations +#define MEMCHECK_SILENT 1 + +// utility functions shared by multiple callers across files +bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len); +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len); +bool validate_string(char* string, SQLLEN& len); +bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const SQLWCHAR* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ); +SQLWCHAR* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); + +//********************************************************************************************************************************* +// Error handling routines and Predefined Errors +//********************************************************************************************************************************* + +enum SQLSRV_ERROR_CODES { + + SQLSRV_ERROR_ODBC, + SQLSRV_ERROR_DRIVER_NOT_INSTALLED, + SQLSRV_ERROR_ZEND_HASH, + SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, + SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, + SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, + SQLSRV_ERROR_ZEND_STREAM, + SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, + SQLSRV_ERROR_FETCH_PAST_END, + SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, + SQLSRV_ERROR_NO_FIELDS, + SQLSRV_ERROR_INVALID_TYPE, + SQLSRV_ERROR_FETCH_NOT_CALLED, + SQLSRV_ERROR_NO_DATA, + SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, + SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, + SQLSRV_ERROR_NEXT_RESULT_PAST_END, + SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, + SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, + SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, + SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, + SQLSRV_ERROR_INVALID_CONNECTION_KEY, + SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, + SQLSRV_ERROR_INVALID_OPTION_KEY, + SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, + SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, + SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, + SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, + SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, + SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, + SQLSRV_ERROR_STREAM_CREATE, + SQLSRV_ERROR_MARS_OFF, + SQLSRV_ERROR_FIELD_INDEX_ERROR, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, + SQLSRV_ERROR_INVALID_BUFFER_LIMIT, + + // Driver specific error codes starts from here. + SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, + +}; + +// the message returned by ODBC Driver 11 for SQL Server +static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command", + "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" }; + +// SQLSTATE for all internal errors +extern SQLCHAR IMSSP[]; + +// SQLSTATE for all internal warnings +extern SQLCHAR SSPWARN[]; + +// flags passed to sqlsrv_errors to filter its return values +enum error_handling_flags { + SQLSRV_ERR_ERRORS, + SQLSRV_ERR_WARNINGS, + SQLSRV_ERR_ALL +}; + +// *** internal error macros and functions *** +// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the +// errno is 1 based. It returns it as an array with 3 members: +// 1/SQLSTATE) sqlstate +// 2/code) driver specific error code +// 3/message) driver specific error message +// The fetch type determines if the indices are numeric, associative, or both. +bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, _Out_ sqlsrv_error_auto_ptr& error, + logging_severity severity TSRMLS_DC ); + +// format and return a driver specfic error +void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, + sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ); + + +// return the message for the HRESULT returned by GetLastError. Some driver errors use this to +// return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails. +const char* get_last_error_message( DWORD last_error = 0 ); + +// a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer +DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ); + +// convenience functions that overload either a reference or a pointer so we can use +// either in the CHECK_* functions. +inline bool call_error_handler( sqlsrv_context& ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) +{ + va_list print_params; + va_start( print_params, warning ); + bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); + va_end( print_params ); + return ignored; +} + +inline bool call_error_handler( sqlsrv_context* ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) +{ + va_list print_params; + va_start( print_params, warning ); + bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); + va_end( print_params ); + return ignored; +} + +// PHP equivalent of ASSERT. C asserts cause a dialog to show and halt the process which +// we don't want on a web server + +#define SQLSRV_ASSERT( condition, msg, ...) if( !(condition)) DIE( msg, ## __VA_ARGS__ ); + +#if defined( PHP_DEBUG ) + +#define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) \ + if( !(condition)) { \ + DIE (msg, ## __VA_ARGS__ ); \ + } + +#else + + #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) ((void)0) + +#endif + +// check to see if the sqlstate is 01004, truncated field retrieved. Used for retrieving large fields. +inline bool is_truncated_warning( SQLCHAR* state ) +{ +#if defined(ZEND_DEBUG) + if( state == NULL || strlen( reinterpret_cast( state )) != 5 ) { \ + DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \ + } +#endif + return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4'); +} + +// Macros for handling errors. These macros are simplified if statements that take boilerplate +// code down to a single line to avoid distractions in the code. + +#define CHECK_ERROR_EX( unique, condition, context, ssphp, ... ) \ + bool flag##unique = (condition); \ + bool ignored##unique = true; \ + if (flag##unique) { \ + ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, ## __VA_ARGS__ ); \ + } \ + if( !ignored##unique ) + +#define CHECK_ERROR_UNIQUE( unique, condition, context, ssphp, ...) \ + CHECK_ERROR_EX( unique, condition, context, ssphp, ## __VA_ARGS__ ) + +#define CHECK_ERROR( condition, context, ... ) \ + CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, 0, ## __VA_ARGS__ ) + +#define CHECK_CUSTOM_ERROR( condition, context, ssphp, ... ) \ + CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, ## __VA_ARGS__ ) + +#define CHECK_SQL_ERROR( result, context, ... ) \ + SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ + CHECK_ERROR( result == SQL_ERROR, context, ## __VA_ARGS__ ) + +#define CHECK_WARNING_AS_ERROR_UNIQUE( unique, condition, context, ssphp, ... ) \ + bool ignored##unique = true; \ + if( condition ) { \ + ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, ## __VA_ARGS__ ); \ + } \ + if( !ignored##unique ) + +#define CHECK_SQL_WARNING_AS_ERROR( result, context, ... ) \ + CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__,( result == SQL_SUCCESS_WITH_INFO ), context, SQLSRV_ERROR_ODBC, ## __VA_ARGS__ ) + +#define CHECK_SQL_WARNING( result, context, ... ) \ + if( result == SQL_SUCCESS_WITH_INFO ) { \ + (void)call_error_handler( context, 0 TSRMLS_CC, /*warning*/ true, ## __VA_ARGS__ ); \ + } + +#define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \ + CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, ## __VA_ARGS__ ) + +#define CHECK_ZEND_ERROR( zr, ctx, error, ... ) \ + CHECK_ERROR_UNIQUE( __COUNTER__, ( zr == FAILURE ), ctx, error, ## __VA_ARGS__ ) \ + +#define CHECK_SQL_ERROR_OR_WARNING( result, context, ... ) \ + SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ + bool ignored = true; \ + if( result == SQL_ERROR ) { \ + ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, false, ##__VA_ARGS__ ); \ + } \ + else if( result == SQL_SUCCESS_WITH_INFO ) { \ + ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, true TSRMLS_CC, ##__VA_ARGS__ ); \ + } \ + if( !ignored ) + +// throw an exception after it has been hooked into the custom error handler +#define THROW_CORE_ERROR( ctx, custom, ... ) \ + (void)call_error_handler( ctx, custom TSRMLS_CC, /*warning*/ false, ## __VA_ARGS__ ); \ + throw core::CoreException(); + +//********************************************************************************************************************************* +// ODBC/Zend function wrappers +//********************************************************************************************************************************* + +namespace core { + + // base exception for the driver + struct CoreException : public std::exception { + + CoreException() + { + } + }; + + inline void check_for_mars_error( sqlsrv_stmt* stmt, SQLRETURN r TSRMLS_DC ) + { + // We check for the 'connection busy' error caused by having MultipleActiveResultSets off + // and return a more helpful message prepended to the ODBC errors if that error occurs + if( !SQL_SUCCEEDED( r )) { + + 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_MESSAGE_LENGTH, &len ); + + CHECK_SQL_ERROR_OR_WARNING( rtemp, stmt ) { + + throw CoreException(); + } + std::size_t driver_version = stmt->conn->driver_version; + if( !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) { + + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); + } + } + } + + // *** ODBC wrappers *** + + // wrap the ODBC functions to throw exceptions rather than use the return value to signal errors + // some of the signatures have been altered to be more convenient since the return value is no longer + // required to return the status of the call (e.g., SQLNumResultCols). + // These functions take the sqlsrv_context type. However, since the error handling code can alter + // the context to hold the error, they are not passed as const. + + inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) + { + SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, + diag_info_buffer, buffer_length, out_buffer_length ); + + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { + throw CoreException(); + } + + return r; + } + + inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle, + _Out_writes_(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); + CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) { + throw CoreException(); + } + } + + inline void SQLBindParameter( sqlsrv_stmt* stmt, + SQLUSMALLINT ParameterNumber, + SQLSMALLINT InputOutputType, + SQLSMALLINT ValueType, + SQLSMALLINT ParameterType, + SQLULEN ColumnSize, + SQLSMALLINT DecimalDigits, + _Inout_ SQLPOINTER ParameterValuePtr, + SQLLEN BufferLength, + _Inout_ SQLLEN * StrLen_Or_IndPtr + TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, + DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, + _Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC ) + { + SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, + buffer_length, out_buffer_length, field_type_num ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + inline void SQLColAttributeW( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, + _Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC ) + { + SQLRETURN r = ::SQLColAttributeW( stmt->handle(), field_index, field_identifier, field_type_char, + buffer_length, out_buffer_length, field_type_num ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLCHAR* col_name, SQLSMALLINT col_name_length, + _Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size, + _Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, + data_type, col_size, decimal_digits, nullable); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + inline void SQLDescribeColW( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLWCHAR* col_name, SQLSMALLINT col_name_length, + _Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size, + _Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLDescribeColW( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, + data_type, col_size, decimal_digits, nullable ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + inline void SQLEndTran( SQLSMALLINT handleType, sqlsrv_conn* conn, SQLSMALLINT completionType TSRMLS_DC ) + { + SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType ); + + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { + throw CoreException(); + } + } + + // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success + inline SQLRETURN SQLExecDirect( sqlsrv_stmt* stmt, char* sql TSRMLS_DC ) + { + SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast( sql ), SQL_NTS ); + + check_for_mars_error( stmt, r TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + + throw CoreException(); + } + return r; + } + + inline SQLRETURN SQLExecDirectW( sqlsrv_stmt* stmt, SQLWCHAR* wsql TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast( wsql ), SQL_NTS ); + + check_for_mars_error( stmt, r TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + return r; + } + + // SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success + inline SQLRETURN SQLExecute( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLExecute( stmt->handle() ); + + check_for_mars_error( stmt, r TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return r; + } + + inline SQLRETURN SQLFetchScroll( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) + { + SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + return r; + } + + + // wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine + inline void SQLFreeHandle( sqlsrv_context& ctx TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() ); + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} + } + + inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ) + { + SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); + + if( r == SQL_NO_DATA ) + return r; + + CHECK_SQL_ERROR( r, stmt ) { + throw CoreException(); + } + + if( handle_warning ) { + CHECK_SQL_WARNING_AS_ERROR( r, stmt ) { + throw CoreException(); + } + } + + return r; + } + + + inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, _Out_ SQLPOINTER info_value, SQLSMALLINT buffer_len, + _Out_ SQLSMALLINT* str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); + + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { + throw CoreException(); + } + } + + + inline void SQLGetTypeInfo( sqlsrv_stmt* stmt, SQLUSMALLINT data_type TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLGetTypeInfo( stmt->handle(), data_type ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + // SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set. + inline SQLRETURN SQLMoreResults( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r = ::SQLMoreResults( stmt->handle() ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return r; + } + + inline SQLSMALLINT SQLNumResultCols( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r; + SQLSMALLINT num_cols; + r = ::SQLNumResultCols( stmt->handle(), &num_cols ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return num_cols; + } + + // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more + // parameters or when the parameters are all processed. + inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, _Out_ SQLPOINTER* value_ptr_ptr TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + return r; + } + + inline void SQLPrepareW( sqlsrv_stmt* stmt, SQLWCHAR * sql, SQLINTEGER sql_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLPrepareW( stmt->handle(), sql, sql_len ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline void SQLPutData( sqlsrv_stmt* stmt, SQLPOINTER data_ptr, SQLLEN strlen_or_ind TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline SQLLEN SQLRowCount( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r; + SQLLEN rows_affected; + + r = ::SQLRowCount( stmt->handle(), &rows_affected ); + + // On Linux platform + // DriverName: libmsodbcsql-13.0.so.0.0 + // DriverODBCVer: 03.52 + // DriverVer: 13.00.0000 + // unixODBC: 2.3.1 + // r = ::SQLRowCount( stmt->handle(), &rows_affected ); + // returns r=-1 for an empty result set. +#ifndef _WIN32 + if( r == -1 && rows_affected == -1 ) + return 0; +#endif // !_WIN32 + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return rows_affected; + } + + + inline void SQLSetConnectAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len ); + + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { + throw CoreException(); + } + } + + + inline void SQLSetEnvAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len ); + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { + throw CoreException(); + } + } + + inline void SQLSetConnectAttr( sqlsrv_conn* conn, SQLINTEGER attribute, SQLPOINTER value_ptr, SQLINTEGER value_len TSRMLS_DC ) + { + SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); + + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { + throw CoreException(); + } + } + + inline void SQLSetStmtAttr( sqlsrv_stmt* stmt, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + // *** zend wrappers *** + + //zend_resource_dtor sets the type of destroyed resources to -1 + #define RSRC_INVALID_TYPE -1 + + // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string + // so allocated memory inside of value_z should be released before assigning it to the new string + inline void sqlsrv_zval_stringl(zval* value_z, const char* str, const std::size_t str_len) + { + if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { + zend_string* temp_zstr = zend_string_init(str, str_len, 0); + zend_string_release(Z_STR_P(value_z)); + ZVAL_NEW_STR(value_z, temp_zstr); + } + else { + ZVAL_STRINGL(value_z, str, str_len); + } + } + + + // exception thrown when a zend function wrapped here fails. + + // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw + // exceptions when an error occurs. They are prefaced with sqlsrv_ because many of the zend functions are + // actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system. + // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error + // that can be thrown from it. + + inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, zend_ulong index, zval* value TSRMLS_DC) + { + int zr = ::add_index_zval( array, index, value ); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_next_index_zval( sqlsrv_context& ctx, zval* array, zval* value TSRMLS_DC) + { + int zr = ::add_next_index_zval( array, value ); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, const char* key TSRMLS_DC ) + { + int zr = ::add_assoc_null( array_z, key ); + CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, const char* key, zend_long val TSRMLS_DC ) + { + int zr = ::add_assoc_long( array_z, key, val ); + CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, const char* key, char* val, bool duplicate TSRMLS_DC ) + { + int zr = ::add_assoc_string(array_z, key, val); + CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + if (duplicate == 0) { + sqlsrv_free(val); + } + } + + inline void sqlsrv_array_init( sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) + { + int zr = ::array_init(new_array); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval* stream_z TSRMLS_DC ) + { + // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment + php_stream_from_zval_no_verify( stream, stream_z ); + CHECK_CUSTOM_ERROR( stream == NULL, ctx, SQLSRV_ERROR_ZEND_STREAM ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_get_current_data(sqlsrv_context& ctx, HashTable* ht, _Out_ zval*& output_data TSRMLS_DC) + { + int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_get_current_data_ptr(sqlsrv_context& ctx, HashTable* ht, _Out_ void*& output_data TSRMLS_DC) + { + int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, zend_ulong index TSRMLS_DC ) + { + int zr = ::zend_hash_index_del( ht, index ); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, zend_ulong index, zval* data_z TSRMLS_DC ) + { + int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_index_update_ptr(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData TSRMLS_DC) + { + int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + + inline void sqlsrv_zend_hash_index_update_mem(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData, std::size_t size TSRMLS_DC) + { + int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, zval* data TSRMLS_DC ) + { + int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert_mem(sqlsrv_context& ctx, HashTable* ht, void* data, uint data_size TSRMLS_DC) + { + int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert_ptr(sqlsrv_context& ctx, HashTable* ht, void* data TSRMLS_DC) + { + int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, HashTable* ht, uint32_t initial_size, + dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) + { + ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); + } + +template +sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) +{ + return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC ); +} + +template +sqlsrv_conn* allocate_conn( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) +{ + return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC ); +} + +} // namespace core + +// connection attribute functions +template +struct str_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), + static_cast(Z_STRLEN_P( value )) TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +#endif // CORE_SQLSRV_H diff --git a/sqlsrv/core_stmt.cpp b/source/shared/core_stmt.cpp similarity index 91% rename from sqlsrv/core_stmt.cpp rename to source/shared/core_stmt.cpp index a4ef0d9d..faa0c523 100644 --- a/sqlsrv/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1,2471 +1,2500 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_stmt.cpp -// -// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -namespace { - -// certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the -// results of every field request, and if it is out of order, we cache those for preceding fields. -struct field_cache { - - void* value; - SQLLEN len; - sqlsrv_phptype type; - - field_cache( void* field_value, SQLLEN field_len, sqlsrv_phptype t ) - : type( t ) - { - // if the value is NULL, then just record a NULL pointer - if( field_value != NULL ) { - value = sqlsrv_malloc( field_len ); - memcpy_s( value, field_len, field_value, field_len ); - len = field_len; - } - else { - value = NULL; - len = 0; - } - } - - // no destructor because we don't want to release the memory when it goes out of scope, but instead we - // rely on the hash table destructor to free the memory -}; - -const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field - -// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads -const unsigned int UTF8_MIDBYTE_MASK = 0xc0; -const unsigned int UTF8_MIDBYTE_TAG = 0x80; -const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0; -const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0; -const unsigned int UTF8_3BYTESEQ_TAG = 0xe0; -const unsigned int UTF8_4BYTESEQ_TAG = 0xf0; -const unsigned int UTF8_NBYTESEQ_MASK = 0xf0; - -// constants used to convert from a DateTime object to a string which is sent to the server. -// Using the format defined by the ODBC documentation at http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx -namespace DateTime { - -const char DATETIME_CLASS_NAME[] = "DateTime"; -const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1; -const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P"; -const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT ); -const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u"; -const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT ); -const char DATE_FORMAT[] = "Y-m-d"; -const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT ); - -} - -// *** internal functions *** -// Only declarations are put here. Functions contain the documentation they need at their definition sites. -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ); -size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ); -bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC ); -bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z ); -void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC); -// returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); -void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); -// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) -void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); -void field_cache_dtor( zval* data_z ); -void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ); -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ); -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ); -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); -// assure there is enough space for the output parameter string -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, - SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, - SQLLEN& buffer_len TSRMLS_DC ); -void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ); -// send all the stream data -void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ); -// called when a bound output string parameter is to be destroyed -void sqlsrv_output_param_dtor( zval* data ); -// called when a bound stream parameter is to be destroyed. -void sqlsrv_stream_dtor( zval* data ); -bool is_streamable_type( SQLINTEGER sql_type ); - -} - -// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier. -sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : - sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ), - conn( c ), - executed( false ), - past_fetch_end( false ), - current_results( NULL ), - cursor_type( SQL_CURSOR_FORWARD_ONLY ), - has_rows( false ), - fetch_called( false ), - last_field_index( -1 ), - past_next_result_end( false ), - param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte - send_streams_at_exec( true ), - current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), - current_stream_read( 0 ), - query_timeout( QUERY_TIMEOUT_INVALID ), - buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) -{ - ZVAL_UNDEF( &active_stream ); - // initialize the input string parameters array (which holds zvals) - core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); - - // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) - ZVAL_NEW_ARR( ¶m_streams ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC); - - // initialize the (input only) datetime parameters of converted date time objects to strings - array_init( ¶m_datetime_buffers ); - - // initialize the output string parameters (which holds sqlsrv_output_param structures) - ZVAL_NEW_ARR( &output_params ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC); - - // initialize the field cache - ZVAL_NEW_ARR( &field_cache ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC); -} - -// desctructor for sqlsrv statement. -sqlsrv_stmt::~sqlsrv_stmt( void ) -{ - if( Z_TYPE( active_stream ) != IS_UNDEF ) { - TSRMLS_FETCH(); - close_active_stream( this TSRMLS_CC ); - } - - // delete any current results - if( current_results ) { - current_results->~sqlsrv_result_set(); - efree( current_results ); - current_results = NULL; - } - - invalidate(); - zval_ptr_dtor( ¶m_input_strings ); - zval_ptr_dtor( &output_params ); - zval_ptr_dtor( ¶m_streams ); - zval_ptr_dtor( ¶m_datetime_buffers ); - zval_ptr_dtor( &field_cache ); -} - - -// centralized place to release (without destroying the hash tables -// themselves) all the parameter data that accrues during the -// execution phase. -void sqlsrv_stmt::free_param_data( TSRMLS_D ) -{ - SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY, - "sqlsrv_stmt::free_param_data: Param zvals aren't arrays." ); - zend_hash_clean( Z_ARRVAL( param_input_strings )); - zend_hash_clean( Z_ARRVAL( output_params )); - zend_hash_clean( Z_ARRVAL( param_streams )); - zend_hash_clean( Z_ARRVAL( param_datetime_buffers )); - zend_hash_clean( Z_ARRVAL( field_cache )); -} - - -// to be called whenever a new result set is created, such as after an -// execute or next_result. Resets the state variables. - -void sqlsrv_stmt::new_result_set( TSRMLS_D ) -{ - this->fetch_called = false; - this->has_rows = false; - this->past_next_result_end = false; - this->past_fetch_end = false; - this->last_field_index = -1; - - // delete any current results - if( current_results ) { - current_results->~sqlsrv_result_set(); - efree( current_results ); - current_results = NULL; - } - - // create a new result set - if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { - current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC ); - } - else { - current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this ); - } -} - -// core_sqlsrv_create_stmt -// Common code to allocate a statement from either driver. Returns a valid driver statement object or -// throws an exception if an error occurs. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// stmt_factory - factory method to create a statement. -// options_ht - A HashTable of user provided options to be set on the statement. -// valid_stmt_opts - An array of valid driver supported statement options. -// err - callback for error handling -// driver - reference to caller -// Return -// Returns the created statement - -sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, - const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ) -{ - sqlsrv_malloc_auto_ptr stmt; - SQLHANDLE stmt_h = SQL_NULL_HANDLE; - - try { - - core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC ); - - stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC ); - - stmt->conn = conn; - - // handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free - // in the catch block below. - stmt_h = SQL_NULL_HANDLE; - - // process the options array given to core_sqlsrv_prepare. - if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - zend_ulong index = -1; - zend_string *key = NULL; - zval* value_z = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) { - - int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); - - const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); - - // if the key didn't match, then return the error to the script. - // The driver layer should ensure that the key is valid. - DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); - - // perform the actions the statement option needs done. - (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - } - - sqlsrv_stmt* return_stmt = stmt; - stmt.transferred(); - - return return_stmt; - } - catch( core::CoreException& ) - { - if( stmt ) { - - conn->set_last_error( stmt->last_error() ); - stmt->~sqlsrv_stmt(); - } - - // if allocating the handle failed before the statement was allocated, free the handle - if( stmt_h != SQL_NULL_HANDLE) { - ::SQLFreeHandle( SQL_HANDLE_STMT, stmt_h ); - } - - throw; - } - catch( ... ) { - - DIE( "core_sqlsrv_allocate_stmt: Unknown exception caught." ); - } -} - - -// core_sqlsrv_bind_param -// Binds a parameter using SQLBindParameter. It allocates memory and handles other details -// in translating between the driver and ODBC. -// Parameters: -// param_num - number of the parameter, 0 based -// param_z - zval of the parameter -// php_out_type - type to return for output parameter -// sql_type - ODBC constant for the SQL Server type (SQL_UNKNOWN_TYPE = 0 means not known, so infer defaults) -// column_size - length of the field on the server (SQLSRV_UKNOWN_SIZE means not known, so infer defaults) -// decimal_digits - if column_size is valid and the type contains a scale, this contains the scale -// Return: -// Nothing, though an exception is thrown if an error occurs -// The php type of the parameter is taken from the zval. -// The sql type is given as a hint if the driver provides it. - -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, - SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, - SQLSMALLINT decimal_digits TSRMLS_DC ) -{ - SQLSMALLINT c_type; - SQLPOINTER buffer = NULL; - SQLLEN buffer_len = 0; - - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT, - "core_sqlsrv_bind_param: Invalid parameter direction." ); - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || php_out_type != SQLSRV_PHPTYPE_INVALID, - "core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param." ); - - try { - - // check is only < because params are 0 based - CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) { - throw core::CoreException(); - } - - // resize the statements array of int_ptrs if the parameter isn't already set. - 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 ]; - - zval* param_ref = param_z; - if ( Z_ISREF_P( param_z ) ) { - ZVAL_DEREF( param_z ); - } - bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL ); - bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE ); - // if the user asks for for a specific type for input and output, make sure the data type we send matches the data we - // type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so - // we always let that match if they want a string back. - if( direction == SQL_PARAM_INPUT_OUTPUT ) { - bool match = false; - switch( php_out_type ) { - case SQLSRV_PHPTYPE_INT: - if( zval_was_null || zval_was_bool ) { - convert_to_long( param_z ); - } - match = Z_TYPE_P( param_z ) == IS_LONG; - break; - case SQLSRV_PHPTYPE_FLOAT: - if( zval_was_null ) { - convert_to_double( param_z ); - } - match = Z_TYPE_P( param_z ) == IS_DOUBLE; - break; - case SQLSRV_PHPTYPE_STRING: - // anything can be converted to a string - convert_to_string( param_z ); - match = true; - break; - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_DATETIME: - case SQLSRV_PHPTYPE_STREAM: - SQLSRV_ASSERT( false, "Invalid type for an output parameter." ); - break; - default: - SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." ); - break; - } - CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) { - throw core::CoreException(); - } - } - - // if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so - // when the buffer is filled, the type is correct - if( direction == SQL_PARAM_OUTPUT ) { - switch( php_out_type ) { - case SQLSRV_PHPTYPE_INT: - convert_to_long( param_z ); - break; - case SQLSRV_PHPTYPE_FLOAT: - convert_to_double( param_z ); - break; - case SQLSRV_PHPTYPE_STRING: - convert_to_string( param_z ); - break; - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_DATETIME: - case SQLSRV_PHPTYPE_STREAM: - SQLSRV_ASSERT( false, "Invalid type for an output parameter" ); - break; - default: - SQLSRV_ASSERT( false, "Uknown SQLSRV_PHPTYPE_* constant given" ); - break; - } - } - - SQLSRV_ASSERT(( Z_TYPE_P( param_z ) != IS_STRING && Z_TYPE_P( param_z ) != IS_RESOURCE ) || - ( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 || - encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" ); - - // if the sql type is unknown, then set the default based on the PHP type passed in - if( sql_type == SQL_UNKNOWN_TYPE ) { - default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); - } - - // if the size is unknown, then set the default based on the PHP type passed in - if( column_size == SQLSRV_UNKNOWN_SIZE ) { - default_sql_size_and_scale( stmt, static_cast( param_num ), param_z, encoding, column_size, decimal_digits TSRMLS_CC ); - } - - // determine the ODBC C type - c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC ); - - // set the buffer based on the PHP parameter type - switch( Z_TYPE_P( param_z )) { - - case IS_NULL: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - ind_ptr = SQL_NULL_DATA; - buffer = NULL; - buffer_len = 0; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - { - // if it is boolean, set the lval to 0 or 1 - convert_to_long( param_z ); - buffer = ¶m_z->value; - buffer_len = sizeof( Z_LVAL_P( param_z )); - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool ); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - } - } - break; - case IS_DOUBLE: - { - buffer = ¶m_z->value; - buffer_len = sizeof( Z_DVAL_P( param_z )); - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_ref, static_cast( param_num ), false ); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - } - } - break; - case IS_STRING: - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) - if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) { - - zval wbuffer_z; - ZVAL_NULL( &wbuffer_z ); - - bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - param_num + 1, get_last_error_message() ) { - throw core::CoreException(); - } - buffer = Z_STRVAL_P( &wbuffer_z ); - buffer_len = Z_STRLEN_P( &wbuffer_z ); - core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC); - } - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, - // we reallocate the string if it's interned - if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) { - core::sqlsrv_zval_stringl( param_z, static_cast(buffer), buffer_len ); - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - } - - // if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR) - // or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type, - // convert it to wchar first - if( direction == SQL_PARAM_INPUT_OUTPUT && - ( c_type == SQL_C_WCHAR || - ( c_type == SQL_C_BINARY && - ( sql_type == SQL_WCHAR || - sql_type == SQL_WVARCHAR || - sql_type == SQL_WLONGVARCHAR )))) { - - bool converted = convert_input_param_to_utf16( param_z, param_z ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - param_num + 1, get_last_error_message() ) { - throw core::CoreException(); - } - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - ind_ptr = buffer_len; - } - - // since this is an output string, assure there is enough space to hold the requested size and - // set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr) - resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, - buffer, buffer_len TSRMLS_CC ); - - // save the parameter to be adjusted and/or converted after the results are processed - sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len )); - - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - - // For output parameters, if we set the column_size to be same as the buffer_len, - // then if there is a truncation due to the data coming from the server being - // greater than the column_size, we don't get any truncation error. In order to - // avoid this silent truncation, we set the column_size to be "MAX" size for - // string types. This will guarantee that there is no silent truncation for - // output parameters. - if( direction == SQL_PARAM_OUTPUT ) { - - switch( sql_type ) { - - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_WVARCHAR: - column_size = SQL_SS_LENGTH_UNLIMITED; - break; - - default: - break; - } - } - } - break; - case IS_RESOURCE: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - sqlsrv_stream stream_encoding( param_z, encoding ); - HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC ); - buffer = reinterpret_cast( param_num ); - Z_TRY_ADDREF_P( param_z ); // so that it doesn't go away while we're using it - buffer_len = 0; - ind_ptr = SQL_DATA_AT_EXEC; - } - break; - case IS_OBJECT: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - zval function_z; - zval buffer_z; - zval format_z; - zval params[1]; - ZVAL_UNDEF( &function_z ); - ZVAL_UNDEF( &buffer_z ); - ZVAL_UNDEF( &format_z ); - ZVAL_UNDEF( params ); - - bool valid_class_name_found = false; - - zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC ); - - while( class_entry != NULL ) { - - if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && - stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) { - valid_class_name_found = true; - break; - } - - else { - - // Check the parent - class_entry = class_entry->parent; - } - } - - CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - - // if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error' - // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' - // sql type, it lacks the timezone. - if( sql_type == SQL_SS_TIMESTAMPOFFSET ) { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), - DateTime::DATETIMEOFFSET_FORMAT_LEN ); - } - else if( sql_type == SQL_TYPE_DATE ) { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); - } - else { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); - } - // call the DateTime::format member function to convert the object to a string that SQL Server understands - core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); - params[0] = format_z; - // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the - // DateTime object and $format_z is the format string. - int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); - zend_string_release( Z_STR( format_z )); - zend_string_release( Z_STR( function_z )); - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - buffer = Z_STRVAL( buffer_z ); - zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z ); - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - buffer_len = Z_STRLEN( buffer_z ) - 1; - ind_ptr = buffer_len; - break; - } - case IS_ARRAY: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ); - break; - default: - DIE( "core_sqlsrv_bind_param: Unsupported PHP type. Only string, float, int, and streams (resource) are supported. " - "It is the responsibilty of the driver layer to convert a parameter to one of these types." ); - break; - } - - if( zval_was_null ) { - ind_ptr = SQL_NULL_DATA; - } - - core::SQLBindParameter( stmt, param_num + 1, direction, - c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); - } - catch( core::CoreException& e ) { - stmt->free_param_data( TSRMLS_C ); - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - throw e; - } -} - - -// core_sqlsrv_execute -// Executes the statement previously prepared -// Parameters: -// stmt - the core sqlsrv_stmt structure that contains the ODBC handle -// Return: -// true if there is data, false if there is not - -void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len ) -{ - SQLRETURN r; - - try { - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - if( sql ) { - - sqlsrv_malloc_auto_ptr wsql_string; - unsigned int wsql_len = 0; - if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { - wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t ))); - wsql_string[0] = L'\0'; - wsql_len = 0; - } - else { - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : - stmt->encoding() ); - wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - sql_len, &wsql_len ); - CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - get_last_error_message() ) { - throw core::CoreException(); - } - } - r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC ); - } - else { - r = core::SQLExecute( stmt TSRMLS_CC ); - } - - // if data is needed (streams were bound) and they should be sent at execute time, then do so now - if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) { - - send_param_streams( stmt TSRMLS_CC ); - } - - stmt->new_result_set( TSRMLS_C ); - stmt->executed = true; - - // if all the data has been sent and no data was returned then finalize the output parameters - if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { - - finalize_output_parameters( stmt TSRMLS_CC ); - } - // stream parameters are sent, clean the Hashtable - if ( stmt->send_streams_at_exec ) { - zend_hash_clean( Z_ARRVAL( stmt->param_streams )); - } - } - catch( core::CoreException& e ) { - - // if the statement executed but failed in a subsequent operation before returning, - // we need to cancel the statement and deref the output and stream parameters - if ( stmt->send_streams_at_exec ) { - zend_hash_clean( Z_ARRVAL( stmt->output_params )); - zend_hash_clean( Z_ARRVAL( stmt->param_streams )); - } - if( stmt->executed ) { - SQLCancel( stmt->handle() ); - // stmt->executed = false; should this be reset if something fails? - } - - throw e; - } -} - - -// core_sqlsrv_fetch -// Moves the cursor according to the parameters (by default, moves to the next row) -// Parameters: -// stmt - the sqlsrv_stmt of the cursor -// fetch_orientation - method to move the cursor -// fetch_offset - if the method has a parameter (such as number of rows to move or literal row number) -// Returns: -// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the -// user scrolls past a non-scrollable result set - -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ) -{ - // pre-condition check - SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE, - "core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." ); - - try { - - // clear the field cache of the previous fetch - zend_hash_clean( Z_ARRVAL( stmt->field_cache )); - - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) { - throw core::CoreException(); - } - - SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); - - CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { - throw core::CoreException(); - } - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - // if the statement has rows and is not scrollable but doesn't yet have - // fetch_called, this must be the first time we've called sqlsrv_fetch. - if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->has_rows && !stmt->fetch_called ) { - stmt->fetch_called = true; - return true; - } - - // move to the record requested. For absolute records, we use a 0 based offset, so +1 since - // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. - SQLRETURN r = stmt->current_results->fetch( fetch_orientation, - ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 - TSRMLS_CC ); - if( r == SQL_NO_DATA ) { - // if this is a forward only cursor, mark that we've passed the end so future calls result in an error - if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { - stmt->past_fetch_end = true; - } - return false; - } - - // mark that we called fetch (which get_field, et. al. uses) and reset our last field retrieved - stmt->fetch_called = true; - stmt->last_field_index = -1; - stmt->has_rows = true; // since we made it this far, we must have at least one row - } - catch (core::CoreException& e) { - throw e; - } - catch ( ... ) { - DIE( "core_sqlsrv_fetch: Unexpected exception occurred." ); - } - - return true; -} - - -// Retrieves metadata for a field of a prepared statement. -// Parameters: -// colno - the index of the field for which to return the metadata. columns are 0 based in PDO -// Return: -// A field_meta_data* consisting of the field metadata. - -field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ) -{ - // pre-condition check - SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." ); - - sqlsrv_malloc_auto_ptr meta_data; - SQLSMALLINT field_name_len = 0; - - meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data(); - meta_data->field_name = static_cast( sqlsrv_malloc( SS_MAXCOLNAMELEN + 1 )); - - try { - core::SQLDescribeCol( stmt, colno + 1, meta_data->field_name.get(), SS_MAXCOLNAMELEN, &field_name_len, - &(meta_data->field_type), &(meta_data->field_size), &(meta_data->field_scale), - &(meta_data->field_is_nullable) TSRMLS_CC ); - } - catch( core::CoreException& e ) { - throw e; - } - - // depending on field type, we add the values into size or precision/scale. - switch( meta_data->field_type ) { - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_BIT: - case SQL_TINYINT: - case SQL_SMALLINT: - case SQL_INTEGER: - case SQL_BIGINT: - case SQL_REAL: - case SQL_FLOAT: - case SQL_DOUBLE: - { - meta_data->field_precision = meta_data->field_size; - meta_data->field_size = 0; - break; - } - default: { - break; - } - } - - // Set the field name lenth - meta_data->field_name_len = field_name_len; - - field_meta_data* result_field_meta_data = meta_data; - meta_data.transferred(); - return result_field_meta_data; -} - - -// core_sqlsrv_get_field -// Return the value of a column from ODBC -// Parameters: -// stmt - the sqlsrv_stmt from which to retrieve the column -// field_index - 0 based index for the column to retrieve -// sqlsrv_php_type_in - sqlsrv_php_type structure that tells what format to return the data in -// field_value - pointer to the data retrieved -// field_len - length of the data in the field_value buffer -// Returns: -// Nothing, excpetion thrown if an error occurs - -void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string, - _Out_ void*& field_value, _Out_ SQLLEN* field_len, bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) -{ - try { - - // close the stream to release the resource - close_active_stream(stmt TSRMLS_CC); - - // if the field has been retrieved before, return the previous result - field_cache* cached = NULL; - if (NULL != ( cached = static_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast( field_index ))))) { - // the field value is NULL - if( cached->value == NULL ) { - field_value = NULL; - *field_len = 0; - if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } - } - else { - - field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); - 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'; - } - *field_len = cached->len; - if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } - } - return; - } - - sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; - - SQLLEN sql_field_type = 0; - SQLLEN sql_field_len = 0; - - // Make sure that the statement was executed and not just prepared. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they - // may also be retrieved. - if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { - sqlsrv_phptype invalid; - invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { - SQLSRV_ASSERT((cached = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->field_cache), i))) == NULL, - "Field already cached." ); - core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, - sqlsrv_php_type_out TSRMLS_CC ); - // delete the value returned since we only want it cached, not the actual value - if( field_value ) { - efree( field_value ); - field_value = NULL; - *field_len = 0; - } - } - } - - // If the php type was not specified set the php type to be the default type. - if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { - - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Get the length of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); - - // Get the corresponding php type from the sql type. - sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast( sql_field_type ), static_cast( sql_field_len ), prefer_string ); - } - - // Verify that we have an acceptable type to convert. - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { - throw core::CoreException(); - } - - if( sqlsrv_php_type_out != NULL ) - *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); - - // Retrieve the data - core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - - // if the user wants us to cache the field, we'll do it - if( cache_field ) { - field_cache cache( field_value, *field_len, sqlsrv_php_type ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); - } - } - - catch( core::CoreException& e ) { - throw e; - } -} - -// core_sqlsrv_has_any_result -// return if any result set or rows affected message is waiting -// to be consumed and moved over by sqlsrv_next_result. -// Parameters: -// stmt - The statement object on which to check for results. -// Return: -// true if any results are present, false otherwise. - -bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - // Use SQLNumResultCols to determine if we have rows or not. - SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - // use SQLRowCount to determine if there is a rows status waiting - SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); - return (num_cols != 0) || (rows_affected > 0); -} - -// core_sqlsrv_next_result -// Advances to the next result set from the last executed query -// Parameters -// stmt - the sqlsrv_stmt structure -// Returns -// Nothing, exception thrown if problem occurs - -void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params, bool throw_on_errors ) -{ - try { - - // make sure that the statement has been executed. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) { - throw core::CoreException(); - } - - close_active_stream( stmt TSRMLS_CC ); - - SQLRETURN r; - if( throw_on_errors ) { - r = core::SQLMoreResults( stmt TSRMLS_CC ); - } - else { - r = SQLMoreResults( stmt->handle() ); - } - - if( r == SQL_NO_DATA ) { - - if( &(stmt->output_params) && finalize_output_params ) { - // if we're finished processing result sets, handle the output parameters - finalize_output_parameters( stmt TSRMLS_CC ); - } - - // mark we are past the end of all results - stmt->past_next_result_end = true; - return; - } - - stmt->new_result_set( TSRMLS_C ); - } - catch( core::CoreException& e ) { - - SQLCancel( stmt->handle() ); - throw e; - } -} - - -// core_sqlsrv_post_param -// Performs any actions post execution for each parameter. For now it cleans up input parameters memory from the statement -// Parameters: -// stmt - the sqlsrv_stmt structure -// param_num - 0 based index of the parameter -// param_z - parameter value itself. -// Returns: -// Nothing, exception thrown if problem occurs - -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* param_z TSRMLS_DC ) -{ - SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); - SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); - - // if the parameter was an input string, delete it from the array holding input parameter strings - if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) { - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC ); - } - - // if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams - // PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it - // with sqlsrv_send_stream_data. - if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) { - sqlsrv_stream* stream_encoding = NULL; - stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->param_streams), param_num)); - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC ); - } -} - -//Calls SQLSetStmtAttr to set a cursor. -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ) -{ - try { - - switch( cursor_type ) { - - case SQL_CURSOR_STATIC: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_DYNAMIC: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_KEYSET_DRIVEN: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_FORWARD_ONLY: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQLSRV_CURSOR_BUFFERED: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); - break; - } - - stmt->cursor_type = cursor_type; - - } - catch( core::CoreException& ) { - throw; - } -} - -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - if( Z_TYPE_P( value_z ) != IS_LONG ) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); - } - - core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); -} - -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ) -{ - if( limit <= 0 ) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); - } - - stmt->buffered_query_limit = limit; -} - - -// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout -// which accepts timeout parameter as a long. If the zval is not of type long -// than throws error. -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - try { - - // validate the value - if( Z_TYPE_P( value_z ) != IS_LONG || Z_LVAL_P( value_z ) < 0 ) { - - convert_to_string( value_z ); - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) ); - } - - core_sqlsrv_set_query_timeout( stmt, static_cast( Z_LVAL_P( value_z )) TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } -} - -// Overloaded. Accepts the timeout as a long. -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." ); - - // set the statement attribute - core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC ); - - // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which - // is represented by -1. - long lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); - - // set the LOCK_TIMEOUT on the server. - char lock_timeout_sql[ 32 ]; - int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", - lock_timeout ); - - SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), - "stmt_option_query_timeout: sprintf_s failed. Shouldn't ever fail." ); - - core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC ); - - stmt->query_timeout = timeout; - } - catch( core::CoreException& ) { - throw; - } -} - -void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - TSRMLS_C; - - // zend_is_true does not fail. It either returns true or false. - stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false; -} - - -// core_sqlsrv_send_stream_packet -// send a single packet from a stream parameter to the database using -// ODBC. This will also handle the transition between parameters. It -// returns true if it is not done sending, false if it is finished. -// return_value is what should be returned to the script if it is -// given. Any errors that occur are posted here. -// Parameters: -// stmt - query to send the next packet for -// Returns: -// true if more data remains to be sent, false if all data processed - -bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - SQLRETURN r = SQL_SUCCESS; - - // if there no current parameter to process, get the next one - // (probably because this is the first call to sqlsrv_send_stream_data) - if( stmt->current_stream.stream_z == NULL ) { - - if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) { - - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); - stmt->current_stream_read = 0; - return false; - } - } - - try { - - // get the stream from the zval we bound - php_stream* param_stream = NULL; - core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); - - // if we're at the end, then release our current parameter - if( php_stream_eof( param_stream )) { - // if no data was actually sent prior, then send a NULL - if( stmt->current_stream_read == 0 ) { - // send an empty string, which is what a 0 length does. - char buff[1]; // temp storage to hand to SQLPutData - core::SQLPutData( stmt, buff, 0 TSRMLS_CC ); - } - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); - stmt->current_stream_read = 0; - } - // 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 ]; - 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 ); - - if (read > UINT_MAX) - { - LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); - throw core::CoreException(); - } - - stmt->current_stream_read += static_cast( read ); - if( read > 0 ) { - // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character - // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it - // twice. - // If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion - // since all other MBCS supported by SQL Server are 2 byte maximum size. - if( stmt->current_stream.encoding == CP_UTF8 ) { - - // 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. - wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; - // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate - int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, static_cast( read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); - if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) { - - // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more - // in, then reattempt the conversion. If it fails the second time, then an error is returned. - size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC ); - // read the missing bytes - size_t new_read = php_stream_read( param_stream, static_cast( buffer ) + read, - need_to_read ); - // if the bytes couldn't be read, then we return an error - CHECK_CUSTOM_ERROR( new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { - throw core::CoreException(); - } - // try the conversion again with the complete character - wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, static_cast( read + new_read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); - // something else must be wrong if it failed - CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { - throw core::CoreException(); - } - } - core::SQLPutData( stmt, wbuffer, wsize * sizeof( wchar_t ) TSRMLS_CC ); - } - else { - core::SQLPutData( stmt, buffer, read TSRMLS_CC ); - } - } - } - - } - catch( core::CoreException& e ) { - stmt->free_param_data( TSRMLS_C ); - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - SQLCancel( stmt->handle() ); - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT ); - stmt->current_stream_read = 0; - throw e; - } - - return true; -} - -void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) -{ - TSRMLS_C; - - // This implementation should never get called. - DIE( "Not implemented." ); -} - -void stmt_option_query_timeout:: operator()( sqlsrv_stmt* stmt, stmt_option const* /**/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_send_at_exec:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC ); -} - - -// internal function to release the active stream. Called by each main API function -// that will alter the statement and cancel any retrieval of data from a stream. -void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) -{ - // if there is no active stream, return - if( Z_TYPE( stmt->active_stream ) == IS_UNDEF ) { - return; - } - - php_stream* stream = NULL; - - // we use no verify since verify would return immediately and we want to assert, not return. - php_stream_from_zval_no_verify( stream, &( stmt->active_stream )); - - SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." ); - - php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here. - - SQLSRV_ASSERT( Z_TYPE( stmt->active_stream ) == IS_UNDEF, "close_active_stream: Active stream not closed." ); - -} - -// local routines not shared by other files (arranged alphabetically) - -namespace { - -bool is_streamable_type( SQLLEN sql_type ) -{ - switch( sql_type ) { - case SQL_CHAR: - case SQL_WCHAR: - case SQL_BINARY: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_SS_XML: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - return true; - } - - return false; -} - -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ) -{ - try { - - switch( sql_type ) { - // for types that are fixed in size or for which the size is unknown, return the display size. - case SQL_BIGINT: - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - case SQL_GUID: - case SQL_FLOAT: - case SQL_DOUBLE: - case SQL_REAL: - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_BINARY: - case SQL_CHAR: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_SS_XML: - case SQL_SS_UDT: - case SQL_WLONGVARCHAR: - case SQL_DATETIME: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - { - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC ); - break; - } - - // for wide char types for which the size is known, return the octet length instead, since it will include the - // the number of bytes necessary for the string, not just the characters - case SQL_WCHAR: - case SQL_WVARCHAR: - { - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC ); - break; - } - - default: - DIE ( "Unexpected SQL type encountered in calc_string_size." ); - } - } - catch( core::CoreException& e ) { - throw e; - } -} - - -// calculates how many characters were cut off from the end of a buffer when reading -// in UTF-8 encoded text - -size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ) -{ - const char* last_char = buffer + buffer_end - 1; - size_t need_to_read = 0; - - // rewind until we are at the byte that starts the cut off character - while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) { - --last_char; - ++need_to_read; - } - - // determine how many bytes we need to read in based on the number of bytes in the character - // (# of high bits set) versus the number of bytes we've already read. - switch( *last_char & UTF8_NBYTESEQ_MASK ) { - case UTF8_2BYTESEQ_TAG1: - case UTF8_2BYTESEQ_TAG2: - need_to_read = 1 - need_to_read; - break; - case UTF8_3BYTESEQ_TAG: - need_to_read = 2 - need_to_read; - break; - case UTF8_4BYTESEQ_TAG: - need_to_read = 3 - need_to_read; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )); - break; - } - - return need_to_read; -} - - -// Caller is responsible for freeing the memory allocated for the field_value. -// The memory allocation has to happen in the core layer because otherwise -// the driver layer would have to calculate size of the field_value -// to decide the amount of memory allocation. -void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) -{ - try { - - close_active_stream( stmt TSRMLS_CC ); - - // make sure that fetch is called before trying to retrieve. - CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { - throw core::CoreException(); - } - - // make sure that fields are not retrieved incorrectly. - CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, - stmt->last_field_index ) { - throw core::CoreException(); - } - - switch( sqlsrv_php_type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_INT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( *field_len == SQL_NULL_DATA ) { - field_value = NULL; - break; - } - - field_value = field_value_temp; - field_value_temp.transferred(); - break; - } - - case SQLSRV_PHPTYPE_FLOAT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( *field_len == SQL_NULL_DATA ) { - field_value = NULL; - break; - } - - field_value = field_value_temp; - field_value_temp.transferred(); - break; - } - - case SQLSRV_PHPTYPE_STRING: - { - get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - break; - } - - // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and - // convert it to a DateTime object and return the created object - case SQLSRV_PHPTYPE_DATETIME: - { - char field_value_temp[ MAX_DATETIME_STRING_LEN ]; - zval params[1]; - zval field_value_temp_z; - zval function_z; - - ZVAL_UNDEF( &field_value_temp_z ); - ZVAL_UNDEF( &function_z ); - ZVAL_UNDEF( params ); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, - MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - zval_auto_ptr return_value_z; - return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); - ZVAL_UNDEF( return_value_z ); - - if( *field_len == SQL_NULL_DATA ) { - ZVAL_NULL( return_value_z ); - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - break; - } - - // Convert the string date to a DateTime object - core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); - core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 ); - params[0] = field_value_temp_z; - - if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, - params TSRMLS_CC ) == FAILURE) { - THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); - } - - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - zend_string_free( Z_STR( field_value_temp_z )); - zend_string_free( Z_STR( function_z )); - break; - } - - // create a stream wrapper around the field and return that object to the PHP script. calls to fread - // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file - // for how these fields are used. - case SQLSRV_PHPTYPE_STREAM: - { - - php_stream* stream = NULL; - sqlsrv_stream* ss = NULL; - SQLLEN sql_type; - - SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { - throw core::CoreException(); - } - - stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); - - CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { - throw core::CoreException(); - } - - ss = static_cast( stream->abstract ); - ss->stmt = stmt; - ss->field_index = field_index; - ss->sql_type = static_cast( sql_type ); - ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); - - zval_auto_ptr return_value_z; - return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); - ZVAL_UNDEF( return_value_z ); - - // turn our stream into a zval to be returned - php_stream_to_zval( stream, return_value_z ); - - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - break; - } - - case SQLSRV_PHPTYPE_NULL: - field_value = NULL; - *field_len = 0; - break; - - default: - DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); - break; - } - - // sucessfully retrieved the field, so update our last retrieved field - if( stmt->last_field_index < field_index ) { - stmt->last_field_index = field_index; - } - } - catch( core::CoreException& e ) { - throw e; - } -} - - -// check_for_next_stream_parameter -// see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise -// returns false -bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) -{ - int stream_index = 0; - SQLRETURN r = SQL_SUCCESS; - sqlsrv_stream* stream_encoding = NULL; - zval* param_z = NULL; - - // get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param - r = core::SQLParamData( stmt, reinterpret_cast( &stream_index ) TSRMLS_CC ); - // if no more data, we've exhausted the bound parameters, so return that we're done - if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - - // we're all done, so return false - return false; - } - - HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); - - // pull out the sqlsrv_encoding struct - stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(streams_ht, stream_index)); - SQLSRV_ASSERT(stream_encoding != NULL, "Stream parameter does not exist"); // if the index isn't in the hash, that's a serious error - - param_z = stream_encoding->stream_z; - - // make the next stream current - stmt->current_stream = sqlsrv_stream( param_z, stream_encoding->encoding ); - stmt->current_stream_read = 0; - - // there are more parameters - return true; -} - - -// utility routine to convert an input parameter from UTF-8 to UTF-16 - -bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z ) -{ - SQLSRV_ASSERT( input_param_z == converted_param_z || Z_TYPE_P( converted_param_z ) == IS_NULL, - "convert_input_param_z called with invalid parameter states" ); - - const char* buffer = Z_STRVAL_P( input_param_z ); - std::size_t buffer_len = Z_STRLEN_P( input_param_z ); - int wchar_size; - - if (buffer_len > INT_MAX) - { - LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); - throw core::CoreException(); - } - - // if the string is empty, then just return that the conversion succeeded as - // MultiByteToWideChar will "fail" on an empty string. - if( buffer_len == 0 ) { - core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); - return true; - } - - // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string - wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, - reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); - - // if there was a problem determining the size of the string, return false - if( wchar_size == 0 ) { - return false; - } - sqlsrv_malloc_auto_ptr wbuffer; - wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) )); - // convert the utf-8 string to a wchar string in the new buffer - int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), - static_cast( buffer_len ), wbuffer, wchar_size ); - // if there was a problem converting the string, then free the memory and return false - if( r == 0 ) { - return false; - } - - // null terminate the string, set the size within the zval, and return success - wbuffer[ wchar_size ] = L'\0'; - core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), - wchar_size * sizeof( wchar_t ) ); - sqlsrv_free(wbuffer); - wbuffer.transferred(); - - return true; -} - -// returns the ODBC C type constant that matches the PHP type and encoding given - -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) -{ - SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P( param_z ); - - switch( php_type ) { - - case IS_NULL: - switch( encoding ) { - // The c type is set to match to the corresponding sql_type. For NULL cases, if the server type - // is a binary type, than the server expects the sql_type to be binary type as well, otherwise - // an error stating "Implicit conversion not allowed.." is thrown by the server. - // For all other server types, setting the sql_type to sql_char works fine. - case SQLSRV_ENCODING_BINARY: - sql_c_type = SQL_C_BINARY; - break; - default: - sql_c_type = SQL_C_CHAR; - break; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - //ODBC 64-bit long and integer type are 4 byte values. - if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) - { - sql_c_type = SQL_C_SBIGINT; - } - else - { - sql_c_type = SQL_C_SLONG; - } - break; - case IS_DOUBLE: - sql_c_type = SQL_C_DOUBLE; - break; - case IS_STRING: - case IS_RESOURCE: - switch( encoding ) { - case SQLSRV_ENCODING_CHAR: - sql_c_type = SQL_C_CHAR; - break; - case SQLSRV_ENCODING_BINARY: - sql_c_type = SQL_C_BINARY; - break; - case CP_UTF8: - sql_c_type = SQL_C_WCHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); - break; - } - break; - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in core_sqlsrv_bind_param. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - sql_c_type = SQL_C_CHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } - - return sql_c_type; -} - - -// given a zval and encoding, determine the appropriate sql type - -void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLSMALLINT& sql_type TSRMLS_DC ) -{ - sql_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P(param_z); - switch( php_type ) { - - case IS_NULL: - switch( encoding ) { - // Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases, - // if the server type is a binary type, than the server expects the sql_type to be binary type - // as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the - // server. For all other server types, setting the sql_type to sql_char works fine. - case SQLSRV_ENCODING_BINARY: - sql_type = SQL_BINARY; - break; - default: - sql_type = SQL_CHAR; - break; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - //ODBC 64-bit long and integer type are 4 byte values. - if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) - { - sql_type = SQL_BIGINT; - } - else - { - sql_type = SQL_INTEGER; - } - - break; - case IS_DOUBLE: - sql_type = SQL_FLOAT; - break; - case IS_RESOURCE: - case IS_STRING: - switch( encoding ) { - case SQLSRV_ENCODING_CHAR: - sql_type = SQL_VARCHAR; - break; - case SQLSRV_ENCODING_BINARY: - sql_type = SQL_VARBINARY; - break; - case CP_UTF8: - sql_type = SQL_WVARCHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); - break; - } - break; - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in the calling function. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - // if the user is sending this type to SQL Server 2005 or earlier, make it appear - // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET - // since these are the date types of the highest precision for their respective server versions - if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { - sql_type = SQL_TYPE_TIMESTAMP; - } - else { - sql_type = SQL_SS_TIMESTAMPOFFSET; - } - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } - -} - - -// given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate) - -void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) -{ - int php_type = Z_TYPE_P( param_z ); - column_size = 0; - decimal_digits = 0; - - switch( php_type ) { - - case IS_NULL: - column_size = 1; - break; - // size is not necessary for these types, they are inferred by ODBC - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - case IS_DOUBLE: - case IS_RESOURCE: - break; - case IS_STRING: - { - size_t char_size = ( encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( wchar_t ) : sizeof( char ); - SQLULEN byte_len = Z_STRLEN_P( param_z ) * char_size; - if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { - column_size = SQL_SERVER_MAX_TYPE_SIZE; - } - else { - column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size; - } - break; - } - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in the calling function. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - // if the user is sending this type to SQL Server 2005 or earlier, make it appear - // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET - // since these are the date types of the highest precision for their respective server versions - if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { - column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION; - decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE; - } - else { - column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION; - decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE; - } - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } -} - -void field_cache_dtor( zval* data_z ) -{ - field_cache* cache = static_cast( Z_PTR_P( data_z )); - if( cache->value ) - { - sqlsrv_free( cache->value ); - } - sqlsrv_free( cache ); -} - - -// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output -// parameters will be present until all results are processed (since output parameters can depend on results -// while being processed). This function updates the lengths of output parameter strings from the ind_ptr -// parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary. -// For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server - -void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - if( Z_ISUNDEF(stmt->output_params) ) - return; - - bool converted = true; - HashTable* params_ht = Z_ARRVAL( stmt->output_params ); - zend_ulong index = -1; - zend_string* key = NULL; - void* output_param_temp = NULL; - - ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { - sqlsrv_output_param* output_param = static_cast( output_param_temp ); - zval* value_z = Z_REFVAL_P( output_param->param_z ); - switch( Z_TYPE_P( value_z )) { - case IS_STRING: - { - // 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 ]; - if( str_len == SQL_NULL_DATA ) { - zend_string_release( Z_STR_P( value_z )); - ZVAL_NULL( value_z ); - continue; - } - - // if there was more to output than buffer size to hold it, then throw a truncation error - int null_size = 0; - switch( output_param->encoding ) { - case SQLSRV_ENCODING_UTF8: - null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16 - break; - case SQLSRV_ENCODING_SYSTEM: - null_size = 1; - break; - case SQLSRV_ENCODING_BINARY: - null_size = 0; - break; - default: - SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); - break; - } - CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { - throw core::CoreException(); - } - - // if it's not in the 8 bit encodings, then it's in UTF-16 - if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { - bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { - // 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'; - core::sqlsrv_zval_stringl(value_z, str, str_len); - } - else { - core::sqlsrv_zval_stringl(value_z, str, str_len); - } - } - 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 ) { - ZVAL_NULL( value_z ); - } - else if( output_param->is_bool ) { - convert_to_boolean( value_z ); - } - else - { - ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); - } - break; - case IS_DOUBLE: - // 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 ) { - ZVAL_NULL( value_z ); - } - break; - default: - DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); - break; - } - value_z = NULL; - } ZEND_HASH_FOREACH_END(); - - // empty the hash table since it's been processed - zend_hash_clean( Z_ARRVAL( stmt->output_params )); - return; -} - -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) -{ - SQLRETURN r; - SQLSMALLINT c_type; - SQLLEN sql_field_type = 0; - SQLSMALLINT extra = 0; - SQLLEN field_len_temp; - SQLLEN sql_display_size = 0; - char* field_value_temp = NULL; - - try { - - DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, - "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); - - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); - } - - // Set the C type and account for null characters at the end of the data. - switch( sqlsrv_php_type.typeinfo.encoding ) { - case CP_UTF8: - c_type = SQL_C_WCHAR; - extra = sizeof( SQLWCHAR ); - break; - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - extra = 0; - break; - default: - c_type = SQL_C_CHAR; - extra = sizeof( SQLCHAR ); - break; - } - - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Calculate the field size. - calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); - - // if this is a large type, then read the first few bytes to get the actual length from SQLGetData - if( sql_display_size == 0 || sql_display_size == INT_MAX || - sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { - - field_len_temp = INITIAL_FIELD_STRING_LEN; - - field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); - - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), - &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( field_len_temp == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( r == SQL_SUCCESS_WITH_INFO ) { - - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - - stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - - if( is_truncated_warning( state )) { - - SQLLEN dummy_field_len; - - // for XML (and possibly other conditions) the field length returned is not the real field length, so - // in every pass, we double the allocation size to retrieve all the contents. - if( field_len_temp == SQL_NO_TOTAL ) { - - // reset the field_len_temp - field_len_temp = INITIAL_FIELD_STRING_LEN; - - do { - SQLLEN initial_field_len = field_len_temp; - // Double the size. - field_len_temp *= 2; - - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - field_len_temp -= initial_field_len; - - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, - field_len_temp + extra, &dummy_field_len, - false /*handle_warning*/ TSRMLS_CC ); - - // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL - // so we calculate the actual length of the string with that. - if( dummy_field_len != SQL_NO_TOTAL ) - field_len_temp += dummy_field_len; - else - field_len_temp += initial_field_len; - - if( r == SQL_SUCCESS_WITH_INFO ) { - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - } - - } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); - } - else { - - // We got the field_len_temp from SQLGetData call. - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - // We have already recieved INITIAL_FIELD_STRING_LEN size data. - field_len_temp -= INITIAL_FIELD_STRING_LEN; - - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, - field_len_temp + extra, &dummy_field_len, - true /*handle_warning*/ TSRMLS_CC ); - - if( dummy_field_len == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - field_len_temp += INITIAL_FIELD_STRING_LEN; - } - - } // if( is_truncation_warning ( state ) ) - else { - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - } - } // if( r == SQL_SUCCESS_WITH_INFO ) - - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { - - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) - - else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { - - // only allow binary retrievals for char and binary types. All others get a string converted - // to the encoding type they asked for. - - // null terminator - if( c_type == SQL_C_CHAR ) { - sql_display_size += sizeof( SQLCHAR ); - } - - // For WCHAR multiply by sizeof(WCHAR) and include the null terminator - else if( c_type == SQL_C_WCHAR ) { - sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); - } - - field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); - - // get the data - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, - &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); - CHECK_SQL_ERROR( r, stmt ) { - throw core::CoreException(); - } - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( field_len_temp == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { - - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) - - else { - - DIE( "Invalid sql_display_size" ); - return; // to eliminate a warning - } - - field_value = field_value_temp; - *field_len = field_len_temp; - - // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP - // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. - // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary - // operator to set add 1 to fill the null terminator - field_value_temp[field_len_temp] = '\0'; - } - - catch( core::CoreException& ) { - - field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - catch ( ... ) { - - field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - -} - - -// return the option from the stmt_opts array that matches the key. If no option found, -// NULL is returned. - -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ) -{ - 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 ]; - } - } - - return NULL; // no option found -} - -// is_fixed_size_type -// returns true if the SQL data type is a fixed length, as opposed to a variable length data type such as varchar or varbinary - -bool is_fixed_size_type( SQLINTEGER sql_type ) -{ - switch( sql_type ) { - - case SQL_BINARY: - case SQL_CHAR: - case SQL_WCHAR: - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_VARBINARY: - case SQL_LONGVARBINARY: - case SQL_SS_XML: - case SQL_SS_UDT: - return false; - } - - return true; -} - -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) -{ - switch( type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - case SQLSRV_PHPTYPE_DATETIME: - return true; - case SQLSRV_PHPTYPE_STRING: - case SQLSRV_PHPTYPE_STREAM: - { - if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR - || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - return true; - } - break; - } - } - - return false; -} - - -// verify there is enough space to hold the output string parameter, and allocate it if needed. The param_z -// is updated to have the new buffer with the correct size and its reference is incremented. The output -// string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and -// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter - -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, - SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, - SQLLEN& buffer_len TSRMLS_DC ) -{ - SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); - buffer_len = Z_STRLEN_P( param_z ); - SQLLEN expected_len; - SQLLEN buffer_null_extra; - SQLLEN elem_size; - SQLLEN without_null_len; - - // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, - // as is a n(var)char/ntext field being returned as a binary field. - elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1; - - // account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning - expected_len = column_size * elem_size + elem_size; - - // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations - buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; - - // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter - without_null_len = column_size * elem_size; - - // increment to include the null terminator since the Zend length doesn't include the null terminator - buffer_len += elem_size; - - // if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new - // length. - if( buffer_len < expected_len ) { - SQLSRV_ASSERT( expected_len >= expected_len - buffer_null_extra, - "Integer overflow/underflow caused a corrupt field length." ); - - // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since - // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about - // not having a NULL terminator on a string. - zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); - - // A zval string len doesn't include the null. This calculates the length it should be - // regardless of whether the ODBC type contains the NULL or not. - - // null terminate the string to avoid a warning in debug PHP builds - ZSTR_VAL(param_z_string)[without_null_len] = '\0'; - ZVAL_NEW_STR(param_z, param_z_string); - - // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the - // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY - buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; - - // Zend string length doesn't include the null terminator - ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; - } - - buffer = Z_STRVAL_P(param_z); - - // 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); - } -} - -// output parameters have their reference count incremented so that they do not disappear -// while the query is executed and processed. They are saved in the statement so that -// their reference count may be decremented later (after results are processed) - -void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ) -{ - HashTable* param_ht = Z_ARRVAL( stmt->output_params ); - zend_ulong paramno = static_cast( param.param_num ); - core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, ¶m, sizeof( sqlsrv_output_param )); - Z_TRY_ADDREF_P( param.param_z ); // we have a reference to the param -} - - -// send all the stream data - -void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { } -} - - -// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed -void sqlsrv_output_param_dtor( zval* data ) -{ - sqlsrv_output_param *output_param = static_cast( Z_PTR_P( data )); - zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold - sqlsrv_free( output_param ); -} - -// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed -void sqlsrv_stream_dtor( zval* data ) -{ - sqlsrv_stream* stream_encoding = static_cast( Z_PTR_P( data )); - zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold - sqlsrv_free( stream_encoding ); -} - -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_stmt.cpp +// +// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +namespace { + +// certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the +// results of every field request, and if it is out of order, we cache those for preceding fields. +struct field_cache { + + void* value; + SQLLEN len; + sqlsrv_phptype type; + + field_cache( void* field_value, SQLLEN field_len, sqlsrv_phptype t ) + : type( t ) + { + // if the value is NULL, then just record a NULL pointer + if( field_value != NULL ) { + value = sqlsrv_malloc( field_len ); + memcpy_s( value, field_len, field_value, field_len ); + len = field_len; + } + else { + value = NULL; + len = 0; + } + } + + // no destructor because we don't want to release the memory when it goes out of scope, but instead we + // rely on the hash table destructor to free the memory +}; + +const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field + +// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads +const unsigned int UTF8_MIDBYTE_MASK = 0xc0; +const unsigned int UTF8_MIDBYTE_TAG = 0x80; +const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0; +const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0; +const unsigned int UTF8_3BYTESEQ_TAG = 0xe0; +const unsigned int UTF8_4BYTESEQ_TAG = 0xf0; +const unsigned int UTF8_NBYTESEQ_MASK = 0xf0; + +// constants used to convert from a DateTime object to a string which is sent to the server. +// Using the format defined by the ODBC documentation at http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx +namespace DateTime { + +const char DATETIME_CLASS_NAME[] = "DateTime"; +const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1; +const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P"; +const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT ); +const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u"; +const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT ); +const char DATE_FORMAT[] = "Y-m-d"; +const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT ); + +} + +// *** internal functions *** +// Only declarations are put here. Functions contain the documentation they need at their definition sites. +void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ); +size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ); +bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC ); +bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z ); +void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype + sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC); +// returns the ODBC C type constant that matches the PHP type and encoding given +SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); +void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); +// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) +void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); +void field_cache_dtor( zval* data_z ); +void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ); +void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, + _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ); +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ); +bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); +// assure there is enough space for the output parameter string +void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, + SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, + SQLLEN& buffer_len TSRMLS_DC ); +void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ); +// send all the stream data +void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ); +// called when a bound output string parameter is to be destroyed +void sqlsrv_output_param_dtor( zval* data ); +// called when a bound stream parameter is to be destroyed. +void sqlsrv_stream_dtor( zval* data ); +bool is_streamable_type( SQLINTEGER sql_type ); + +} + +// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier. +sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : + sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ), + conn( c ), + executed( false ), + past_fetch_end( false ), + current_results( NULL ), + cursor_type( SQL_CURSOR_FORWARD_ONLY ), + has_rows( false ), + fetch_called( false ), + last_field_index( -1 ), + past_next_result_end( false ), + query_timeout( QUERY_TIMEOUT_INVALID ), + 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 ), + current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), + current_stream_read( 0 ) +{ + ZVAL_UNDEF( &active_stream ); + // initialize the input string parameters array (which holds zvals) + core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); + + // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) + ZVAL_NEW_ARR( ¶m_streams ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC); + + // initialize the (input only) datetime parameters of converted date time objects to strings + array_init( ¶m_datetime_buffers ); + + // initialize the output string parameters (which holds sqlsrv_output_param structures) + ZVAL_NEW_ARR( &output_params ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC); + + // initialize the field cache + ZVAL_NEW_ARR( &field_cache ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC); +} + +// desctructor for sqlsrv statement. +sqlsrv_stmt::~sqlsrv_stmt( void ) +{ + if( Z_TYPE( active_stream ) != IS_UNDEF ) { + TSRMLS_FETCH(); + close_active_stream( this TSRMLS_CC ); + } + + // delete any current results + if( current_results ) { + current_results->~sqlsrv_result_set(); + efree( current_results ); + current_results = NULL; + } + + invalidate(); + zval_ptr_dtor( ¶m_input_strings ); + zval_ptr_dtor( &output_params ); + zval_ptr_dtor( ¶m_streams ); + zval_ptr_dtor( ¶m_datetime_buffers ); + zval_ptr_dtor( &field_cache ); +} + + +// centralized place to release (without destroying the hash tables +// themselves) all the parameter data that accrues during the +// execution phase. +void sqlsrv_stmt::free_param_data( TSRMLS_D ) +{ + SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY, + "sqlsrv_stmt::free_param_data: Param zvals aren't arrays." ); + zend_hash_clean( Z_ARRVAL( param_input_strings )); + zend_hash_clean( Z_ARRVAL( output_params )); + zend_hash_clean( Z_ARRVAL( param_streams )); + zend_hash_clean( Z_ARRVAL( param_datetime_buffers )); + zend_hash_clean( Z_ARRVAL( field_cache )); +} + + +// to be called whenever a new result set is created, such as after an +// execute or next_result. Resets the state variables. + +void sqlsrv_stmt::new_result_set( TSRMLS_D ) +{ + this->fetch_called = false; + this->has_rows = false; + this->past_next_result_end = false; + this->past_fetch_end = false; + this->last_field_index = -1; + + // delete any current results + if( current_results ) { + current_results->~sqlsrv_result_set(); + efree( current_results ); + current_results = NULL; + } + + // create a new result set + if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { + current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC ); + } + else { + current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this ); + } +} + +// core_sqlsrv_create_stmt +// Common code to allocate a statement from either driver. Returns a valid driver statement object or +// throws an exception if an error occurs. +// Parameters: +// conn - The connection resource by which the client and server are connected. +// stmt_factory - factory method to create a statement. +// options_ht - A HashTable of user provided options to be set on the statement. +// valid_stmt_opts - An array of valid driver supported statement options. +// err - callback for error handling +// driver - reference to caller +// Return +// Returns the created statement + +sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, + const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ) +{ + sqlsrv_malloc_auto_ptr stmt; + SQLHANDLE stmt_h = SQL_NULL_HANDLE; + sqlsrv_stmt* return_stmt = NULL; + + try { + + core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC ); + + stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC ); + + stmt->conn = conn; + + // handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free + // in the catch block below. + stmt_h = SQL_NULL_HANDLE; + + // process the options array given to core_sqlsrv_prepare. + if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { + zend_ulong index = -1; + zend_string *key = NULL; + zval* value_z = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) { + + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); + + const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); + + // if the key didn't match, then return the error to the script. + // The driver layer should ensure that the key is valid. + DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); + + // perform the actions the statement option needs done. + (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); + } + + return_stmt = stmt; + stmt.transferred(); + } + catch( core::CoreException& ) + { + if( stmt ) { + + conn->set_last_error( stmt->last_error() ); + stmt->~sqlsrv_stmt(); + } + + // if allocating the handle failed before the statement was allocated, free the handle + if( stmt_h != SQL_NULL_HANDLE) { + ::SQLFreeHandle( SQL_HANDLE_STMT, stmt_h ); + } + + throw; + } + catch( ... ) { + + DIE( "core_sqlsrv_allocate_stmt: Unknown exception caught." ); + } + + return return_stmt; +} + + +// core_sqlsrv_bind_param +// Binds a parameter using SQLBindParameter. It allocates memory and handles other details +// in translating between the driver and ODBC. +// Parameters: +// param_num - number of the parameter, 0 based +// param_z - zval of the parameter +// php_out_type - type to return for output parameter +// sql_type - ODBC constant for the SQL Server type (SQL_UNKNOWN_TYPE = 0 means not known, so infer defaults) +// column_size - length of the field on the server (SQLSRV_UKNOWN_SIZE means not known, so infer defaults) +// decimal_digits - if column_size is valid and the type contains a scale, this contains the scale +// Return: +// Nothing, though an exception is thrown if an error occurs +// The php type of the parameter is taken from the zval. +// The sql type is given as a hint if the driver provides it. + +void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, + SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, + SQLSMALLINT decimal_digits TSRMLS_DC ) +{ + SQLSMALLINT c_type; + SQLPOINTER buffer = NULL; + SQLLEN buffer_len = 0; + + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT, + "core_sqlsrv_bind_param: Invalid parameter direction." ); + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || php_out_type != SQLSRV_PHPTYPE_INVALID, + "core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param." ); + + try { + + // check is only < because params are 0 based + CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) { + throw core::CoreException(); + } + + // resize the statements array of int_ptrs if the parameter isn't already set. + 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 ]; + + zval* param_ref = param_z; + if ( Z_ISREF_P( param_z ) ) { + ZVAL_DEREF( param_z ); + } + bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL ); + bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE ); + // if the user asks for for a specific type for input and output, make sure the data type we send matches the data we + // type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so + // we always let that match if they want a string back. + if( direction == SQL_PARAM_INPUT_OUTPUT ) { + bool match = false; + switch( php_out_type ) { + case SQLSRV_PHPTYPE_INT: + if( zval_was_null || zval_was_bool ) { + convert_to_long( param_z ); + } + match = Z_TYPE_P( param_z ) == IS_LONG; + break; + case SQLSRV_PHPTYPE_FLOAT: + if( zval_was_null ) { + convert_to_double( param_z ); + } + match = Z_TYPE_P( param_z ) == IS_DOUBLE; + break; + case SQLSRV_PHPTYPE_STRING: + // anything can be converted to a string + convert_to_string( param_z ); + match = true; + break; + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_DATETIME: + case SQLSRV_PHPTYPE_STREAM: + SQLSRV_ASSERT( false, "Invalid type for an output parameter." ); + break; + default: + SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." ); + break; + } + CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) { + throw core::CoreException(); + } + } + + // if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so + // when the buffer is filled, the type is correct + if( direction == SQL_PARAM_OUTPUT ) { + switch( php_out_type ) { + case SQLSRV_PHPTYPE_INT: + convert_to_long( param_z ); + break; + case SQLSRV_PHPTYPE_FLOAT: + convert_to_double( param_z ); + break; + case SQLSRV_PHPTYPE_STRING: + convert_to_string( param_z ); + break; + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_DATETIME: + case SQLSRV_PHPTYPE_STREAM: + SQLSRV_ASSERT( false, "Invalid type for an output parameter" ); + break; + default: + SQLSRV_ASSERT( false, "Uknown SQLSRV_PHPTYPE_* constant given" ); + break; + } + } + + SQLSRV_ASSERT(( Z_TYPE_P( param_z ) != IS_STRING && Z_TYPE_P( param_z ) != IS_RESOURCE ) || + ( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 || + encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" ); + + // if the sql type is unknown, then set the default based on the PHP type passed in + if( sql_type == SQL_UNKNOWN_TYPE ) { + default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); + } + + // if the size is unknown, then set the default based on the PHP type passed in + if( column_size == SQLSRV_UNKNOWN_SIZE ) { + default_sql_size_and_scale( stmt, static_cast( param_num ), param_z, encoding, column_size, decimal_digits TSRMLS_CC ); + } + + // determine the ODBC C type + c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC ); + + // set the buffer based on the PHP parameter type + switch( Z_TYPE_P( param_z )) { + + case IS_NULL: + { + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); + ind_ptr = SQL_NULL_DATA; + buffer = NULL; + buffer_len = 0; + } + break; + case IS_TRUE: + case IS_FALSE: + case IS_LONG: + { + // if it is boolean, set the lval to 0 or 1 + convert_to_long( param_z ); + buffer = ¶m_z->value; + buffer_len = sizeof( Z_LVAL_P( param_z )); + ind_ptr = buffer_len; + if( direction != SQL_PARAM_INPUT ) { + // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned + sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool ); + save_output_param_for_later( stmt, output_param TSRMLS_CC ); + } + } + break; + case IS_DOUBLE: + { + buffer = ¶m_z->value; + buffer_len = sizeof( Z_DVAL_P( param_z )); + ind_ptr = buffer_len; + if( direction != SQL_PARAM_INPUT ) { + // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned + sqlsrv_output_param output_param( param_ref, static_cast( param_num ), false ); + save_output_param_for_later( stmt, output_param TSRMLS_CC ); + } + } + break; + case IS_STRING: + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) + if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) { + + zval wbuffer_z; + ZVAL_NULL( &wbuffer_z ); + + bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z ); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + param_num + 1, get_last_error_message() ) { + throw core::CoreException(); + } + buffer = Z_STRVAL_P( &wbuffer_z ); + buffer_len = Z_STRLEN_P( &wbuffer_z ); + core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC); + } + ind_ptr = buffer_len; + if( direction != SQL_PARAM_INPUT ) { + // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, + // we reallocate the string if it's interned + if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) { + core::sqlsrv_zval_stringl( param_z, static_cast(buffer), buffer_len ); + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + } + + // if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR) + // or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type, + // convert it to wchar first + if( direction == SQL_PARAM_INPUT_OUTPUT && + ( c_type == SQL_C_WCHAR || + ( c_type == SQL_C_BINARY && + ( sql_type == SQL_WCHAR || + sql_type == SQL_WVARCHAR || + sql_type == SQL_WLONGVARCHAR )))) { + + bool converted = convert_input_param_to_utf16( param_z, param_z ); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + param_num + 1, get_last_error_message() ) { + throw core::CoreException(); + } + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + ind_ptr = buffer_len; + } + + // since this is an output string, assure there is enough space to hold the requested size and + // set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr) + resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, + buffer, buffer_len TSRMLS_CC ); + + // save the parameter to be adjusted and/or converted after the results are processed + sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len )); + + save_output_param_for_later( stmt, output_param TSRMLS_CC ); + + // For output parameters, if we set the column_size to be same as the buffer_len, + // then if there is a truncation due to the data coming from the server being + // greater than the column_size, we don't get any truncation error. In order to + // avoid this silent truncation, we set the column_size to be "MAX" size for + // string types. This will guarantee that there is no silent truncation for + // output parameters. + if( direction == SQL_PARAM_OUTPUT ) { + + switch( sql_type ) { + + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_WVARCHAR: + column_size = SQL_SS_LENGTH_UNLIMITED; + break; + + default: + break; + } + } + } + break; + case IS_RESOURCE: + { + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); + sqlsrv_stream stream_encoding( param_z, encoding ); + HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC ); + buffer = reinterpret_cast( param_num ); + Z_TRY_ADDREF_P( param_z ); // so that it doesn't go away while we're using it + buffer_len = 0; + ind_ptr = SQL_DATA_AT_EXEC; + } + break; + case IS_OBJECT: + { + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); + zval function_z; + zval buffer_z; + zval format_z; + zval params[1]; + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( &buffer_z ); + ZVAL_UNDEF( &format_z ); + ZVAL_UNDEF( params ); + + bool valid_class_name_found = false; + + zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC ); + + while( class_entry != NULL ) { + + if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && + stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) { + valid_class_name_found = true; + break; + } + + else { + + // Check the parent + class_entry = class_entry->parent; + } + } + + CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { + throw core::CoreException(); + } + + // if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error' + // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' + // sql type, it lacks the timezone. + if( sql_type == SQL_SS_TIMESTAMPOFFSET ) { + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), + DateTime::DATETIMEOFFSET_FORMAT_LEN ); + } + else if( sql_type == SQL_TYPE_DATE ) { + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); + } + else { + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); + } + // call the DateTime::format member function to convert the object to a string that SQL Server understands + core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); + params[0] = format_z; + // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the + // DateTime object and $format_z is the format string. + int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); + zend_string_release( Z_STR( format_z )); + zend_string_release( Z_STR( function_z )); + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { + throw core::CoreException(); + } + buffer = Z_STRVAL( buffer_z ); + zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z ); + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { + throw core::CoreException(); + } + buffer_len = Z_STRLEN( buffer_z ) - 1; + ind_ptr = buffer_len; + break; + } + case IS_ARRAY: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ); + break; + default: + DIE( "core_sqlsrv_bind_param: Unsupported PHP type. Only string, float, int, and streams (resource) are supported. " + "It is the responsibilty of the driver layer to convert a parameter to one of these types." ); + break; + } + + if( zval_was_null ) { + ind_ptr = SQL_NULL_DATA; + } + + core::SQLBindParameter( stmt, param_num + 1, direction, + c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); + } + catch( core::CoreException& e ) { + stmt->free_param_data( TSRMLS_C ); + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); + throw e; + } +} + + +// core_sqlsrv_execute +// Executes the statement previously prepared +// Parameters: +// stmt - the core sqlsrv_stmt structure that contains the ODBC handle +// Return: +// true if there is data, false if there is not + +void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len ) +{ + SQLRETURN r; + + try { + + // close the stream to release the resource + close_active_stream( stmt TSRMLS_CC ); + + if( sql ) { + + sqlsrv_malloc_auto_ptr wsql_string; + unsigned int wsql_len = 0; + if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { + wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( SQLWCHAR ))); + wsql_string[0] = L'\0'; + wsql_len = 0; + } + else { + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding() ); + wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), + sql_len, &wsql_len ); + CHECK_CUSTOM_ERROR( wsql_string == 0, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + get_last_error_message() ) { + throw core::CoreException(); + } + } + r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC ); + } + else { + r = core::SQLExecute( stmt TSRMLS_CC ); + } + + // if data is needed (streams were bound) and they should be sent at execute time, then do so now + if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) { + + send_param_streams( stmt TSRMLS_CC ); + } + + stmt->new_result_set( TSRMLS_C ); + stmt->executed = true; + + // if all the data has been sent and no data was returned then finalize the output parameters + if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { + + finalize_output_parameters( stmt TSRMLS_CC ); + } + // stream parameters are sent, clean the Hashtable + if ( stmt->send_streams_at_exec ) { + zend_hash_clean( Z_ARRVAL( stmt->param_streams )); + } + } + catch( core::CoreException& e ) { + + // if the statement executed but failed in a subsequent operation before returning, + // we need to cancel the statement and deref the output and stream parameters + if ( stmt->send_streams_at_exec ) { + zend_hash_clean( Z_ARRVAL( stmt->output_params )); + zend_hash_clean( Z_ARRVAL( stmt->param_streams )); + } + if( stmt->executed ) { + SQLCancel( stmt->handle() ); + // stmt->executed = false; should this be reset if something fails? + } + + throw e; + } +} + + +// core_sqlsrv_fetch +// Moves the cursor according to the parameters (by default, moves to the next row) +// Parameters: +// stmt - the sqlsrv_stmt of the cursor +// fetch_orientation - method to move the cursor +// fetch_offset - if the method has a parameter (such as number of rows to move or literal row number) +// Returns: +// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the +// user scrolls past a non-scrollable result set + +bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ) +{ + // pre-condition check + SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE, + "core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." ); + + try { + + // clear the field cache of the previous fetch + zend_hash_clean( Z_ARRVAL( stmt->field_cache )); + + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) { + throw core::CoreException(); + } + + SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + + CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { + throw core::CoreException(); + } + + // close the stream to release the resource + close_active_stream( stmt TSRMLS_CC ); + + // if the statement has rows and is not scrollable but doesn't yet have + // fetch_called, this must be the first time we've called sqlsrv_fetch. + if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->has_rows && !stmt->fetch_called ) { + stmt->fetch_called = true; + return true; + } + + // move to the record requested. For absolute records, we use a 0 based offset, so +1 since + // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. + SQLRETURN r = stmt->current_results->fetch( fetch_orientation, ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 TSRMLS_CC ); + if( r == SQL_NO_DATA ) { + // if this is a forward only cursor, mark that we've passed the end so future calls result in an error + if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { + stmt->past_fetch_end = true; + } + return false; + } + + // mark that we called fetch (which get_field, et. al. uses) and reset our last field retrieved + stmt->fetch_called = true; + stmt->last_field_index = -1; + stmt->has_rows = true; // since we made it this far, we must have at least one row + } + catch (core::CoreException& e) { + throw e; + } + catch ( ... ) { + DIE( "core_sqlsrv_fetch: Unexpected exception occurred." ); + } + + return true; +} + + +// Retrieves metadata for a field of a prepared statement. +// Parameters: +// colno - the index of the field for which to return the metadata. columns are 0 based in PDO +// Return: +// A field_meta_data* consisting of the field metadata. + +field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ) +{ + // pre-condition check + SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." ); + + sqlsrv_malloc_auto_ptr meta_data; + sqlsrv_malloc_auto_ptr field_name_temp; + SQLSMALLINT field_len_temp = 0; + SQLLEN field_name_len = 0; + + meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data(); + field_name_temp = static_cast( sqlsrv_malloc( ( SS_MAXCOLNAMELEN + 1 ) * sizeof( SQLWCHAR ) )); + SQLSRV_ENCODING encoding = ( (stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); + try{ + core::SQLDescribeColW( stmt, colno + 1, field_name_temp, SS_MAXCOLNAMELEN + 1, &field_len_temp, + &( meta_data->field_type ), & ( meta_data->field_size ), & ( meta_data->field_scale ), + &( meta_data->field_is_nullable ) TSRMLS_CC ); + } + catch ( core::CoreException& e ) { + throw e; + } + + bool converted = convert_string_from_utf16( encoding, field_name_temp, field_len_temp, ( char** ) &( meta_data->field_name ), field_name_len ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { + throw core::CoreException(); + } + + // depending on field type, we add the values into size or precision/scale. + switch( meta_data->field_type ) { + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TYPE_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_BIT: + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIGINT: + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: + { + meta_data->field_precision = meta_data->field_size; + meta_data->field_size = 0; + break; + } + default: { + break; + } + } + + // Set the field name lenth + meta_data->field_name_len = field_name_len; + + field_meta_data* result_field_meta_data = meta_data; + meta_data.transferred(); + return result_field_meta_data; +} + + +// core_sqlsrv_get_field +// Return the value of a column from ODBC +// Parameters: +// stmt - the sqlsrv_stmt from which to retrieve the column +// field_index - 0 based index for the column to retrieve +// sqlsrv_php_type_in - sqlsrv_php_type structure that tells what format to return the data in +// field_value - pointer to the data retrieved +// field_len - length of the data in the field_value buffer +// Returns: +// Nothing, excpetion thrown if an error occurs + +void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string, + _Out_ void*& field_value, _Out_ SQLLEN* field_len, bool cache_field, + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) +{ + try { + + // close the stream to release the resource + close_active_stream(stmt TSRMLS_CC); + + // if the field has been retrieved before, return the previous result + field_cache* cached = NULL; + if (NULL != ( cached = static_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast( field_index ))))) { + // the field value is NULL + if( cached->value == NULL ) { + field_value = NULL; + *field_len = 0; + if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } + } + else { + + field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); + 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'; + } + *field_len = cached->len; + if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } + } + return; + } + + sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; + + SQLLEN sql_field_type = 0; + SQLLEN sql_field_len = 0; + + // Make sure that the statement was executed and not just prepared. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } + + // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they + // may also be retrieved. + if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { + sqlsrv_phptype invalid; + invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { + SQLSRV_ASSERT( reinterpret_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), i )) == NULL, "Field already cached." ); + core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, sqlsrv_php_type_out TSRMLS_CC ); + // delete the value returned since we only want it cached, not the actual value + if( field_value ) { + efree( field_value ); + field_value = NULL; + *field_len = 0; + } + } + } + + // If the php type was not specified set the php type to be the default type. + if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { + + // Get the SQL type of the field. + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + + // Get the length of the field. + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); + + // Get the corresponding php type from the sql type. + sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast( sql_field_type ), static_cast( sql_field_len ), prefer_string ); + } + + // Verify that we have an acceptable type to convert. + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { + throw core::CoreException(); + } + + if( sqlsrv_php_type_out != NULL ) + *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); + + // Retrieve the data + core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + + // if the user wants us to cache the field, we'll do it + if( cache_field ) { + field_cache cache( field_value, *field_len, sqlsrv_php_type ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); + } + } + + catch( core::CoreException& e ) { + throw e; + } +} + +// core_sqlsrv_has_any_result +// return if any result set or rows affected message is waiting +// to be consumed and moved over by sqlsrv_next_result. +// Parameters: +// stmt - The statement object on which to check for results. +// Return: +// true if any results are present, false otherwise. + +bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + // Use SQLNumResultCols to determine if we have rows or not. + SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + // use SQLRowCount to determine if there is a rows status waiting + SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); + return (num_cols != 0) || (rows_affected > 0); +} + +// core_sqlsrv_next_result +// Advances to the next result set from the last executed query +// Parameters +// stmt - the sqlsrv_stmt structure +// Returns +// Nothing, exception thrown if problem occurs + +void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params, bool throw_on_errors ) +{ + try { + + // make sure that the statement has been executed. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) { + throw core::CoreException(); + } + + close_active_stream( stmt TSRMLS_CC ); + + SQLRETURN r; + if( throw_on_errors ) { + r = core::SQLMoreResults( stmt TSRMLS_CC ); + } + else { + r = SQLMoreResults( stmt->handle() ); + } + + if( r == SQL_NO_DATA ) { + + if( &(stmt->output_params) && finalize_output_params ) { + // if we're finished processing result sets, handle the output parameters + finalize_output_parameters( stmt TSRMLS_CC ); + } + + // mark we are past the end of all results + stmt->past_next_result_end = true; + return; + } + + stmt->new_result_set( TSRMLS_C ); + } + catch( core::CoreException& e ) { + + SQLCancel( stmt->handle() ); + throw e; + } +} + + +// core_sqlsrv_post_param +// Performs any actions post execution for each parameter. For now it cleans up input parameters memory from the statement +// Parameters: +// stmt - the sqlsrv_stmt structure +// param_num - 0 based index of the parameter +// param_z - parameter value itself. +// Returns: +// Nothing, exception thrown if problem occurs + +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* param_z TSRMLS_DC ) +{ + SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); + SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); + + // if the parameter was an input string, delete it from the array holding input parameter strings + if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) { + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC ); + } + + // if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams + // PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it + // with sqlsrv_send_stream_data. + if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) { + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC ); + } +} + +//Calls SQLSetStmtAttr to set a cursor. +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ) +{ + try { + + switch( cursor_type ) { + + case SQL_CURSOR_STATIC: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQL_CURSOR_DYNAMIC: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQL_CURSOR_KEYSET_DRIVEN: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQL_CURSOR_FORWARD_ONLY: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQLSRV_CURSOR_BUFFERED: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); + break; + } + + stmt->cursor_type = cursor_type; + + } + catch( core::CoreException& ) { + throw; + } +} + +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + if( Z_TYPE_P( value_z ) != IS_LONG ) { + + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); + } + + core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); +} + +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ) +{ + if( limit <= 0 ) { + + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); + } + + stmt->buffered_query_limit = limit; +} + + +// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout +// which accepts timeout parameter as a long. If the zval is not of type long +// than throws error. +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + try { + + // validate the value + if( Z_TYPE_P( value_z ) != IS_LONG || Z_LVAL_P( value_z ) < 0 ) { + + convert_to_string( value_z ); + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) ); + } + + core_sqlsrv_set_query_timeout( stmt, static_cast( Z_LVAL_P( value_z )) TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } +} + +// Overloaded. Accepts the timeout as a long. +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." ); + + // set the statement attribute + core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC ); + + // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which + // is represented by -1. + int lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); + + // set the LOCK_TIMEOUT on the server. + char lock_timeout_sql[ 32 ]; + + 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 )), + "stmt_option_query_timeout: snprintf failed. Shouldn't ever fail." ); + + core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC ); + + stmt->query_timeout = timeout; + } + catch( core::CoreException& ) { + throw; + } +} + +void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + TSRMLS_C; + + // zend_is_true does not fail. It either returns true or false. + stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false; +} + + +// core_sqlsrv_send_stream_packet +// send a single packet from a stream parameter to the database using +// ODBC. This will also handle the transition between parameters. It +// returns true if it is not done sending, false if it is finished. +// return_value is what should be returned to the script if it is +// given. Any errors that occur are posted here. +// Parameters: +// stmt - query to send the next packet for +// Returns: +// true if more data remains to be sent, false if all data processed + +bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + // if there no current parameter to process, get the next one + // (probably because this is the first call to sqlsrv_send_stream_data) + if( stmt->current_stream.stream_z == NULL ) { + + if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) { + + stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); + stmt->current_stream_read = 0; + return false; + } + } + + try { + + // get the stream from the zval we bound + php_stream* param_stream = NULL; + core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); + + // if we're at the end, then release our current parameter + if( php_stream_eof( param_stream )) { + // if no data was actually sent prior, then send a NULL + if( stmt->current_stream_read == 0 ) { + // send an empty string, which is what a 0 length does. + char buff[1]; // temp storage to hand to SQLPutData + core::SQLPutData( stmt, buff, 0 TSRMLS_CC ); + } + stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); + stmt->current_stream_read = 0; + } + // 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 ]; + 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 ); + + if (read > UINT_MAX) + { + LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); + throw core::CoreException(); + } + + stmt->current_stream_read += static_cast( read ); + if( read > 0 ) { + // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character + // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it + // twice. + // If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion + // since all other MBCS supported by SQL Server are 2 byte maximum size. + if( stmt->current_stream.encoding == CP_UTF8 ) { + + // 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 ]; + 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 +#ifndef _WIN32 + int wsize = SystemLocale::ToUtf16Strict( stmt->current_stream.encoding, buffer, static_cast(read), wbuffer, wbuffer_size, &last_error_code ); +#else + int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, buffer, static_cast( read ), wbuffer, wbuffer_size ); + last_error_code = GetLastError(); +#endif // !_WIN32 + + if( wsize == 0 && last_error_code == ERROR_NO_UNICODE_TRANSLATION ) { + + // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more + // in, then reattempt the conversion. If it fails the second time, then an error is returned. + size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC ); + // read the missing bytes + size_t new_read = php_stream_read( param_stream, static_cast( buffer ) + read, + need_to_read ); + // if the bytes couldn't be read, then we return an error + CHECK_CUSTOM_ERROR( new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { + throw core::CoreException(); + } + // try the conversion again with the complete character +#ifndef _WIN32 + wsize = SystemLocale::ToUtf16Strict( stmt->current_stream.encoding, buffer, static_cast(read + new_read), wbuffer, static_cast(sizeof( wbuffer ) / sizeof( SQLWCHAR ))); +#else + wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, buffer, static_cast( read + new_read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); +#endif //!_WIN32 + // something else must be wrong if it failed + CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { + throw core::CoreException(); + } + } + core::SQLPutData( stmt, wbuffer, wsize * sizeof( SQLWCHAR ) TSRMLS_CC ); + } + else { + core::SQLPutData( stmt, buffer, read TSRMLS_CC ); + } + } + } + + } + catch( core::CoreException& e ) { + stmt->free_param_data( TSRMLS_C ); + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); + SQLCancel( stmt->handle() ); + stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT ); + stmt->current_stream_read = 0; + throw e; + } + + return true; +} + +void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) +{ + TSRMLS_C; + + // This implementation should never get called. + DIE( "Not implemented." ); +} + +void stmt_option_query_timeout:: operator()( sqlsrv_stmt* stmt, stmt_option const* /**/, zval* value_z TSRMLS_DC ) +{ + core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_send_at_exec:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC ); +} + + +// internal function to release the active stream. Called by each main API function +// that will alter the statement and cancel any retrieval of data from a stream. +void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +{ + // if there is no active stream, return + if( Z_TYPE( stmt->active_stream ) == IS_UNDEF ) { + return; + } + + php_stream* stream = NULL; + + // we use no verify since verify would return immediately and we want to assert, not return. + php_stream_from_zval_no_verify( stream, &( stmt->active_stream )); + + SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." ); + + php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here. + + SQLSRV_ASSERT( Z_TYPE( stmt->active_stream ) == IS_UNDEF, "close_active_stream: Active stream not closed." ); + +} + +// local routines not shared by other files (arranged alphabetically) + +namespace { + +bool is_streamable_type( SQLLEN sql_type ) +{ + switch( sql_type ) { + case SQL_CHAR: + case SQL_WCHAR: + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_WVARCHAR: + case SQL_SS_XML: + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + return true; + } + + return false; +} + +void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ) +{ + try { + + switch( sql_type ) { + // for types that are fixed in size or for which the size is unknown, return the display size. + case SQL_BIGINT: + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + case SQL_GUID: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_REAL: + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TYPE_TIMESTAMP: + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_BINARY: + case SQL_CHAR: + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_SS_XML: + case SQL_SS_UDT: + case SQL_WLONGVARCHAR: + case SQL_DATETIME: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + { + // unixODBC 2.3.1 requires wide calls to support pooling + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC ); + break; + } + + // for wide char types for which the size is known, return the octet length instead, since it will include the + // the number of bytes necessary for the string, not just the characters + case SQL_WCHAR: + case SQL_WVARCHAR: + { + // unixODBC 2.3.1 requires wide calls to support pooling + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC ); + break; + } + + default: + DIE ( "Unexpected SQL type encountered in calc_string_size." ); + } + } + catch( core::CoreException& e ) { + throw e; + } +} + + +// calculates how many characters were cut off from the end of a buffer when reading +// in UTF-8 encoded text + +size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ) +{ + const char* last_char = buffer + buffer_end - 1; + size_t need_to_read = 0; + + // rewind until we are at the byte that starts the cut off character + while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) { + --last_char; + ++need_to_read; + } + + // determine how many bytes we need to read in based on the number of bytes in the character + // (# of high bits set) versus the number of bytes we've already read. + switch( *last_char & UTF8_NBYTESEQ_MASK ) { + case UTF8_2BYTESEQ_TAG1: + case UTF8_2BYTESEQ_TAG2: + need_to_read = 1 - need_to_read; + break; + case UTF8_3BYTESEQ_TAG: + need_to_read = 2 - need_to_read; + break; + case UTF8_4BYTESEQ_TAG: + need_to_read = 3 - need_to_read; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )); + break; + } + + return need_to_read; +} + + +// Caller is responsible for freeing the memory allocated for the field_value. +// The memory allocation has to happen in the core layer because otherwise +// the driver layer would have to calculate size of the field_value +// to decide the amount of memory allocation. +void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype + sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) +{ + try { + + close_active_stream( stmt TSRMLS_CC ); + + // make sure that fetch is called before trying to retrieve. + CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { + throw core::CoreException(); + } + + // make sure that fields are not retrieved incorrectly. + CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, + stmt->last_field_index ) { + throw core::CoreException(); + } + + switch( sqlsrv_php_type.typeinfo.type ) { + + case SQLSRV_PHPTYPE_INT: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); + *field_value_temp = 0; + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), + field_len, true /*handle_warning*/ TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( *field_len == SQL_NULL_DATA ) { + field_value = NULL; + break; + } + + field_value = field_value_temp; + field_value_temp.transferred(); + break; + } + + case SQLSRV_PHPTYPE_FLOAT: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), + field_len, true /*handle_warning*/ TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( *field_len == SQL_NULL_DATA ) { + field_value = NULL; + break; + } + + field_value = field_value_temp; + field_value_temp.transferred(); + break; + } + + case SQLSRV_PHPTYPE_STRING: + { + get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + break; + } + + // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and + // convert it to a DateTime object and return the created object + case SQLSRV_PHPTYPE_DATETIME: + { + char field_value_temp[ MAX_DATETIME_STRING_LEN ]; + zval params[1]; + zval field_value_temp_z; + zval function_z; + + ZVAL_UNDEF( &field_value_temp_z ); + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( params ); + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, + MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + zval_auto_ptr return_value_z; + return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); + ZVAL_UNDEF( return_value_z ); + + if( *field_len == SQL_NULL_DATA ) { + ZVAL_NULL( return_value_z ); + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + break; + } + + // Convert the string date to a DateTime object + core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); + core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 ); + params[0] = field_value_temp_z; + + if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, + params TSRMLS_CC ) == FAILURE) { + THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); + } + + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + zend_string_free( Z_STR( field_value_temp_z )); + zend_string_free( Z_STR( function_z )); + break; + } + + // create a stream wrapper around the field and return that object to the PHP script. calls to fread + // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file + // for how these fields are used. + case SQLSRV_PHPTYPE_STREAM: + { + + php_stream* stream = NULL; + sqlsrv_stream* ss = NULL; + SQLLEN sql_type; + + SQLRETURN r = SQLColAttributeW( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { + throw core::CoreException(); + } + + stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); + + CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { + throw core::CoreException(); + } + + ss = static_cast( stream->abstract ); + ss->stmt = stmt; + ss->field_index = field_index; + ss->sql_type = static_cast( sql_type ); + ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); + + zval_auto_ptr return_value_z; + return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); + ZVAL_UNDEF( return_value_z ); + + // turn our stream into a zval to be returned + php_stream_to_zval( stream, return_value_z ); + + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + break; + } + + case SQLSRV_PHPTYPE_NULL: + field_value = NULL; + *field_len = 0; + break; + + default: + DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); + break; + } + + // sucessfully retrieved the field, so update our last retrieved field + if( stmt->last_field_index < field_index ) { + stmt->last_field_index = field_index; + } + } + catch( core::CoreException& e ) { + throw e; + } +} + + +// check_for_next_stream_parameter +// see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise +// returns false +bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +{ + zend_ulong stream_index = 0; + SQLRETURN r = SQL_SUCCESS; + sqlsrv_stream* stream_encoding = NULL; + zval* param_z = NULL; + + // get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param + r = core::SQLParamData( stmt, reinterpret_cast( &stream_index ) TSRMLS_CC ); + // if no more data, we've exhausted the bound parameters, so return that we're done + if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { + + // we're all done, so return false + return false; + } + + HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); + + // pull out the sqlsrv_encoding struct + stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(streams_ht, stream_index)); + SQLSRV_ASSERT(stream_encoding != NULL, "Stream parameter does not exist"); // if the index isn't in the hash, that's a serious error + + param_z = stream_encoding->stream_z; + + // make the next stream current + stmt->current_stream = sqlsrv_stream( param_z, stream_encoding->encoding ); + stmt->current_stream_read = 0; + + // there are more parameters + return true; +} + + +// utility routine to convert an input parameter from UTF-8 to UTF-16 + +bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z ) +{ + SQLSRV_ASSERT( input_param_z == converted_param_z || Z_TYPE_P( converted_param_z ) == IS_NULL, + "convert_input_param_z called with invalid parameter states" ); + + const char* buffer = Z_STRVAL_P( input_param_z ); + std::size_t buffer_len = Z_STRLEN_P( input_param_z ); + int wchar_size; + + if (buffer_len > INT_MAX) + { + LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } + + // if the string is empty, then just return that the conversion succeeded as + // MultiByteToWideChar will "fail" on an empty string. + if( buffer_len == 0 ) { + core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); + return true; + } + + // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string +#ifndef _WIN32 + wchar_size = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); +#else + wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); +#endif // !_WIN32 + + // if there was a problem determining the size of the string, return false + if( wchar_size == 0 ) { + return false; + } + sqlsrv_malloc_auto_ptr wbuffer; + wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( SQLWCHAR ) )); + // convert the utf-8 string to a wchar string in the new buffer +#ifndef _WIN32 + int r = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast( buffer ), static_cast( buffer_len ), wbuffer, wchar_size ); +#else + int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), static_cast( buffer_len ), wbuffer, wchar_size ); +#endif // !_WIN32 + // if there was a problem converting the string, then free the memory and return false + if( r == 0 ) { + return false; + } + + // null terminate the string, set the size within the zval, and return success + 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(); + + return true; +} + +// returns the ODBC C type constant that matches the PHP type and encoding given + +SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) +{ + SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; + int php_type = Z_TYPE_P( param_z ); + + switch( php_type ) { + + case IS_NULL: + switch( encoding ) { + // The c type is set to match to the corresponding sql_type. For NULL cases, if the server type + // is a binary type, than the server expects the sql_type to be binary type as well, otherwise + // an error stating "Implicit conversion not allowed.." is thrown by the server. + // For all other server types, setting the sql_type to sql_char works fine. + case SQLSRV_ENCODING_BINARY: + sql_c_type = SQL_C_BINARY; + break; + default: + sql_c_type = SQL_C_CHAR; + break; + } + break; + case IS_TRUE: + case IS_FALSE: + sql_c_type = SQL_C_SLONG; + break; + case IS_LONG: + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) { + sql_c_type = SQL_C_SBIGINT; + } + else { + sql_c_type = SQL_C_SLONG; + } + break; + case IS_DOUBLE: + sql_c_type = SQL_C_DOUBLE; + break; + case IS_STRING: + case IS_RESOURCE: + switch( encoding ) { + case SQLSRV_ENCODING_CHAR: + sql_c_type = SQL_C_CHAR; + break; + case SQLSRV_ENCODING_BINARY: + sql_c_type = SQL_C_BINARY; + break; + case CP_UTF8: + sql_c_type = SQL_C_WCHAR; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); + break; + } + break; + // it is assumed that an object is a DateTime since it's the only thing we support. + // verification that it's a real DateTime object occurs in core_sqlsrv_bind_param. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + sql_c_type = SQL_C_CHAR; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); + break; + } + + return sql_c_type; +} + + +// given a zval and encoding, determine the appropriate sql type +void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLSMALLINT& sql_type TSRMLS_DC ) +{ + sql_type = SQL_UNKNOWN_TYPE; + int php_type = Z_TYPE_P(param_z); + switch( php_type ) { + + case IS_NULL: + switch( encoding ) { + // Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases, + // if the server type is a binary type, than the server expects the sql_type to be binary type + // as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the + // server. For all other server types, setting the sql_type to sql_char works fine. + case SQLSRV_ENCODING_BINARY: + sql_type = SQL_BINARY; + break; + default: + sql_type = SQL_CHAR; + break; + } + break; + case IS_TRUE: + case IS_FALSE: + sql_type = SQL_INTEGER; + break; + case IS_LONG: + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) { + sql_type = SQL_BIGINT; + } + else { + sql_type = SQL_INTEGER; + } + break; + case IS_DOUBLE: + sql_type = SQL_FLOAT; + break; + case IS_RESOURCE: + case IS_STRING: + switch( encoding ) { + case SQLSRV_ENCODING_CHAR: + sql_type = SQL_VARCHAR; + break; + case SQLSRV_ENCODING_BINARY: + sql_type = SQL_VARBINARY; + break; + case CP_UTF8: + sql_type = SQL_WVARCHAR; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); + break; + } + break; + // it is assumed that an object is a DateTime since it's the only thing we support. + // verification that it's a real DateTime object occurs in the calling function. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + // if the user is sending this type to SQL Server 2005 or earlier, make it appear + // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET + // since these are the date types of the highest precision for their respective server versions + if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { + sql_type = SQL_TYPE_TIMESTAMP; + } + else { + sql_type = SQL_SS_TIMESTAMPOFFSET; + } + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); + break; + } + +} + + +// given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate) + +void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) +{ + int php_type = Z_TYPE_P( param_z ); + column_size = 0; + decimal_digits = 0; + + switch( php_type ) { + + case IS_NULL: + column_size = 1; + break; + // size is not necessary for these types, they are inferred by ODBC + case IS_TRUE: + case IS_FALSE: + case IS_LONG: + case IS_DOUBLE: + case IS_RESOURCE: + break; + case IS_STRING: + { + size_t char_size = (encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( SQLWCHAR ) : sizeof( char ); + SQLULEN byte_len = Z_STRLEN_P(param_z) * char_size; + if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { + column_size = SQL_SERVER_MAX_TYPE_SIZE; + } + else { + column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size; + } + break; + } + // it is assumed that an object is a DateTime since it's the only thing we support. + // verification that it's a real DateTime object occurs in the calling function. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + // if the user is sending this type to SQL Server 2005 or earlier, make it appear + // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET + // since these are the date types of the highest precision for their respective server versions + if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { + column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION; + decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE; + } + else { + column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION; + decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE; + } + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); + break; + } +} + +void field_cache_dtor( zval* data_z ) +{ + field_cache* cache = static_cast( Z_PTR_P( data_z )); + if( cache->value ) + { + sqlsrv_free( cache->value ); + } + sqlsrv_free( cache ); +} + + +// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output +// parameters will be present until all results are processed (since output parameters can depend on results +// while being processed). This function updates the lengths of output parameter strings from the ind_ptr +// parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary. +// For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server + +void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + if( Z_ISUNDEF(stmt->output_params) ) + return; + + HashTable* params_ht = Z_ARRVAL( stmt->output_params ); + zend_ulong index = -1; + zend_string* key = NULL; + void* output_param_temp = NULL; + + ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { + sqlsrv_output_param* output_param = static_cast( output_param_temp ); + zval* value_z = Z_REFVAL_P( output_param->param_z ); + switch( Z_TYPE_P( value_z )) { + case IS_STRING: + { + // 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 ]; + if( str_len == SQL_NULL_DATA || str_len == 0 ) { + zend_string_release( Z_STR_P( value_z )); + ZVAL_NULL( value_z ); + continue; + } + + // if there was more to output than buffer size to hold it, then throw a truncation error + int null_size = 0; + switch( output_param->encoding ) { + case SQLSRV_ENCODING_UTF8: + null_size = sizeof( SQLWCHAR ); // string isn't yet converted to UTF-8, still UTF-16 + break; + case SQLSRV_ENCODING_SYSTEM: + null_size = 1; + break; + case SQLSRV_ENCODING_BINARY: + null_size = 0; + break; + default: + SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); + break; + } + CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { + throw core::CoreException(); + } + + // if it's not in the 8 bit encodings, then it's in UTF-16 + if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { + bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { + // 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'; + core::sqlsrv_zval_stringl(value_z, str, str_len); + } + else { + core::sqlsrv_zval_stringl(value_z, str, str_len); + } + } + 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 ) { + ZVAL_NULL( value_z ); + } + else if( output_param->is_bool ) { + convert_to_boolean( value_z ); + } + else { + ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); + } + break; + case IS_DOUBLE: + // 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 ) { + ZVAL_NULL( value_z ); + } + break; + default: + DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); + break; + } + value_z = NULL; + } ZEND_HASH_FOREACH_END(); + + // empty the hash table since it's been processed + zend_hash_clean( Z_ARRVAL( stmt->output_params )); + return; +} + +void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, + _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) +{ + SQLRETURN r; + SQLSMALLINT c_type; + SQLLEN sql_field_type = 0; + SQLSMALLINT extra = 0; + SQLLEN field_len_temp; + SQLLEN sql_display_size = 0; + char* field_value_temp = NULL; + + try { + + DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, + "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); + + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); + } + + // Set the C type and account for null characters at the end of the data. + switch( sqlsrv_php_type.typeinfo.encoding ) { + case CP_UTF8: + c_type = SQL_C_WCHAR; + extra = sizeof( SQLWCHAR ); + break; + case SQLSRV_ENCODING_BINARY: + c_type = SQL_C_BINARY; + extra = 0; + break; + default: + c_type = SQL_C_CHAR; + extra = sizeof( SQLCHAR ); + break; + } + + // Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + + // Calculate the field size. + calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); + + // if this is a large type, then read the first few bytes to get the actual length from SQLGetData + if( sql_display_size == 0 || sql_display_size == INT_MAX || + sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { + + field_len_temp = INITIAL_FIELD_STRING_LEN; + SQLLEN initiallen = field_len_temp + extra; + + + field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); + + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), + &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( field_len_temp == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + if( r == SQL_SUCCESS_WITH_INFO ) { + + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; + + stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); + + // with Linux connection pooling may not get a truncated warning back but the actual field_len_temp + // can be greater than the initallen value. +#ifndef _WIN32 + if( is_truncated_warning( state ) || initiallen < field_len_temp) { +#else + if( is_truncated_warning( state ) ) { +#endif // !_WIN32 + + SQLLEN dummy_field_len; + + // for XML (and possibly other conditions) the field length returned is not the real field length, so + // in every pass, we double the allocation size to retrieve all the contents. + if( field_len_temp == SQL_NO_TOTAL ) { + + // reset the field_len_temp + field_len_temp = INITIAL_FIELD_STRING_LEN; + + do { + SQLLEN initial_field_len = field_len_temp; + // Double the size. + field_len_temp *= 2; + + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); + + field_len_temp -= initial_field_len; + + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, + field_len_temp + extra, &dummy_field_len, + false /*handle_warning*/ TSRMLS_CC ); + + // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL + // so we calculate the actual length of the string with that. + if( dummy_field_len != SQL_NO_TOTAL ) + field_len_temp += dummy_field_len; + else + field_len_temp += initial_field_len; + + if( r == SQL_SUCCESS_WITH_INFO ) { + core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len + TSRMLS_CC ); + } + + } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); + } + else { + + // We got the field_len_temp from SQLGetData call. + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); + + // We have already recieved INITIAL_FIELD_STRING_LEN size data. + field_len_temp -= INITIAL_FIELD_STRING_LEN; + + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, + field_len_temp + extra, &dummy_field_len, + true /*handle_warning*/ TSRMLS_CC ); + + if( dummy_field_len == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + field_len_temp += INITIAL_FIELD_STRING_LEN; + } + + } // if( is_truncation_warning ( state ) ) + else { + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + } + } // if( r == SQL_SUCCESS_WITH_INFO ) + + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { + + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) + + else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { + + // only allow binary retrievals for char and binary types. All others get a string converted + // to the encoding type they asked for. + + // null terminator + if( c_type == SQL_C_CHAR ) { + sql_display_size += sizeof( SQLCHAR ); + } + + // For WCHAR multiply by sizeof(WCHAR) and include the null terminator + else if( c_type == SQL_C_WCHAR ) { + sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); + } + + field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); + + // get the data + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, + &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); + CHECK_SQL_ERROR( r, stmt ) { + throw core::CoreException(); + } + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( field_len_temp == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { + + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) + + else { + + DIE( "Invalid sql_display_size" ); + return; // to eliminate a warning + } + + field_value = field_value_temp; + *field_len = field_len_temp; + + // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP + // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. + // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary + // operator to set add 1 to fill the null terminator + + // with unixODBC connection pooling sometimes field_len_temp can be SQL_NO_DATA. + // In that cause do not set null terminator and set length to 0. + if ( field_len_temp > 0 ) + { + field_value_temp[field_len_temp] = '\0'; + } + else + { + *field_len = 0; + } + } + + catch( core::CoreException& ) { + + field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } + catch ( ... ) { + + field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } + +} + + +// return the option from the stmt_opts array that matches the key. If no option found, +// NULL is returned. + +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ) +{ + 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 ]; + } + } + + return NULL; // no option found +} + +// is_fixed_size_type +// returns true if the SQL data type is a fixed length, as opposed to a variable length data type such as varchar or varbinary + +bool is_fixed_size_type( SQLINTEGER sql_type ) +{ + switch( sql_type ) { + + case SQL_BINARY: + case SQL_CHAR: + case SQL_WCHAR: + case SQL_VARCHAR: + case SQL_WVARCHAR: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + case SQL_SS_XML: + case SQL_SS_UDT: + return false; + } + + return true; +} + +bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) +{ + switch( type.typeinfo.type ) { + + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_FLOAT: + case SQLSRV_PHPTYPE_DATETIME: + return true; + case SQLSRV_PHPTYPE_STRING: + case SQLSRV_PHPTYPE_STREAM: + { + if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR + || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + return true; + } + break; + } + } + + return false; +} + + +// verify there is enough space to hold the output string parameter, and allocate it if needed. The param_z +// is updated to have the new buffer with the correct size and its reference is incremented. The output +// string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and +// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter + +void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, + SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, + SQLLEN& buffer_len TSRMLS_DC ) +{ + SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); + buffer_len = Z_STRLEN_P( param_z ); + SQLLEN expected_len; + SQLLEN buffer_null_extra; + SQLLEN elem_size; + SQLLEN without_null_len; + + // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, + // as is a n(var)char/ntext field being returned as a binary field. + elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1; + + // account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning + expected_len = column_size * elem_size + elem_size; + + // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations + buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; + + // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter + without_null_len = column_size * elem_size; + + // increment to include the null terminator since the Zend length doesn't include the null terminator + buffer_len += elem_size; + + // if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new + // length. + if( buffer_len < expected_len ) { + SQLSRV_ASSERT( expected_len >= expected_len - buffer_null_extra, + "Integer overflow/underflow caused a corrupt field length." ); + + // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since + // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about + // not having a NULL terminator on a string. + zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); + + // A zval string len doesn't include the null. This calculates the length it should be + // regardless of whether the ODBC type contains the NULL or not. + + // null terminate the string to avoid a warning in debug PHP builds + ZSTR_VAL(param_z_string)[without_null_len] = '\0'; + ZVAL_NEW_STR(param_z, param_z_string); + + // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the + // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY + buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; + + // Zend string length doesn't include the null terminator + ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; + } + + buffer = Z_STRVAL_P(param_z); + + // 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); + } +} + +// output parameters have their reference count incremented so that they do not disappear +// while the query is executed and processed. They are saved in the statement so that +// their reference count may be decremented later (after results are processed) + +void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ) +{ + HashTable* param_ht = Z_ARRVAL( stmt->output_params ); + zend_ulong paramno = static_cast( param.param_num ); + core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, ¶m, sizeof( sqlsrv_output_param )); + Z_TRY_ADDREF_P( param.param_z ); // we have a reference to the param +} + + +// send all the stream data + +void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { } +} + + +// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed +void sqlsrv_output_param_dtor( zval* data ) +{ + sqlsrv_output_param *output_param = static_cast( Z_PTR_P( data )); + zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold + sqlsrv_free( output_param ); +} + +// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed +void sqlsrv_stream_dtor( zval* data ) +{ + sqlsrv_stream* stream_encoding = static_cast( Z_PTR_P( data )); + zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold + sqlsrv_free( stream_encoding ); +} + +} diff --git a/pdo_sqlsrv/core_stream.cpp b/source/shared/core_stream.cpp similarity index 84% rename from pdo_sqlsrv/core_stream.cpp rename to source/shared/core_stream.cpp index b85c63ce..ad915ad7 100644 --- a/pdo_sqlsrv/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -1,261 +1,269 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_stream.cpp -// -// Contents: Implementation of PHP streams for reading SQL Server data -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" -#include - -namespace { - -// close a stream and free the PHP resources used by it - -int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) -{ - sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); - - // free the stream resources in the Zend engine - php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM ); - - // UNDEF the stream zval and delete our reference count to it. - ZVAL_UNDEF( &( ss->stmt->active_stream ) ); - - sqlsrv_free( ss ); - stream->abstract = NULL; - - return 0; -} - - -// read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are -// set when sqlsrv_get_field is called by the user specifying which field type they want. - -size_t sqlsrv_stream_read( php_stream* stream, _Out_writes_bytes_(count) char* buf, size_t count TSRMLS_DC ) -{ - SQLLEN read = 0; - SQLSMALLINT c_type = SQL_C_CHAR; - char* get_data_buffer = buf; - sqlsrv_malloc_auto_ptr temp_buf; - - sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); - - try { - - if( stream->eof ) { - return 0; - }; - - switch( ss->encoding ) { - case SQLSRV_ENCODING_CHAR: - c_type = SQL_C_CHAR; - break; - - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - break; - - case CP_UTF8: - { - c_type = SQL_C_WCHAR; - count /= 2; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes - if( count > PHP_STREAM_BUFFER_SIZE ) { - count = PHP_STREAM_BUFFER_SIZE; - } - - // use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16 - temp_buf = static_cast( sqlsrv_malloc( PHP_STREAM_BUFFER_SIZE )); - get_data_buffer = temp_buf; - break; - } - - default: - DIE( "Unknown encoding type when reading from a stream" ); - break; - } - - SQLRETURN r = SQLGetData( ss->stmt->handle(), ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read ); - - CHECK_SQL_ERROR( r, ss->stmt ) { - stream->eof = 1; - throw core::CoreException(); - } - - // if the stream returns either no data, NULL data, or returns data < than the count requested then - // we are at the "end of the stream" so we mark it - if( r == SQL_NO_DATA || read == SQL_NULL_DATA || ( static_cast( read ) <= count && read != SQL_NO_TOTAL )) { - stream->eof = 1; - } - - // if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator - // if it's not a binary encoded field - if( r == SQL_SUCCESS_WITH_INFO ) { - - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - - ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - - if( read == SQL_NO_TOTAL ) { - SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it " - "did not occur." ); - } - - if( is_truncated_warning( state ) ) { - switch( c_type ) { - - // As per SQLGetData documentation, if the length of character data exceeds the BufferLength, - // SQLGetData truncates the data to BufferLength less the length of null-termination character. - case SQL_C_BINARY: - read = count; - break; - case SQL_C_WCHAR: - read = ( count % 2 == 0 ? count - 2 : count - 3 ); - break; - case SQL_C_CHAR: - read = count - 1; - break; - default: - DIE( "sqlsrv_stream_read: should have never reached in this switch case."); - break; - } - } - else { - CHECK_SQL_WARNING( r, ss->stmt ); - } - } - - // if the encoding is UTF-8 - if (c_type == SQL_C_WCHAR) { - - count *= 2; // undo the shift to use the full buffer - - // flags set to 0 by default, which means that any invalid characters are dropped rather than causing - // an error. This happens only on XP. - DWORD flags = 0; - - // convert to UTF-8 - if (g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER) { - // Vista (and later) will detect invalid UTF-16 characters and raise an error. - flags = WC_ERR_INVALID_CHARS; - } - - if ( count > INT_MAX || (read >> 1) > INT_MAX ) - { - LOG(SEV_ERROR, "UTF-16 (wide character) string mapping: buffer length exceeded."); - throw core::CoreException(); - } - - int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast( temp_buf.get() ), - static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); - - if( enc_len == 0 ) { - - stream->eof = 1; - THROW_CORE_ERROR( ss->stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ); - } - - read = enc_len; - } - - return read; - } - - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." ); - return 0; - } -} - -// function table for stream operations. We only support reading and closing the stream -php_stream_ops sqlsrv_stream_ops = { - NULL, - sqlsrv_stream_read, - sqlsrv_stream_close, - NULL, - SQLSRV_STREAM, - NULL, - NULL, - NULL, - NULL -}; - -// open a stream and return the sqlsrv_stream_ops function table as part of the -// return value. There is only one valid way to open a stream, using sqlsrv_get_field on -// certain field types. A sqlsrv stream may only be opened in read mode. -static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, _In_ const char*, _In_ const char* mode, - int options, _In_ zend_string **, php_stream_context* STREAMS_DC TSRMLS_DC ) -{ - -#if ZEND_DEBUG - SQLSRV_UNUSED( __zend_orig_lineno ); - SQLSRV_UNUSED( __zend_orig_filename ); - SQLSRV_UNUSED( __zend_lineno ); - SQLSRV_UNUSED( __zend_filename ); - SQLSRV_UNUSED( __php_stream_call_depth ); -#endif - - sqlsrv_malloc_auto_ptr ss; - - ss = static_cast( sqlsrv_malloc( sizeof( sqlsrv_stream ))); - memset( ss, 0, sizeof( sqlsrv_stream )); - - // check for valid options - if( options != REPORT_ERRORS ) { - php_stream_wrapper_log_error( wrapper, options TSRMLS_CC, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" ); - return NULL; - } - - // allocate the stream from PHP - php_stream* php_str = php_stream_alloc( &sqlsrv_stream_ops, ss, 0, mode ); - if( php_str != NULL ) { - ss.transferred(); - } - - return php_str; -} - -// information structure that contains PHP stream wrapper info. We supply the minimal -// possible, including the open function and the name only. - -php_stream_wrapper_ops sqlsrv_stream_wrapper_ops = { - sqlsrv_stream_opener, - NULL, - NULL, - NULL, - NULL, - SQLSRV_STREAM_WRAPPER, - NULL, - NULL, - NULL, - NULL -}; - -} - -// structure used by PHP to get the function table for opening, closing, etc. of the stream -php_stream_wrapper g_sqlsrv_stream_wrapper = { - &sqlsrv_stream_wrapper_ops, - NULL, - 0 -}; +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_stream.cpp +// +// Contents: Implementation of PHP streams for reading SQL Server data +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +namespace { + +// close a stream and free the PHP resources used by it + +int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) +{ + sqlsrv_stream* ss = static_cast( stream->abstract ); + SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); + + // free the stream resources in the Zend engine + php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM ); + + // UNDEF the stream zval and delete our reference count to it. + ZVAL_UNDEF( &( ss->stmt->active_stream ) ); + + sqlsrv_free( ss ); + stream->abstract = NULL; + + return 0; +} + + +// read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are +// set when sqlsrv_get_field is called by the user specifying which field type they want. + +size_t sqlsrv_stream_read( php_stream* stream, _Out_writes_bytes_(count) char* buf, size_t count TSRMLS_DC ) +{ + SQLLEN read = 0; + SQLSMALLINT c_type = SQL_C_CHAR; + char* get_data_buffer = buf; + sqlsrv_malloc_auto_ptr temp_buf; + + sqlsrv_stream* ss = static_cast( stream->abstract ); + SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); + + try { + + if( stream->eof ) { + return 0; + }; + + switch( ss->encoding ) { + case SQLSRV_ENCODING_CHAR: + c_type = SQL_C_CHAR; + break; + + case SQLSRV_ENCODING_BINARY: + c_type = SQL_C_BINARY; + break; + + case CP_UTF8: + { + c_type = SQL_C_WCHAR; + count /= 2; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes + if( count > PHP_STREAM_BUFFER_SIZE ) { + count = PHP_STREAM_BUFFER_SIZE; + } + + // use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16 + temp_buf = static_cast( sqlsrv_malloc( PHP_STREAM_BUFFER_SIZE )); + memset(temp_buf, 0, PHP_STREAM_BUFFER_SIZE); + get_data_buffer = temp_buf; + break; + } + + default: + DIE( "Unknown encoding type when reading from a stream" ); + break; + } + + SQLRETURN r = SQLGetData( ss->stmt->handle(), ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read ); + + CHECK_SQL_ERROR( r, ss->stmt ) { + stream->eof = 1; + throw core::CoreException(); + } + + // if the stream returns either no data, NULL data, or returns data < than the count requested then + // we are at the "end of the stream" so we mark it + if( r == SQL_NO_DATA || read == SQL_NULL_DATA || ( static_cast( read ) <= count && read != SQL_NO_TOTAL )) { + stream->eof = 1; + } + + // if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator + // if it's not a binary encoded field + if( r == SQL_SUCCESS_WITH_INFO ) { + + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; + + ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); + + if( read == SQL_NO_TOTAL ) { + SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it " + "did not occur." ); + } + + // with unixODBC connection pooling enabled the truncated state may not be returned so check the actual length read + // with buffer length. + #ifndef _WIN32 + if( is_truncated_warning( state ) || count < read) { + #else + if( is_truncated_warning( state ) ) { + #endif // !_WIN32 + switch( c_type ) { + + // As per SQLGetData documentation, if the length of character data exceeds the BufferLength, + // SQLGetData truncates the data to BufferLength less the length of null-termination character. + case SQL_C_BINARY: + read = count; + break; + case SQL_C_WCHAR: + read = ( count % 2 == 0 ? count - 2 : count - 3 ); + break; + case SQL_C_CHAR: + read = count - 1; + break; + default: + DIE( "sqlsrv_stream_read: should have never reached in this switch case."); + break; + } + } + else { + CHECK_SQL_WARNING( r, ss->stmt ); + } + } + + // if the encoding is UTF-8 + if( c_type == SQL_C_WCHAR ) { + count *= 2; + // undo the shift to use the full buffer + // flags set to 0 by default, which means that any invalid characters are dropped rather than causing + // an error. This happens only on XP. + // convert to UTF-8 +#ifdef _WIN32 + DWORD flags = 0; + if( g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { + // Vista (and later) will detect invalid UTF-16 characters and raise an error. + flags = WC_ERR_INVALID_CHARS; + } +#endif // _WIN32 + if( count > INT_MAX || (read >> 1) > INT_MAX ) { + LOG(SEV_ERROR, "UTF-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } + +#ifndef _WIN32 + int enc_len = SystemLocale::FromUtf16( ss->encoding, reinterpret_cast( temp_buf.get() ), + static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); +#else + int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast( temp_buf.get() ), + static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); +#endif // !_WIN32 + if( enc_len == 0 ) { + + stream->eof = 1; + THROW_CORE_ERROR( ss->stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ); + } + + read = enc_len; + } + + return read; + } + + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." ); + return 0; + } +} + +// function table for stream operations. We only support reading and closing the stream +php_stream_ops sqlsrv_stream_ops = { + NULL, + sqlsrv_stream_read, + sqlsrv_stream_close, + NULL, + SQLSRV_STREAM, + NULL, + NULL, + NULL, + NULL +}; + +// open a stream and return the sqlsrv_stream_ops function table as part of the +// return value. There is only one valid way to open a stream, using sqlsrv_get_field on +// certain field types. A sqlsrv stream may only be opened in read mode. +static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, _In_ const char*, _In_ const char* mode, + int options, _In_ zend_string **, php_stream_context* STREAMS_DC TSRMLS_DC ) +{ + +#if ZEND_DEBUG + SQLSRV_UNUSED( __zend_orig_lineno ); + SQLSRV_UNUSED( __zend_orig_filename ); + SQLSRV_UNUSED( __zend_lineno ); + SQLSRV_UNUSED( __zend_filename ); + SQLSRV_UNUSED( __php_stream_call_depth ); +#endif + + sqlsrv_malloc_auto_ptr ss; + + ss = static_cast( sqlsrv_malloc( sizeof( sqlsrv_stream ))); + memset( ss, 0, sizeof( sqlsrv_stream )); + + // check for valid options + if( options != REPORT_ERRORS ) { + php_stream_wrapper_log_error( wrapper, options TSRMLS_CC, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" ); + return NULL; + } + + // allocate the stream from PHP + php_stream* php_str = php_stream_alloc( &sqlsrv_stream_ops, ss, 0, mode ); + if( php_str != NULL ) { + ss.transferred(); + } + + return php_str; +} + +// information structure that contains PHP stream wrapper info. We supply the minimal +// possible, including the open function and the name only. + +php_stream_wrapper_ops sqlsrv_stream_wrapper_ops = { + sqlsrv_stream_opener, + NULL, + NULL, + NULL, + NULL, + SQLSRV_STREAM_WRAPPER, + NULL, + NULL, + NULL, + NULL +}; + +} + +// structure used by PHP to get the function table for opening, closing, etc. of the stream +php_stream_wrapper g_sqlsrv_stream_wrapper = { + &sqlsrv_stream_wrapper_ops, + NULL, + 0 +}; diff --git a/sqlsrv/core_util.cpp b/source/shared/core_util.cpp similarity index 82% rename from sqlsrv/core_util.cpp rename to source/shared/core_util.cpp index 8bc410f3..94b5bf6d 100644 --- a/sqlsrv/core_util.cpp +++ b/source/shared/core_util.cpp @@ -1,400 +1,390 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_util.cpp -// -// Contents: Utility functions used by both connection or statement functions for both the PDO and sqlsrv drivers -// -// Comments: Mostly error handling and some type handling -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include - -namespace { - -// *** internal constants *** -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 - -// routine used by utf16_string_from_mbcs_string -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, - unsigned int mbcs_len, - _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, - unsigned int utf16_len ); -} - -// SQLSTATE for all internal errors -SQLCHAR IMSSP[] = "IMSSP"; - -// SQLSTATE for all internal warnings -SQLCHAR SSPWARN[] = "01SSP"; - -// write to the php log if the severity and subsystem match the filters currently set in the INI or -// the script (sqlsrv_configure). -void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ...) -{ - SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." ); - - va_list args; - va_start( args, msg ); - - g_driver_log( severity TSRMLS_CC, msg, &args ); - - va_end( args ); -} - -void core_sqlsrv_register_logger( log_callback driver_logger ) -{ - g_driver_log = driver_logger; -} - - -// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new -// length in the len parameter. If no errors occurred during convertion, true is returned and the original -// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed -// and false is returned. - -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len) -{ - SQLSRV_ASSERT( string != NULL, "String must be specified" ); - - if (validate_string(*string, len)) { - return true; - } - - char* outString = NULL; - SQLLEN outLen = 0; - - bool result = convert_string_from_utf16( encoding, - reinterpret_cast(*string), int(len / sizeof(wchar_t)), &outString, outLen); - - if (result) - { - sqlsrv_free( *string ); - *string = outString; - len = outLen; - } - - return result; -} - -bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len) -{ - char* string = Z_STRVAL_P(value_z); - - if (validate_string(string, len)) { - return true; - } - - char* outString = NULL; - SQLLEN outLen = 0; - - bool result = convert_string_from_utf16(encoding, - reinterpret_cast(string), int(len / sizeof(wchar_t)), &outString, outLen); - - if (result) - { - core::sqlsrv_zval_stringl(value_z, outString, outLen); - sqlsrv_free(outString); - len = outLen; - } - - return result; -} - -bool validate_string(char* string, SQLLEN& len) -{ - SQLSRV_ASSERT(string != NULL, "String must be specified"); - - //for the empty string, we simply returned we converted it - if (len == 0 && string[0] == '\0') { - return true; - } - - if ((len / sizeof(wchar_t)) > INT_MAX) - { - LOG(SEV_ERROR, "UTP-16 (wide character) string mapping: buffer length exceeded."); - throw core::CoreException(); - } - - return false; -} - -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ) -{ - SQLSRV_ASSERT( inString != NULL, "Input string must be specified" ); - SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" ); - SQLSRV_ASSERT( *outString == NULL, "Output buffer pointer must not be set" ); - - if (cchInLen == 0 && inString[0] == L'\0') { - *outString = reinterpret_cast( sqlsrv_malloc ( 1 ) ); - *outString[0] = '\0'; - cchOutLen = 0; - return true; - } - - // flags set to 0 by default, which means that any invalid characters are dropped rather than causing - // an error. This happens only on XP. - DWORD flags = 0; - if( encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { - // Vista (and later) will detect invalid UTF-16 characters and raise an error. - flags = WC_ERR_INVALID_CHARS; - } - - // calculate the number of characters needed - cchOutLen = WideCharToMultiByte( encoding, flags, - inString, cchInLen, - NULL, 0, NULL, NULL ); - if( cchOutLen == 0 ) { - return false; - } - - // Create a buffer to fit the encoded string - char* newString = reinterpret_cast( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ )); - int rc = WideCharToMultiByte( encoding, flags, - inString, cchInLen, - newString, static_cast(cchOutLen), NULL, NULL ); - if( rc == 0 ) { - cchOutLen = 0; - sqlsrv_free( newString ); - return false; - } - - *outString = newString; - newString[cchOutLen] = '\0'; // null terminate the encoded string - - return true; -} - -// thin wrapper around convert_string_from_default_encoding that handles -// allocation of the destination string. An empty string passed in returns -// failure since it's a failure case for convert_string_from_default_encoding. -wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len, - unsigned int* utf16_len ) -{ - *utf16_len = (mbcs_len + 1); - wchar_t* utf16_string = reinterpret_cast( sqlsrv_malloc( *utf16_len * sizeof( wchar_t ))); - *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, - utf16_string, *utf16_len ); - if( *utf16_len == 0 ) { - // we preserve the error and reset it because sqlsrv_free resets the last error - DWORD last_error = GetLastError(); - sqlsrv_free( utf16_string ); - SetLastError( last_error ); - return NULL; - } - - return utf16_string; -} - -// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the -// errno is 1 based. It returns it as an array with 3 members: -// 1/SQLSTATE) sqlstate -// 2/code) driver specific error code -// 3/message) driver specific error message -// The fetch type determines if the indices are numeric, associative, or both. - -bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_error_auto_ptr& error, logging_severity severity - TSRMLS_DC ) -{ - SQLHANDLE h = ctx.handle(); - SQLSMALLINT h_type = ctx.handle_type(); - - if( h == NULL ) { - return false; - } - - zval* ssphp_z = NULL; - int zr = SUCCESS; - zval* temp = NULL; - SQLRETURN r = SQL_SUCCESS; - SQLSMALLINT wmessage_len = 0; - SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ]; - SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLSRV_ENCODING enc = ctx.encoding(); - - switch( h_type ) { - - case SQL_HANDLE_STMT: - { - sqlsrv_stmt* stmt = static_cast( &ctx ); - if( stmt->current_results != NULL ) { - - error = stmt->current_results->get_diag_rec( record_number ); - // don't use the CHECK* macros here since it will trigger reentry into the error handling system - if( error == NULL ) { - return false; - } - break; - } - - // convert the error into the encoding of the context - if( enc == SQLSRV_ENCODING_DEFAULT ) { - enc = stmt->conn->encoding(); - } - } - - - default: - - error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); - r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message, - SQL_MAX_MESSAGE_LENGTH + 1, &wmessage_len ); - // don't use the CHECK* macros here since it will trigger reentry into the error handling system - if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - return false; - } - - SQLLEN sqlstate_len = 0; - convert_string_from_utf16(enc, wsqlstate, sizeof(wsqlstate), (char**)&error->sqlstate, sqlstate_len); - - SQLLEN message_len = 0; - convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); - break; - } - - - // log the error first - LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate ); - LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), error->native_code ); - LOG( severity, "%1!s!: message = %2!s!", ctx.func(), error->native_message ); - - error->format = false; - - return true; -} - -// format and return a driver specfic error -void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, - sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ) -{ - // allocate space for the formatted message - formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); - formatted_error->sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - formatted_error->native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast( custom_error->native_message ), 0, 0, - reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, args ); - if( rc == 0 ) { - strcpy_s( reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, - reinterpret_cast( INTERNAL_FORMAT_ERROR )); - } - - strcpy_s( reinterpret_cast( formatted_error->sqlstate ), SQL_SQLSTATE_BUFSIZE, - reinterpret_cast( custom_error->sqlstate )); - formatted_error->native_code = custom_error->native_code; - - // log the error - LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), formatted_error->sqlstate ); - LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), formatted_error->native_code ); - LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); -} - -DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ) -{ - va_list format_args; - va_start( format_args, format ); - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast(output_buffer), SQL_MAX_MESSAGE_LENGTH, &format_args ); - - va_end( format_args ); - - return rc; -} - -// return an error message for GetLastError using FormatMessage. -// this function returns the msg pointer so that it may be used within -// another function call such as handle_error -const char* get_last_error_message( DWORD last_error ) -{ - if( last_error == 0 ) { - last_error = GetLastError(); - } - - DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), - last_err_msg, sizeof( last_err_msg ), NULL ); - - if( r == 0 ) { - SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( last_err_msg )); - std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), last_err_msg ); - } - - return last_err_msg; -} - - -// die -// Terminate the PHP request with an error message -// We use this function rather than php_error directly because we use the FormatMessage syntax in most other -// places within the extension (e.g., LOG), whereas php_error uses the printf format syntax. There were -// places where we were using the FormatMessage syntax inadvertently with DIE which left messages without -// proper information. Rather than convert those messages and try and remember the difference between LOG and -// DIE, it is simpler to make the format syntax common between them. -void die( const char* msg, ... ) -{ - va_list format_args; - 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 )); - } - - php_error( E_ERROR, last_err_msg ); -} - -namespace { - -// convert from the default encoding specified by the "CharacterSet" -// connection option to UTF-16. mbcs_len and utf16_len are sizes in -// bytes. The return is the number of UTF-16 characters in the string -// returned in utf16_out_string. An empty string passed in will result as -// a failure since MBTWC returns 0 for both an empty string and failure -// to convert. -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, - unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, - unsigned int utf16_len ) -{ - unsigned int win_encoding = CP_ACP; - switch( php_encoding ) { - case SQLSRV_ENCODING_CHAR: - win_encoding = CP_ACP; - break; - // this shouldn't ever be set - case SQLSRV_ENCODING_BINARY: - DIE( "Invalid encoding." ); - break; - default: - win_encoding = php_encoding; - break; - } - unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len, - utf16_out_string, utf16_len ); - if( required_len == 0 ) { - return 0; - } - utf16_out_string[ required_len ] = '\0'; - - return required_len; -} - -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_util.cpp +// +// Contents: Utility functions used by both connection or statement functions for both the PDO and sqlsrv drivers +// +// Comments: Mostly error handling and some type handling +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +namespace { + +// *** internal constants *** +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 + +// routine used by utf16_string_from_mbcs_string +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, + unsigned int mbcs_len, + _Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string, + unsigned int utf16_len ); +} + +// SQLSTATE for all internal errors +SQLCHAR IMSSP[] = "IMSSP"; + +// SQLSTATE for all internal warnings +SQLCHAR SSPWARN[] = "01SSP"; + +// write to the php log if the severity and subsystem match the filters currently set in the INI or +// the script (sqlsrv_configure). +void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ...) +{ + SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." ); + + va_list args; + va_start( args, msg ); + + g_driver_log( severity TSRMLS_CC, msg, &args ); + + va_end( args ); +} + +void core_sqlsrv_register_logger( log_callback driver_logger ) +{ + g_driver_log = driver_logger; +} + + +// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new +// length in the len parameter. If no errors occurred during convertion, true is returned and the original +// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed +// and false is returned. + +bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len) +{ + SQLSRV_ASSERT( string != NULL, "String must be specified" ); + + if (validate_string(*string, len)) { + return true; + } + + char* outString = NULL; + SQLLEN outLen = 0; + + bool result = convert_string_from_utf16( encoding, reinterpret_cast(*string), int(len / sizeof(SQLWCHAR)), &outString, outLen ); + + if (result) + { + sqlsrv_free( *string ); + *string = outString; + len = outLen; + } + + return result; +} + +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len) +{ + char* string = Z_STRVAL_P(value_z); + + if( validate_string(string, len)) { + return true; + } + + char* outString = NULL; + SQLLEN outLen = 0; + bool result = convert_string_from_utf16( encoding, reinterpret_cast(string), int(len / sizeof(SQLWCHAR)), &outString, outLen ); + if( result ) { + core::sqlsrv_zval_stringl( value_z, outString, outLen ); + sqlsrv_free( outString ); + len = outLen; + } + return result; +} + +bool validate_string(char* string, SQLLEN& len) +{ + SQLSRV_ASSERT(string != NULL, "String must be specified"); + + //for the empty string, we simply returned we converted it + if( len == 0 && string[0] == '\0') { + return true; + } + if ((len / sizeof(SQLWCHAR)) > INT_MAX) { + LOG(SEV_ERROR, "UTP-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } + return false; +} + +bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const SQLWCHAR* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ) +{ + SQLSRV_ASSERT( inString != NULL, "Input string must be specified" ); + SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" ); + SQLSRV_ASSERT( *outString == NULL, "Output buffer pointer must not be set" ); + + if (cchInLen == 0 && inString[0] == L'\0') { + *outString = reinterpret_cast( sqlsrv_malloc ( 1 ) ); + *outString[0] = '\0'; + cchOutLen = 0; + return true; + } + + // flags set to 0 by default, which means that any invalid characters are dropped rather than causing + // an error. This happens only on XP. + DWORD flags = 0; + if( encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { + // Vista (and later) will detect invalid UTF-16 characters and raise an error. + flags = WC_ERR_INVALID_CHARS; + } + + // calculate the number of characters needed +#ifndef _WIN32 + cchOutLen = SystemLocale::FromUtf16Strict( encoding, inString, cchInLen, NULL, 0 ); +#else + cchOutLen = WideCharToMultiByte( encoding, flags, + inString, cchInLen, + NULL, 0, NULL, NULL ); +#endif // !_WIN32 + + if( cchOutLen == 0 ) { + return false; + } + + // Create a buffer to fit the encoded string + char* newString = reinterpret_cast( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ )); + +#ifndef _WIN32 + int rc = SystemLocale::FromUtf16( encoding, inString, cchInLen, newString, static_cast(cchOutLen)); +#else + int rc = WideCharToMultiByte( encoding, flags, inString, cchInLen, newString, static_cast(cchOutLen), NULL, NULL ); +#endif // !_WIN32 + if( rc == 0 ) { + cchOutLen = 0; + sqlsrv_free( newString ); + return false; + } + + *outString = newString; + newString[cchOutLen] = '\0'; // null terminate the encoded string + + return true; +} + +// thin wrapper around convert_string_from_default_encoding that handles +// allocation of the destination string. An empty string passed in returns +// failure since it's a failure case for convert_string_from_default_encoding. +SQLWCHAR* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len, + unsigned int* utf16_len ) +{ + *utf16_len = (mbcs_len + 1); + SQLWCHAR* utf16_string = reinterpret_cast( sqlsrv_malloc( *utf16_len * sizeof( SQLWCHAR ))); + *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, utf16_string, *utf16_len ); + + if( *utf16_len == 0 ) { + // we preserve the error and reset it because sqlsrv_free resets the last error + DWORD last_error = GetLastError(); + sqlsrv_free( utf16_string ); + SetLastError( last_error ); + return NULL; + } + + return utf16_string; +} + +// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the +// errno is 1 based. It returns it as an array with 3 members: +// 1/SQLSTATE) sqlstate +// 2/code) driver specific error code +// 3/message) driver specific error message +// The fetch type determines if the indices are numeric, associative, or both. + +bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_error_auto_ptr& error, logging_severity severity + TSRMLS_DC ) +{ + SQLHANDLE h = ctx.handle(); + SQLSMALLINT h_type = ctx.handle_type(); + + if( h == NULL ) { + return false; + } + + SQLRETURN r = SQL_SUCCESS; + SQLSMALLINT wmessage_len = 0; + SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ] = { L'\0' }; + SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ] = { L'\0' }; + SQLSRV_ENCODING enc = ctx.encoding(); + + switch( h_type ) { + + case SQL_HANDLE_STMT: + { + sqlsrv_stmt* stmt = static_cast( &ctx ); + if( stmt->current_results != NULL ) { + + error = stmt->current_results->get_diag_rec( record_number ); + // don't use the CHECK* macros here since it will trigger reentry into the error handling system + if( error == 0 ) { + return false; + } + break; + } + // convert the error into the encoding of the context + if( enc == SQLSRV_ENCODING_DEFAULT ) { + enc = stmt->conn->encoding(); + } + } + default: + error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); + r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message, + SQL_MAX_MESSAGE_LENGTH + 1, &wmessage_len ); + // don't use the CHECK* macros here since it will trigger reentry into the error handling system + if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { + return false; + } + + // We need to calculate number of characters + SQLLEN wsqlstate_len = sizeof( wsqlstate ) / sizeof( SQLWCHAR ); + SQLLEN sqlstate_len = 0; + 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); + break; + } + + + // log the error first + LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate ); + LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), error->native_code ); + LOG( severity, "%1!s!: message = %2!s!", ctx.func(), error->native_message ); + + error->format = false; + + return true; +} + +// format and return a driver specfic error +void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, + sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ) +{ + // allocate space for the formatted message + formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); + formatted_error->sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); + formatted_error->native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast( custom_error->native_message ), 0, 0, + reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, args ); + if( rc == 0 ) { + strcpy_s( reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, + reinterpret_cast( INTERNAL_FORMAT_ERROR )); + } + + strcpy_s( reinterpret_cast( formatted_error->sqlstate ), SQL_SQLSTATE_BUFSIZE, + reinterpret_cast( custom_error->sqlstate )); + formatted_error->native_code = custom_error->native_code; + + // log the error + LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), formatted_error->sqlstate ); + LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), formatted_error->native_code ); + LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); +} + +DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ) +{ + va_list format_args; + va_start( format_args, format ); + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast(output_buffer), output_len, &format_args ); + va_end( format_args ); + return rc; +} + +// return an error message for GetLastError using FormatMessage. +// this function returns the msg pointer so that it may be used within +// another function call such as handle_error +const char* get_last_error_message( DWORD last_error ) +{ + if( last_error == 0 ) { + last_error = GetLastError(); + } + + DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + last_err_msg, sizeof( last_err_msg ), NULL ); + + if( r == 0 ) { + SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( last_err_msg )); + std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), last_err_msg ); + } + + return last_err_msg; +} + + +// die +// Terminate the PHP request with an error message +// We use this function rather than php_error directly because we use the FormatMessage syntax in most other +// places within the extension (e.g., LOG), whereas php_error uses the printf format syntax. There were +// places where we were using the FormatMessage syntax inadvertently with DIE which left messages without +// proper information. Rather than convert those messages and try and remember the difference between LOG and +// DIE, it is simpler to make the format syntax common between them. +void die( const char* msg, ... ) +{ + va_list format_args; + 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 )); + } + + php_error( E_ERROR, last_err_msg ); +} + +namespace { + +// convert from the default encoding specified by the "CharacterSet" +// connection option to UTF-16. mbcs_len and utf16_len are sizes in +// bytes. The return is the number of UTF-16 characters in the string +// returned in utf16_out_string. An empty string passed in will result as +// a failure since MBTWC returns 0 for both an empty string and failure +// to convert. +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, + unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string, + unsigned int utf16_len ) +{ + unsigned int win_encoding = CP_ACP; + switch( php_encoding ) { + case SQLSRV_ENCODING_CHAR: + win_encoding = CP_ACP; + break; + // this shouldn't ever be set + case SQLSRV_ENCODING_BINARY: + DIE( "Invalid encoding." ); + break; + default: + win_encoding = php_encoding; + break; + } +#ifndef _WIN32 + unsigned int required_len = SystemLocale::ToUtf16( win_encoding, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len ); +#else + unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len ); +#endif // !_Win32 + + if( required_len == 0 ) { + return 0; + } + utf16_out_string[ required_len ] = '\0'; + + return required_len; +} + +} diff --git a/source/shared/globalization.h b/source/shared/globalization.h new file mode 100644 index 00000000..62985cbb --- /dev/null +++ b/source/shared/globalization.h @@ -0,0 +1,517 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: Globalization.h +// +// Contents: Contains functions for handling Windows format strings +// and UTF-16 on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#if !defined(_GLOBALIZATION_H_) +#define _GLOBALIZATION_H_ + +#include "xplat.h" +#include "typedefs_for_linux.h" +#include + +#if defined(MPLAT_UNIX) +#include + +const iconv_t INVALID_ICONV = (iconv_t)(-1); + +class IConvCache : public SLIST_ENTRY +{ + iconv_t m_iconv; + + // Prevent copying + IConvCache( const IConvCache & ); + IConvCache & operator=( const IConvCache & ); + +public: + IConvCache( int dstIdx, int srcIdx ); + ~IConvCache(); + + iconv_t GetIConv() const + { + return m_iconv; + } +}; + +#endif + + +class EncodingConverter +{ + UINT m_dstCodePage; + UINT m_srcCodePage; +#if defined(MPLAT_UNIX) + const IConvCache * m_pCvtCache; + + bool IsValidIConv() const + { + return (NULL != m_pCvtCache && INVALID_ICONV != m_pCvtCache->GetIConv()); + } + + template< typename T > + struct iconv_buffer + { + char * m_pBytes; + size_t m_nBytesLeft; + + iconv_buffer( char * buffer, size_t cchSize ) + : m_pBytes(buffer), m_nBytesLeft(sizeof(T)*cchSize) {} + ~iconv_buffer() {} + + void Reset( char * buffer, size_t cchSize ) + { + m_pBytes = buffer; + m_nBytesLeft = cchSize*sizeof(T); + } + + void SkipSingleCh() + { + assert( sizeof(T) <= m_nBytesLeft ); + m_nBytesLeft -= sizeof(T); + m_pBytes += sizeof(T); + } + void SkipDoubleCh() + { + SkipSingleCh(); + // Only skip second half if there's bytes left and it is non-NULL + if ( m_nBytesLeft && 0 != *(UNALIGNED T *)m_pBytes ) + SkipSingleCh(); + } + void SkipUtf8Ch() + { + assert( 1 == sizeof(T) ); + const char * pNext = SystemLocale::NextChar( CP_UTF8, m_pBytes, m_nBytesLeft ); + assert( m_pBytes < pNext && (size_t)(pNext-m_pBytes) <= SystemLocale::MaxCharCchSize(CP_UTF8) ); + + UINT toTrim = (UINT)(pNext - m_pBytes); + assert( toTrim <= m_nBytesLeft ); + assert( 0 < toTrim ); + + m_nBytesLeft -= toTrim; + m_pBytes += toTrim; + } + + static char DefaultChar( UINT srcDataCP ) + { + return 0x3f; + } + static WCHAR DefaultWChar( UINT srcDataCP ) + { + return (CP_UTF8 == srcDataCP ? 0xfffd // Unicode to Unicode, use Unicode default char + : (932 == srcDataCP ? 0x30fb // 932 to Unicode has special default char + : 0x003f)); // WCP source, use '?' + } + void AssignDefault( UINT srcDataCP ) + { + assert( sizeof(T) <= m_nBytesLeft ); + if ( 1 == sizeof(T) ) + { + *m_pBytes = DefaultChar( srcDataCP ); + --m_nBytesLeft; + ++m_pBytes; + } + else + { + *(UNALIGNED T *)m_pBytes = DefaultWChar( srcDataCP ); + m_nBytesLeft -= sizeof(T); + m_pBytes += sizeof(T); + } + } + bool AssignDefaultUtf8( UINT srcDataCP ) + { + // This is a utf8 buffer so T must be char + assert( 1 == sizeof(T) ); + if ( CP_UTF16 == srcDataCP ) + { + // If source codepage is UTF16 then use Unicode default char + // UTF8 default char is 3 bytes long + if ( m_nBytesLeft < 3 ) + return false; + + *m_pBytes++ = (T)0xef; + *m_pBytes++ = (T)0xbf; + *m_pBytes++ = (T)0xbd; + m_nBytesLeft -= 3; + } + else if ( 932 == srcDataCP ) + { + // If source codepage is 932 then use special default char + // UTF8 default char for 932 is 3 bytes long + if ( m_nBytesLeft < 3 ) + return false; + + *m_pBytes++ = (T)0xe3; + *m_pBytes++ = (T)0x83; + *m_pBytes++ = (T)0xbb; + m_nBytesLeft -= 3; + } + else + { + *m_pBytes = DefaultChar( srcDataCP ); + ++m_pBytes; + --m_nBytesLeft; + } + return true; + } + + // Prevent compiler from generating these + iconv_buffer(); + iconv_buffer( const iconv_buffer & other ); + iconv_buffer & operator=( const iconv_buffer & other ); + }; + + template< class DestType > + bool AddDefault( iconv_buffer * dest, bool * pHasLoss, DWORD * pErrorCode ) const + { + if ( NULL != pHasLoss ) + *pHasLoss = true; + + if ( CP_UTF8 != m_dstCodePage ) + dest->AssignDefault( m_srcCodePage ); + else if ( !dest->AssignDefaultUtf8(m_srcCodePage) ) + { + // Not enough room for the default char + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return false; + } + return true; + } + + template< class DestType, class SrcType > + size_t Convert( + iconv_buffer & dest, + iconv_buffer & src, + bool failIfLossy = false, bool * pHasLoss = NULL, DWORD * pErrorCode = NULL ) const + { + if ( !IsValidIConv() ) + return 0; + + size_t iconv_ret; + size_t cchDest = dest.m_nBytesLeft/sizeof(DestType); + + if ( NULL != pHasLoss ) + *pHasLoss = false; + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + + while ( 0 < dest.m_nBytesLeft && 0 < src.m_nBytesLeft ) + { + // First clear any intermediate state left over from previous conversions + iconv_ret = iconv( m_pCvtCache->GetIConv(), NULL, NULL, NULL, NULL ); + assert( 0 == iconv_ret ); + + // Now attempt conversion + iconv_ret = iconv( m_pCvtCache->GetIConv(), &src.m_pBytes, &src.m_nBytesLeft, &dest.m_pBytes, &dest.m_nBytesLeft ); + if ( iconv_ret == (size_t)(-1) ) + { + // If there's no dest bytes left, then treat as E2BIG even if the error + // is EILSEQ, etc. We want E2BIG to take precedence like Windows. + int err = (0 < dest.m_nBytesLeft ? errno : E2BIG); + if ( E2BIG != err && failIfLossy ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_NO_UNICODE_TRANSLATION; + return 0; + } + + switch ( err ) + { + case EILSEQ: // Invalid multibyte sequence in input + if ( CP_UTF8 == m_srcCodePage ) + src.SkipUtf8Ch(); + else if ( 1 == sizeof(SrcType) ) + src.SkipDoubleCh(); // DBCS + else + src.SkipSingleCh(); // utf32 or incomplate utf16 surrogate + + if ( !AddDefault(&dest, pHasLoss, pErrorCode) ) + return 0; + + break; + case EINVAL: // Incomplete multibyte sequence in input + if ( CP_UTF8 == m_srcCodePage ) + src.SkipUtf8Ch(); + else + src.SkipSingleCh(); + + if ( !AddDefault(&dest, pHasLoss, pErrorCode) ) + return 0; + + break; + case E2BIG: // Output buffer is out of room + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + default: + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + } + } + + return cchDest - (dest.m_nBytesLeft / sizeof(DestType)); + } + +#elif defined(MPLAT_WWOWH) + + size_t ReturnCchResult( int cch, DWORD * pErrorCode ) const + { + if ( cch < 0 ) + cch = 0; + + if ( NULL != pErrorCode ) + *pErrorCode = (0 == cch ? GetLastError() : ERROR_SUCCESS); + + return cch; + } + +#endif // defined(MPLAT_WWOWH) + + +public: + EncodingConverter( UINT dstCodePage, UINT srcCodePage ); + ~EncodingConverter(); + + bool Initialize(); + + // Performs an encoding conversion. + // Returns the number of dest chars written. + // Input and output buffers should not overlap. + template< class DestType, class SrcType, class AllocT > + size_t Convert( + DestType ** destBuffer, + const SrcType * srcBuffer, size_t cchSource, + bool failIfLossy = false, bool * pHasLoss = NULL, DWORD * pErrorCode = NULL ) const + { +#if defined(MPLAT_UNIX) + + if ( !IsValidIConv() ) + return 0; + + iconv_buffer src( + reinterpret_cast< char * >( const_cast< SrcType * >(srcBuffer) ), + cchSource ); + + size_t cchDest = cchSource; + AutoArray< DestType, AllocT > newDestBuffer( cchDest ); + + iconv_buffer dest( + reinterpret_cast< char * >(newDestBuffer.m_ptr), + cchDest ); + + size_t cchPrevCvt = 0; + DWORD rcCvt; + while ( true ) + { + size_t cchCvt = Convert( dest, src, failIfLossy, pHasLoss, &rcCvt ); + if ( 0 == cchCvt ) + { + if ( ERROR_INSUFFICIENT_BUFFER == rcCvt ) + { + // Alloc more and continue + cchPrevCvt = cchDest; + cchDest *= 2; + if ( !newDestBuffer.Realloc(cchDest) ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_NOT_ENOUGH_MEMORY; + return 0; + } + // Fill newly allocated part of buffer + dest.Reset( reinterpret_cast< char * >(newDestBuffer.m_ptr+cchPrevCvt), cchDest ); + } + else + { + if ( NULL != pErrorCode ) + *pErrorCode = rcCvt; + return 0; + } + } + else + { + if ( NULL != pErrorCode ) + *pErrorCode = rcCvt; + *destBuffer = newDestBuffer.Detach(); + return cchPrevCvt + cchCvt; + } + } + +#elif defined(MPLAT_WWOWH) + // WWOWH unit testing code + // Can only convert between ansi and utf16 + if ( 1 == sizeof(DestType) && 2 == sizeof(SrcType) ) + { + // utf16 to ansi + const wchar_t * srcPtr = reinterpret_cast< const wchar_t * >( srcBuffer ); + BOOL loss = FALSE; + int converted = WideCharToMultiByte( + m_dstCodePage, 0, + srcPtr, (int)cchSource, + NULL, 0, + NULL, &loss ); + + if ( 0 < converted ) + { + AutoArray< char, AllocT > newDestBuffer( converted ); + char * dstPtr = newDestBuffer.m_ptr; + converted = WideCharToMultiByte( + m_dstCodePage, 0, + srcPtr, (int)cchSource, + newDestBuffer.m_ptr, converted, + NULL, &loss ); + if ( 0 < converted ) + *destBuffer = newDestBuffer.Detach(); + if ( NULL != pHasLoss ) + *pHasLoss = (FALSE != loss); + } + return ReturnCchResult( converted, pErrorCode ); + } + else if ( 2 == sizeof(DestType) && 1 == sizeof(SrcType) ) + { + // ansi to utf16 + const char * srcPtr = reinterpret_cast< const char * >( srcBuffer ); + int converted = MultiByteToWideChar( + m_srcCodePage, (failIfLossy ? MB_ERR_INVALID_CHARS : 0), + srcPtr, (int)cchSource, + NULL, 0 ); + + if ( 0 < converted ) + { + AutoArray< WCHAR, AllocT > newDestBuffer( converted ); + converted = MultiByteToWideChar( + m_srcCodePage, (failIfLossy ? MB_ERR_INVALID_CHARS : 0), + srcPtr, (int)cchSource, + newDestBuffer.m_ptr, converted ); + if ( 0 < converted ) + *destBuffer = newDestBuffer.Detach(); + if ( NULL != pHasLoss ) + *pHasLoss = false; + } + return ReturnCchResult( converted, pErrorCode ); + } + else + { + assert( false ); + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_NOT_SUPPORTED; + return 0; + } + +#endif // defined(MPLAT_WWOWH) + } + // Performs an encoding conversion. + // Returns the number of dest chars written. + // Input and output buffers should not overlap. + template< class DestType, class SrcType > + size_t Convert( + DestType * destBuffer, size_t cchDest, + const SrcType * srcBuffer, size_t cchSource, + bool failIfLossy = false, bool * pHasLoss = NULL, DWORD * pErrorCode = NULL ) const + { +#if defined(MPLAT_UNIX) + + if ( !IsValidIConv() ) + return 0; + + iconv_buffer src( + reinterpret_cast< char * >( const_cast< SrcType * >(srcBuffer) ), + cchSource ); + if ( 0 < cchDest ) + { + iconv_buffer dest( + reinterpret_cast< char * >(destBuffer), + cchDest ); + return Convert( dest, src, failIfLossy, pHasLoss, pErrorCode ); + } + else + { + // 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) ]; + iconv_buffer dest( + &fixed_buf[0], + CCH_FIXED_SIZE ); + + bool hasLoss = false; + DWORD rcCvt = ERROR_SUCCESS; + size_t cchOnce = 0; + size_t cchCumulative = 0; + + while ( 0 < src.m_nBytesLeft + && 0 == (cchOnce = Convert(dest, src, failIfLossy, &hasLoss, &rcCvt)) + && ERROR_INSUFFICIENT_BUFFER == rcCvt ) + { + cchCumulative += CCH_FIXED_SIZE; + cchCumulative -= dest.m_nBytesLeft; + dest.Reset( &fixed_buf[0], CCH_FIXED_SIZE ); + } + if ( 0 < cchOnce ) + cchCumulative += cchOnce; + if ( NULL != pErrorCode ) + *pErrorCode = (0 < cchCumulative ? ERROR_SUCCESS : rcCvt); + if ( NULL != pHasLoss ) + *pHasLoss |= hasLoss; + return cchCumulative; + } + +#elif defined(MPLAT_WWOWH) + // WWOWH unit testing code + // Can only convert between ansi and utf16 + if ( 1 == sizeof(DestType) && 2 == sizeof(SrcType) ) + { + // utf16 to ansi + char * dstPtr = reinterpret_cast< char * >( destBuffer ); + const wchar_t * srcPtr = reinterpret_cast< const wchar_t * >( srcBuffer ); + BOOL loss = FALSE; + int converted = WideCharToMultiByte( + m_dstCodePage, 0, + srcPtr, (int)cchSource, + dstPtr, (int)cchDest, + NULL, &loss ); + if ( NULL != pHasLoss ) + *pHasLoss = (FALSE != loss); + return ReturnCchResult( converted, pErrorCode ); + } + else if ( 2 == sizeof(DestType) && 1 == sizeof(SrcType) ) + { + // ansi to utf16 + wchar_t * dstPtr = reinterpret_cast< wchar_t * >( destBuffer ); + const char * srcPtr = reinterpret_cast< const char * >( srcBuffer ); + int converted = MultiByteToWideChar( + m_srcCodePage, (failIfLossy ? MB_ERR_INVALID_CHARS : 0), + srcPtr, (int)cchSource, + dstPtr, (int)cchDest ); + if ( NULL != pHasLoss ) + *pHasLoss = false; + return ReturnCchResult( converted, pErrorCode ); + } + else + { + assert( false ); + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_NOT_SUPPORTED; + return 0; + } + +#endif // defined(MPLAT_WWOWH) + } +}; + +#endif // _GLOBALIZATION_H_ diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h new file mode 100644 index 00000000..71ee5196 --- /dev/null +++ b/source/shared/interlockedatomic.h @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: InterlockedAtomic.h +// +// Contents: Contains a portable abstraction for interlocked, atomic +// operations on int32_t and pointer types. +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __INTERLOCKEDATOMIC_H__ +#define __INTERLOCKEDATOMIC_H__ + +// Forward references and contract specifications +// + +// Increments and returns new value +LONG InterlockedIncrement( LONG volatile * atomic ); + +// Decrements and returns new value +LONG InterlockedDecrement( LONG volatile * atomic ); + +// Always returns old value +// Sets to new value if old value equals compareTo +LONG InterlockedCompareExchange( LONG volatile * atomic, LONG newValue, LONG compareTo ); + +// Sets to new value and returns old value +LONG InterlockedExchange( LONG volatile * atomic, LONG newValue ); + +// Sets to new value and returns old value +PVOID InterlockedExchangePointer( PVOID volatile * atomic, PVOID newValue); + +// Adds the amount and returns the old value +LONG InterlockedExchangeAdd( LONG volatile * atomic, LONG add ); + +// Always returns the old value +// Sets the new value if old value equals compareTo +PVOID InterlockedCompareExchangePointer( PVOID volatile * atomic, PVOID newValue, PVOID compareTo ); + + + +// Use conditional compilation to load the implementation +// +#if defined(_MSC_VER) +#include "InterlockedAtomic_WwoWH.h" +#elif defined(__GNUC__) +#include "interlockedatomic_gcc.h" +#else +#error "Unsupported compiler" +#endif + +#endif // __INTERLOCKEDATOMIC_H__ diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h new file mode 100644 index 00000000..6ab246e6 --- /dev/null +++ b/source/shared/interlockedatomic_gcc.h @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: InterlockedAtomic_gcc.h +// +// Contents: Contains a portable abstraction for interlocked, atomic +// operations on int32_t and pointer types. +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __INTERLOCKEDATOMIC_GCC_H__ +#define __INTERLOCKEDATOMIC_GCC_H__ + +#if !defined(__GNUC__) +#error "Incorrect compiler configuration in InterlockedAtomic.h. Was expecting GCC." +#endif + +inline LONG InterlockedIncrement( LONG volatile * atomic ) +{ + return __sync_add_and_fetch( atomic, 1 ); +} + +inline LONG InterlockedDecrement( LONG volatile * atomic ) +{ + return __sync_sub_and_fetch( atomic, 1 ); +} + +inline LONG InterlockedCompareExchange( LONG volatile * atomic, LONG newValue, LONG compareTo ) +{ + return __sync_val_compare_and_swap( atomic, compareTo, newValue ); +} + +inline LONG InterlockedExchange( LONG volatile * atomic, LONG newValue ) +{ + return __sync_lock_test_and_set( atomic, newValue ); +} + +inline PVOID InterlockedExchangePointer( PVOID volatile * atomic, PVOID newValue) +{ + return __sync_lock_test_and_set( atomic, newValue ); +} + +inline LONG InterlockedExchangeAdd( LONG volatile * atomic, LONG add ) +{ + return __sync_fetch_and_add( atomic, add ); +} + +inline PVOID InterlockedCompareExchangePointer( PVOID volatile * atomic, PVOID newValue, PVOID compareTo ) +{ + return __sync_val_compare_and_swap( atomic, compareTo, newValue ); +} + +#endif // __INTERLOCKEDATOMIC_GCC_H__ diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h new file mode 100644 index 00000000..43d0bde9 --- /dev/null +++ b/source/shared/interlockedslist.h @@ -0,0 +1,142 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: InterlockedSList.h +// +// Contents: Contains a portable abstraction for interlocked, singly +// linked list. +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __INTERLOCKEDSLIST_H__ +#define __INTERLOCKEDSLIST_H__ + +#include "interlockedatomic.h" + +#define SLIST_ENTRY SINGLE_LIST_ENTRY + +#define PSLIST_ENTRY PSINGLE_LIST_ENTRY + +typedef struct _SINGLE_LIST_ENTRY { + // Want a volatile pointer to non-volatile data so place after all type info + struct _SINGLE_LIST_ENTRY * volatile Next; +} SINGLE_LIST_ENTRY, *PSINGLE_LIST_ENTRY; + +typedef union _SLIST_HEADER { + // Provides 8 byte alignment for 32-bit builds. Technically, not needed for + // current implementation below but leaving for future use. + ULONGLONG Alignment; + struct { + // Want a volatile pointer to non-volatile data so place after all type info + PSLIST_ENTRY volatile Head; + volatile LONG Depth; + volatile LONG Mutex; + } List; +} SLIST_HEADER, *PSLIST_HEADER; + + +inline VOID InitializeSListHead( PSLIST_HEADER slist ) +{ + assert( NULL != slist ); + + slist->List.Head = NULL; + slist->List.Depth = 0; + slist->List.Mutex = 0; +} + +inline PSLIST_ENTRY InterlockedPopEntrySList( PSLIST_HEADER slist ) +{ + assert( NULL != slist ); + + // Exit prior to 'mutex' if we think it is empty + // Some callers (like sqlncli/msdart/dll/dynslist.h) rely on a NULL + // result from Pop to indicate the list is empty. This early exit + // is an optimization and not technically needed for correctness. + PSLIST_ENTRY oldHead = slist->List.Head; + if ( NULL == oldHead ) + { + return NULL; + } + + while ( 0 != slist->List.Mutex || 0 != InterlockedCompareExchange( &slist->List.Mutex, 1, 0 ) ) + { + // Spin until 'mutex' is free + } + + // We have the 'mutex' so proceed with update + oldHead = slist->List.Head; + if ( NULL != oldHead ) + { + slist->List.Head = oldHead->Next; + --(slist->List.Depth); + assert( 0 <= slist->List.Depth ); + } + + // Free the 'mutex' + slist->List.Mutex = 0; + + return oldHead; +} + +inline PSLIST_ENTRY InterlockedPushEntrySList( PSLIST_HEADER slist, PSLIST_ENTRY newEntry ) +{ + assert( NULL != slist ); + + while ( 0 != slist->List.Mutex || 0 != InterlockedCompareExchange( &slist->List.Mutex, 1, 0 ) ) + { + // Spin until 'mutex' is free + } + + // We have the 'mutex' so proceed with update + PSLIST_ENTRY oldHead = slist->List.Head; + newEntry->Next = oldHead; + slist->List.Head = newEntry; + ++(slist->List.Depth); + + // Free the 'mutex' + slist->List.Mutex = 0; + + return oldHead; +} + +inline PSLIST_ENTRY InterlockedFlushSList( PSLIST_HEADER slist ) +{ + assert( NULL != slist ); + + while ( 0 != slist->List.Mutex || 0 != InterlockedCompareExchange( &slist->List.Mutex, 1, 0 ) ) + { + // Spin until 'mutex' is free + } + + // We have the 'mutex' so proceed with update + PSLIST_ENTRY oldHead = slist->List.Head; + slist->List.Head = NULL; + slist->List.Depth = 0; + + // Free the 'mutex' + slist->List.Mutex = 0; + + return oldHead; +} + +// If the list has more than USHORT nodes then this method +// will not return reliable results. +inline USHORT QueryDepthSList( PSLIST_HEADER slist ) +{ + assert( NULL != slist ); + + return static_cast(slist->List.Depth); +} + + +#endif // __INTERLOCKEDSLIST_H__ diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp new file mode 100644 index 00000000..a7307889 --- /dev/null +++ b/source/shared/localization.hpp @@ -0,0 +1,878 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: Localization.hpp +// +// Contents: Contains portable classes for localization +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __LOCALIZATION_HPP__ +#define __LOCALIZATION_HPP__ + +#include +#include +#include "typedefs_for_linux.h" + +#ifdef MPLAT_UNIX +namespace std +{ + // Forward reference + class locale; +} +#endif + +#define CP_UTF8 65001 +#define CP_UTF16 1200 +#define CP_UTF32 12000 +#define CP_ACP 0 // default to ANSI code page + +// This class provides allocation policies for the SystemLocale and AutoArray classes. +// This is primarily needed for the self-allocating ToUtf16/FromUtf16 methods. +// SNI needs all its allocations to use its own allocator so it would create a separate +// class that obeys this interface and provide it as a template parameter. +template< typename ArrayT > +struct ArrayTAllocator +{ + static ArrayT * Alloc( size_t cch ) + { + return reinterpret_cast< ArrayT * >( malloc(cch*sizeof(ArrayT)) ); + } + // Realloc will free the 'old' memory if new memory was successfully allocated + // and copied to. + static ArrayT * Realloc( ArrayT * old, size_t cchNewSize ) + { + return reinterpret_cast< ArrayT * >( realloc(old, cchNewSize*sizeof(ArrayT)) ); + } + static void Free( ArrayT * mem ) + { + free( mem ); + } +}; + +// This is an auto_ptr-like class that is used with the SystemLocale. +// It allows for automatic freeing of the memory using the allocator policy. +// Callers would not normally use this class directly but would use one of the +// two specializations: AutoCharArray AutoWCharArray. +template< typename ArrayT, typename AllocT = ArrayTAllocator< ArrayT > > +struct AutoArray +{ + size_t m_cchSize; + ArrayT * m_ptr; + + AutoArray( const AutoArray & ); + AutoArray & operator=( const AutoArray & ); + + AutoArray() + : m_cchSize( 0 ), m_ptr( NULL ) + { + } + explicit AutoArray( size_t cchSize ) + : m_cchSize( cchSize ), m_ptr( AllocT::Alloc(cchSize) ) + { + } + virtual ~AutoArray() + { + Free(); + } + void Free() + { + if ( NULL != m_ptr ) + { + AllocT::Free( m_ptr ); + m_ptr = NULL; + m_cchSize = 0; + } + } + bool Realloc( size_t cchSize ) + { + ArrayT * newPtr = AllocT::Realloc( m_ptr, cchSize ); + if ( NULL != newPtr ) + { + // Safe to overwrite since Realloc freed m_ptr. + m_ptr = newPtr; + m_cchSize = cchSize; + return true; + } + return false; + } + ArrayT * Detach() + { + ArrayT * oldPtr = m_ptr; + m_ptr = NULL; + m_cchSize = 0; + return oldPtr; + } + void UpdateSize() + { + if ( NULL == m_ptr ) + { + m_cchSize = 0; + } + else + { + // XPLAT_ODBC_TODO VSTS 819733 MPlat: Reconcile std c++ usage between platforms + // Should use char_traits::length + ArrayT * end = m_ptr; + while ( (ArrayT)0 != *end++ ) + ; + // Want the null terminator included + m_cchSize = end - m_ptr; + } + } +}; + + +class SystemLocale +{ +public: + // ----------------------------------------------------------------------- + // Public Static Functions +#ifdef MPLAT_UNIX + static const SystemLocale & Singleton(); +#else + // Windows returns by value since this is an empty class + static const SystemLocale Singleton(); +#endif + +#ifdef MPLAT_UNIX + int GetResourcePath( char * buffer, size_t cchBuffer ) const; + + static const int MINS_PER_HOUR = 60; + static const int MINS_PER_DAY = 24 * MINS_PER_HOUR; + + // Returns the bias between the supplied utc and local times. + // utc = local + bias + static int BiasInMinutes( const struct tm & utc, const struct tm & local ) + { + int bias = 0; + if ( utc.tm_mon != local.tm_mon ) + { + // Offset crosses month boundary so one of two must be first day of month + if ( 1 == utc.tm_mday ) + bias += MINS_PER_DAY; + else + { + assert( 1 == local.tm_mday ); + bias -= MINS_PER_DAY; + } + } + else + { + bias += MINS_PER_DAY * (utc.tm_mday - local.tm_mday); + } + + bias += MINS_PER_HOUR * (utc.tm_hour - local.tm_hour); + bias += (utc.tm_min - local.tm_min); + + // Round based on diff in secs, in case utc/local straddle a day with leap seconds + int secs_diff = (utc.tm_sec - local.tm_sec); + if ( 29 < secs_diff ) + ++bias; + else if ( secs_diff < -29 ) + --bias; + + return bias; + } + + // Returns both standard and daylight savings biases for the current year + // utc = local + bias + // Both might be equal if DST is not honored + // If platform doesn't know if bias is DST or standard (ie. unknown) + // then standard time is assumed. + // Note that applying current year's biases to dates from other years may result + // in incorrect time adjustments since regions change their rules over time. + // The current SNAC driver code uses this approach as well so we are doing this + // to preserve consistent behavior. If SNAC changes to lookup the offsets that + // were effective for a given date then we should update our logic here as well. + static DWORD TimeZoneBiases( int * stdInMinutes, int * dstInMinutes ) + { + struct tm local, utc; + // Find current year + time_t now = time( NULL ); + if ( (time_t)(-1) == now || NULL == localtime_r(&now, &local) ) + return ERROR_INVALID_DATA; + + // Find bias for first of each month until both STD and DST are found + // Possible perf improvements (can wait until perf tests indicate a need): + // Just use Dec 21 and Jun 21 (near the two soltices) + // Or calc once and cache (must be thread safe) + bool foundUNK = false; + bool foundSTD = false; + bool foundDST = false; + int std_bias = 0; + int dst_bias = 0; + + local.tm_mday = 1; + for ( int mon = 0; mon < 12; ++mon ) + { + local.tm_mon = mon; + if ( (time_t)(-1) == (now = mktime(&local)) || NULL == gmtime_r(&now, &utc) ) + return ERROR_INVALID_DATA; + + if ( 0 < local.tm_isdst ) + { + if ( !foundDST ) + { + dst_bias = BiasInMinutes( utc, local ); + foundDST = true; + if ( foundSTD ) + break; // Done checking when both STD & DST are found + } + } + else + { + // Time is STD or unknown, put in STD + if ( !foundSTD ) + { + std_bias = BiasInMinutes( utc, local ); + if ( local.tm_isdst < 0 ) + foundUNK = true; + else + { + foundSTD = true; + if ( foundDST ) + break; // Done checking when both STD and DST are found + } + } + } + } + + // At least one of STD, DST, or unknown must have been set + assert( foundSTD || foundDST || foundUNK ); + + // For zones that don't observe DST (somewhat common), + // report DST bias as the same as STD + if ( !foundDST ) + dst_bias = std_bias; + + // For zones that ONLY observe DST (extremely rare if at all), + // report STD bias as the same as DST + if ( !foundSTD && !foundUNK ) + std_bias = dst_bias; + + *stdInMinutes = std_bias; + *dstInMinutes = dst_bias; + + return ERROR_SUCCESS; + } +#endif + + static DWORD CurrentLocalTime( LPSYSTEMTIME pTime ); + + // Multi-byte UTF8 code points start with '11xx xxxx' + static bool IsUtf8LeadByte( BYTE utf8 ) + { + return (0xC0 == (utf8 & 0xC0)); + } + + // Maximum number of storage units (char or WCHAR) + // for a code page (e.g. UTF16 == 2 for surrogates) + static UINT MaxCharCchSize( UINT codepage ); + + // Inspects the byte at start, and returns the start + // of the next code point (possibly multiple bytes later). + // If NULL or start points at null terminator, than start is returned. + // If start points at a dangling UTF8 trail byte, then (start+1) is + // returned since we can't know how large this code point is. + static char * NextChar( UINT codepage, const char * start ); +#ifdef MPLAT_UNIX + // This version is for non-null terminated strings. + // Last ptr will be one past end of buffer. + static char * NextChar( UINT codepage, const char * start, size_t cchBytesLeft ); +#endif + + // Given the start byte, how many total bytes are expected for + // this code point. If start is a UTF8 trail byte, then 1 is returned. + static UINT CchExpectedNextChar( UINT codepage, BYTE start ) + { + if ( 0 == (start & (char)0x80) ) + return 1; // ASCII + else if ( CP_UTF8 == codepage ) + return IsUtf8LeadByte(start) ? CchUtf8CodePt(start) : 1; + else if ( IsDBCSLeadByteEx(codepage, start) ) + return 2; + else + return 1; + } + + // Returns the number of bytes that need to be trimmed to avoid splitting + // a multi-byte code point sequence at the end of the buffer. + // Returns zero if a trailing UTF8 code value is found but no + // matching lead byte was found for it (ie. invalid, dangling trail byte). + _Ret_range_(0, cchBuffer) static UINT TrimPartialCodePt( UINT codepage, _In_count_(cchBuffer) const BYTE * buffer, size_t cchBuffer ) + { + if ( 0 == cchBuffer ) + return 0; + + if ( CP_UTF8 == codepage ) + { + return TrimPartialUtf8CodePt( buffer, cchBuffer ); + } + else + { + size_t i = cchBuffer; + for ( ; 0 < i; --i ) + { + if ( !IsDBCSLeadByteEx( codepage, buffer[i-1] ) ) + break; + } + // If odd, then last byte is truly a lead byte so return 1 byte to trim + return ((cchBuffer-i) & 1) ? 1 : 0; + } + } + + // For all transcoding functions + // Returns zero on error. Do not call GetLastError() since that is not portable (pErrorCode has result of GetLastError()). + // pHasDataLoss will be true if an unrecognized code point was encountered in the source and a default output instead. + // Replaces calls to MultiByteToWideChar and WideCharToMultiByte + + // Transcode between a code page and UTF16 + static size_t ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, + __out_ecount_opt(cchDest) WCHAR * dest, size_t cchDest, + DWORD * pErrorCode = NULL ); + static size_t ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, + __out_ecount_opt(cchDest) WCHAR * dest, size_t cchDest, + DWORD * pErrorCode = NULL ); + static size_t FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, + __out_ecount_opt(cchDest) char * dest, size_t cchDest, + bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL ); + static size_t FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, + __out_ecount_opt(cchDest) char * dest, size_t cchDest, + bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL); + // Allocates destination buffer to match required size + // Template is used so call can provide allocation policy + // Used instead of the Windows API pattern of calling with zero dest buffer size to find + // required buffer size, followed by second call with newly allocated buffer. + template< typename AllocT > + static size_t ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, __deref_out_ecount(1) WCHAR ** dest, DWORD * pErrorCode = NULL ); + template< typename AllocT > + static size_t ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, __deref_out_ecount(1) WCHAR ** dest, DWORD * pErrorCode = NULL ); + template< typename AllocT > + static size_t FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, __deref_out_ecount(1) char ** dest, bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL ); + template< typename AllocT > + static size_t FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, __deref_out_ecount(1) char ** dest, bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL); + + + + // ----------------------------------------------------------------------- + // Public Member Functions + +#ifndef TIME_ZONE_ID_UNKNOWN + #define TIME_ZONE_ID_UNKNOWN 0 + #define TIME_ZONE_ID_STANDARD 1 + #define TIME_ZONE_ID_DAYLIGHT 2 +#endif + // pTZInfo, if supplied, holds one of the above defined values + DWORD CurrentTimeZoneBias( LONG * offsetInMinutes, DWORD * pTZInfo = NULL ) const; + + // The Ansi code page, always UTF8 for Linux + UINT AnsiCP() const; + // Used for files (e.g. returns 437 on US Windows, UTF8 for Linux) + UINT OemCP() const; + // Returns UTF-16LE for all platforms (LE == Little Endian) + UINT WideCP() const + { + return CP_UTF16; + } + + // Performs case folding to lower case using the current system locale + // Replaces calls to LCMapStringA + size_t ToLower( const char * src, SSIZE_T cchSrc, __out_ecount_opt(cchDest) char * dest, size_t cchDest, DWORD * pErrorCode = NULL ) const; + +#ifndef CSTR_ERROR + #define CSTR_ERROR 0 // compare failed + #define CSTR_LESS_THAN 1 // string 1 less than string 2 + #define CSTR_EQUAL 2 // string 1 equal to string 2 + #define CSTR_GREATER_THAN 3 // string 1 greater than string 2 +#endif + // String comparison using the rules of the current system locale. + // Replaces calls to CompareString + // Ignoring width (Bing for "Full Width Characters") has no affect on Linux + // Return value is one of the above defined values. + // On error, pErrorCode has result of GetLastError() (do not call GetLastError directly since it isn't portable). + int Compare( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode = NULL ) const; + int CompareIgnoreCase( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode = NULL ) const; + int CompareIgnoreWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode = NULL ) const; + int CompareIgnoreCaseAndWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode = NULL ) const; + + + + +private: + // Prevent copying. + // Also prevents misuse of return from Singleton() method. + // Since return types are different on Windows vs Linux, + // callers should not cache the result of Singleton(). + SystemLocale( const SystemLocale & ); + SystemLocale & operator=( const SystemLocale & ); + +#ifdef MPLAT_UNIX +// MPLAT_UNIX ---------------------------------------------------------------- + + std::locale * m_pLocale; + + explicit SystemLocale( const char * localeName ); + ~SystemLocale(); + + static UINT ExpandSpecialCP( UINT codepage ) + { + // Convert CP_ACP, CP_OEM to CP_UTF8 + return (codepage < 2 ? CP_UTF8 : codepage); + } + +// MPLAT_UNIX ---------------------------------------------------------------- +#else +// !MPLAT_UNIX --------------------------------------------------------------- + + SystemLocale() {} + + static size_t ReturnCchResult( SSIZE_T cch, DWORD * pErrorCode ) + { + if ( cch < 0 ) + { + cch = 0; + } + if ( NULL != pErrorCode ) + { + *pErrorCode = (0 == cch ? GetLastError() : ERROR_SUCCESS); + } + return static_cast(cch); + } + + static int CompareWithFlags( DWORD flags, const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode = NULL ); + + static size_t FastAsciiMultiByteToWideChar + ( + UINT CodePage, + __in_ecount(cch) const char *pch, // IN | source string + SSIZE_T cch, // IN | count of characters or -1 + __out_ecount_opt(cwch) PWCHAR pwch, // IN | Result string + size_t cwch, // IN | count of wchars of result buffer or 0 + DWORD* pErrorCode, // OUT | optional pointer to return error code + bool bStrict = false // IN | Return error if invalid chars in src + ); + static size_t FastAsciiWideCharToMultiByte + ( + UINT CodePage, + const WCHAR *pwch, // IN | source string + SSIZE_T cwch, // IN | count of characters or -1 + __out_bcount(cch) char *pch, // IN | Result string + size_t cch, // IN | Length of result buffer or 0 + BOOL *pfDataLoss, // OUT | True if there was data loss during CP conversion + DWORD *pErrorCode // OUT | optional pointer to return error code + ); + +// !MPLAT_UNIX --------------------------------------------------------------- +#endif + + // Returns the number of bytes this UTF8 code point expects + static UINT CchUtf8CodePt( BYTE codept ) + { + assert( IsUtf8LeadByte(codept) ); + + // Initial byte of utf8 sequence indicates its length + // 110x xxxx = 2 bytes + // 1110 xxxx = 3 bytes + // 1111 0xxx = 4 bytes + // 1111 10xx = 5 bytes, future Unicode extension not covered by this logic + // 1111 110x = 6 bytes, future Unicode extension not covered by this logic + UINT expected_size = (0xC0 == (codept & 0xE0)) ? 2 : (0xE0 == (codept & 0xF0)) ? 3 : 4; + + // Verify constraints + assert( 4 == MaxCharCchSize(CP_UTF8) ); + + return expected_size; + } + + // Returns the number of bytes that need to be trimmed to avoid splitting + // a UTF8 code point sequence at the end of the buffer. + // Returns zero for ASCII. + // Also returns zero if a trailing UTF8 code value is found but no + // matching lead byte was found for it (ie. invalid, dangling trail byte). + static UINT TrimPartialUtf8CodePt( const BYTE * buffer, size_t cchBuffer ) + { + if ( 0 == cchBuffer ) + return 0; + + if ( 0 == (buffer[cchBuffer-1] & 0x80) ) + { + // Last char is ASCII so no trim needed + return 0; + } + + // Last char is non-initial byte of multibyte utf8 sequence + // Need to determine if it is the last (ie. no trim need) + UINT cchMax = MaxCharCchSize( CP_UTF8 ); + for ( UINT i = 1; 0 < cchBuffer && i <= cchMax; --cchBuffer, ++i ) + { + if ( IsUtf8LeadByte(buffer[cchBuffer-1]) ) + { + // Found initial byte, verify size of sequence + UINT cchExpected = CchUtf8CodePt( buffer[cchBuffer-1] ); + if ( i == cchExpected ) + return 0; // utf8 sequence is complete so no trim needed + else + { + assert( i <= cchBuffer ); + return i; // trim the incomplete sequence + } + } + } + + // Did not find initial utf8 byte so trim nothing + return 0; + } +}; + + + +// Convenience wrapper for converting from UTF16 into a newly +// allocated char[]. Class behaves like auto_ptr (will free in dtor, +// but has Release method so caller can take ownership of memory). +template< typename AllocT = ArrayTAllocator< char > > +struct AutoCharArray : public AutoArray< char, AllocT > +{ + size_t AllocConvertFromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL ) + { + char * converted = NULL; + size_t cchCvt = SystemLocale::FromUtf16< AllocT >( destCodePage, src, cchSrc, &converted, pHasDataLoss, pErrorCode ); + if ( 0 < cchCvt ) + { + this->Free(); + this->m_ptr = converted; + this->m_cchSize = cchCvt; + } + return cchCvt; + } +}; + +// Convenience wrapper for converting to UTF16 into a newly +// allocated WCHAR[]. Class behaves like auto_ptr (will free in dtor, +// but has Release method so caller can take ownership of memory). +template< typename AllocT = ArrayTAllocator< WCHAR > > +struct AutoWCharArray : public AutoArray< WCHAR, AllocT > +{ + size_t AllocConvertToUtf16( UINT destCodePage, const char * src, SSIZE_T cchSrc, bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL ) + { + WCHAR * converted = NULL; + size_t cchCvt = SystemLocale::ToUtf16< AllocT >( destCodePage, src, cchSrc, &converted, pErrorCode ); + if ( 0 < cchCvt ) + { + this->Free(); + this->m_ptr = converted; + this->m_cchSize = cchCvt; + } + return cchCvt; + } +}; + + + +// --------------------------------------------------------------------------- +// Inlines that vary by platform + +#if defined(MPLAT_UNIX) +// MPLAT_UNIX ---------------------------------------------------------------- + +#include "globalization.h" + +inline UINT SystemLocale::AnsiCP() const +{ + return CP_UTF8; +} + +inline UINT SystemLocale::OemCP() const +{ + return CP_UTF8; +} + +inline UINT SystemLocale::MaxCharCchSize( UINT codepage ) +{ + codepage = ExpandSpecialCP( codepage ); + switch ( codepage ) + { + case CP_UTF8: + return 4; + case 932: + case 936: + case 949: + case 950: + case CP_UTF16: + return 2; + default: + return 1; + } +} + +inline int SystemLocale::CompareIgnoreWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + // XPLAT_ODBC_TODO: VSTS 806013 MPLAT: Support IgnoreWidth for SNI string comparisons + return Compare( left, cchLeft, right, cchRight, pErrorCode ); +} + +inline int SystemLocale::CompareIgnoreCaseAndWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + // XPLAT_ODBC_TODO: VSTS 806013 MPLAT: Support IgnoreWidth for SNI string comparisons + return CompareIgnoreCase( left, cchLeft, right, cchRight, pErrorCode ); +} + +template< typename AllocT > +inline size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR ** dest, DWORD * pErrorCode ) +{ + srcCodePage = ExpandSpecialCP( srcCodePage ); + EncodingConverter cvt( CP_UTF16, srcCodePage ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert< WCHAR, char, AllocT >( dest, src, cchSrcActual, false, &hasLoss, pErrorCode ); +} + +template< typename AllocT > +inline size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR ** dest, DWORD * pErrorCode ) +{ + srcCodePage = ExpandSpecialCP( srcCodePage ); + EncodingConverter cvt( CP_UTF16, srcCodePage ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert< WCHAR, char, AllocT >( dest, src, cchSrcActual, true, &hasLoss, pErrorCode ); +} + +template< typename AllocT > +inline size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char ** dest, bool * pHasDataLoss, DWORD * pErrorCode ) +{ + destCodePage = ExpandSpecialCP( destCodePage ); + EncodingConverter cvt( destCodePage, CP_UTF16 ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+mplat_wcslen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert< char, WCHAR, AllocT >( dest, src, cchSrcActual, false, &hasLoss, pErrorCode ); +} + + +// MPLAT_UNIX ---------------------------------------------------------------- +#else +// ! MPLAT_UNIX ---------------------------------------------------------------- + + +inline const SystemLocale SystemLocale::Singleton() +{ + // On Windows, Localization is an empty class so creation of this + // should be optimized away. Empty classes have a sizeof 1 so there's + // something to take the address of. + C_ASSERT( 1 == sizeof(SystemLocale) ); + return SystemLocale(); +} + +inline DWORD SystemLocale::CurrentTimeZoneBias( LONG * offsetInMinutes, DWORD * pTZInfo ) const +{ + TIME_ZONE_INFORMATION tzi; + DWORD tzInfo; + if ( NULL == offsetInMinutes ) + return ERROR_INVALID_PARAMETER; + else if ( TIME_ZONE_ID_INVALID == (tzInfo = GetTimeZoneInformation(&tzi)) ) + return GetLastError(); + else + { + *offsetInMinutes = tzi.Bias; + if ( NULL != pTZInfo ) + *pTZInfo = tzInfo; + + return ERROR_SUCCESS; + } +} + +inline DWORD SystemLocale::CurrentLocalTime( LPSYSTEMTIME pTime ) +{ + GetLocalTime( pTime ); + return ERROR_SUCCESS; +} + +inline UINT SystemLocale::AnsiCP() const +{ + return GetACP(); +} + +inline UINT SystemLocale::OemCP() const +{ + return GetOEMCP(); +} + +inline UINT SystemLocale::MaxCharCchSize( UINT codepage ) +{ + CPINFO cpinfo; + BOOL rc = GetCPInfo( codepage, &cpinfo ); + return (rc ? cpinfo.MaxCharSize : 0); +} + +inline size_t SystemLocale::ToLower( const char * src, SSIZE_T cchSrc, char * dest, size_t cchDest, DWORD * pErrorCode ) const +{ + // Windows API takes 'int' sized parameters + if ( cchSrc < -1 || 0x7FFFFFF < cchSrc || 0x7FFFFFF < cchDest ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + +OACR_WARNING_PUSH +OACR_WARNING_DISABLE(SYSTEM_LOCALE_MISUSE , " INTERNATIONALIZATION BASELINE AT KATMAI RTM. FUTURE ANALYSIS INTENDED. ") +OACR_WARNING_DISABLE(ANSI_APICALL, " Keeping the ANSI API for now. ") + int cch = LCMapStringA( + LOCALE_SYSTEM_DEFAULT, + LCMAP_LOWERCASE, + src, + (int)cchSrc, + dest, + (int)cchDest ); +OACR_WARNING_POP + + return ReturnCchResult( cch, pErrorCode ); +} + +inline int SystemLocale::CompareWithFlags( DWORD flags, const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) +{ + // Windows API takes 'int' sized parameters + if ( cchLeft < -1 || 0x7FFFFFF < cchLeft || cchRight < -1 || 0x7FFFFFF < cchRight ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + +OACR_WARNING_PUSH +OACR_WARNING_DISABLE(SYSTEM_LOCALE_MISUSE , " INTERNATIONALIZATION BASELINE AT KATMAI RTM. FUTURE ANALYSIS INTENDED. ") + int cmp = CompareStringA( LOCALE_SYSTEM_DEFAULT, flags, left, (int)cchLeft, right, (int)cchRight ); +OACR_WARNING_POP + if ( NULL != pErrorCode ) + { + *pErrorCode = (CSTR_ERROR == cmp ? GetLastError() : ERROR_SUCCESS); + } + return cmp; +} + +inline int SystemLocale::Compare( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + return CompareWithFlags( 0, left, cchLeft, right, cchRight, pErrorCode ); +} + +inline int SystemLocale::CompareIgnoreCase( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + return CompareWithFlags( NORM_IGNORECASE, left, cchLeft, right, cchRight, pErrorCode ); +} + +inline int SystemLocale::CompareIgnoreCaseAndWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + return CompareWithFlags( NORM_IGNORECASE|NORM_IGNOREWIDTH, left, cchLeft, right, cchRight, pErrorCode ); +} + +inline int SystemLocale::CompareIgnoreWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + return CompareWithFlags( NORM_IGNOREWIDTH, left, cchLeft, right, cchRight, pErrorCode ); +} + +inline char * SystemLocale::NextChar( UINT codepage, const char * start ) +{ + return CharNextExA( (WORD)codepage, start, 0 ); +} + +inline size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode ) +{ + return FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, dest, cchDest, pErrorCode ); +} + +inline size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode ) +{ + return FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, dest, cchDest, pErrorCode, true ); +} + +inline size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode ) +{ + BOOL dataloss = FALSE; + size_t cchCvt = FastAsciiWideCharToMultiByte( destCodePage, src, cchSrc, dest, cchDest, &dataloss, pErrorCode ); + if ( NULL != pHasDataLoss ) + { + *pHasDataLoss = (FALSE != dataloss); + } + return cchCvt; +} + +template< typename AllocT > +inline size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR ** dest, DWORD * pErrorCode ) +{ + size_t cchCvt = FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, NULL, 0, pErrorCode ); + if ( 0 < cchCvt ) + { + AutoArray< WCHAR, AllocT > newDestBuffer( cchCvt ); + cchCvt = FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, newDestBuffer.m_ptr, cchCvt, pErrorCode ); + if ( 0 < cchCvt ) + *dest = newDestBuffer.Detach(); + } + return cchCvt; +} + +template< typename AllocT > +inline size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR ** dest, DWORD * pErrorCode ) +{ + size_t cchCvt = FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, NULL, 0, pErrorCode, true ); + if ( 0 < cchCvt ) + { + AutoArray< WCHAR, AllocT > newDestBuffer( cchCvt ); + cchCvt = FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, newDestBuffer.m_ptr, cchCvt, pErrorCode, true ); + if ( 0 < cchCvt ) + *dest = newDestBuffer.Detach(); + } + return cchCvt; +} + +template< typename AllocT > +inline size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char ** dest, bool * pHasDataLoss, DWORD * pErrorCode ) +{ + BOOL dataloss = FALSE; + size_t cchCvt = FastAsciiWideCharToMultiByte( destCodePage, src, cchSrc, NULL, 0, &dataloss, pErrorCode ); + if ( 0 < cchCvt ) + { + AutoArray< char, AllocT > newDestBuffer( cchCvt ); + cchCvt = FastAsciiWideCharToMultiByte( destCodePage, src, cchSrc, newDestBuffer.m_ptr, cchCvt, &dataloss, pErrorCode ); + if ( 0 < cchCvt ) + *dest = newDestBuffer.Detach(); + } + if ( NULL != pHasDataLoss ) + { + *pHasDataLoss = (FALSE != dataloss); + } + return cchCvt; +} + +// ! MPLAT_UNIX ---------------------------------------------------------------- +#endif + +#endif // __LOCALIZATION_HPP__ diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp new file mode 100644 index 00000000..eb66d34a --- /dev/null +++ b/source/shared/localizationimpl.cpp @@ -0,0 +1,1043 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: LocalizationImpl.hpp +// +// Contents: Contains non-inline code for the SystemLocale class +// Must be included in one c/cpp file per binary +// A build error will occur if this inclusion policy is not followed +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "localization.hpp" + +#include "globalization.h" +#include "StringFunctions.h" + +struct cp_iconv +{ + UINT CodePage; + const char * IConvEncoding; + + static const cp_iconv g_cp_iconv[]; + static const size_t g_cp_iconv_count; + + static int GetIndex( UINT codepage ) + { + for ( size_t idx = 0; idx < g_cp_iconv_count; ++idx ) + { + if ( g_cp_iconv[idx].CodePage == codepage ) + return (int)idx; + } + // Should never be an unknown code page + assert( false ); + return -1; + } +}; + +// Array of CodePage-to-IConvEncoding mappings +// First few elements are most commonly used +// CodePage 2 corresponds to binary. If the attribute PDO::SQLSRV_ENCODING_BINARY +// is set, GetIndex() above hits the assert(false) directive unless we include +// CodePage 2 below and assign an empty string to it. +const cp_iconv cp_iconv::g_cp_iconv[] = { + { 65001, "UTF-8" }, + { 1200, "UTF-16LE" }, + { 3, "UTF-8" }, + { 2, "" }, + { 1252, "CP1252//TRANSLIT" }, + { 850, "CP850//TRANSLIT" }, + { 437, "CP437//TRANSLIT" }, + { 874, "CP874//TRANSLIT" }, + { 932, "CP932//TRANSLIT" }, + { 936, "CP936//TRANSLIT" }, + { 949, "CP949//TRANSLIT" }, + { 950, "CP950//TRANSLIT" }, + { 1250, "CP1250//TRANSLIT" }, + { 1251, "CP1251//TRANSLIT" }, + { 1253, "CP1253//TRANSLIT" }, + { 1254, "CP1254//TRANSLIT" }, + { 1255, "CP1255//TRANSLIT" }, + { 1256, "CP1256//TRANSLIT" }, + { 1257, "CP1257//TRANSLIT" }, + { 1258, "CP1258//TRANSLIT" }, + { 12000, "UTF-32LE" } +}; +const size_t cp_iconv::g_cp_iconv_count = ARRAYSIZE(cp_iconv::g_cp_iconv); + +#ifdef MPLAT_UNIX + +class IConvCachePool +{ + SLIST_HEADER m_Pool[cp_iconv::g_cp_iconv_count][cp_iconv::g_cp_iconv_count]; + + IConvCachePool( const IConvCachePool & ); + IConvCachePool & operator=( const IConvCachePool & ); + + // This bool indicates that the iconv pool is no longer available. + // For the driver,lis flag indicates the pool can no longer be used. + // Global destructors will be called by a single thread so this flag does not + // need thread synch protection. + static bool s_PoolDestroyed; + + IConvCachePool() + { + for ( int dstIdx = 0; dstIdx < cp_iconv::g_cp_iconv_count; ++dstIdx ) + { + for ( int srcIdx = 0; srcIdx < cp_iconv::g_cp_iconv_count; ++srcIdx ) + { + InitializeSListHead( &m_Pool[dstIdx][srcIdx] ); + } + } + } + + ~IConvCachePool() + { + IConvCachePool::s_PoolDestroyed = true; + + // Clean up remaining nodes + for ( int dstIdx = 0; dstIdx < cp_iconv::g_cp_iconv_count; ++dstIdx ) + { + for ( int srcIdx = 0; srcIdx < cp_iconv::g_cp_iconv_count; ++srcIdx ) + { + IConvCache * pNode = static_cast( InterlockedFlushSList(&m_Pool[dstIdx][srcIdx]) ); + while ( NULL != pNode ) + { + IConvCache * pNext = static_cast( pNode->Next ); + delete pNode; + pNode = pNext; + } + } + } + } + + USHORT Depth( int dstIdx, int srcIdx ) + { + assert( 0 <= dstIdx && dstIdx < cp_iconv::g_cp_iconv_count ); + assert( 0 <= srcIdx && srcIdx < cp_iconv::g_cp_iconv_count ); + return QueryDepthSList( &m_Pool[dstIdx][srcIdx] ); + } + + // If this returns NULL, then caller must allocate their own iconv_t. + // It will return NULL if allocation for a new instance failed (out of memory). + const IConvCache * Borrow( int dstIdx, int srcIdx ) + { + assert( 0 <= dstIdx && dstIdx < cp_iconv::g_cp_iconv_count ); + assert( 0 <= srcIdx && srcIdx < cp_iconv::g_cp_iconv_count ); + + const IConvCache * pCache = static_cast( InterlockedPopEntrySList(&m_Pool[dstIdx][srcIdx]) ); + if ( NULL == pCache ) + { + const IConvCache * pNewCache = new IConvCache( dstIdx, srcIdx ); + if ( NULL != pNewCache ) + { + if ( INVALID_ICONV != pNewCache->GetIConv() ) + pCache = pNewCache; + else + delete pNewCache; + } + } + return pCache; + } + + void Return( const IConvCache * pCache, int dstIdx, int srcIdx ) + { + assert( pCache ); + assert( 0 <= dstIdx && dstIdx < cp_iconv::g_cp_iconv_count ); + assert( 0 <= srcIdx && srcIdx < cp_iconv::g_cp_iconv_count ); + + // Setting an arbitrary limit to prevent unbounded memory use by the pool. + // Want this to be large enough for a substantial number of concurrent threads. + const USHORT MAX_POOL_SIZE = 1024; + + if ( INVALID_ICONV != pCache->GetIConv() && Depth(dstIdx, srcIdx) < MAX_POOL_SIZE ) + { + SLIST_ENTRY * pNode = const_cast( pCache ); + InterlockedPushEntrySList( &m_Pool[dstIdx][srcIdx], pNode ); + } + else + { + delete pCache; + } + } + + static IConvCachePool & Singleton() + { + // GCC ensures that function scoped static initializers are threadsafe + // We must not use the -fno-threadsafe-statics compiler option +#if !defined(__GNUC__) || defined(NO_THREADSAFE_STATICS) + #error "Relying on GCC's threadsafe initialization of local statics." +#endif + static IConvCachePool s_Pool; + return s_Pool; + } + +public: + static const IConvCache * BorrowCache( UINT dstCP, UINT srcCP ) + { + int dstIdx = cp_iconv::GetIndex(dstCP); + int srcIdx = cp_iconv::GetIndex(srcCP); + + if ( -1 == dstIdx || -1 == srcIdx ) + return NULL; + else if ( !s_PoolDestroyed ) + return Singleton().Borrow( dstIdx, srcIdx ); + else + return new IConvCache( dstIdx, srcIdx ); + } + + static void ReturnCache( const IConvCache * pCache, UINT dstCP, UINT srcCP ) + { + int dstIdx = cp_iconv::GetIndex(dstCP); + int srcIdx = cp_iconv::GetIndex(srcCP); + + if ( -1 != dstIdx && -1 != srcIdx && !s_PoolDestroyed ) + Singleton().Return( pCache, dstIdx, srcIdx ); + else + delete pCache; + } + + static USHORT Depth( UINT dstCP, UINT srcCP ) + { + if ( IConvCachePool::s_PoolDestroyed ) + return 0; + else + { + int dstIdx = cp_iconv::GetIndex(dstCP); + int srcIdx = cp_iconv::GetIndex(srcCP); + + if ( -1 == dstIdx || -1 == srcIdx ) + return 0; + else + return Singleton().Depth( dstIdx, srcIdx ); + } + } +}; + + +bool IConvCachePool::s_PoolDestroyed = false; + +#ifdef DEBUG +// This is only used by unit tests. +// Product code should directly use IConvCachePool::Depth from +// within this translation unit. +USHORT GetIConvCachePoolDepth( UINT dstCP, UINT srcCP ) +{ + return IConvCachePool::Depth( dstCP, srcCP ); +} +#endif // DEBUG + +IConvCache::IConvCache( int dstIdx, int srcIdx ) + : m_iconv( iconv_open( + cp_iconv::g_cp_iconv[dstIdx].IConvEncoding, + cp_iconv::g_cp_iconv[srcIdx].IConvEncoding) ) +{ +} + +IConvCache::~IConvCache() +{ + if ( INVALID_ICONV != m_iconv ) + iconv_close( m_iconv ); +} + +#endif // MPLAT_UNIX + +EncodingConverter::EncodingConverter( UINT dstCodePage, UINT srcCodePage ) + : m_dstCodePage( dstCodePage ), + m_srcCodePage( srcCodePage ) +#ifdef MPLAT_UNIX + , m_pCvtCache( NULL ) +#endif +{ +} + +EncodingConverter::~EncodingConverter() +{ +#ifdef MPLAT_UNIX + if ( NULL != m_pCvtCache ) + { + IConvCachePool::ReturnCache( m_pCvtCache, m_dstCodePage, m_srcCodePage ); + } +#endif +} + +bool EncodingConverter::Initialize() +{ +#if defined(MPLAT_UNIX) + if ( !IsValidIConv() ) + { + m_pCvtCache = IConvCachePool::BorrowCache( m_dstCodePage, m_srcCodePage ); + } + return IsValidIConv(); +#elif defined(MPLAT_WWOWH) + return true; +#endif +} + +//#endif + +#ifdef MPLAT_UNIX +// MPLAT_UNIX ---------------------------------------------------------------- +#include + +using namespace std; + + + +SystemLocale::SystemLocale( const char * localeName ) + : m_pLocale( new std::locale(localeName) ) +{ +} + +SystemLocale::~SystemLocale() +{ + delete m_pLocale; +} + +const SystemLocale & SystemLocale::Singleton() +{ + // GCC ensures that function scoped static initializers are threadsafe + // We must not use the -fno-threadsafe-statics compiler option +#if !defined(__GNUC__) || defined(NO_THREADSAFE_STATICS) + #error "Relying on GCC's threadsafe initialization of local statics." +#endif + static const SystemLocale s_Default( "en_US.utf8" ); + return s_Default; +} + +int SystemLocale::GetResourcePath( char * buffer, size_t cchBuffer ) const +{ + // XPLAT_ODBC_TODO: VSTS 718708 Localization + // Also need to use AdjustLCID logic when handling more locales + return snprintf( buffer, cchBuffer, "/opt/microsoft/msodbcsql/share/resources/en_US/"); +} + +DWORD SystemLocale::CurrentTimeZoneBias( LONG * offsetInMinutes, DWORD * tzinfo ) const +{ + if ( NULL == offsetInMinutes ) + return ERROR_INVALID_PARAMETER; + + time_t now = time( NULL ); + if ( (time_t)(-1) == now ) + return ERROR_NOT_SUPPORTED; + + struct tm utc, local; + if ( NULL == gmtime_r(&now, &utc) || NULL == localtime_r(&now, &local) ) + return ERROR_INVALID_DATA; + + *offsetInMinutes = BiasInMinutes( utc, local ); + + if ( NULL != tzinfo ) + { + *tzinfo = (0 == local.tm_isdst ? TIME_ZONE_ID_STANDARD : (0 < local.tm_isdst ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_UNKNOWN)); + } + + return ERROR_SUCCESS; +} + +DWORD SystemLocale::CurrentLocalTime( LPSYSTEMTIME pTime ) +{ + if ( NULL == pTime ) + return ERROR_INVALID_PARAMETER; + + memset( pTime, 0, sizeof(SYSTEMTIME) ); + + time_t now = time( NULL ); + if ( (time_t)(-1) == now ) + return ERROR_NOT_SUPPORTED; + + struct tm local; + if ( NULL == localtime_r(&now, &local) ) + return ERROR_INVALID_DATA; + + pTime->wYear = local.tm_year + 1900; + pTime->wMonth = local.tm_mon + 1; + pTime->wDay = local.tm_mday; + pTime->wHour = local.tm_hour; + pTime->wMinute = local.tm_min; + pTime->wSecond = local.tm_sec; + pTime->wMilliseconds = 0; + pTime->wDayOfWeek = local.tm_wday; + + return ERROR_SUCCESS; +} + +size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode ) +{ + srcCodePage = ExpandSpecialCP( srcCodePage ); + EncodingConverter cvt( CP_UTF16, srcCodePage ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert( dest, cchDest, src, cchSrcActual, false, &hasLoss, pErrorCode ); +} + +size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode ) +{ + srcCodePage = ExpandSpecialCP( srcCodePage ); + EncodingConverter cvt( CP_UTF16, srcCodePage ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert( dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode ); +} + +size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode ) +{ + destCodePage = ExpandSpecialCP( destCodePage ); + EncodingConverter cvt( destCodePage, CP_UTF16 ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+mplat_wcslen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert( dest, cchDest, src, cchSrcActual, false, &hasLoss, pErrorCode ); +} + +size_t SystemLocale::FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode) +{ + destCodePage = ExpandSpecialCP(destCodePage); + EncodingConverter cvt(destCodePage, CP_UTF16); + if (!cvt.Initialize()) + { + if (NULL != pErrorCode) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1 + mplat_wcslen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert(dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode); +} + +size_t SystemLocale::ToLower( const char * src, SSIZE_T cchSrc, char * dest, size_t cchDest, DWORD * pErrorCode ) const +{ + size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc); + if ( 0 == cchSrcActual ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + if ( 0 == cchDest ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return cchSrcActual; + } + else if ( cchDest < cchSrcActual ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + memcpy_s( dest, cchSrcActual, src, cchSrcActual ); + + use_facet< ctype< char > >(*m_pLocale).tolower( dest, dest+cchSrcActual ); + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return cchSrcActual; +} + +int SystemLocale::Compare( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + if ( NULL == left || NULL == right || 0 == cchLeft || 0 == cchRight ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return CSTR_ERROR; + } + + size_t cchLeftActual = (cchLeft < 0 ? strlen(left) : cchLeft); + size_t cchRightActual = (cchRight < 0 ? strlen(right) : cchRight); + + int cmp = strncmp( left, right, min(cchLeftActual, cchRightActual) ); + if ( 0 == cmp ) + { + if ( cchLeftActual < cchRightActual ) + cmp = -1; + else if ( cchLeftActual > cchRightActual ) + cmp = 1; + } + else if ( cmp < 0 ) + cmp = 1; // CompareString is inverse of strcmp + else + cmp = -1; // CompareString is inverse of strcmp + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return cmp+2; +} + +int SystemLocale::CompareIgnoreCase( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + if ( NULL == left || NULL == right || 0 == cchLeft || 0 == cchRight ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return CSTR_ERROR; + } + + size_t cchLeftActual = (cchLeft < 0 ? strlen(left) : cchLeft); + size_t cchRightActual = (cchRight < 0 ? strlen(right) : cchRight); + + int cmp = strncasecmp( left, right, min(cchLeftActual, cchRightActual) ); + if ( 0 == cmp ) + { + if ( cchLeftActual < cchRightActual ) + cmp = -1; + else if ( cchLeftActual > cchRightActual ) + cmp = 1; + } + else if ( cmp < 0 ) + cmp = 1; // CompareString is inverse of strcmp + else + cmp = -1; // CompareString is inverse of strcmp + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return cmp+2; +} + +char * SystemLocale::NextChar( UINT codepage, const char * start, size_t cchBytesLeft ) +{ + if ( NULL == start || '\0' == *start || 0 == cchBytesLeft ) + return const_cast( start ); + + char first = *start; + codepage = ExpandSpecialCP( codepage ); + if ( CP_UTF8 != codepage ) + { + if ( !IsDBCSLeadByteEx(codepage, first) || '\0' == *(start+1) ) + return const_cast( start+1 ); // single byte char or truncated double byte char + else + return const_cast( start+2 ); // double byte char + } + + // CP_UTF8 + // MB utf8 sequences have this format + // Lead byte starts with 2 set bits, '11' + // Rest of bytes start with one set and one not, '10' + + // ASCII or not first of utf8 sequence + // If this isn't the first byte of a utf8 sequence, just move one byte at a time + // since we don't know where the correct boundary is located. + if ( (char)0 == (first & (char)0x80) || !SystemLocale::IsUtf8LeadByte((BYTE)first) ) + return const_cast( start+1 ); + else + { + // Initial char tells us how many bytes are supposed to be in this sequence + UINT cchExpectedSize = SystemLocale::CchUtf8CodePt( (BYTE)first ); + + // Skip lead bye + ++start; + --cchExpectedSize; + --cchBytesLeft; + + // Proceed to end of utf8 sequence, null term, or end of expected size + while ( 0 < cchExpectedSize && 0 < cchBytesLeft && (char)0x80 == (*start & (char)0xC0) ) + { + ++start; + --cchExpectedSize; + --cchBytesLeft; + } + return const_cast( start ); + } +} + +char * SystemLocale::NextChar( UINT codepage, const char * start ) +{ + // Just assume some large max buffer size since caller is saying + // start is null terminated. + return NextChar( codepage, start, DWORD_MAX ); +} + +// MPLAT_UNIX ---------------------------------------------------------------- +#else +// !MPLAT_UNIX ---------------------------------------------------------------- + +//----------------------------------------------------------------------------------- +// IsW2CZeroFlagCodePage +// +// @func Does this code page need special handling for WideCharToMultiByte or +// MultiByteToWideChar to avoid error code as ERROR_INVALID_PARAMETER to be returned +// +// @rdesc bool +// @flag TRUE | needs special handling +// @flag FALSE | doesn't need special handling +//----------------------------------------------------------------------------------- + +#define IsW2CZeroFlagCodePage(codePage) (((codePage) < 50220) ? FALSE : _IsW2CZeroFlagCodePage(codePage)) + +inline BOOL _IsW2CZeroFlagCodePage +( + UINT CodePage +) +{ + assert(CodePage >= 50220); + + // According to MSDN, these code pages need special handling + // during WideCharToMultiByte call w/r its parameter flags + if (CodePage == 50220 || + CodePage == 50221 || + CodePage == 50222 || + CodePage == 50225 || + CodePage == 50227 || + CodePage == 50229 || + CodePage == 52936 || + CodePage == 54936 || + CodePage == 65000 || + CodePage == 65001 || + CodePage >= 57002 && CodePage <= 57011) + { + return TRUE; + } + + return FALSE; +} + +//------------------------------------------------------------------- +// Custom version of MultiByteToWideChar (faster for all ASCII strings) +// Convert ASCII data (0x00-0x7f) until first non-ASCII data, +// calling OS MultiByteToWideChar in that case. +// +size_t SystemLocale::FastAsciiMultiByteToWideChar( + UINT CodePage, + __in_ecount(cch) const char *pch, // IN | source string + SSIZE_T cch, // IN | count of characters or -1 + __out_ecount_opt(cwch) PWCHAR pwch, // IN | Result string + size_t cwch, // IN | counter of wcharacters of result buffer or 0 + DWORD* pErrorCode, // OUT | optional pointer to return error code + bool bStrict // IN | Return error if invalid chars in src +) +{ + if ( 0 == cch || cch < -1 || NULL == pch ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + + const char *pchStart = pch; + + // Divide into + // Case 1a: cch, do convert + // Case 1b: cch, just count + // Case 2a: null-term, do convert + // Case 2b: null-term, just count + if (-1 != cch) + { + // 0 <= cch + // + // Case 1: We have counter of characters + if (0 != cch) + { + if (0 != cwch) + { + // Case 1a: Have to convert, not just calculate necessary space + + // Optimization: When converting first cwch characters, it's not + // necessary to check for buffer overflow. Also, loop is unrolled. + size_t cquads = min((size_t)cch, cwch) >> 2; + + while (0 != cquads) + { + unsigned quad = *(unsigned UNALIGNED *)pch; + + if (quad & 0x80808080) + goto general; + + OACR_WARNING_SUPPRESS ( INCORRECT_VALIDATION, "Due to performance, we suppress this PREFast warning" ); + *(unsigned UNALIGNED *)pwch = (quad & 0x7F) | ((quad & 0x7F00) << 8); + + quad >>= 16; + + OACR_WARNING_SUPPRESS ( POTENTIAL_BUFFER_OVERFLOW_HIGH_PRIORITY, "PREFast incorrectly warns of buffer overrun for cwch < 4, which won't enter this loop." ); + *(unsigned UNALIGNED *)(pwch+2) = (quad & 0x7F) | ((quad & 0x7F00) << 8); + + pch += 4; + pwch += 4; + cch -= 4; + cquads --; + } + + // Convert end of string - slower, but the loop will be executed 3 times max + if (0 != cch) + { + const char *pchEnd = pchStart + cwch; + + do + { + unsigned ch = (unsigned)*pch; + + if (pch == pchEnd) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; // Not enough space + } + + if (ch > 0x7F) + goto general; + + *(pwch++) = (WCHAR)ch; + + pch++; + cch--; + } while (0 != cch); + + } + } + else + { + // Case 1b: Have to calculate necessary space only + if (SystemLocale::MaxCharCchSize(CodePage) == 1) // SBCS code pages 1char = 1 unc char + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(cch); + } + + do + { + if ((unsigned)*pch > 0x7F) + goto general; + + pch++; + } while (0 != --cch); + } + } + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pch - pchStart); + } + else + { + // Case 2: zero-terminated string + if (0 != cwch) + { + // Case 2a: Have to convert, not just calculate necessary space + const char *pchEnd = pch + cwch; + + do + { + unsigned ch = (unsigned)*pch; + + if (ch > 0x7F) + goto general; + else + { + *pwch = (WCHAR)ch; + pch ++; + if (0 == ch) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pch - pchStart); + } + pwch ++; + } + } while (pch != pchEnd); + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; // Not enough space + } + else + { + // Case 2b: Have to calculate necessary space + unsigned ch; + + do + { + ch = (unsigned)*pch; + + if (ch > 0x7F) + goto general; + pch ++; + } while (0 != ch); + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pch - pchStart); + } + } + + // Have to call Win32 API +general: + { + size_t cwchConverted; + size_t cwchUnicode; + + cwchConverted = (pch - pchStart); + + if ( cwch > cwchConverted ) + cwch -= cwchConverted; + else + cwch = 0; + + // Windows MBtoWC takes int inputs + if ( INT32_MAX < cch || INT32_MAX < cwch ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + + cwchUnicode = (UINT)MultiByteToWideChar( + CodePage, + (IsW2CZeroFlagCodePage(CodePage) ? 0 : MB_PRECOMPOSED) + | (bStrict ? MB_ERR_INVALID_CHARS : 0), + pch, + (int)cch, + pwch, + (int)cwch); + + if ( 0 == cwchUnicode ) + { + if ( NULL != pErrorCode ) + *pErrorCode = GetLastError(); + return 0; + } + else + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return (cwchConverted + cwchUnicode); + } + } +} + +//------------------------------------------------------------------- +// Custom version of WideCharToMultiByte (faster for all ASCII strings) +// Convert ASCII data (0x00-0x7f) until first non-ASCII data, +// calling OS WideCharToMultiByte in that case. +size_t SystemLocale::FastAsciiWideCharToMultiByte +( + UINT CodePage, + const WCHAR *pwch, // IN | source string + SSIZE_T cwch, // IN | count of characters or -1 + __out_ecount(cch) char *pch, // IN | Result string + size_t cch, // IN | Length of result buffer or 0 + BOOL *pfDataLoss, // IN | True if there was data loss during CP conversion + DWORD *pErrorCode +) +{ + if ( 0 == cwch || NULL == pwch || cwch < -1 ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + + const WCHAR *pwchStart = pwch; + const char *pchStart = pch; + + // Divide into + // Case 1a: cwch, do convert + // Case 1b: cwch, just count + // Case 2a: null-term, do convert + // Case 2b: null-term, just count + if (-1 != cwch) + { + // Case 1: We have counter of characters + if (0 != cwch) + { + if (0 != cch) + { + // Case 1a: Have to convert, not just calculate necessary space + + // Optimization: When converting first cch characters, it's not + // necessary to check for buffer overflow. Also, loop is unrolled. + size_t cquads = cch >> 2; + + while (0 != cquads && 4 <= cwch) + { + unsigned pairLo = *(unsigned UNALIGNED *)pwch; + unsigned pairHi = *(unsigned UNALIGNED *)(pwch+2); + + if ((pairLo | pairHi) & 0xFF80FF80) + goto general; + + *(unsigned UNALIGNED *)pch = (pairLo & 0x7F) | + ((pairLo >> 8) & 0x7F00) | + ((pairHi & 0x7F) << 16) | + ((pairHi & 0x7F0000) << 8); + pch += 4; + pwch += 4; + cwch -= 4; + cquads --; + } + // Convert end of string - slower, but the loop will be executed 3 times max + if (0 != cwch) + { + const char *pchEnd = pchStart + cch; + + do + { + unsigned wch = (unsigned)*pwch; + + if (pch == pchEnd) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; // Not enough space + } + + if ((unsigned)*pwch > 0x7F) + goto general; + + *(pch ++) = (char) wch; + pwch ++; + cwch --; + } while (0 != cwch); + } + } + else + { + // Case 1b: Have to calculate necessary space + do + { + if ((unsigned)*pwch > 0x7F) + goto general; + + pwch ++; + cwch --; + } while (0 != cwch); + } + } + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pwch - pwchStart); + } + else + { + // Case 2: zero-terminated string + if (0 != cch) + { + // Case 2a: Have to convert, not just calculate necessary space + const char *pchEnd = pch + cch; + + do + { + unsigned wch = (unsigned)*pwch; + + if (wch > 0x7F) + goto general; + else + { + *pch = (char) wch; + pwch ++; + if (0 == wch) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pwch - pwchStart); + } + pch ++; + } + } while (pch != pchEnd); + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; // Not enough space + } + else + { + // Case 2b: Have to calculate necessary space + unsigned wch; + + do + { + wch = (unsigned)*pwch; + if (wch > 0x7F) + goto general; + pwch ++; + } while (0 != wch); + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pwch - pwchStart); + } + } + + // Have to call Win32 API +general: + { + size_t cchConverted; + size_t cchUnicode; + + // initialize output param if any + if (pfDataLoss) + *pfDataLoss = FALSE; + + cchConverted = (pwch - pwchStart); + + if ( cch > cchConverted ) + cch -= cchConverted; + else + cch = 0; + + // Windows MBtoWC takes int inputs + if ( INT32_MAX < cch || INT32_MAX < cwch ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + + cchUnicode = (UINT)WideCharToMultiByte ( + CodePage, + 0, + pwch, + (int)cwch, + pch, + (int)cch, + NULL, + IsW2CZeroFlagCodePage(CodePage) ? NULL : pfDataLoss); + + if ( 0 == cchUnicode ) + { + if ( NULL != pErrorCode ) + *pErrorCode = GetLastError(); + return 0; + } + else + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return (cchConverted + cchUnicode); + } + } +} + +// !MPLAT_UNIX ---------------------------------------------------------------- +#endif + diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h new file mode 100644 index 00000000..278a8617 --- /dev/null +++ b/source/shared/msodbcsql.h @@ -0,0 +1,426 @@ +#ifndef __msodbcsql_h__ +#define __msodbcsql_h__ + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: msodbcsql.h +// +// Contents: Routines that use statement handles +// +// Contents: This SDK is not supported under any Microsoft standard support +// program or service. The information is provided AS IS without +// warranty of any kind. Microsoft disclaims all implied +// warranties including, without limitation, any implied +// warranties of merchantability or of fitness for a particular +// purpose. The entire risk arising out of the use of this SDK +// remains with you. In no event shall Microsoft, its authors, or +// anyone else involved in the creation, production, or delivery +// of this SDK be liable for any damages whatsoever (including, +// without limitation, damages for loss of business profits, +// business interruption, loss of business information, or other +// 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 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + + +#if !defined(SQLODBC_VER) +#define SQLODBC_VER 1100 +#endif + +#if SQLODBC_VER >= 1300 + +#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 13 for SQL Server" +#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server" +#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 13 for SQL Server" +#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server" + +#endif /* SQLODBC_VER >= 1300 */ + +#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI +#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI +#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI +#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI + +#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER + +/* max SQL Server identifier length */ +#define SQL_MAX_SQLSERVERNAME 128 + +/* + * SQLSetConnectAttr driver specific defines. + * Microsoft has 1200 thru 1249 reserved for Microsoft ODBC Driver for SQL Server usage. + * Connection attributes + */ +#define SQL_COPT_SS_BASE 1200 +#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) /* Force integrated security on login */ +#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) /* Perform code page translation */ +#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) /* Allow strong encryption for data */ +#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) /* Multiple active result set per connection */ +#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) /* Used to set/get any driver-specific or ODBC-defined TXN iso level */ +#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) /* Trust server certificate */ +/* + * SQLSetStmtAttr Microsoft ODBC Driver for SQL Server specific defines. + * Statement attributes + */ +#define SQL_SOPT_SS_BASE 1225 +#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) /* Text pointer logging */ +#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) /* Set NOBROWSETABLE option */ +#define SQL_SOPT_SS_COLUMN_ENCRYPTION (SQL_SOPT_SS_BASE+13)/* Sets the column encryption mode */ +/* Define old names */ +#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING +#define SQL_COPT_SS_BASE_EX 1240 +#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) /* Issues warning when data from the server had a loss during code page conversion. */ +#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) /* dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check */ +#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) /* Application Intent */ +#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) /* Multi-subnet Failover */ +#define SQL_COPT_SS_TNIR (SQL_COPT_SS_BASE_EX+9) /* Transparent Network IP Resolution */ +#define SQL_COPT_SS_COLUMN_ENCRYPTION (SQL_COPT_SS_BASE_EX+10) /* Always Encrypted Enabled or Disabled */ +#define SQL_COPT_SS_AEKEYSTOREPROVIDER (SQL_COPT_SS_BASE_EX+11) /* Load a keystore provider or read the list of loaded keystore providers */ +#define SQL_COPT_SS_AEKEYSTOREDATA (SQL_COPT_SS_BASE_EX+12) /* Communicate with a loaded keystore provider */ +#define SQL_COPT_SS_AETRUSTEDCMKPATHS (SQL_COPT_SS_BASE_EX+13) /* List of trusted CMK paths */ +#define SQL_COPT_SS_AECEKCACHETTL (SQL_COPT_SS_BASE_EX+14)// Symmetric Key Cache TTL +/* + * SQLColAttributes driver specific defines. + * SQLSetDescField/SQLGetDescField driver specific defines. + * Microsoft has 1200 thru 1249 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_CA_SS_BASE 1200 +#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) /* dbcoltype/dbalttype */ +#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) /* dbcolutype/dbaltutype */ +#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) /* dbnumorders */ +#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) /* dbordercol */ +#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) /* dbvarylen */ +#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) /* dbnumcompute */ +#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) /* dbnextrow status return */ +#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) /* dbbylist */ +#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) /* dbaltcolid */ +#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) /* dbaltop */ +#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) /* dbcollen */ +#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) /* Column is hidden (FOR BROWSE) */ +#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) /* Column is key column (FOR BROWSE) */ +#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) /* Column collation (only for chars) */ +#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15) +#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16) +#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17) + +/* XML, CLR UDT, and table valued parameter related metadata */ +#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) /* UDT catalog name */ +#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) /* UDT schema name */ +#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) /* UDT type name */ +#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) /* Name of the catalog that contains XML Schema collection */ +#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) /* Name of the schema that contains XML Schema collection */ +#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) /* Name of the XML Schema collection */ +#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) /* Catalog name */ +#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) /* Schema name */ +#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) /* Type name */ + +/* table valued parameter related metadata */ +#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) /* column is computed */ +#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) /* column is part of a unique key */ +#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) /* column sort order */ +#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) /* column sort ordinal */ +#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) /* column has default value for all rows of the table valued parameter */ + +/* sparse column related metadata */ +#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) /* column is a column-set column for sparse columns */ + +/* Legacy datetime related metadata */ +#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) /* column type to send on the wire for datetime types */ + +/* force column encryption */ +#define SQL_CA_SS_FORCE_ENCRYPT (SQL_CA_SS_BASE+36) /* indicate mandatory encryption for this parameter */ + +/* Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only */ +#define SQL_IS_OFF 0L /* Integrated security isn't used */ +#define SQL_IS_ON 1L /* Integrated security is used */ +#define SQL_IS_DEFAULT SQL_IS_OFF +/* Defines for use with SQL_COPT_SS_TRANSLATE */ +#define SQL_XL_OFF 0L /* Code page translation is not performed */ +#define SQL_XL_ON 1L /* Code page translation is performed */ +#define SQL_XL_DEFAULT SQL_XL_ON +/* Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING */ +#define SQL_TL_OFF 0L /* No logging on text pointer ops */ +#define SQL_TL_ON 1L /* Logging occurs on text pointer ops */ +#define SQL_TL_DEFAULT SQL_TL_ON +/* Defines for use with SQL_SOPT_SS_NOBROWSETABLE */ +#define SQL_NB_OFF 0L /* NO_BROWSETABLE is off */ +#define SQL_NB_ON 1L /* NO_BROWSETABLE is on */ +#define SQL_NB_DEFAULT SQL_NB_OFF +/* Defines for use with SQL_SOPT_SS_COLUMN_ENCRYPTION */ +#define SQL_CE_DISABLED 0L /* Disabled */ +#define SQL_CE_RESULTSETONLY 1L /* Decryption Only (resultsets and return values) */ +#define SQL_CE_ENABLED 3L /* Enabled (both encryption and decryption) */ + +/* SQL_COPT_SS_ENCRYPT */ +#define SQL_EN_OFF 0L +#define SQL_EN_ON 1L +/* SQL_COPT_SS_TRUST_SERVER_CERTIFICATE */ +#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L +#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L +/* SQL_COPT_SS_WARN_ON_CP_ERROR */ +#define SQL_WARN_NO 0L +#define SQL_WARN_YES 1L +/* SQL_COPT_SS_MARS_ENABLED */ +#define SQL_MARS_ENABLED_NO 0L +#define SQL_MARS_ENABLED_YES 1L +/* SQL_TXN_ISOLATION_OPTION bitmasks */ +#define SQL_TXN_SS_SNAPSHOT 0x00000020L +/* SQL_COPT_SS_COLUMN_ENCRYPTION */ +#define SQL_COLUMN_ENCRYPTION_DISABLE 0L +#define SQL_COLUMN_ENCRYPTION_ENABLE 1L +#define SQL_COLUMN_ENCRYPTION_DEFAULT SQL_COLUMN_ENCRYPTION_DISABLE +// Defines for use with SQL_COPT_SS_AECEKCACHETTL +#define SQL_AECEKCACHETTL_DEFAULT 7200L // TTL value in seconds (2 hours) + +/* The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER */ +#define SQL_SS_ORDER_UNSPECIFIED 0L +#define SQL_SS_DESCENDING_ORDER 1L +#define SQL_SS_ASCENDING_ORDER 2L +#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED + +/* + * Driver specific SQL data type defines. + * Microsoft has -150 thru -199 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_SS_VARIANT (-150) +#define SQL_SS_UDT (-151) +#define SQL_SS_XML (-152) +#define SQL_SS_TABLE (-153) +#define SQL_SS_TIME2 (-154) +#define SQL_SS_TIMESTAMPOFFSET (-155) + +/* Local types to be used with SQL_CA_SS_SERVER_TYPE */ +#define SQL_SS_TYPE_DEFAULT 0L +#define SQL_SS_TYPE_SMALLDATETIME 1L +#define SQL_SS_TYPE_DATETIME 2L + +/* Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager. */ +#define SQL_C_TYPES_EXTENDED 0x04000L + +/* + * SQL_SS_LENGTH_UNLIMITED is used to describe the max length of + * VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns + */ +#define SQL_SS_LENGTH_UNLIMITED 0 + +/* + * User Data Type definitions. + * Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE. + */ +#define SQLudtBINARY 3 +#define SQLudtBIT 16 +#define SQLudtBITN 0 +#define SQLudtCHAR 1 +#define SQLudtDATETIM4 22 +#define SQLudtDATETIME 12 +#define SQLudtDATETIMN 15 +#define SQLudtDECML 24 +#define SQLudtDECMLN 26 +#define SQLudtFLT4 23 +#define SQLudtFLT8 8 +#define SQLudtFLTN 14 +#define SQLudtIMAGE 20 +#define SQLudtINT1 5 +#define SQLudtINT2 6 +#define SQLudtINT4 7 +#define SQLudtINTN 13 +#define SQLudtMONEY 11 +#define SQLudtMONEY4 21 +#define SQLudtMONEYN 17 +#define SQLudtNUM 10 +#define SQLudtNUMN 25 +#define SQLudtSYSNAME 18 +#define SQLudtTEXT 19 +#define SQLudtTIMESTAMP 80 +#define SQLudtUNIQUEIDENTIFIER 0 +#define SQLudtVARBINARY 4 +#define SQLudtVARCHAR 2 +#define MIN_USER_DATATYPE 256 +/* + * Aggregate operator types. + * Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP. + */ +#define SQLAOPSTDEV 0x30 /* Standard deviation */ +#define SQLAOPSTDEVP 0x31 /* Standard deviation population */ +#define SQLAOPVAR 0x32 /* Variance */ +#define SQLAOPVARP 0x33 /* Variance population */ +#define SQLAOPCNT 0x4b /* Count */ +#define SQLAOPSUM 0x4d /* Sum */ +#define SQLAOPAVG 0x4f /* Average */ +#define SQLAOPMIN 0x51 /* Min */ +#define SQLAOPMAX 0x52 /* Max */ +#define SQLAOPANY 0x53 /* Any */ +#define SQLAOPNOOP 0x56 /* None */ +/* + * SQLGetDiagField driver specific defines. + * Microsoft has -1150 thru -1199 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_DIAG_SS_BASE (-1150) +#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE) +#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1) +#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2) +#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3) +#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4) +/* + * SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines. + * Microsoft has -200 thru -299 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_DIAG_DFC_SS_BASE (-200) +#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0) +#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1) +#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2) +#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3) +#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4) +#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5) +#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6) +#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7) +#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8) +#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9) +#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10) +#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11) +#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12) +#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13) +#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14) +#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15) +#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16) +#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17) +#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18) +#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19) +#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) +#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) +#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21) +#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) +#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) +#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23) +#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24) +#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25) +#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26) +#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26) +#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) +#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) +#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28) +#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) +#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) +#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30) +#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31) +#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32) +#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33) +#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34) +#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35) +#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36) +#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37) +#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38) +#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39) +#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40) +#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41) +#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42) +#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43) +#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44) +#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45) +#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46) +#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47) +#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48) +#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49) +#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50) +#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51) +#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52) +#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53) +#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54) +#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55) +#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56) + +/* Severity codes for SQL_DIAG_SS_SEVERITY */ +#define EX_ANY 0 +#define EX_INFO 10 +#define EX_MAXISEVERITY EX_INFO +#define EX_MISSING 11 +#define EX_TYPE 12 +#define EX_DEADLOCK 13 +#define EX_PERMIT 14 +#define EX_SYNTAX 15 +#define EX_USER 16 +#define EX_RESOURCE 17 +#define EX_INTOK 18 +#define MAXUSEVERITY EX_INTOK +#define EX_LIMIT 19 +#define EX_CMDFATAL 20 +#define MINFATALERR EX_CMDFATAL +#define EX_DBFATAL 21 +#define EX_TABCORRUPT 22 +#define EX_DBCORRUPT 23 +#define EX_HARDWARE 24 +#define EX_CONTROL 25 + +/* Keystore Provider interface definition */ + +typedef void errFunc(void *ctx, const wchar_t *msg, ...); + +#define IDS_MSG(x) ((const wchar_t*)(x)) + +typedef struct AEKeystoreProvider +{ + wchar_t *Name; + int (*Init)(void *ctx, errFunc *onError); + int (*Read)(void *ctx, errFunc *onError, void *data, unsigned int *len); + int (*Write)(void *ctx, errFunc *onError, void *data, unsigned int len); + int (*DecryptCEK)( + void *ctx, + errFunc *onError, + const wchar_t *keyPath, + const wchar_t *alg, + unsigned char *ecek, + unsigned short ecek_len, + unsigned char **cek_out, + unsigned short *cek_len); + void (*Free)(); +} AEKEYSTOREPROVIDER; + +/* Data is defined to be past the end of the structure header. + This is accepted by MSVC, GCC, and C99 standard but former emits + unnecessary warning, hence it has to be disabled. +*/ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4200) +#endif + +typedef struct AEKeystoreData +{ + wchar_t *Name; + unsigned int dataSize; + char Data[]; +} AEKEYSTOREPROVIDERDATA; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +/* The following constants are for the Azure Key Vault configuration interface */ +#define AKV_CONFIG_FLAGS 0 + #define AKVCFG_USECLIENTID 0x00000001 + #define AKVCFG_AUTORENEW 0x00000002 + +#define AKV_CONFIG_CLIENTID 1 +#define AKV_CONFIG_CLIENTKEY 2 + +#define AKV_CONFIG_ACCESSTOKEN 3 +#define AKV_CONFIG_TOKENEXPIRY 4 + +#define AKV_CONFIG_MAXRETRIES 5 +#define AKV_CONFIG_RETRYTIMEOUT 6 +#define AKV_CONFIG_RETRYWAIT 7 + +#endif /* __msodbcsql_h__ */ + diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h new file mode 100644 index 00000000..b51b7f2a --- /dev/null +++ b/source/shared/sal_def.h @@ -0,0 +1,796 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: sal_def.h +// +// Contents: Contains the minimal definitions to build on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef XPLAT_SAL_DEFINED +#define XPLAT_SAL_DEFINED + +#define __allocator +#define __analysis_assert(e) +#define __analysis_assume(e) +#define __bcount(size) +#define __bcount_opt(size) +#define __blocksOn(resource) +#define __bound +#define __callback +#define __checkReturn +#define __control_entrypoint(category) +#define __data_entrypoint(category) +#define __deref +#define __deref_bcount(size) +#define __deref_bcount_opt(size) +#define __deref_ecount(size) +#define __deref_ecount_opt(size) +#define __deref_in +#define __deref_in +#define __deref_in_bcount(size) +#define __deref_in_bcount_opt(size) +#define __deref_in_ecount(size) +#define __deref_in_ecount_opt(size) +#define __deref_in_opt +#define __deref_in_range(lb,ub) +#define __deref_in_xcount(size) +#define __deref_in_xcount_opt(size) +#define __deref_inout +#define __deref_inout_bcount(size) +#define __deref_inout_bcount_full(size) +#define __deref_inout_bcount_full_opt(size) +#define __deref_inout_bcount_nz(size) +#define __deref_inout_bcount_nz_opt(size) +#define __deref_inout_bcount_opt(size) +#define __deref_inout_bcount_part(size,length) +#define __deref_inout_bcount_part_opt(size,length) +#define __deref_inout_bcount_z(size) +#define __deref_inout_bcount_z_opt(size) +#define __deref_inout_ecount(size) +#define __deref_inout_ecount_full(size) +#define __deref_inout_ecount_full_opt(size) +#define __deref_inout_ecount_nz(size) +#define __deref_inout_ecount_nz_opt(size) +#define __deref_inout_ecount_opt(size) +#define __deref_inout_ecount_part(size,length) +#define __deref_inout_ecount_part_opt(size,length) +#define __deref_inout_ecount_z(size) +#define __deref_inout_ecount_z_opt(size) +#define __deref_inout_nz +#define __deref_inout_nz_opt +#define __deref_inout_opt +#define __deref_inout_xcount(size) +#define __deref_inout_xcount_full(size) +#define __deref_inout_xcount_full_opt(size) +#define __deref_inout_xcount_opt(size) +#define __deref_inout_xcount_part(size,length) +#define __deref_inout_xcount_part_opt(size,length) +#define __deref_inout_z +#define __deref_inout_z_opt +#define __deref_opt_bcount(size) +#define __deref_opt_bcount_opt(size) +#define __deref_opt_ecount(size) +#define __deref_opt_ecount_opt(size) +#define __deref_opt_in +#define __deref_opt_in_bcount(size) +#define __deref_opt_in_bcount_opt(size) +#define __deref_opt_in_ecount(size) +#define __deref_opt_in_ecount_opt(size) +#define __deref_opt_in_opt +#define __deref_opt_in_xcount(size) +#define __deref_opt_in_xcount_opt(size) +#define __deref_opt_inout +#define __deref_opt_inout_bcount(size) +#define __deref_opt_inout_bcount_full(size) +#define __deref_opt_inout_bcount_full_opt(size) +#define __deref_opt_inout_bcount_nz(size) +#define __deref_opt_inout_bcount_nz_opt(size) +#define __deref_opt_inout_bcount_opt(size) +#define __deref_opt_inout_bcount_part(size,length) +#define __deref_opt_inout_bcount_part_opt(size,length) +#define __deref_opt_inout_bcount_z(size) +#define __deref_opt_inout_bcount_z_opt(size) +#define __deref_opt_inout_ecount(size) +#define __deref_opt_inout_ecount_full(size) +#define __deref_opt_inout_ecount_full_opt(size) +#define __deref_opt_inout_ecount_nz(size) +#define __deref_opt_inout_ecount_nz_opt(size) +#define __deref_opt_inout_ecount_opt(size) +#define __deref_opt_inout_ecount_part(size,length) +#define __deref_opt_inout_ecount_part_opt(size,length) +#define __deref_opt_inout_ecount_z(size) +#define __deref_opt_inout_ecount_z_opt(size) +#define __deref_opt_inout_nz +#define __deref_opt_inout_nz_opt +#define __deref_opt_inout_opt +#define __deref_opt_inout_xcount(size) +#define __deref_opt_inout_xcount_full(size) +#define __deref_opt_inout_xcount_full_opt(size) +#define __deref_opt_inout_xcount_opt(size) +#define __deref_opt_inout_xcount_part(size,length) +#define __deref_opt_inout_xcount_part_opt(size,length) +#define __deref_opt_inout_z +#define __deref_opt_inout_z_opt +#define __deref_opt_out +#define __deref_opt_out_bcount(size) +#define __deref_opt_out_bcount_full(size) +#define __deref_opt_out_bcount_full_opt(size) +#define __deref_opt_out_bcount_nz_opt(size) +#define __deref_opt_out_bcount_opt(size) +#define __deref_opt_out_bcount_part(size,length) +#define __deref_opt_out_bcount_part_opt(size,length) +#define __deref_opt_out_bcount_z_opt(size) +#define __deref_opt_out_ecount(size) +#define __deref_opt_out_ecount_full(size) +#define __deref_opt_out_ecount_full_opt(size) +#define __deref_opt_out_ecount_nz_opt(size) +#define __deref_opt_out_ecount_opt(size) +#define __deref_opt_out_ecount_part(size,length) +#define __deref_opt_out_ecount_part_opt(size,length) +#define __deref_opt_out_ecount_z_opt(size) +#define __deref_opt_out_nz_opt +#define __deref_opt_out_opt +#define __deref_opt_out_xcount(size) +#define __deref_opt_out_xcount_full(size) +#define __deref_opt_out_xcount_full_opt(size) +#define __deref_opt_out_xcount_opt(size) +#define __deref_opt_out_xcount_part(size,length) +#define __deref_opt_out_xcount_part_opt(size,length) +#define __deref_opt_out_z +#define __deref_opt_out_z_opt +#define __deref_opt_xcount(size) +#define __deref_opt_xcount_opt(size) +#define __deref_out +#define __deref_out_bcount(size) +#define __deref_out_bcount_full(size) +#define __deref_out_bcount_full_opt(size) +#define __deref_out_bcount_nz(size) +#define __deref_out_bcount_nz_opt(size) +#define __deref_out_bcount_opt(size) +#define __deref_out_bcount_part(size,length) +#define __deref_out_bcount_part_opt(size,length) +#define __deref_out_bcount_z(size) +#define __deref_out_bcount_z_opt(size) +#define __deref_out_bound +#define __deref_out_ecount(size) +#define __deref_out_ecount_full(size) +#define __deref_out_ecount_full_opt(size) +#define __deref_out_ecount_nz(size) +#define __deref_out_ecount_nz_opt(size) +#define __deref_out_ecount_opt(size) +#define __deref_out_ecount_part(size,length) +#define __deref_out_ecount_part_opt(size,length) +#define __deref_out_ecount_z(size) +#define __deref_out_ecount_z_opt(size) +#define __deref_out_nz +#define __deref_out_nz_opt +#define __deref_out_opt +#define __deref_out_range(lb,ub) +#define __deref_out_xcount(size) +#define __deref_out_xcount_full(size) +#define __deref_out_xcount_full_opt(size) +#define __deref_out_xcount_opt(size) +#define __deref_out_xcount_part(size,length) +#define __deref_out_xcount_part_opt(size,length) +#define __deref_out_z +#define __deref_out_z_opt +#define __deref_xcount(size) +#define __deref_xcount_opt(size) +#define __ecount(size) +#define __ecount_opt(size) +#define __fallthrough +#define __field_bcount(size) +#define __field_bcount_full(size) +#define __field_bcount_full_opt(size) +#define __field_bcount_opt(size) +#define __field_bcount_part(size,init) +#define __field_bcount_part_opt(size,init) +#define __field_data_source(src_sym) +#define __field_ecount(size) +#define __field_ecount_full(size) +#define __field_ecount_full_opt(size) +#define __field_ecount_opt(size) +#define __field_ecount_part(size,init) +#define __field_ecount_part_opt(size,init) +#define __field_range(lb,ub) +#define __field_xcount(size) +#define __field_xcount_full(size) +#define __field_xcount_full_opt(size) +#define __field_xcount_opt(size) +#define __field_xcount_part(size,init) +#define __field_xcount_part_opt(size,init) +#define __format_string +#define __inn +#define __in_bcount(size) +#define __in_bcount_nz(size) +#define __in_bcount_nz_opt(size) +#define __in_bcount_opt(size) +#define __in_bcount_z(size) +#define __in_bcount_z_opt(size) +#define __in_bound +#define __in_ecount(size) +#define __in_ecount_nz(size) +#define __in_ecount_nz_opt(size) +#define __in_ecount_opt(size) +#define __in_ecount_z(size) +#define __in_ecount_z_opt(size) +#define __in_nz +#define __in_nz_opt +#define __in_opt +#define __in_range(lb,ub) +#define __in_xcount(size) +#define __in_xcount_opt(size) +#define __in_z +#define __in_z_opt +#define __inexpressible_readableTo(size) +#define __inexpressible_writableTo(size) +#define __inner_allocator +#define __inner_assume_bound(i) +#define __inner_assume_bound_dec +#define __inner_bound +#define __inner_range(lb,ub) +#define __inout +#define __inout_bcount(size) +#define __inout_bcount_full(size) +#define __inout_bcount_full_opt(size) +#define __inout_bcount_nz(size) +#define __inout_bcount_nz_opt(size) +#define __inout_bcount_opt(size) +#define __inout_bcount_part(size,length) +#define __inout_bcount_part_opt(size,length) +#define __inout_bcount_z(size) +#define __inout_bcount_z_opt(size) +#define __inout_ecount(size) +#define __inout_ecount_full(size) +#define __inout_ecount_full_opt(size) +#define __inout_ecount_nz(size) +#define __inout_ecount_nz_opt(size) +#define __inout_ecount_opt(size) +#define __inout_ecount_part(size,length) +#define __inout_ecount_part_opt(size,length) +#define __inout_ecount_z(size) +#define __inout_ecount_z_opt(size) +#define __inout_nz +#define __inout_nz_opt +#define __inout_opt +#define __inout_xcount(size) +#define __inout_xcount_full(size) +#define __inout_xcount_full_opt(size) +#define __inout_xcount_opt(size) +#define __inout_xcount_opt(size) +#define __inout_xcount_part(size,length) +#define __inout_xcount_part_opt(size,length) +#define __inout_z +#define __inout_z_opt +#define __nullnullterminated +#define __nullterminated +#define __outt +#define __out_bcount(size) +#define __out_bcount_full(size) +#define __out_bcount_full_opt(size) +#define __out_bcount_full_z(size) +#define __out_bcount_full_z_opt(size) +#define __out_bcount_nz(size) +#define __out_bcount_nz_opt(size) +#define __out_bcount_opt(size) +#define __out_bcount_part(size,length) +#define __out_bcount_part_opt(size,length) +#define __out_bcount_part_z(size,length) +#define __out_bcount_part_z_opt(size,length) +#define __out_bcount_z(size) +#define __out_bcount_z_opt(size) +#define __out_bound +#define __out_ecount(size) +#define __out_ecount_full(size) +#define __out_ecount_full_opt(size) +#define __out_ecount_full_z(size) +#define __out_ecount_full_z_opt(size) +#define __out_ecount_nz(size) +#define __out_ecount_nz_opt(size) +#define __out_ecount_opt(size) +#define __out_ecount_part(size,length) +#define __out_ecount_part_opt(size,length) +#define __out_ecount_part_z(size,length) +#define __out_ecount_part_z_opt(size,length) +#define __out_ecount_z(size) +#define __out_ecount_z_opt(size) +#define __out_nz +#define __out_nz_opt +#define __out_opt +#define __out_range(lb,ub) +#define __out_xcount(size) +#define __out_xcount_full(size) +#define __out_xcount_full_opt(size) +#define __out_xcount_opt(size) +#define __out_xcount_part(size,length) +#define __out_xcount_part_opt(size,length) +#define __out_z +#define __out_z_opt +#define __override +#define __range(lb,ub) +#define __reserved +#define __sql_escaped_and_delimited_right_bracket +#define __struct_bcount(size) +#define __struct_xcount(size) +#define __success(expr) +#define __transfer(formal) +#define __typefix(ctype) +#define __xcount(size) +#define __xcount_opt(size) +#define _Analysis_assume_ +#define _Check_return_ +#define _Check_return_ +#define _Deref +#define _Deref_in_bound_ +#define _Deref_in_range_(lb,ub) +#define _Deref_inout_bound_ +#define _Deref_inout_z_ +#define _Deref_inout_z_bytecap_c_(size) +#define _Deref_inout_z_cap_c_(size) +#define _Deref_opt_out_ +#define _Deref_opt_out_opt_ +#define _Deref_opt_out_opt_z_ +#define _Deref_opt_out_z_ +#define _Deref_out_ +#define _Deref_out_bound_ +#define _Deref_out_opt_ +#define _Deref_out_opt_z_ +#define _Deref_out_range_(lb,ub) +#define _Deref_out_z_ +#define _Deref_out_z_bytecap_c_(size) +#define _Deref_out_z_cap_c_(size) +#define _Deref_post_bytecap_(size) +#define _Deref_post_bytecap_c_(size) +#define _Deref_post_bytecap_x_(size) +#define _Deref_post_bytecount_(size) +#define _Deref_post_bytecount_c_(size) +#define _Deref_post_bytecount_x_(size) +#define _Deref_post_cap_(size) +#define _Deref_post_cap_c_(size) +#define _Deref_post_cap_x_(size) +#define _Deref_post_count_(size) +#define _Deref_post_count_c_(size) +#define _Deref_post_count_x_(size) +#define _Deref_post_maybenull_ +#define _Deref_post_notnull_ +#define _Deref_post_null_ +#define _Deref_post_opt_bytecap_(size) +#define _Deref_post_opt_bytecap_c_(size) +#define _Deref_post_opt_bytecap_x_(size) +#define _Deref_post_opt_bytecount_(size) +#define _Deref_post_opt_bytecount_c_(size) +#define _Deref_post_opt_bytecount_x_(size) +#define _Deref_post_opt_cap_(size) +#define _Deref_post_opt_cap_c_(size) +#define _Deref_post_opt_cap_x_(size) +#define _Deref_post_opt_count_(size) +#define _Deref_post_opt_count_c_(size) +#define _Deref_post_opt_count_x_(size) +#define _Deref_post_opt_valid_ +#define _Deref_post_opt_valid_bytecap_(size) +#define _Deref_post_opt_valid_bytecap_c_(size) +#define _Deref_post_opt_valid_bytecap_x_(size) +#define _Deref_post_opt_valid_cap_(size) +#define _Deref_post_opt_valid_cap_c_(size) +#define _Deref_post_opt_valid_cap_x_(size) +#define _Deref_post_opt_z_ +#define _Deref_post_opt_z_bytecap_(size) +#define _Deref_post_opt_z_bytecap_c_(size) +#define _Deref_post_opt_z_bytecap_x_(size) +#define _Deref_post_opt_z_cap_(size) +#define _Deref_post_opt_z_cap_c_(size) +#define _Deref_post_opt_z_cap_x_(size) +#define _Deref_post_valid_ +#define _Deref_post_valid_bytecap_(size) +#define _Deref_post_valid_bytecap_c_(size) +#define _Deref_post_valid_bytecap_x_(size) +#define _Deref_post_valid_cap_(size) +#define _Deref_post_valid_cap_c_(size) +#define _Deref_post_valid_cap_x_(size) +#define _Deref_post_z_ +#define _Deref_post_z_ +#define _Deref_post_z_bytecap_(size) +#define _Deref_post_z_bytecap_c_(size) +#define _Deref_post_z_bytecap_x_(size) +#define _Deref_post_z_cap_(size) +#define _Deref_post_z_cap_c_(size) +#define _Deref_post_z_cap_x_(size) +#define _Deref_pre_bytecap_(size) +#define _Deref_pre_bytecap_c_(size) +#define _Deref_pre_bytecap_x_(size) +#define _Deref_pre_bytecount_(size) +#define _Deref_pre_bytecount_c_(size) +#define _Deref_pre_bytecount_x_(size) +#define _Deref_pre_cap_(size) +#define _Deref_pre_cap_c_(size) +#define _Deref_pre_cap_x_(size) +#define _Deref_pre_count_(size) +#define _Deref_pre_count_c_(size) +#define _Deref_pre_count_x_(size) +#define _Deref_pre_invalid_ +#define _Deref_pre_maybenull_ +#define _Deref_pre_notnull_ +#define _Deref_pre_null_ +#define _Deref_pre_opt_bytecap_(size) +#define _Deref_pre_opt_bytecap_c_(size) +#define _Deref_pre_opt_bytecap_x_(size) +#define _Deref_pre_opt_bytecount_(size) +#define _Deref_pre_opt_bytecount_c_(size) +#define _Deref_pre_opt_bytecount_x_(size) +#define _Deref_pre_opt_cap_(size) +#define _Deref_pre_opt_cap_c_(size) +#define _Deref_pre_opt_cap_x_(size) +#define _Deref_pre_opt_count_(size) +#define _Deref_pre_opt_count_c_(size) +#define _Deref_pre_opt_count_x_(size) +#define _Deref_pre_opt_valid_ +#define _Deref_pre_opt_valid_bytecap_(size) +#define _Deref_pre_opt_valid_bytecap_c_(size) +#define _Deref_pre_opt_valid_bytecap_x_(size) +#define _Deref_pre_opt_valid_cap_(size) +#define _Deref_pre_opt_valid_cap_c_(size) +#define _Deref_pre_opt_valid_cap_x_(size) +#define _Deref_pre_opt_z_ +#define _Deref_pre_opt_z_bytecap_(size) +#define _Deref_pre_opt_z_bytecap_c_(size) +#define _Deref_pre_opt_z_bytecap_x_(size) +#define _Deref_pre_opt_z_cap_(size) +#define _Deref_pre_opt_z_cap_c_(size) +#define _Deref_pre_opt_z_cap_x_(size) +#define _Deref_pre_readonly_ +#define _Deref_pre_valid_ +#define _Deref_pre_valid_bytecap_(size) +#define _Deref_pre_valid_bytecap_c_(size) +#define _Deref_pre_valid_bytecap_x_(size) +#define _Deref_pre_valid_cap_(size) +#define _Deref_pre_valid_cap_c_(size) +#define _Deref_pre_valid_cap_x_(size) +#define _Deref_pre_writeonly_ +#define _Deref_pre_z_ +#define _Deref_pre_z_bytecap_(size) +#define _Deref_pre_z_bytecap_c_(size) +#define _Deref_pre_z_bytecap_x_(size) +#define _Deref_pre_z_cap_(size) +#define _Deref_pre_z_cap_c_(size) +#define _Deref_pre_z_cap_x_(size) +#define _Deref_prepost_bytecap_(size) +#define _Deref_prepost_bytecap_x_(size) +#define _Deref_prepost_bytecount_(size) +#define _Deref_prepost_bytecount_x_(size) +#define _Deref_prepost_cap_(size) +#define _Deref_prepost_cap_x_(size) +#define _Deref_prepost_count_(size) +#define _Deref_prepost_count_x_(size) +#define _Deref_prepost_opt_bytecap_(size) +#define _Deref_prepost_opt_bytecap_x_(size) +#define _Deref_prepost_opt_bytecount_(size) +#define _Deref_prepost_opt_bytecount_x_(size) +#define _Deref_prepost_opt_cap_(size) +#define _Deref_prepost_opt_cap_x_(size) +#define _Deref_prepost_opt_count_(size) +#define _Deref_prepost_opt_count_x_(size) +#define _Deref_prepost_opt_valid_ +#define _Deref_prepost_opt_valid_bytecap_(size) +#define _Deref_prepost_opt_valid_bytecap_x_(size) +#define _Deref_prepost_opt_valid_cap_(size) +#define _Deref_prepost_opt_valid_cap_x_(size) +#define _Deref_prepost_opt_z_ +#define _Deref_prepost_opt_z_bytecap_(size) +#define _Deref_prepost_opt_z_cap_(size) +#define _Deref_prepost_valid_ +#define _Deref_prepost_valid_bytecap_(size) +#define _Deref_prepost_valid_bytecap_x_(size) +#define _Deref_prepost_valid_cap_(size) +#define _Deref_prepost_valid_cap_x_(size) +#define _Deref_prepost_z_ +#define _Deref_prepost_z_bytecap_(size) +#define _Deref_prepost_z_cap_(size) +#define _Deref_ret_bound_ +#define _Deref_ret_opt_z_ +#define _Deref_ret_range_(lb,ub) +#define _Deref_ret_z_ +#define _FormatMessage_format_string_ +#define _In_ +#define _In_bound_ +#define _In_bytecount_(size) +#define _In_bytecount_c_(size) +#define _In_bytecount_x_(size) +#define _In_count_(size) +#define _In_count_c_(size) +#define _In_count_x_(size) +#define _In_opt_ +#define _In_opt_ +#define _In_opt_bytecount_(size) +#define _In_opt_bytecount_c_(size) +#define _In_opt_bytecount_x_(size) +#define _In_opt_count_(size) +#define _In_opt_count_c_(size) +#define _In_opt_count_x_(size) +#define _In_opt_ptrdiff_count_(size) +#define _In_opt_z_ +#define _In_opt_z_bytecount_(size) +#define _In_opt_z_bytecount_c_(size) +#define _In_opt_z_count_(size) +#define _In_opt_z_count_c_(size) +#define _In_ptrdiff_count_(size) +#define _In_range_(lb,ub) +#define _In_reads_bytes_(size) +#define _In_z_ +#define _In_z_bytecount_(size) +#define _In_z_bytecount_c_(size) +#define _In_z_count_(size) +#define _In_z_count_c_(size) +#define _Inout_ +#define _Inout_bytecap_(size) +#define _Inout_bytecap_c_(size) +#define _Inout_bytecap_x_(size) +#define _Inout_bytecount_(size) +#define _Inout_bytecount_c_(size) +#define _Inout_bytecount_x_(size) +#define _Inout_cap_(size) +#define _Inout_cap_c_(size) +#define _Inout_cap_x_(size) +#define _Inout_count_(size) +#define _Inout_count_c_(size) +#define _Inout_count_x_(size) +#define _Inout_opt_ +#define _Inout_opt_bytecap_(size) +#define _Inout_opt_bytecap_c_(size) +#define _Inout_opt_bytecap_x_(size) +#define _Inout_opt_bytecount_(size) +#define _Inout_opt_bytecount_c_(size) +#define _Inout_opt_bytecount_x_(size) +#define _Inout_opt_cap_(size) +#define _Inout_opt_cap_c_(size) +#define _Inout_opt_cap_x_(size) +#define _Inout_opt_count_(size) +#define _Inout_opt_count_c_(size) +#define _Inout_opt_count_x_(size) +#define _Inout_opt_ptrdiff_count_(size) +#define _Inout_opt_z_ +#define _Inout_opt_z_bytecap_(size) +#define _Inout_opt_z_bytecap_c_(size) +#define _Inout_opt_z_bytecap_x_(size) +#define _Inout_opt_z_bytecount_(size) +#define _Inout_opt_z_bytecount_c_(size) +#define _Inout_opt_z_cap_(size) +#define _Inout_opt_z_cap_c_(size) +#define _Inout_opt_z_cap_x_(size) +#define _Inout_opt_z_count_(size) +#define _Inout_opt_z_count_c_(size) +#define _Inout_ptrdiff_count_(size) +#define _Inout_z_ +#define _Inout_z_bytecap_(size) +#define _Inout_z_bytecap_c_(size) +#define _Inout_z_bytecap_x_(size) +#define _Inout_z_bytecount_(size) +#define _Inout_z_bytecount_c_(size) +#define _Inout_z_cap_(size) +#define _Inout_z_cap_c_(size) +#define _Inout_z_cap_x_(size) +#define _Inout_z_count_(size) +#define _Inout_z_count_c_(size) +#define _Out_ +#define _Out_bound_ +#define _Out_bytecap_(size) +#define _Out_bytecap_c_(size) +#define _Out_bytecap_post_bytecount_(cap,count) +#define _Out_bytecap_x_(size) +#define _Out_bytecapcount_(capcount) +#define _Out_bytecapcount_x_(capcount) +#define _Out_cap_(size) +#define _Out_cap_c_(size) +#define _Out_cap_m_(mult,size) +#define _Out_cap_post_count_(cap,count) +#define _Out_cap_x_(size) +#define _Out_capcount_(capcount) +#define _Out_capcount_x_(capcount) +#define _Out_opt_ +#define _Out_opt_bytecap_(size) +#define _Out_opt_bytecap_c_(size) +#define _Out_opt_bytecap_post_bytecount_(cap,count) +#define _Out_opt_bytecap_x_(size) +#define _Out_opt_bytecapcount_(capcount) +#define _Out_opt_bytecapcount_x_(capcount) +#define _Out_opt_cap_(size) +#define _Out_opt_cap_c_(size) +#define _Out_opt_cap_m_(mult,size) +#define _Out_opt_cap_post_count_(cap,count) +#define _Out_opt_cap_x_(size) +#define _Out_opt_capcount_(capcount) +#define _Out_opt_capcount_x_(capcount) +#define _Out_opt_ptrdiff_cap_(size) +#define _Out_opt_z_bytecap_(size) +#define _Out_opt_z_bytecap_c_(size) +#define _Out_opt_z_bytecap_post_bytecount_(cap,count) +#define _Out_opt_z_bytecap_x_(size) +#define _Out_opt_z_bytecapcount_(capcount) +#define _Out_opt_z_cap_(size) +#define _Out_opt_z_cap_c_(size) +#define _Out_opt_z_cap_m_(mult,size) +#define _Out_opt_z_cap_post_count_(cap,count) +#define _Out_opt_z_cap_x_(size) +#define _Out_opt_z_capcount_(capcount) +#define _Out_ptrdiff_cap_(size) +#define _Out_range_(lb,ub) +#define _Out_writes_(size) +#define _Out_writes_bytes_(count) +#define _Out_z_bytecap_(size) +#define _Out_z_bytecap_c_(size) +#define _Out_z_bytecap_post_bytecount_(cap,count) +#define _Out_z_bytecap_x_(size) +#define _Out_z_bytecapcount_(capcount) +#define _Out_z_cap_(size) +#define _Out_z_cap_c_(size) +#define _Out_z_cap_m_(mult,size) +#define _Out_z_cap_post_count_(cap,count) +#define _Out_z_cap_x_(size) +#define _Out_z_capcount_(capcount) +#define _Post_bytecap_(size) +#define _Post_bytecount_(size) +#define _Post_bytecount_c_(size) +#define _Post_bytecount_x_(size) +#define _Post_cap_(size) +#define _Post_count_(size) +#define _Post_count_c_(size) +#define _Post_count_x_(size) +#define _Post_invalid_ +#define _Post_maybez_ +#define _Post_notnull_ +#define _Post_ptr_invalid_ +#define _Post_valid_ +#define _Post_z_ +#define _Post_z_bytecount_(size) +#define _Post_z_bytecount_c_(size) +#define _Post_z_bytecount_x_(size) +#define _Post_z_count_(size) +#define _Post_z_count_c_(size) +#define _Post_z_count_x_(size) +#define _Pre_bytecap_(size) +#define _Pre_bytecap_c_(size) +#define _Pre_bytecap_x_(size) +#define _Pre_bytecount_(size) +#define _Pre_bytecount_c_(size) +#define _Pre_bytecount_x_(size) +#define _Pre_cap_(size) +#define _Pre_cap_c_(size) +#define _Pre_cap_for_(param) +#define _Pre_cap_m_(mult,size) +#define _Pre_cap_x_(size) +#define _Pre_count_(size) +#define _Pre_count_c_(size) +#define _Pre_count_x_(size) +#define _Pre_invalid_ +#define _Pre_maybenull_ +#define _Pre_notnull_ +#define _Pre_null_ +#define _Pre_opt_bytecap_(size) +#define _Pre_opt_bytecap_c_(size) +#define _Pre_opt_bytecap_x_(size) +#define _Pre_opt_bytecount_(size) +#define _Pre_opt_bytecount_c_(size) +#define _Pre_opt_bytecount_x_(size) +#define _Pre_opt_cap_(size) +#define _Pre_opt_cap_c_(size) +#define _Pre_opt_cap_for_(param) +#define _Pre_opt_cap_m_(mult,size) +#define _Pre_opt_cap_x_(size) +#define _Pre_opt_count_(size) +#define _Pre_opt_count_c_(size) +#define _Pre_opt_count_x_(size) +#define _Pre_opt_ptrdiff_cap_(ptr) +#define _Pre_opt_ptrdiff_count_(ptr) +#define _Pre_opt_valid_ +#define _Pre_opt_valid_bytecap_(size) +#define _Pre_opt_valid_bytecap_c_(size) +#define _Pre_opt_valid_bytecap_x_(size) +#define _Pre_opt_valid_cap_(size) +#define _Pre_opt_valid_cap_c_(size) +#define _Pre_opt_valid_cap_x_(size) +#define _Pre_opt_z_ +#define _Pre_opt_z_bytecap_(size) +#define _Pre_opt_z_bytecap_c_(size) +#define _Pre_opt_z_bytecap_x_(size) +#define _Pre_opt_z_cap_(size) +#define _Pre_opt_z_cap_c_(size) +#define _Pre_opt_z_cap_x_(size) +#define _Pre_ptrdiff_cap_(ptr) +#define _Pre_ptrdiff_count_(ptr) +#define _Pre_readonly_ +#define _Pre_valid_ +#define _Pre_valid_bytecap_(size) +#define _Pre_valid_bytecap_c_(size) +#define _Pre_valid_bytecap_x_(size) +#define _Pre_valid_cap_(size) +#define _Pre_valid_cap_c_(size) +#define _Pre_valid_cap_x_(size) +#define _Pre_writeonly_ +#define _Pre_z_ +#define _Pre_z_bytecap_(size) +#define _Pre_z_bytecap_c_(size) +#define _Pre_z_bytecap_x_(size) +#define _Pre_z_cap_(size) +#define _Pre_z_cap_c_(size) +#define _Pre_z_cap_x_(size) +#define _Prepost_bytecount_(size) +#define _Prepost_bytecount_c_(size) +#define _Prepost_bytecount_x_(size) +#define _Prepost_count_(size) +#define _Prepost_count_c_(size) +#define _Prepost_count_x_(size) +#define _Prepost_opt_bytecount_(size) +#define _Prepost_opt_bytecount_c_(size) +#define _Prepost_opt_bytecount_x_(size) +#define _Prepost_opt_count_(size) +#define _Prepost_opt_count_c_(size) +#define _Prepost_opt_count_x_(size) +#define _Prepost_opt_valid_ +#define _Prepost_opt_z_ +#define _Prepost_valid_ +#define _Prepost_z_ +#define _Printf_format_string_ +#define _Ret_ +#define _Ret_bound_ +#define _Ret_bytecap_(size) +#define _Ret_bytecap_c_(size) +#define _Ret_bytecap_x_(size) +#define _Ret_bytecount_(size) +#define _Ret_bytecount_c_(size) +#define _Ret_bytecount_x_(size) +#define _Ret_cap_(size) +#define _Ret_cap_c_(size) +#define _Ret_cap_x_(size) +#define _Ret_count_(size) +#define _Ret_count_c_(size) +#define _Ret_count_x_(size) +#define _Ret_maybenull_ +#define _Ret_notnull_ +#define _Ret_null_ +#define _Ret_opt_ +#define _Ret_opt_ +#define _Ret_opt_bytecap_(size) +#define _Ret_opt_bytecap_c_(size) +#define _Ret_opt_bytecap_x_(size) +#define _Ret_opt_bytecount_(size) +#define _Ret_opt_bytecount_c_(size) +#define _Ret_opt_bytecount_x_(size) +#define _Ret_opt_cap_(size) +#define _Ret_opt_cap_c_(size) +#define _Ret_opt_cap_x_(size) +#define _Ret_opt_count_(size) +#define _Ret_opt_count_c_(size) +#define _Ret_opt_count_x_(size) +#define _Ret_opt_valid_ +#define _Ret_opt_z_ +#define _Ret_opt_z_ +#define _Ret_opt_z_bytecap_(size) +#define _Ret_opt_z_bytecount_(size) +#define _Ret_opt_z_cap_(size) +#define _Ret_opt_z_count_(size) +#define _Ret_range_(lb,ub) +#define _Ret_valid_ +#define _Ret_z_ +#define _Ret_z_ +#define _Ret_z_bytecap_(size) +#define _Ret_z_bytecount_(size) +#define _Ret_z_cap_(size) +#define _Ret_z_count_(size) +#define _Scanf_format_string_ +#define _Scanf_s_format_string_ +#define _Success_(expr) +#define _Outptr_ +#define _Notnull_ + +#endif // XPLAT_SAL_DEFINED + diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h new file mode 100644 index 00000000..45798b86 --- /dev/null +++ b/source/shared/typedefs_for_linux.h @@ -0,0 +1,103 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: typedefs_for_linux.h +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __linux_typedefs__ +#define __linux_typedefs__ + +#define MPLAT_UNIX + +#include "xplat.h" +#include "interlockedslist.h" + +#define CP_OEMCP 1 // default to OEM code page +#define CP_MACCP 2 // default to MAC code page +#define CP_THREAD_ACP 3 // current thread's ANSI code page +#define CP_SYMBOL 42 // SYMBOL translations + +#define CP_UTF7 65000 // UTF-7 translation + +#define MAKELANGID(p, s) ((((WORD )(s)) << 10) | (WORD )(p)) +#define LANG_NEUTRAL 0x00 +#define SUBLANG_DEFAULT 0x01 // user default + +DWORD FormatMessageA( + DWORD dwFlags, + LPCVOID lpSource, + DWORD dwMessageId, + DWORD dwLanguageId, + LPTSTR lpBuffer, + DWORD nSize, + va_list *Arguments + ); + +DWORD FormatMessageW( + DWORD dwFlags, + LPCVOID lpSource, + DWORD dwMessageId, + DWORD dwLanguageId, + LPWSTR lpBuffer, + DWORD nSize, + va_list *Arguments + ); + +#ifndef _WIN32 +#define FormatMessage FormatMessageA +#else +#define FormatMessage FormatMessageW +#endif // !_WIN32 + +#define FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100 +#define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 +#define FORMAT_MESSAGE_FROM_STRING 0x00000400 +#define FORMAT_MESSAGE_FROM_HMODULE 0x00000800 +#define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 +#define FORMAT_MESSAGE_ARGUMENT_ARRAY 0x00002000 +#define FORMAT_MESSAGE_MAX_WIDTH_MASK 0x000000FF + +#define ERROR_NO_UNICODE_TRANSLATION 1113L +#define ERROR_SUCCESS 0L +#define ERROR_INVALID_DATA 13L + +typedef int errno_t; +int mplat_snwprintf_s(WCHAR *str, size_t sizeOfBuffer, size_t count, const WCHAR *format, ...); +int mplat_vsnwprintf( WCHAR * buffer, size_t count, const WCHAR * format, va_list args ); +int mplat_snprintf_s(char *str, size_t sizeOfBuffer, size_t count, const char *format, ...); +int mplat_vsnprintf( char * buffer, size_t count, const char * format, va_list args ); +errno_t mplat_wctomb_s(int *pRetValue, char *mbchar, size_t sizeInBytes, WCHAR wchar); +WCHAR * mplat_wcscpy(WCHAR * _Dst, const WCHAR * _Src); +char * mplat_cscpy(char * _Dst, const char * _Src); +BOOL IsDBCSLeadByteEx(__inn UINT CodePage, __inn BYTE TestChar); + +typedef struct _SYSTEMTIME { + WORD wYear; + WORD wMonth; + WORD wDayOfWeek; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; +} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME; + +typedef HINSTANCE HMODULE; /* HMODULEs can be used in place of HINSTANCEs */ + +//From sqlext.h +#define SQL_GUID (-11) + +size_t mplat_wcslen( const WCHAR * ); + +#endif // __linux_typedefs__ diff --git a/sqlsrv/version.h b/source/shared/version.h similarity index 70% rename from sqlsrv/version.h rename to source/shared/version.h index 93bb6d47..c6c5a0c9 100644 --- a/sqlsrv/version.h +++ b/source/shared/version.h @@ -1,27 +1,37 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: version.h -// Contents: Version number constants -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#define VER_FILEVERSION_STR "4.1.0.0" -#define _FILEVERSION 4,1,0,0 -#define SQLVERSION_MAJOR 4 -#define SQLVERSION_MINOR 1 -#define SQLVERSION_MMDD 0 -#define SQLVERSION_REVISION 0 - - - +#ifndef VERSION_H +#define VERSION_H +//--------------------------------------------------------------------------------------------------------------------------------- +// File: version.h +// Contents: Version number constants +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +// helper macros to stringify the a macro value +#define STRINGIFY(a) TOSTRING(a) +#define TOSTRING(a) #a + +#define SQLVERSION_MAJOR 4 +#define SQLVERSION_MINOR 1 +#define SQLVERSION_RELEASE 6 +#define SQLVERSION_BUILD 0 + +#define VER_FILEVERSION_STR STRINGIFY( SQLVERSION_MAJOR ) "." STRINGIFY( SQLVERSION_MINOR ) "." STRINGIFY( SQLVERSION_RELEASE ) "." STRINGIFY( SQLVERSION_BUILD ) +#define _FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_RELEASE,SQLVERSION_BUILD +#define PHP_SQLSRV_VERSION STRINGIFY( SQLVERSION_MAJOR ) "." STRINGIFY( SQLVERSION_MINOR ) "." STRINGIFY( SQLVERSION_RELEASE ) +#define PHP_PDO_SQLSRV_VERSION PHP_SQLSRV_VERSION + +#endif // VERSION_H + + diff --git a/source/shared/xplat.h b/source/shared/xplat.h new file mode 100644 index 00000000..7bc12faa --- /dev/null +++ b/source/shared/xplat.h @@ -0,0 +1,490 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: xplat.h +// +// Contents: include for definition of Windows types for non-Windows platforms +// +// Microsoft Drivers 4.0 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __XPLAT_H__ +#define __XPLAT_H__ + +#ifndef _WCHART_DEFINED +#define _WCHART_DEFINED +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +// Turned on all warnings in WwoWH projects +// These warnings need to be disabled to be build warning free +// Note that some of these should be enabled and the code fixed +#pragma warning( disable : 4668 ) // preprocessor macro not defined +#pragma warning( disable : 4820 ) // padding after data member +#pragma warning( disable : 4201 ) // nonstandard: nameless union +#pragma warning( disable : 4100 ) // unreferenced formal parameter +#pragma warning( disable : 4514 ) // unreferenced inline function +#pragma warning( disable : 4505 ) // unreferenced inline function +#pragma warning( disable : 4710 ) // function not inlined +#pragma warning( disable : 4191 ) // unsafe conversion +#pragma warning( disable : 4365 ) // signed/unsigned argument conversion +#pragma warning( disable : 4245 ) // signed/unsigned assignment conversion +#pragma warning( disable : 4389 ) // signed/unsigned == +#pragma warning( disable : 4987 ) // nonstandard: throw(...) +#pragma warning( disable : 4510 ) // default ctor could not be generated +#pragma warning( disable : 4512 ) // operator= could not be generated +#pragma warning( disable : 4626 ) // operator= could not be generated +#pragma warning( disable : 4625 ) // copy ctor could not be generated or accessed +#pragma warning( disable : 4189 ) // unused initialized local variable +#pragma warning( disable : 4127 ) // constant conditional test +#pragma warning( disable : 4061 ) // Unused enum values in switch +#pragma warning( disable : 4062 ) // Unused enum values in switch +#pragma warning( disable : 4706 ) // assignment within conditional +#pragma warning( disable : 4610 ) // can never be instantiated +#pragma warning( disable : 4244 ) // possible loss of data in conversion +#pragma warning( disable : 4701 ) // possible use of uninitialized variable +#pragma warning( disable : 4918 ) // invalid pragma optimization parameter +#pragma warning( disable : 4702 ) // unreachable code +#pragma warning( disable : 4265 ) // class with virtual fxns has non-virtual dtor +#pragma warning( disable : 4238 ) // nonstandard: class rvalue used as lvalue +#pragma warning( disable : 4310 ) // cast truncates constant value +#pragma warning( disable : 4946 ) // reinterpret_cast between related classes +#pragma warning( disable : 4264 ) // no matching override, hides base fxn +#pragma warning( disable : 4242 ) // conversion: possible loss of data +#pragma warning( disable : 4820 ) // added padding bytes +#endif + +// Compiler specific items +#define _cdecl +#define __cdecl +#define __fastcall +#define _inline inline +#define __inline inline +#define __forceinline inline +#define __stdcall + +#if !defined(_MSC_VER) +#define __declspec__noinline __attribute__((noinline)) +#define __declspec__selectany +#define __declspec(a) __declspec__##a +#define __FUNCTION__ __func__ + +#define __int8 char +#define __int32 int + +// __int64 +// This type must be defined in a way that allows "unsigned __int64" as a valid type declaration. +// That precludes using the obvious "int64_t" from stdint.h, because "unsigned int64_t" is not allowed +// (one should use "uint64_t" for unsigned 64-bit integers). As a result, we must use compiler-specific +// types such as GCC's "long long" instead +#if defined(_LP64) +#define __int64 long +#elif defined(__GNUC__) +#define __int64 long long +#else +#error "Compiler-specific definition required for __int64 in 32-bit builds" +#endif +#endif + +// GCC-specific definitions +#if defined(__GNUC__) +#define MPLAT_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif // defined(__GNUC__) + +// For compilers that don't support cross-module inlining (part of whole-program/link-time +// optimization), such as the current MPLAT compilers (GCC 4.1.2 for RHEL5 and GCC 4.4 for RHEL6), +// we must force the generation of out-of-line definitions for functions that otherwise +// are only defined inline when those functions are called from other translation units. +// There are a handful of instances of these in SNI code as well as ODBC code. +// +// To force the compiler to emit an out-of-line definition for a function, just add an otherwise +// unused global (external linkage) non-const pointer pointing to the function: +// +// #if defined(MPLAT_NO_LTO) +// void (* g_pfnMyFunctionUnused)(MyFunctionArguments *) = MyFunction; +// #endif // defined(MPLAT_NO_LTO) +// +// This works because, absent whole-program optimization, the compiler cannot determine that the +// pointers are never called through, and the out-of-line definition cannot be optimized out, +// giving calling translation units something to link to. +// +// GCC adds LTO as of version 4.5 +//JL - TODO: this version check doesn't work in Ubuntu +//#if defined(__GNUC__) && MPLAT_GCC_VERSION < 40500 +#define MPLAT_NO_LTO +//#endif + +#ifdef MPLAT_UNIX + +// Needed to use the standard library min and max +#include +using std::min; +using std::max; + +#elif MPLAT_WWOWH + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#endif // MPLAT_WWOWH + +// Deal with differences between Windows and *nix interpretations of the C/C++ 'long' data type. +// +// On 64-bit Windows, 'long' is 32 bits. On 64-bit Linux, 'long' is 64 bits. Assuming the Windows code +// depends on it being 32 bits, use a definition that provides a guaranteed 32-bit type definition. +// +// Similarly, because 'long long' (and its cousin 'unsigned long long') are not portable across +// Linux/UNIX platforms and compilers, provide common definitions for 64-bit types as well. +// +// These types are used in this file primarily to define common Windows types (DWORD, LONG, etc.) +// Cross-platform code should use either the Windows types or appropriate types from . +#if defined(_MSC_VER) // WwoWH +typedef long windowsLong_t; +typedef unsigned long windowsULong_t; +typedef __int64 windowsLongLong_t; +typedef unsigned __int64 windowsULongLong_t; +#else // *nix (!WwoWH) +#include // Use standard bit-specific types (signed/unsigned integrated) +typedef int32_t windowsLong_t; +typedef uint32_t windowsULong_t; +typedef int64_t windowsLongLong_t; +typedef uint64_t windowsULongLong_t; +#endif +typedef windowsLong_t LONG, *PLONG, *LPLONG; +typedef windowsLongLong_t LONGLONG; +typedef windowsULongLong_t ULONGLONG; + +#include +#include +#include "xplat_intsafe.h" + +// Exclude these headers in Windows machines (just for building on Windows w/o Windows headers) +#define SPECSTRINGS_H // specstrings.h +#define ASOSHOST_DEFINED // asoshost.h +#define _WINDOWS_ // windows.h +#define _INC_WINDOWSX // windowsx.h +#define _WINBASE_ // winbase.h +#define _WINNLS_ // winnls.h +#define _WINERROR_ // winerror.h +#define NETCONS_INCLUDED // lmcons.h +#define __WINCRYPT_H__ // wincrypt.h +#define _INC_TCHAR // tchar.h +#define _INC_FCNTL // fcntl.h +#define _INC_SHARE // share.h +#define _INC_IO // io.h +#define _INC_TYPES // sys/types.h +#define _INC_STAT // sys/stat.h +#define _INC_TIMEB // sys/timeb.h +#define __unknwn_h__ // unknwn.h +#define __objidl_h__ // objidl.h +#define _OBJBASE_H_ // objbase.h +#define __RPC_H__ // rpc.h +#define __RPCNDR_H__ // rpcndr.h +#define _NP_HPP_ // np.hpp (no named pipes) +#define _SM_HPP_ // sm.hpp (no shared memory) +#define VIA_HEADER // via.hpp (no via) +#define _WINUSER_ // winuser.h + +#define interface struct + +// What we need from dlgattr.h +#define OPTIONON L"Yes" +#define OPTIONOFF L"No" + + +//----------------------------------------------------------------------------- +// Definitions for UnixODBC Driver Manager + +// Define this to enable driver code to conditionalize around UnixODBC Driver +// Manager "quirks"... +#ifndef MPLAT_WWOWH +#define UNIXODBC +#endif + +/* can be defined in php sources */ +#ifdef ODBCVER +#undef ODBCVER +#endif + +// Build the mplat driver as an ODBC 3.8 driver, so that all of the +// source code shared with Windows SNAC (which is ODBC 3.8) compiles. +#define ODBCVER 0x0380 + + +// Define this to indicate that we provide our own definitions for Windows types +#define ALLREADY_HAVE_WINDOWS_TYPE + +// Definitions not otherwise provided in sqltypes.h, given that we define our own Windows types +#define SQL_API +typedef signed char SCHAR; +typedef SCHAR SQLSCHAR; +typedef int SDWORD; +typedef unsigned int UDWORD; +typedef signed short int SWORD; +typedef signed short SSHORT; +typedef double SDOUBLE; +typedef double LDOUBLE; +typedef float SFLOAT; +typedef void* PTR; +typedef signed short RETCODE; +typedef void* SQLHWND; + +// Definitions missing from sql.h +#define SQL_PARAM_DATA_AVAILABLE 101 +#define SQL_APD_TYPE (-100) + +// Bid control bit, only for xplat +// It traces everything we current enabled for bid. +// The correlated tracing feature is not enabled. +#define DEFAULT_BID_CORT_BIT 0xFFFFBFFFF + +// End definitions for UnixODBC SQL headers +// ---------------------------------------------------------------------------- + +#define UNREFERENCED_PARAMETER(arg) + +// From share.h +#define _SH_DENYNO 0x40 /* deny none mode */ + + +// WinNT.h +#define CONST const +#define VOID void +#define DLL_PROCESS_ATTACH 1 +#define DLL_THREAD_ATTACH 2 +#define DLL_THREAD_DETACH 3 +#define DLL_PROCESS_DETACH 0 +#define VER_GREATER_EQUAL 3 +#define VER_MINORVERSION 0x0000001 +#define VER_MAJORVERSION 0x0000002 +#define VER_SERVICEPACKMINOR 0x0000010 +#define VER_SERVICEPACKMAJOR 0x0000020 +#define VER_SET_CONDITION(_m_,_t_,_c_) \ + ((_m_)=VerSetConditionMask((_m_),(_t_),(_c_))) + + +// Predeclared types from windef needed for remaining WinNT types +// to break circular dependency between WinNT.h and windef.h types. +//typedef ULONG DWORD; +typedef unsigned char BYTE; +typedef unsigned char UCHAR; +typedef UCHAR *PUCHAR; + +typedef DWORD LCID; +typedef LONG HRESULT; +typedef char CHAR; +typedef CHAR *LPSTR, *PSTR; +typedef CHAR *PCHAR, *LPCH, *PCH; +typedef CONST CHAR *LPCCH, *PCCH; +#ifdef SQL_WCHART_CONVERT +typedef wchar_t WCHAR; +#else +typedef unsigned short WCHAR; +#endif +typedef WCHAR *LPWSTR; +typedef WCHAR *PWSTR; +typedef CONST WCHAR *LPCWSTR; +typedef CONST WCHAR *PCWSTR; +typedef CONST CHAR *LPCSTR, *PCSTR; +typedef void *PVOID; +typedef PVOID HANDLE; +typedef BYTE BOOLEAN; +typedef BOOLEAN *PBOOLEAN; +typedef HANDLE *PHANDLE; +typedef WCHAR *PWCHAR, *LPWCH, *PWCH; +typedef CONST WCHAR *LPCWCH, *PCWCH; +typedef int HFILE; + +typedef short SHORT; +typedef CONST CHAR *LPCCH, *PCCH; + +typedef unsigned short WORD; + +#define RTL_NUMBER_OF_V1(A) (sizeof(A)/sizeof((A)[0])) +#define ARRAYSIZE(A) RTL_NUMBER_OF_V1(A) +#define STATUS_STACK_OVERFLOW ((DWORD )0xC00000FDL) +typedef union _LARGE_INTEGER { + struct { + DWORD LowPart; + LONG HighPart; + }; + struct { + DWORD LowPart; + LONG HighPart; + } u; + LONGLONG QuadPart; +} LARGE_INTEGER; +typedef LARGE_INTEGER *PLARGE_INTEGER; +typedef void * RPC_IF_HANDLE; + +typedef WORD LANGID; + +typedef enum _HEAP_INFORMATION_CLASS { + + HeapCompatibilityInformation, + HeapEnableTerminationOnCorruption + + +} HEAP_INFORMATION_CLASS; + + +#define REG_SZ ( 1 ) // Unicode nul terminated string +#define REG_DWORD ( 4 ) // 32-bit number + +#define RTL_NUMBER_OF_V1(A) (sizeof(A)/sizeof((A)[0])) +#define RTL_NUMBER_OF(A) RTL_NUMBER_OF_V1(A) + + +// windef.h +typedef VOID *LPVOID; +typedef CONST void *LPCVOID; +typedef int INT; +typedef int *LPINT; +typedef unsigned int UINT; +typedef ULONGLONG UINT64; +typedef unsigned int *PUINT; +typedef unsigned char BYTE; +typedef BYTE *PBYTE; +typedef BYTE *LPBYTE; +typedef const BYTE *LPCBYTE; +#define _LPCBYTE_DEFINED +//typedef int BOOL; +typedef BOOL * LPBOOL; +typedef unsigned short WORD; +typedef WORD * LPWORD; +typedef WORD UWORD; +typedef DWORD * LPDWORD; +typedef DWORD * PDWORD; +typedef unsigned short USHORT; +#define CDECL // TODO _cdecl and cdecl not portable? +#define WINAPI // TODO __stdcall not portable? +#define MAX_PATH 260 +typedef HANDLE HINSTANCE; +typedef HANDLE HGLOBAL; +typedef ULONGLONG DWORDLONG; +typedef DWORDLONG *PDWORDLONG; +typedef float FLOAT; + +typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME, *PFILETIME, *LPFILETIME; +typedef double DOUBLE; +#define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16)) + +// INT_PTR - http://msdn.microsoft.com/en-us/library/aa384154(VS.85).aspx +#ifdef _WIN64 +typedef __int64 INT_PTR; +#else +typedef int INT_PTR; +#endif + +typedef INT_PTR (*FARPROC)(); +typedef INT_PTR (*NEARPROC)(); +typedef INT_PTR (*PROC)(); + + +DWORD GetFileSize( + __inn HANDLE hFile, + __out_opt LPDWORD lpFileSizeHigh +); + +typedef union _ULARGE_INTEGER { + struct { + DWORD LowPart; + DWORD HighPart; + }; + struct { + DWORD LowPart; + DWORD HighPart; + } u; + ULONGLONG QuadPart; +} ULARGE_INTEGER; + +typedef ULARGE_INTEGER *PULARGE_INTEGER; + +#ifndef IN +#define IN +#endif + +#ifndef OUT +#define OUT +#endif + +#ifndef OPTIONAL +#define OPTIONAL +#endif + + +ULONGLONG +VerSetConditionMask( + IN ULONGLONG ConditionMask, + IN DWORD TypeMask, + IN BYTE Condition + ); + + +//// ntdef.h +#define __unaligned +#ifndef UNALIGNED +#define UNALIGNED +#endif +//typedef __nullterminated WCHAR UNALIGNED *LPUWSTR; + +//// crtdefs.h +//#if !defined(_TRUNCATE) +//#define _TRUNCATE ((size_t)-1) +//#endif + +//// ?? +//typedef ULONG_PTR DWORD_PTR; +#define FALSE ((BOOL)0) +#define TRUE ((BOOL)1) + + +//// asoshost.h (excluded above) +//struct ISOSHost_MemObj; +//struct ISOSHost; +//extern ISOSHost_MemObj *g_pMO; +//extern ISOSHost *g_pISOSHost; +//inline HRESULT CreateSQLSOSHostInterface() { return 0; } +//inline HRESULT CreateGlobalSOSHostInterface() { return 0; } + +//// These are temporary solution versions of the real files that contain the minimal declarations +//// needed to compile for non-Windows platforms. See the special include path for the +//// location of these files. + +#include "xplat_winerror.h" + +//#define LMEM_FIXED 0 +typedef void * HLOCAL; +HLOCAL LocalAlloc(UINT uFlags, SIZE_T uBytes); +//HLOCAL LocalReAlloc(HLOCAL hMem, SIZE_T uBytes, UINT uFlags); +HLOCAL LocalFree(HLOCAL hMem); + +// End of xplat.h +#endif //__XPLAT_H__ diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h new file mode 100644 index 00000000..e0cdd76b --- /dev/null +++ b/source/shared/xplat_intsafe.h @@ -0,0 +1,7635 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: xplat_intsafe.h +// +// Contents: This module defines helper functions to prevent +// integer overflow bugs. +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + + +#ifndef XPLAT_INTSAFE_H +#define XPLAT_INTSAFE_H + +#if (_MSC_VER > 1000) +#pragma once +#endif + +#if !defined(_W64) +#if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && (_MSC_VER >= 1300) +#define _W64 __w64 +#else +#define _W64 +#endif +#endif + +#include "sal_def.h" +#include + +// +// typedefs +// +typedef char CHAR; +typedef signed char INT8; +typedef unsigned char UCHAR; +typedef unsigned char UINT8; +typedef unsigned char BYTE; +typedef short SHORT; +typedef signed short INT16; +typedef unsigned short USHORT; +typedef unsigned short UINT16; +typedef unsigned short WORD; +typedef int INT; +typedef signed int INT32; +typedef unsigned int UINT; +typedef unsigned int UINT32; +typedef windowsLong_t LONG; +typedef windowsULong_t DWORD; +typedef windowsLongLong_t LONGLONG; +typedef windowsLongLong_t LONG64; +typedef windowsLongLong_t INT64; +typedef windowsULongLong_t ULONGLONG; +typedef windowsULongLong_t DWORDLONG; +typedef windowsULongLong_t ULONG64; +typedef windowsULongLong_t DWORD64; +typedef windowsULongLong_t UINT64; + +#if (__midl > 501) +typedef [public] __int3264 INT_PTR; +typedef [public] unsigned __int3264 UINT_PTR; +typedef [public] __int3264 LONG_PTR; +typedef [public] unsigned __int3264 ULONG_PTR; +#else +#ifdef _WIN64 +typedef __int64 INT_PTR, *PINT_PTR; +typedef unsigned __int64 UINT_PTR, *PUINT_PTR; +typedef __int64 LONG_PTR, *PLONG_PTR; +typedef unsigned __int64 ULONG_PTR, *PULONG_PTR; +#else +typedef _W64 int INT_PTR, *PINT_PTR; +typedef _W64 unsigned int UINT_PTR, *PUINT_PTR; +typedef _W64 windowsLong_t LONG_PTR, *PLONG_PTR; +typedef _W64 windowsULong_t ULONG_PTR, *PULONG_PTR; +#endif // WIN64 +#endif // (__midl > 501) + +typedef ULONG_PTR DWORD_PTR; +typedef LONG_PTR SSIZE_T; +typedef ULONG_PTR SIZE_T; + +#if defined(_AMD64_) +#ifdef __cplusplus +extern "C" { +#endif + +#define UnsignedMultiply128 _umul128 + +ULONG64 +UnsignedMultiply128 ( + __inn ULONG64 Multiplier, + __inn ULONG64 Multiplicand, + __outt __deref_out_range(==,Multiplier * Multiplicand) ULONG64 *HighProduct + ); +#pragma intrinsic(_umul128) + +#ifdef __cplusplus +} +#endif +#endif // _AMD64_ + + + +typedef __success(return >= 0) windowsLong_t HRESULT; + +#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0) +#define FAILED(hr) (((HRESULT)(hr)) < 0) + +#define S_OK ((HRESULT)0L) + +#define INTSAFE_E_ARITHMETIC_OVERFLOW ((HRESULT)0x80070216L) // 0x216 = 534 = ERROR_ARITHMETIC_OVERFLOW +#ifndef SORTPP_PASS +// compiletime asserts (failure results in error C2118: negative subscript) +#define C_ASSERT(e) typedef char __C_ASSERT__[(e)?1:-1] +#else +#define C_ASSERT(e) +#endif + +// +// UInt32x32To64 macro +// +#define UInt32x32To64(a, b) ((windowsULongLong_t)(((windowsULongLong_t)(a)) * ((windowsULong_t)(b)))) + +// +// Min/Max type values +// +//#define INT8_MIN (-127 - 1) +#define SHORT_MIN (-32768) +//#define INT16_MIN (-32767 - 1) +//#define INT_MIN (-2147483647 - 1) +//#define INT32_MIN (-2147483647l - 1) +//#define LONG_MIN (-2147483647L - 1) +#define LONGLONG_MIN (-9223372036854775807ll - 1) +#define LONG64_MIN (-9223372036854775807ll - 1) +//#define INT64_MIN (-9223372036854775807ll - 1) +#define INT128_MIN (-170141183460469231731687303715884105727i128 - 1) + +#ifdef _WIN64 +#define INT_PTR_MIN (-9223372036854775807ll - 1) +#define LONG_PTR_MIN (-9223372036854775807ll - 1) +#define PTRDIFF_T_MIN (-9223372036854775807ll - 1) +#define SSIZE_T_MIN (-9223372036854775807ll - 1) +#else +#define INT_PTR_MIN (-2147483647 - 1) +#define LONG_PTR_MIN (-2147483647L - 1) +#define PTRDIFF_T_MIN (-2147483647 - 1) +#define SSIZE_T_MIN (-2147483647L - 1) +#endif + +//#define INT8_MAX 127 +//#define UINT8_MAX 0xff +#define BYTE_MAX 0xff +#define SHORT_MAX 32767 +//#define INT16_MAX 32767 +#define USHORT_MAX 0xffff +//#define UINT16_MAX 0xffff +#define WORD_MAX 0xffff +//#define INT_MAX 2147483647 +//#define INT32_MAX 2147483647l +//#define UINT_MAX 0xffffffff +//#define UINT32_MAX 0xfffffffful +//#define LONG_MAX 2147483647L +//#define ULONG_MAX 0xffffffffUL +#define DWORD_MAX 0xffffffffUL +#define LONGLONG_MAX 9223372036854775807ll +#define LONG64_MAX 9223372036854775807ll +//#define INT64_MAX 9223372036854775807ll +#define ULONGLONG_MAX 0xffffffffffffffffull +#define DWORDLONG_MAX 0xffffffffffffffffull +#define ULONG64_MAX 0xffffffffffffffffull +#define DWORD64_MAX 0xffffffffffffffffull +//#define UINT64_MAX 0xffffffffffffffffull +#define INT128_MAX 170141183460469231731687303715884105727i128 +#define UINT128_MAX 0xffffffffffffffffffffffffffffffffui128 + +#ifndef _I64_MIN +#define _I64_MIN INT64_MIN +#define _I64_MAX INT64_MAX +#define _UI64_MIN UINT64_MIN +#define _UI64_MAX UINT64_MAX +#endif + +#undef SIZE_T_MAX + +#ifdef _WIN64 +#define INT_PTR_MAX 9223372036854775807ll +#define UINT_PTR_MAX 0xffffffffffffffffull +#define LONG_PTR_MAX 9223372036854775807ll +#define ULONG_PTR_MAX 0xffffffffffffffffull +#define DWORD_PTR_MAX 0xffffffffffffffffull +#define PTRDIFF_T_MAX 9223372036854775807ll +#define SIZE_T_MAX 0xffffffffffffffffull +#define SSIZE_T_MAX 9223372036854775807ll +#define _SIZE_T_MAX 0xffffffffffffffffull +#else +#define INT_PTR_MAX 2147483647 +#define UINT_PTR_MAX 0xffffffff +#define LONG_PTR_MAX 2147483647L +#define ULONG_PTR_MAX 0xffffffffUL +#define DWORD_PTR_MAX 0xffffffffUL +#define PTRDIFF_T_MAX 2147483647 +#define SIZE_T_MAX 0xffffffff +#define SSIZE_T_MAX 2147483647L +#define _SIZE_T_MAX 0xffffffffUL +#endif + + +// +// It is common for -1 to be used as an error value +// +#define INT8_ERROR (-1) +#define UINT8_ERROR 0xff +#define BYTE_ERROR 0xff +#define SHORT_ERROR (-1) +#define INT16_ERROR (-1) +#define USHORT_ERROR 0xffff +#define UINT16_ERROR 0xffff +#define WORD_ERROR 0xffff +#define INT_ERROR (-1) +#define INT32_ERROR (-1l) +#define UINT_ERROR 0xffffffff +#define UINT32_ERROR 0xfffffffful +#define LONG_ERROR (-1L) +#define ULONG_ERROR 0xffffffffUL +#define DWORD_ERROR 0xffffffffUL +#define LONGLONG_ERROR (-1ll) +#define LONG64_ERROR (-1ll) +#define INT64_ERROR (-1ll) +#define ULONGLONG_ERROR 0xffffffffffffffffull +#define DWORDLONG_ERROR 0xffffffffffffffffull +#define ULONG64_ERROR 0xffffffffffffffffull +#define UINT64_ERROR 0xffffffffffffffffull + +#ifdef _WIN64 +#define INT_PTR_ERROR (-1ll) +#define UINT_PTR_ERROR 0xffffffffffffffffull +#define LONG_PTR_ERROR (-1ll) +#define ULONG_PTR_ERROR 0xffffffffffffffffull +#define DWORD_PTR_ERROR 0xffffffffffffffffull +#define PTRDIFF_T_ERROR (-1ll) +#define SIZE_T_ERROR 0xffffffffffffffffull +#define SSIZE_T_ERROR (-1ll) +#define _SIZE_T_ERROR 0xffffffffffffffffull +#else +#define INT_PTR_ERROR (-1) +#define UINT_PTR_ERROR 0xffffffff +#define LONG_PTR_ERROR (-1L) +#define ULONG_PTR_ERROR 0xffffffffUL +#define DWORD_PTR_ERROR 0xffffffffUL +#define PTRDIFF_T_ERROR (-1) +#define SIZE_T_ERROR 0xffffffff +#define SSIZE_T_ERROR (-1L) +#define _SIZE_T_ERROR 0xffffffffUL +#endif + + +// +// We make some assumptions about the sizes of various types. Let's be +// explicit about those assumptions and check them. +// +C_ASSERT(sizeof(USHORT) == 2); +C_ASSERT(sizeof(INT) == 4); +C_ASSERT(sizeof(UINT) == 4); +C_ASSERT(sizeof(LONG) == 4); +C_ASSERT(sizeof(ULONG) == 8); +C_ASSERT(sizeof(UINT_PTR) == sizeof(ULONG_PTR)); + + +//============================================================================= +// Conversion functions +// +// There are three reasons for having conversion functions: +// +// 1. We are converting from a signed type to an unsigned type of the same +// size, or vice-versa. +// +// Since we only have unsigned math functions, this allows people to convert +// to unsigned, do the math, and then convert back to signed. +// +// 2. We are converting to a smaller type, and we could therefore possibly +// overflow. +// +// 3. We are converting to a bigger type, and we are signed and the type we are +// converting to is unsigned. +// +//============================================================================= + + +// +// INT8 -> UCHAR conversion +// +__inline +HRESULT +Int8ToUChar( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) UCHAR* pch) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pch = (UCHAR)i8Operand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> UINT8 conversion +// +__inline +HRESULT +Int8ToUInt8( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) UINT8* pu8Result) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pu8Result = (UINT8)i8Operand; + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> BYTE conversion +// +#define Int8ToByte Int8ToUInt8 + +// +// INT8 -> USHORT conversion +// +__inline +HRESULT +Int8ToUShort( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) USHORT* pusResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pusResult = (USHORT)i8Operand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> UINT16 conversion +// +#define Int8ToUInt16 Int8ToUShort + +// +// INT8 -> WORD conversion +// +#define Int8ToWord Int8ToUShort + +// +// INT8 -> UINT conversion +// +__inline +HRESULT +Int8ToUInt( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) UINT* puResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *puResult = (UINT)i8Operand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> UINT32 conversion +// +#define Int8ToUInt32 Int8ToUInt + +// +// INT8 -> UINT_PTR conversion +// +__inline +HRESULT +Int8ToUIntPtr( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) UINT_PTR* puResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *puResult = (UINT_PTR)i8Operand; + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> ULONG conversion +// +__inline +HRESULT +Int8ToULong( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) ULONG* pulResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pulResult = (ULONG)i8Operand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> ULONG_PTR conversion +// +__inline +HRESULT +Int8ToULongPtr( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pulResult = (ULONG_PTR)i8Operand; + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> DWORD conversion +// +#define Int8ToDWord Int8ToULong + +// +// INT8 -> DWORD_PTR conversion +// +#define Int8ToDWordPtr Int8ToULongPtr + +// +// INT8 -> ULONGLONG conversion +// +__inline +HRESULT +Int8ToULongLong( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pullResult = (ULONGLONG)i8Operand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> DWORDLONG conversion +// +#define Int8ToDWordLong Int8ToULongLong + +// +// INT8 -> ULONG64 conversion +// +#define Int8ToULong64 Int8ToULongLong + +// +// INT8 -> DWORD64 conversion +// +#define Int8ToDWord64 Int8ToULongLong + +// +// INT8 -> UINT64 conversion +// +#define Int8ToUInt64 Int8ToULongLong + +// +// INT8 -> size_t conversion +// +#define Int8ToSizeT Int8ToUIntPtr + +// +// INT8 -> SIZE_T conversion +// +#define Int8ToSIZET Int8ToULongPtr + +// +// UINT8 -> INT8 conversion +// +__inline +HRESULT +UInt8ToInt8( + __inn UINT8 u8Operand, + __outt __deref_out_range(==,u8Operand) INT8* pi8Result) +{ + HRESULT hr; + + if (u8Operand <= INT8_MAX) + { + *pi8Result = (INT8)u8Operand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT8 -> CHAR conversion +// +__forceinline +HRESULT +UInt8ToChar( + __inn UINT8 u8Operand, + __outt __deref_out_range(==,u8Operand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + *pch = (CHAR)u8Operand; + return S_OK; +#else + return UInt8ToInt8(u8Operand, (INT8*)pch); +#endif +} + +// +// BYTE -> INT8 conversion +// +__inline +HRESULT +ByteToInt8( + __inn BYTE bOperand, + __outt __deref_out_range(==,bOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (bOperand <= INT8_MAX) + { + *pi8Result = (INT8)bOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// BYTE -> CHAR conversion +// +__forceinline +HRESULT +ByteToChar( + __inn BYTE bOperand, + __outt __deref_out_range(==,bOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + *pch = (CHAR)bOperand; + return S_OK; +#else + return ByteToInt8(bOperand, (INT8*)pch); +#endif +} + +// +// SHORT -> INT8 conversion +// +__inline +HRESULT +ShortToInt8( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((sOperand >= INT8_MIN) && (sOperand <= INT8_MAX)) + { + *pi8Result = (INT8)sOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> UCHAR conversion +// +__inline +HRESULT +ShortToUChar( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((sOperand >= 0) && (sOperand <= 255)) + { + *pch = (UCHAR)sOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> CHAR conversion +// +__forceinline +HRESULT +ShortToChar( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return ShortToUChar(sOperand, (UCHAR*)pch); +#else + return ShortToInt8(sOperand, (INT8*)pch); +#endif // _CHAR_UNSIGNED +} + +// +// SHORT -> UINT8 conversion +// +__inline +HRESULT +ShortToUInt8( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if ((sOperand >= 0) && (sOperand <= UINT8_MAX)) + { + *pui8Result = (UINT8)sOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> BYTE conversion +// +#define ShortToByte ShortToUInt8 + +// +// SHORT -> USHORT conversion +// +__inline +HRESULT +ShortToUShort( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *pusResult = (USHORT)sOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> UINT16 conversion +// +#define ShortToUInt16 ShortToUShort + +// +// SHORT -> WORD conversion +// +#define ShortToWord ShortToUShort + +// +// SHORT -> UINT conversion +// +__inline +HRESULT +ShortToUInt( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) UINT* puResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *puResult = (UINT)sOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> UINT32 conversion +// +#define ShortToUInt32 ShortToUInt + +// +// SHORT -> UINT_PTR conversion +// +__inline +HRESULT +ShortToUIntPtr( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) UINT_PTR* puResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *puResult = (UINT_PTR)sOperand; + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> ULONG conversion +// +__inline +HRESULT +ShortToULong( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *pulResult = (ULONG)sOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> ULONG_PTR conversion +// +__inline +HRESULT +ShortToULongPtr( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *pulResult = (ULONG_PTR)sOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> DWORD conversion +// +#define ShortToDWord ShortToULong + +// +// SHORT -> DWORD_PTR conversion +// +__inline +HRESULT +ShortToDWordPtr( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) DWORD_PTR* pdwResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *pdwResult = (DWORD_PTR)sOperand; + hr = S_OK; + } + else + { + *pdwResult = DWORD_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> ULONGLONG conversion +// +__inline +HRESULT +ShortToULongLong( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *pullResult = (ULONGLONG)sOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> DWORDLONG conversion +// +#define ShortToDWordLong ShortToULongLong + +// +// SHORT -> ULONG64 conversion +// +#define ShortToULong64 ShortToULongLong + +// +// SHORT -> DWORD64 conversion +// +#define ShortToDWord64 ShortToULongLong + +// +// SHORT -> UINT64 conversion +// +#define ShortToUInt64 ShortToULongLong + +// +// SHORT -> size_t conversion +// +#define ShortToSizeT ShortToUIntPtr + +// +// SHORT -> SIZE_T conversion +// +#define ShortToSIZET ShortToULongPtr + +// +// INT16 -> CHAR conversion +// +#define Int16ToChar ShortToChar + +// +// INT16 -> INT8 conversion +// +#define Int16ToInt8 ShortToInt8 + +// +// INT16 -> UCHAR conversion +// +#define Int16ToUChar ShortToUChar + +// +// INT16 -> UINT8 conversion +// +#define Int16ToUInt8 ShortToUInt8 + +// +// INT16 -> BYTE conversion +// +#define Int16ToByte ShortToUInt8 + +// +// INT16 -> USHORT conversion +// +#define Int16ToUShort ShortToUShort + +// +// INT16 -> UINT16 conversion +// +#define Int16ToUInt16 ShortToUShort + +// +// INT16 -> WORD conversion +// +#define Int16ToWord ShortToUShort + +// +// INT16 -> UINT conversion +// +#define Int16ToUInt ShortToUInt + +// +// INT16 -> UINT32 conversion +// +#define Int16ToUInt32 ShortToUInt + +// +// INT16 -> UINT_PTR conversion +// +#define Int16ToUIntPtr ShortToUIntPtr + +// +// INT16 -> ULONG conversion +// +#define Int16ToULong ShortToULong + +// +// INT16 -> ULONG_PTR conversion +// +#define Int16ToULongPtr ShortToULongPtr + +// +// INT16 -> DWORD conversion +// +#define Int16ToDWord ShortToULong + +// +// INT16 -> DWORD_PTR conversion +// +#define Int16ToDWordPtr ShortToULongPtr + +// +// INT16 -> ULONGLONG conversion +// +#define Int16ToULongLong ShortToULongLong + +// +// INT16 -> DWORDLONG conversion +// +#define Int16ToDWordLong ShortToULongLong + +// +// INT16 -> ULONG64 conversion +// +#define Int16ToULong64 ShortToULongLong + +// +// INT16 -> DWORD64 conversion +// +#define Int16ToDWord64 ShortToULongLong + +// +// INT16 -> UINT64 conversion +// +#define Int16ToUInt64 ShortToULongLong + +// +// INT16 -> size_t conversion +// +#define Int16ToSizeT ShortToUIntPtr + +// +// INT16 -> SIZE_T conversion +// +#define Int16ToSIZET ShortToULongPtr + +// +// USHORT -> INT8 conversion +// +__inline +HRESULT +UShortToInt8( + __inn USHORT usOperand, + __outt __deref_out_range(==,usOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (usOperand <= INT8_MAX) + { + *pi8Result = (INT8)usOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT -> UCHAR conversion +// +__inline +HRESULT +UShortToUChar( + __inn USHORT usOperand, + __outt __deref_out_range(==,usOperand) UCHAR* pch) +{ + HRESULT hr; + + if (usOperand <= 255) + { + *pch = (UCHAR)usOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT -> CHAR conversion +// +__forceinline +HRESULT +UShortToChar( + __inn USHORT usOperand, + __outt __deref_out_range(==,usOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return UShortToUChar(usOperand, (UCHAR*)pch); +#else + return UShortToInt8(usOperand, (INT8*)pch); +#endif // _CHAR_UNSIGNED +} + +// +// USHORT -> UINT8 conversion +// +__inline +HRESULT +UShortToUInt8( + __inn USHORT usOperand, + __outt __deref_out_range(==,usOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if (usOperand <= UINT8_MAX) + { + *pui8Result = (UINT8)usOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT -> BYTE conversion +// +#define UShortToByte UShortToUInt8 + +// +// USHORT -> SHORT conversion +// +__inline +HRESULT +UShortToShort( + __inn USHORT usOperand, + __outt __deref_out_range(==,usOperand) SHORT* psResult) +{ + HRESULT hr; + + if (usOperand <= SHORT_MAX) + { + *psResult = (SHORT)usOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT -> INT16 conversion +// +#define UShortToInt16 UShortToShort + +// +// UINT16 -> CHAR conversion +// +#define UInt16ToChar UShortToChar + +// +// UINT16 -> INT8 conversion +// +#define UInt16ToInt8 UShortToInt8 + +// +// UINT16 -> UCHAR conversion +// +#define UInt16ToUChar UShortToUChar + +// +// UINT16 -> UINT8 conversion +// +#define UInt16ToUInt8 UShortToUInt8 + +// +// UINT16 -> BYTE conversion +// +#define UInt16ToByte UShortToUInt8 + +// +// UINT16 -> SHORT conversion +// +#define UInt16ToShort UShortToShort + +// +// UINT16 -> INT16 conversion +// +#define UInt16ToInt16 UShortToShort + +// +// WORD -> INT8 conversion +// +#define WordToInt8 UShortToInt8 + +// +// WORD -> CHAR conversion +// +#define WordToChar UShortToChar + +// +// WORD -> UCHAR conversion +// +#define WordToUChar UShortToUChar + +// +// WORD -> UINT8 conversion +// +#define WordToUInt8 UShortToUInt8 + +// +// WORD -> BYTE conversion +// +#define WordToByte UShortToUInt8 + +// +// WORD -> SHORT conversion +// +#define WordToShort UShortToShort + +// +// WORD -> INT16 conversion +// +#define WordToInt16 UShortToShort + +// +// INT -> INT8 conversion +// +__inline +HRESULT +IntToInt8( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((iOperand >= INT8_MIN) && (iOperand <= INT8_MAX)) + { + *pi8Result = (INT8)iOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> UCHAR conversion +// +__inline +HRESULT +IntToUChar( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= 255)) + { + *pch = (UCHAR)iOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> CHAR conversion +// +__forceinline +HRESULT +IntToChar( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return IntToUChar(iOperand, (UCHAR*)pch); +#else + return IntToInt8(iOperand, (INT8*)pch); +#endif // _CHAR_UNSIGNED +} + +// +// INT -> BYTE conversion +// +#define IntToByte IntToUInt8 + +// +// INT -> UINT8 conversion +// +__inline +HRESULT +IntToUInt8( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= UINT8_MAX)) + { + *pui8Result = (UINT8)iOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> SHORT conversion +// +__inline +HRESULT +IntToShort( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) SHORT* psResult) +{ + HRESULT hr; + + if ((iOperand >= SHORT_MIN) && (iOperand <= SHORT_MAX)) + { + *psResult = (SHORT)iOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> INT16 conversion +// +#define IntToInt16 IntToShort + +// +// INT -> USHORT conversion +// +__inline +HRESULT +IntToUShort( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) USHORT* pusResult) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= USHORT_MAX)) + { + *pusResult = (USHORT)iOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> UINT16 conversion +// +#define IntToUInt16 IntToUShort + +// +// INT -> WORD conversion +// +#define IntToWord IntToUShort + +// +// INT -> UINT conversion +// +__inline +HRESULT +IntToUInt( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) UINT* puResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *puResult = (UINT)iOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> UINT_PTR conversion +// +#ifdef _WIN64 +#define IntToUIntPtr IntToULongLong +#else +#define IntToUIntPtr IntToUInt +#endif + +// +// INT -> ULONG conversion +// +__inline +HRESULT +IntToULong( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *pulResult = (ULONG)iOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> ULONG_PTR conversion +// +#ifdef _WIN64 +#define IntToULongPtr IntToULongLong +#else +#define IntToULongPtr IntToULong +#endif + +// +// INT -> DWORD conversion +// +#define IntToDWord IntToULong + +// +// INT -> DWORD_PTR conversion +// +#define IntToDWordPtr IntToULongPtr + +// +// INT -> ULONGLONG conversion +// +__inline +HRESULT +IntToULongLong( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *pullResult = (ULONGLONG)iOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> DWORDLONG conversion +// +#define IntToDWordLong IntToULongLong + +// +// INT -> ULONG64 conversion +// +#define IntToULong64 IntToULongLong + +// +// INT -> DWORD64 conversion +// +#define IntToDWord64 IntToULongLong + +// +// INT -> UINT64 conversion +// +#define IntToUInt64 IntToULongLong + +// +// INT -> size_t conversion +// +#define IntToSizeT IntToUIntPtr + +// +// INT -> SIZE_T conversion +// +#define IntToSIZET IntToULongPtr + +// +// INT32 -> CHAR conversion +// +#define Int32ToChar IntToChar + +// +// INT32 -> INT328 conversion +// +#define Int32ToInt8 IntToInt8 + +// +// INT32 -> UCHAR conversion +// +#define Int32ToUChar IntToUChar + +// +// INT32 -> BYTE conversion +// +#define Int32ToByte IntToUInt8 + +// +// INT32 -> UINT8 conversion +// +#define Int32ToUInt8 IntToUInt8 + +// +// INT32 -> SHORT conversion +// +#define Int32ToShort IntToShort + +// +// INT32 -> INT16 conversion +// +#define Int32ToInt16 IntToShort + +// +// INT32 -> USHORT conversion +// +#define Int32ToUShort IntToUShort + +// +// INT32 -> UINT16 conversion +// +#define Int32ToUInt16 IntToUShort + +// +// INT32 -> WORD conversion +// +#define Int32ToWord IntToUShort + +// +// INT32 -> UINT conversion +// +#define Int32ToUInt IntToUInt + +// +// INT32 -> UINT32 conversion +// +#define Int32ToUInt32 IntToUInt + +// +// INT32 -> UINT_PTR conversion +// +#define Int32ToUIntPtr IntToUIntPtr + +// +// INT32 -> ULONG conversion +// +#define Int32ToULong IntToULong + +// +// INT32 -> ULONG_PTR conversion +// +#define Int32ToULongPtr IntToULongPtr + +// +// INT32 -> DWORD conversion +// +#define Int32ToDWord IntToULong + +// +// INT32 -> DWORD_PTR conversion +// +#define Int32ToDWordPtr IntToULongPtr + +// +// INT32 -> ULONGLONG conversion +// +#define Int32ToULongLong IntToULongLong + +// +// INT32 -> DWORDLONG conversion +// +#define Int32ToDWordLong IntToULongLong + +// +// INT32 -> ULONG64 conversion +// +#define Int32ToULong64 IntToULongLong + +// +// INT32 -> DWORD64 conversion +// +#define Int32ToDWord64 IntToULongLong + +// +// INT32 -> UINT64 conversion +// +#define Int32ToUInt64 IntToULongLong + +// +// INT32 -> size_t conversion +// +#define Int32ToSizeT IntToUIntPtr + +// +// INT32 -> SIZE_T conversion +// +#define Int32ToSIZET IntToULongPtr + +// +// INT_PTR -> INT8 conversion +// +__inline +HRESULT +IntPtrToInt8( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((iOperand >= INT8_MIN) && (iOperand <= INT8_MAX)) + { + *pi8Result = (INT8)iOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT_PTR -> UCHAR conversion +// +__inline +HRESULT +IntPtrToUChar( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= 255)) + { + *pch = (UCHAR)iOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT_PTR -> CHAR conversion +// +__forceinline +HRESULT +IntPtrToChar( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return IntPtrToUChar(iOperand, (UCHAR*)pch); +#else + return IntPtrToInt8(iOperand, (INT8*)pch); +#endif // _CHAR_UNSIGNED +} + +// +// INT_PTR -> UINT8 conversion +// +__inline +HRESULT +IntPtrToUInt8( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= UINT8_MAX)) + { + *pui8Result = (UINT8)iOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT_PTR -> BYTE conversion +// +#define IntPtrToByte IntPtrToUInt8 + +// +// INT_PTR -> SHORT conversion +// +__inline +HRESULT +IntPtrToShort( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) SHORT* psResult) +{ + HRESULT hr; + + if ((iOperand >= SHORT_MIN) && (iOperand <= SHORT_MAX)) + { + *psResult = (SHORT)iOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT_PTR -> INT16 conversion +// +#define IntPtrToInt16 IntPtrToShort + +// +// INT_PTR -> USHORT conversion +// +__inline +HRESULT +IntPtrToUShort( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) USHORT* pusResult) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= USHORT_MAX)) + { + *pusResult = (USHORT)iOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT_PTR -> UINT16 conversion +// +#define IntPtrToUInt16 IntPtrToUShort + +// +// INT_PTR -> WORD conversion +// +#define IntPtrToWord IntPtrToUShort + +// +// INT_PTR -> INT conversion +// +#ifdef _WIN64 +#define IntPtrToInt LongLongToInt +#else +__inline +HRESULT +IntPtrToInt( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) INT* piResult) +{ + *piResult = (INT)iOperand; + return S_OK; +} +#endif + +// +// INT_PTR -> INT32 conversion +// +#define IntPtrToInt32 IntPtrToInt + +// +// INT_PTR -> UINT conversion +// +#ifdef _WIN64 +#define IntPtrToUInt LongLongToUInt +#else +__inline +HRESULT +IntPtrToUInt( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) UINT* puResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *puResult = (UINT)iOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// INT_PTR -> UINT32 conversion +// +#define IntPtrToUInt32 IntPtrToUInt + +// +// INT_PTR -> UINT_PTR conversion +// +#ifdef _WIN64 +#define IntPtrToUIntPtr LongLongToULongLong +#else +__inline +HRESULT +IntPtrToUIntPtr( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) UINT_PTR* puResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *puResult = (UINT_PTR)iOperand; + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// INT_PTR -> LONG conversion +// +#ifdef _WIN64 +#define IntPtrToLong LongLongToLong +#else +__inline +HRESULT +IntPtrToLong( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) LONG* plResult) +{ + *plResult = (LONG)iOperand; + return S_OK; +} +#endif + +// +// INT_PTR -> LONG_PTR conversion +// +__inline +HRESULT +IntPtrToLongPtr( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) LONG_PTR* plResult) +{ + *plResult = (LONG_PTR)iOperand; + return S_OK; +} + +// +// INT_PTR -> ULONG conversion +// +#ifdef _WIN64 +#define IntPtrToULong LongLongToULong +#else +__inline +HRESULT +IntPtrToULong( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *pulResult = (ULONG)iOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// INT_PTR -> ULONG_PTR conversion +// +#ifdef _WIN64 +#define IntPtrToULongPtr LongLongToULongLong +#else +__inline +HRESULT +IntPtrToULongPtr( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *pulResult = (ULONG_PTR)iOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// INT_PTR -> DWORD conversion +// +#define IntPtrToDWord IntPtrToULong + +// +// INT_PTR -> DWORD_PTR conversion +// +#define IntPtrToDWordPtr IntPtrToULongPtr + +// +// INT_PTR -> ULONGLONG conversion +// +#ifdef _WIN64 +#define IntPtrToULongLong LongLongToULongLong +#else +__inline +HRESULT +IntPtrToULongLong( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *pullResult = (ULONGLONG)iOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// INT_PTR -> DWORDLONG conversion +// +#define IntPtrToDWordLong IntPtrToULongLong + +// +// INT_PTR -> ULONG64 conversion +// +#define IntPtrToULong64 IntPtrToULongLong + +// +// INT_PTR -> DWORD64 conversion +// +#define IntPtrToDWord64 IntPtrToULongLong + +// +// INT_PTR -> UINT64 conversion +// +#define IntPtrToUInt64 IntPtrToULongLong + +// +// INT_PTR -> size_t conversion +// +#define IntPtrToSizeT IntPtrToUIntPtr + +// +// INT_PTR -> SIZE_T conversion +// +#define IntPtrToSIZET IntPtrToULongPtr + +// +// UINT -> INT8 conversion +// +__inline +HRESULT +UIntToInt8( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (uOperand <= INT8_MAX) + { + *pi8Result = (INT8)uOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> UCHAR conversion +// +__inline +HRESULT +UIntToUChar( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) UCHAR* pch) +{ + HRESULT hr; + + if (uOperand <= 255) + { + *pch = (UCHAR)uOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> CHAR conversion +// +__forceinline +HRESULT +UIntToChar( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return UIntToUChar(uOperand, (UCHAR*)pch); +#else + return UIntToInt8(uOperand, (INT8*)pch); +#endif +} + +// +// UINT -> UINT8 conversion +// +__inline +HRESULT +UIntToUInt8( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if (uOperand <= UINT8_MAX) + { + *pui8Result = (UINT8)uOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> BYTE conversion +// +#define UIntToByte UIntToUInt8 + +// +// UINT -> SHORT conversion +// +__inline +HRESULT +UIntToShort( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) SHORT* psResult) +{ + HRESULT hr; + + if (uOperand <= SHORT_MAX) + { + *psResult = (SHORT)uOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> INT16 conversion +// +#define UIntToInt16 UIntToShort + +// +// UINT -> USHORT conversion +// +__inline +HRESULT +UIntToUShort( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (uOperand <= USHORT_MAX) + { + *pusResult = (USHORT)uOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> UINT16 conversion +// +#define UIntToUInt16 UIntToUShort + +// +// UINT -> WORD conversion +// +#define UIntToWord UIntToUShort + +// +// UINT -> INT conversion +// +__inline +HRESULT +UIntToInt( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) INT* piResult) +{ + HRESULT hr; + + if (uOperand <= INT_MAX) + { + *piResult = (INT)uOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> INT32 conversion +// +#define UIntToInt32 UIntToInt + +// +// UINT -> INT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +UIntToIntPtr( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) INT_PTR* piResult) +{ + *piResult = uOperand; + return S_OK; +} +#else +#define UIntToIntPtr UIntToInt +#endif + +// +// UINT -> LONG conversion +// +__inline +HRESULT +UIntToLong( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) LONG* plResult) +{ + HRESULT hr; + + if (uOperand <= INT32_MAX) + { + *plResult = (LONG)uOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> LONG_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +UIntToLongPtr( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) LONG_PTR* plResult) +{ + *plResult = uOperand; + return S_OK; +} +#else +#define UIntToLongPtr UIntToLong +#endif + +// +// UINT -> ptrdiff_t conversion +// +#define UIntToPtrdiffT UIntToIntPtr + +// +// UINT -> SSIZE_T conversion +// +#define UIntToSSIZET UIntToLongPtr + +// +// UINT32 -> CHAR conversion +// +#define UInt32ToChar UIntToChar + +// +// UINT32 -> INT8 conversion +// +#define UInt32ToInt8 UIntToInt8 + +// +// UINT32 -> UCHAR conversion +// +#define UInt32ToUChar UIntToUChar + +// +// UINT32 -> UINT8 conversion +// +#define UInt32ToUInt8 UIntToUInt8 + +// +// UINT32 -> BYTE conversion +// +#define UInt32ToByte UInt32ToUInt8 + +// +// UINT32 -> SHORT conversion +// +#define UInt32ToShort UIntToShort + +// +// UINT32 -> INT16 conversion +// +#define UInt32ToInt16 UIntToShort + +// +// UINT32 -> USHORT conversion +// +#define UInt32ToUShort UIntToUShort + +// +// UINT32 -> UINT16 conversion +// +#define UInt32ToUInt16 UIntToUShort + +// +// UINT32 -> WORD conversion +// +#define UInt32ToWord UIntToUShort + +// +// UINT32 -> INT conversion +// +#define UInt32ToInt UIntToInt + +// +// UINT32 -> INT_PTR conversion +// +#define UInt32ToIntPtr UIntToIntPtr + +// +// UINT32 -> INT32 conversion +// +#define UInt32ToInt32 UIntToInt + +// +// UINT32 -> LONG conversion +// +#define UInt32ToLong UIntToLong + +// +// UINT32 -> LONG_PTR conversion +// +#define UInt32ToLongPtr UIntToLongPtr + +// +// UINT32 -> ptrdiff_t conversion +// +#define UInt32ToPtrdiffT UIntToPtrdiffT + +// +// UINT32 -> SSIZE_T conversion +// +#define UInt32ToSSIZET UIntToSSIZET + +// +// UINT_PTR -> INT8 conversion +// +__inline +HRESULT +UIntPtrToInt8( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (uOperand <= INT8_MAX) + { + *pi8Result = (INT8)uOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> UCHAR conversion +// +__inline +HRESULT +UIntPtrToUChar( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) UCHAR* pch) +{ + HRESULT hr; + + if (uOperand <= 255) + { + *pch = (UCHAR)uOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> CHAR conversion +// +__forceinline +HRESULT +UIntPtrToChar( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return UIntPtrToUChar(uOperand, (UCHAR*)pch); +#else + return UIntPtrToInt8(uOperand, (INT8*)pch); +#endif +} + +// +// UINT_PTR -> UINT8 conversion +// +__inline +HRESULT +UIntPtrToUInt8( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) UINT8* pu8Result) +{ + HRESULT hr; + + if (uOperand <= UINT8_MAX) + { + *pu8Result = (UINT8)uOperand; + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> BYTE conversion +// +#define UIntPtrToByte UIntPtrToUInt8 + +// +// UINT_PTR -> SHORT conversion +// +__inline +HRESULT +UIntPtrToShort( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) SHORT* psResult) +{ + HRESULT hr; + + if (uOperand <= SHORT_MAX) + { + *psResult = (SHORT)uOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> INT16 conversion +// +__inline +HRESULT +UIntPtrToInt16( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) INT16* pi16Result) +{ + HRESULT hr; + + if (uOperand <= INT16_MAX) + { + *pi16Result = (INT16)uOperand; + hr = S_OK; + } + else + { + *pi16Result = INT16_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> USHORT conversion +// +__inline +HRESULT +UIntPtrToUShort( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (uOperand <= USHORT_MAX) + { + *pusResult = (USHORT)uOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> UINT16 conversion +// +__inline +HRESULT +UIntPtrToUInt16( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) UINT16* pu16Result) +{ + HRESULT hr; + + if (uOperand <= UINT16_MAX) + { + *pu16Result = (UINT16)uOperand; + hr = S_OK; + } + else + { + *pu16Result = UINT16_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> WORD conversion +// +#define UIntPtrToWord UIntPtrToUShort + +// +// UINT_PTR -> INT conversion +// +__inline +HRESULT +UIntPtrToInt( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) INT* piResult) +{ + HRESULT hr; + + if (uOperand <= INT_MAX) + { + *piResult = (INT)uOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> INT32 conversion +// +#define UIntPtrToInt32 UIntPtrToInt + +// +// UINT_PTR -> INT_PTR conversion +// +__inline +HRESULT +UIntPtrToIntPtr( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) INT_PTR* piResult) +{ + HRESULT hr; + + if (uOperand <= INT_PTR_MAX) + { + *piResult = (INT_PTR)uOperand; + hr = S_OK; + } + else + { + *piResult = INT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> UINT conversion +// +#ifdef _WIN64 +#define UIntPtrToUInt ULongLongToUInt +#else +__inline +HRESULT +UIntPtrToUInt( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) UINT* puResult) +{ + *puResult = (UINT)uOperand; + return S_OK; +} +#endif + +// +// UINT_PTR -> UINT32 conversion +// +#define UIntPtrToUInt32 UIntPtrToUInt + +// +// UINT_PTR -> LONG conversion +// +__inline +HRESULT +UIntPtrToLong( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) LONG* plResult) +{ + HRESULT hr; + + if (uOperand <= INT32_MAX) + { + *plResult = (LONG)uOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> LONG_PTR conversion +// +__inline +HRESULT +UIntPtrToLongPtr( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) LONG_PTR* plResult) +{ + HRESULT hr; + + if (uOperand <= LONG_PTR_MAX) + { + *plResult = (LONG_PTR)uOperand; + hr = S_OK; + } + else + { + *plResult = LONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> ULONG conversion +// +#ifdef _WIN64 +#define UIntPtrToULong ULongLongToULong +#else +__inline +HRESULT +UIntPtrToULong( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) ULONG* pulResult) +{ + *pulResult = (ULONG)uOperand; + return S_OK; +} +#endif + +// +// UINT_PTR -> DWORD conversion +// +#define UIntPtrToDWord UIntPtrToULong + +// +// UINT_PTR -> LONGLONG conversion +// +#ifdef _WIN64 +#define UIntPtrToLongLong ULongLongToLongLong +#else +__inline +HRESULT +UIntPtrToLongLong( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) LONGLONG* pllResult) +{ + *pllResult = (LONGLONG)uOperand; + return S_OK; +} +#endif + +// +// UINT_PTR -> LONG64 conversion +// +#define UIntPtrToLong64 UIntPtrToLongLong + +// +// UINT_PTR -> INT64 conversion +// +#define UIntPtrToInt64 UIntPtrToLongLong + +// +// UINT_PTR -> ptrdiff_t conversion +// +#define UIntPtrToPtrdiffT UIntPtrToIntPtr + +// +// UINT_PTR -> SSIZE_T conversion +// +#define UIntPtrToSSIZET UIntPtrToLongPtr + +// +// LONG -> INT8 conversion +// +__inline +HRESULT +LongToInt8( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((lOperand >= INT8_MIN) && (lOperand <= INT8_MAX)) + { + *pi8Result = (INT8)lOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> UCHAR conversion +// +__inline +HRESULT +LongToUChar( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= 255)) + { + *pch = (UCHAR)lOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> CHAR conversion +// +__forceinline +HRESULT +LongToChar( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return LongToUChar(lOperand, (UCHAR*)pch); +#else + return LongToInt8(lOperand, (INT8*)pch); +#endif +} + +// +// LONG -> UINT8 conversion +// +__inline +HRESULT +LongToUInt8( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= UINT8_MAX)) + { + *pui8Result = (UINT8)lOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> BYTE conversion +// +#define LongToByte LongToUInt8 + +// +// LONG -> SHORT conversion +// +__inline +HRESULT +LongToShort( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) SHORT* psResult) +{ + HRESULT hr; + + if ((lOperand >= SHORT_MIN) && (lOperand <= SHORT_MAX)) + { + *psResult = (SHORT)lOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> INT16 conversion +// +#define LongToInt16 LongToShort + +// +// LONG -> USHORT conversion +// +__inline +HRESULT +LongToUShort( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) USHORT* pusResult) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= USHORT_MAX)) + { + *pusResult = (USHORT)lOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> UINT16 conversion +// +#define LongToUInt16 LongToUShort + +// +// LONG -> WORD conversion +// +#define LongToWord LongToUShort + +// +// LONG -> INT conversion +// +__inline +HRESULT +LongToInt( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) INT* piResult) +{ + C_ASSERT(sizeof(INT) == sizeof(LONG)); + *piResult = (INT)lOperand; + return S_OK; +} + +// +// LONG -> INT32 conversion +// +#define LongToInt32 LongToInt + +// +// LONG -> INT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +LongToIntPtr( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) INT_PTR* piResult) +{ + *piResult = lOperand; + return S_OK; +} +#else +#define LongToIntPtr LongToInt +#endif + +// +// LONG -> UINT conversion +// +__inline +HRESULT +LongToUInt( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) UINT* puResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *puResult = (UINT)lOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> UINT32 conversion +// +#define LongToUInt32 LongToUInt + +// +// LONG -> UINT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +LongToUIntPtr( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) UINT_PTR* puResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *puResult = (UINT_PTR)lOperand; + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#else +#define LongToUIntPtr LongToUInt +#endif + +// +// LONG -> ULONG conversion +// +__inline +HRESULT +LongToULong( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pulResult = (ULONG)lOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> ULONG_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +LongToULongPtr( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pulResult = (ULONG_PTR)lOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#else +#define LongToULongPtr LongToULong +#endif + +// +// LONG -> DWORD conversion +// +#define LongToDWord LongToULong + +// +// LONG -> DWORD_PTR conversion +// +#define LongToDWordPtr LongToULongPtr + +// +// LONG -> ULONGLONG conversion +// +__inline +HRESULT +LongToULongLong( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pullResult = (ULONGLONG)lOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> DWORDLONG conversion +// +#define LongToDWordLong LongToULongLong + +// +// LONG -> ULONG64 conversion +// +#define LongToULong64 LongToULongLong + +// +// LONG -> DWORD64 conversion +// +#define LongToDWord64 LongToULongLong + +// +// LONG -> UINT64 conversion +// +#define LongToUInt64 LongToULongLong + +// +// LONG -> ptrdiff_t conversion +// +#define LongToPtrdiffT LongToIntPtr + +// +// LONG -> size_t conversion +// +#define LongToSizeT LongToUIntPtr + +// +// LONG -> SIZE_T conversion +// +#define LongToSIZET LongToULongPtr + +// +// LONG_PTR -> INT8 conversion +// +__inline +HRESULT +LongPtrToInt8( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((lOperand >= INT8_MIN) && (lOperand <= INT8_MAX)) + { + *pi8Result = (INT8)lOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> UCHAR conversion +// +__inline +HRESULT +LongPtrToUChar( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= 255)) + { + *pch = (UCHAR)lOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> CHAR conversion +// +__forceinline +HRESULT +LongPtrToChar( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return LongPtrToUChar(lOperand, (UCHAR*)pch); +#else + return LongPtrToInt8(lOperand, (INT8*)pch); +#endif +} + +// +// LONG_PTR -> UINT8 conversion +// +__inline +HRESULT +LongPtrToUInt8( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= UINT8_MAX)) + { + *pui8Result = (UINT8)lOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> BYTE conversion +// +#define LongPtrToByte LongPtrToUInt8 + +// +// LONG_PTR -> SHORT conversion +// +__inline +HRESULT +LongPtrToShort( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) SHORT* psResult) +{ + HRESULT hr; + + if ((lOperand >= SHORT_MIN) && (lOperand <= SHORT_MAX)) + { + *psResult = (SHORT)lOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> INT16 conversion +// +#define LongPtrToInt16 LongPtrToShort + +// +// LONG_PTR -> USHORT conversion +// +__inline +HRESULT +LongPtrToUShort( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) USHORT* pusResult) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= USHORT_MAX)) + { + *pusResult = (USHORT)lOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> UINT16 conversion +// +#define LongPtrToUInt16 LongPtrToUShort + +// +// LONG_PTR -> WORD conversion +// +#define LongPtrToWord LongPtrToUShort + +// +// LONG_PTR -> INT conversion +// +#ifdef _WIN64 +#define LongPtrToInt LongLongToInt +#else +__inline +HRESULT +LongPtrToInt( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) INT* piResult) +{ + C_ASSERT(sizeof(INT) == sizeof(LONG_PTR)); + *piResult = (INT)lOperand; + return S_OK; +} +#endif + +// +// LONG_PTR -> INT32 conversion +// +#define LongPtrToInt32 LongPtrToInt + +// +// LONG_PTR -> INT_PTR conversion +// +__inline +HRESULT +LongPtrToIntPtr( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) INT_PTR* piResult) +{ + C_ASSERT(sizeof(LONG_PTR) == sizeof(INT_PTR)); + *piResult = (INT_PTR)lOperand; + return S_OK; +} + +// +// LONG_PTR -> UINT conversion +// +#ifdef _WIN64 +#define LongPtrToUInt LongLongToUInt +#else +__inline +HRESULT +LongPtrToUInt( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) UINT* puResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *puResult = (UINT)lOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// LONG_PTR -> UINT32 conversion +// +#define LongPtrToUInt32 LongPtrToUInt + +// +// LONG_PTR -> UINT_PTR conversion +// +__inline +HRESULT +LongPtrToUIntPtr( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) UINT_PTR* puResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *puResult = (UINT_PTR)lOperand; + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> LONG conversion +// +#ifdef _WIN64 +#define LongPtrToLong LongLongToLong +#else +__inline +HRESULT +LongPtrToLong( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) LONG* plResult) +{ + *plResult = (LONG)lOperand; + return S_OK; +} +#endif + +// +// LONG_PTR -> ULONG conversion +// +#ifdef _WIN64 +#define LongPtrToULong LongLongToULong +#else +__inline +HRESULT +LongPtrToULong( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pulResult = (ULONG)lOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// LONG_PTR -> ULONG_PTR conversion +// +__inline +HRESULT +LongPtrToULongPtr( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pulResult = (ULONG_PTR)lOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> DWORD conversion +// +#define LongPtrToDWord LongPtrToULong + +// +// LONG_PTR -> DWORD_PTR conversion +// +#define LongPtrToDWordPtr LongPtrToULongPtr + +// +// LONG_PTR -> ULONGLONG conversion +// +__inline +HRESULT +LongPtrToULongLong( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pullResult = (ULONGLONG)lOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> DWORDLONG conversion +// +#define LongPtrToDWordLong LongPtrToULongLong + +// +// LONG_PTR -> ULONG64 conversion +// +#define LongPtrToULong64 LongPtrToULongLong + +// +// LONG_PTR -> DWORD64 conversion +// +#define LongPtrToDWord64 LongPtrToULongLong + +// +// LONG_PTR -> UINT64 conversion +// +#define LongPtrToUInt64 LongPtrToULongLong + +// +// LONG_PTR -> size_t conversion +// +#define LongPtrToSizeT LongPtrToUIntPtr + +// +// LONG_PTR -> SIZE_T conversion +// +#define LongPtrToSIZET LongPtrToULongPtr + +// +// ULONG -> INT8 conversion +// +__inline +HRESULT +ULongToInt8( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (ulOperand <= INT8_MAX) + { + *pi8Result = (INT8)ulOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> UCHAR conversion +// +__inline +HRESULT +ULongToUChar( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) UCHAR* pch) +{ + HRESULT hr; + + if (ulOperand <= 255) + { + *pch = (UCHAR)ulOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> CHAR conversion +// +__forceinline +HRESULT +ULongToChar( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return ULongToUChar(ulOperand, (UCHAR*)pch); +#else + return ULongToInt8(ulOperand, (INT8*)pch); +#endif +} + +// +// ULONG -> UINT8 conversion +// +__inline +HRESULT +ULongToUInt8( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if (ulOperand <= UINT8_MAX) + { + *pui8Result = (UINT8)ulOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> BYTE conversion +// +#define ULongToByte ULongToUInt8 + +// +// ULONG -> SHORT conversion +// +__inline +HRESULT +ULongToShort( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) SHORT* psResult) +{ + HRESULT hr; + + if (ulOperand <= SHORT_MAX) + { + *psResult = (SHORT)ulOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> INT16 conversion +// +#define ULongToInt16 ULongToShort + +// +// ULONG -> USHORT conversion +// +__inline +HRESULT +ULongToUShort( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (ulOperand <= USHORT_MAX) + { + *pusResult = (USHORT)ulOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> UINT16 conversion +// +#define ULongToUInt16 ULongToUShort + +// +// ULONG -> WORD conversion +// +#define ULongToWord ULongToUShort + +// +// ULONG -> INT conversion +// +__inline +HRESULT +ULongToInt( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) INT* piResult) +{ + HRESULT hr; + + if (ulOperand <= INT_MAX) + { + *piResult = (INT)ulOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> INT32 conversion +// +#define ULongToInt32 ULongToInt + +// +// ULONG -> INT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +ULongToIntPtr( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) INT_PTR* piResult) +{ + *piResult = (INT_PTR)ulOperand; + return S_OK; +} +#else +#define ULongToIntPtr ULongToInt +#endif + +// +// ULONG -> UINT conversion +// +__inline +HRESULT +ULongToUInt( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) UINT* puResult) +{ + //C_ASSERT(sizeof(ULONG) == sizeof(UINT)); + *puResult = (UINT)ulOperand; + return S_OK; +} + +// +// ULONG -> UINT32 conversion +// +#define ULongToUInt32 ULongToUInt + +// +// ULONG -> UINT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +ULongToUIntPtr( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) UINT_PTR* puiResult) +{ + C_ASSERT(sizeof(UINT_PTR) > sizeof(ULONG)); + *puiResult = (UINT_PTR)ulOperand; + return S_OK; +} +#else +#define ULongToUIntPtr ULongToUInt +#endif + +// +// ULONG -> LONG conversion +// +__inline +HRESULT +ULongToLong( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) LONG* plResult) +{ + HRESULT hr; + + if (ulOperand <= INT32_MAX) + { + *plResult = (LONG)ulOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> LONG_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +ULongToLongPtr( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) LONG_PTR* plResult) +{ + C_ASSERT(sizeof(LONG_PTR) > sizeof(ULONG)); + *plResult = (LONG_PTR)ulOperand; + return S_OK; +} +#else +#define ULongToLongPtr ULongToLong +#endif + +// +// ULONG -> ptrdiff_t conversion +// +#define ULongToPtrdiffT ULongToIntPtr + +// +// ULONG -> SSIZE_T conversion +// +#define ULongToSSIZET ULongToLongPtr + +// +// ULONG_PTR -> INT8 conversion +// +__inline +HRESULT +ULongPtrToInt8( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (ulOperand <= INT8_MAX) + { + *pi8Result = (INT8)ulOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> UCHAR conversion +// +__inline +HRESULT +ULongPtrToUChar( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) UCHAR* pch) +{ + HRESULT hr; + + if (ulOperand <= 255) + { + *pch = (UCHAR)ulOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> CHAR conversion +// +__forceinline +HRESULT +ULongPtrToChar( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return ULongPtrToUChar(ulOperand, (UCHAR*)pch); +#else + return ULongPtrToInt8(ulOperand, (INT8*)pch); +#endif +} + +// +// ULONG_PTR -> UINT8 conversion +// +__inline +HRESULT +ULongPtrToUInt8( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if (ulOperand <= UINT8_MAX) + { + *pui8Result = (UINT8)ulOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> BYTE conversion +// +#define ULongPtrToByte ULongPtrToUInt8 + +// +// ULONG_PTR -> SHORT conversion +// +__inline +HRESULT +ULongPtrToShort( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) SHORT* psResult) +{ + HRESULT hr; + + if (ulOperand <= SHORT_MAX) + { + *psResult = (SHORT)ulOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> INT16 conversion +// +#define ULongPtrToInt16 ULongPtrToShort + +// +// ULONG_PTR -> USHORT conversion +// +__inline +HRESULT +ULongPtrToUShort( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (ulOperand <= USHORT_MAX) + { + *pusResult = (USHORT)ulOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> UINT16 conversion +// +#define ULongPtrToUInt16 ULongPtrToUShort + +// +// ULONG_PTR -> WORD conversion +// +#define ULongPtrToWord ULongPtrToUShort + +// +// ULONG_PTR -> INT conversion +// +__inline +HRESULT +ULongPtrToInt( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) INT* piResult) +{ + HRESULT hr; + + if (ulOperand <= INT_MAX) + { + *piResult = (INT)ulOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> INT32 conversion +// +#define ULongPtrToInt32 ULongPtrToInt + +// +// ULONG_PTR -> INT_PTR conversion +// +__inline +HRESULT +ULongPtrToIntPtr( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) INT_PTR* piResult) +{ + HRESULT hr; + + if (ulOperand <= INT_PTR_MAX) + { + *piResult = (INT_PTR)ulOperand; + hr = S_OK; + } + else + { + *piResult = INT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> UINT conversion +// +#ifdef _WIN64 +#define ULongPtrToUInt ULongLongToUInt +#else +__inline +HRESULT +ULongPtrToUInt( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) UINT* puResult) +{ + C_ASSERT(sizeof(ULONG_PTR) == sizeof(UINT)); + *puResult = (UINT)ulOperand; + return S_OK; +} +#endif + +// +// ULONG_PTR -> UINT32 conversion +// +#define ULongPtrToUInt32 ULongPtrToUInt + +// +// ULONG_PTR -> UINT_PTR conversion +// +__inline +HRESULT +ULongPtrToUIntPtr( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) UINT_PTR* puResult) +{ + *puResult = (UINT_PTR)ulOperand; + return S_OK; +} + +// +// ULONG_PTR -> LONG conversion +// +__inline +HRESULT +ULongPtrToLong( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) LONG* plResult) +{ + HRESULT hr; + + if (ulOperand <= INT32_MAX) + { + *plResult = (LONG)ulOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> LONG_PTR conversion +// +__inline +HRESULT +ULongPtrToLongPtr( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) LONG_PTR* plResult) +{ + HRESULT hr; + + if (ulOperand <= LONG_PTR_MAX) + { + *plResult = (LONG_PTR)ulOperand; + hr = S_OK; + } + else + { + *plResult = LONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> ULONG conversion +// +#ifdef _WIN64 +#define ULongPtrToULong ULongLongToULong +#else +__inline +HRESULT +ULongPtrToULong( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) ULONG* pulResult) +{ + *pulResult = (ULONG)ulOperand; + return S_OK; +} +#endif + +// +// ULONG_PTR -> DWORD conversion +// +#define ULongPtrToDWord ULongPtrToULong + +// +// ULONG_PTR -> LONGLONG conversion +// +#ifdef _WIN64 +#define ULongPtrToLongLong ULongLongToLongLong +#else +__inline +HRESULT +ULongPtrToLongLong( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) LONGLONG* pllResult) +{ + *pllResult = (LONGLONG)ulOperand; + return S_OK; +} +#endif + +// +// ULONG_PTR -> LONG64 conversion +// +#define ULongPtrToLong64 ULongPtrToLongLong + +// +// ULONG_PTR -> INT64 +// +#define ULongPtrToInt64 ULongPtrToLongLong + +// +// ULONG_PTR -> ptrdiff_t conversion +// +#define ULongPtrToPtrdiffT ULongPtrToIntPtr + +// +// ULONG_PTR -> SSIZE_T conversion +// +#define ULongPtrToSSIZET ULongPtrToLongPtr + +// +// DWORD -> INT8 conversion +// +#define DWordToInt8 ULongToInt8 + +// +// DWORD -> CHAR conversion +// +#define DWordToChar ULongToChar + +// +// DWORD -> UCHAR conversion +// +#define DWordToUChar ULongToUChar + +// +// DWORD -> UINT8 conversion +// +#define DWordToUInt8 ULongToUInt8 + +// +// DWORD -> BYTE conversion +// +#define DWordToByte ULongToUInt8 + +// +// DWORD -> SHORT conversion +// +#define DWordToShort ULongToShort + +// +// DWORD -> INT16 conversion +// +#define DWordToInt16 ULongToShort + +// +// DWORD -> USHORT conversion +// +#define DWordToUShort ULongToUShort + +// +// DWORD -> UINT16 conversion +// +#define DWordToUInt16 ULongToUShort + +// +// DWORD -> WORD conversion +// +#define DWordToWord ULongToUShort + +// +// DWORD -> INT conversion +// +#define DWordToInt ULongToInt + +// +// DWORD -> INT32 conversion +// +#define DWordToInt32 ULongToInt + +// +// DWORD -> INT_PTR conversion +// +#define DWordToIntPtr ULongToIntPtr + +// +// DWORD -> UINT conversion +// +#define DWordToUInt ULongToUInt + +// +// DWORD -> UINT32 conversion +// +#define DWordToUInt32 ULongToUInt + +// +// DWORD -> UINT_PTR conversion +// +#define DWordToUIntPtr ULongToUIntPtr + +// +// DWORD -> LONG conversion +// +#define DWordToLong ULongToLong + +// +// DWORD -> LONG_PTR conversion +// +#define DWordToLongPtr ULongToLongPtr + +// +// DWORD -> ptrdiff_t conversion +// +#define DWordToPtrdiffT ULongToIntPtr + +// +// DWORD -> SSIZE_T conversion +// +#define DWordToSSIZET ULongToLongPtr + +// +// DWORD_PTR -> INT8 conversion +// +#define DWordPtrToInt8 ULongPtrToInt8 + +// +// DWORD_PTR -> UCHAR conversion +// +#define DWordPtrToUChar ULongPtrToUChar + +// +// DWORD_PTR -> CHAR conversion +// +#define DWordPtrToChar ULongPtrToChar + +// +// DWORD_PTR -> UINT8 conversion +// +#define DWordPtrToUInt8 ULongPtrToUInt8 + +// +// DWORD_PTR -> BYTE conversion +// +#define DWordPtrToByte ULongPtrToUInt8 + +// +// DWORD_PTR -> SHORT conversion +// +#define DWordPtrToShort ULongPtrToShort + +// +// DWORD_PTR -> INT16 conversion +// +#define DWordPtrToInt16 ULongPtrToShort + +// +// DWORD_PTR -> USHORT conversion +// +#define DWordPtrToUShort ULongPtrToUShort + +// +// DWORD_PTR -> UINT16 conversion +// +#define DWordPtrToUInt16 ULongPtrToUShort + +// +// DWORD_PTR -> WORD conversion +// +#define DWordPtrToWord ULongPtrToUShort + +// +// DWORD_PTR -> INT conversion +// +#define DWordPtrToInt ULongPtrToInt + +// +// DWORD_PTR -> INT32 conversion +// +#define DWordPtrToInt32 ULongPtrToInt + +// +// DWORD_PTR -> INT_PTR conversion +// +#define DWordPtrToIntPtr ULongPtrToIntPtr + +// +// DWORD_PTR -> UINT conversion +// +#define DWordPtrToUInt ULongPtrToUInt + +// +// DWORD_PTR -> UINT32 conversion +// +#define DWordPtrToUInt32 ULongPtrToUInt + +// +// DWODR_PTR -> UINT_PTR conversion +// +#define DWordPtrToUIntPtr ULongPtrToUIntPtr + +// +// DWORD_PTR -> LONG conversion +// +#define DWordPtrToLong ULongPtrToLong + +// +// DWORD_PTR -> LONG_PTR conversion +// +#define DWordPtrToLongPtr ULongPtrToLongPtr + +// +// DWORD_PTR -> ULONG conversion +// +#define DWordPtrToULong ULongPtrToULong + +// +// DWORD_PTR -> DWORD conversion +// +#define DWordPtrToDWord ULongPtrToULong + +// +// DWORD_PTR -> LONGLONG conversion +// +#define DWordPtrToLongLong ULongPtrToLongLong + +// +// DWORD_PTR -> LONG64 conversion +// +#define DWordPtrToLong64 ULongPtrToLongLong + +// +// DWORD_PTR -> INT64 conversion +// +#define DWordPtrToInt64 ULongPtrToLongLong + +// +// DWORD_PTR -> ptrdiff_t conversion +// +#define DWordPtrToPtrdiffT ULongPtrToIntPtr + +// +// DWORD_PTR -> SSIZE_T conversion +// +#define DWordPtrToSSIZET ULongPtrToLongPtr + +// +// LONGLONG -> INT8 conversion +// +__inline +HRESULT +LongLongToInt8( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((llOperand >= INT8_MIN) && (llOperand <= INT8_MAX)) + { + *pi8Result = (INT8)llOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> UCHAR conversion +// +__inline +HRESULT +LongLongToUChar( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((llOperand >= 0) && (llOperand <= 255)) + { + *pch = (UCHAR)llOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> CHAR conversion +// +__forceinline +HRESULT +LongLongToChar( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return LongLongToUChar(llOperand, (UCHAR*)pch); +#else + return LongLongToInt8(llOperand, (INT8*)pch); +#endif +} + +// +// LONGLONG -> UINT8 conversion +// +__inline +HRESULT +LongLongToUInt8( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) UINT8* pu8Result) +{ + HRESULT hr; + + if ((llOperand >= 0) && (llOperand <= UINT8_MAX)) + { + *pu8Result = (UINT8)llOperand; + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> BYTE conversion +// +#define LongLongToByte LongLongToUInt8 + +// +// LONGLONG -> SHORT conversion +// +__inline +HRESULT +LongLongToShort( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) SHORT* psResult) +{ + HRESULT hr; + + if ((llOperand >= SHORT_MIN) && (llOperand <= SHORT_MAX)) + { + *psResult = (SHORT)llOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> INT16 conversion +// +#define LongLongToInt16 LongLongToShort + +// +// LONGLONG -> USHORT conversion +// +__inline +HRESULT +LongLongToUShort( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) USHORT* pusResult) +{ + HRESULT hr; + + if ((llOperand >= 0) && (llOperand <= USHORT_MAX)) + { + *pusResult = (USHORT)llOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> UINT16 conversion +// +#define LongLongToUInt16 LongLongToUShort + +// +// LONGLONG -> WORD conversion +// +#define LongLongToWord LongLongToUShort + +// +// LONGLONG -> INT conversion +// +__inline +HRESULT +LongLongToInt( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) INT* piResult) +{ + HRESULT hr; + + if ((llOperand >= INT_MIN) && (llOperand <= INT_MAX)) + { + *piResult = (INT)llOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> INT32 conversion +// +#define LongLongToInt32 LongLongToInt + +// +// LONGLONG -> INT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +LongLongToIntPtr( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) INT_PTR* piResult) +{ + *piResult = llOperand; + return S_OK; +} +#else +#define LongLongToIntPtr LongLongToInt +#endif + +// +// LONGLONG -> UINT conversion +// +__inline +HRESULT +LongLongToUInt( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) UINT* puResult) +{ + HRESULT hr; + + if ((llOperand >= 0) && (llOperand <= UINT_MAX)) + { + *puResult = (UINT)llOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> UINT32 conversion +// +#define LongLongToUInt32 LongLongToUInt + +// +// LONGLONG -> UINT_PTR conversion +// +#ifdef _WIN64 +#define LongLongToUIntPtr LongLongToULongLong +#else +#define LongLongToUIntPtr LongLongToUInt +#endif + +// +// LONGLONG -> LONG conversion +// +__inline +HRESULT +LongLongToLong( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) LONG* plResult) +{ + HRESULT hr; + + if ((llOperand >= INT32_MIN) && (llOperand <= INT32_MAX)) + { + *plResult = (LONG)llOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> LONG_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +LongLongToLongPtr( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) LONG_PTR* plResult) +{ + *plResult = (LONG_PTR)llOperand; + return S_OK; +} +#else +#define LongLongToLongPtr LongLongToLong +#endif + +// +// LONGLONG -> ULONG conversion +// +__inline +HRESULT +LongLongToULong( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) ULONG* pulResult) +{ + HRESULT hr; + + if ((llOperand >= 0) && (llOperand <= (LONGLONG)(ULONGLONG)UINT32_MAX)) + { + *pulResult = (ULONG)llOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> ULONG_PTR conversion +// +#ifdef _WIN64 +#define LongLongToULongPtr LongLongToULongLong +#else +#define LongLongToULongPtr LongLongToULong +#endif + +// +// LONGLONG -> DWORD conversion +// +#define LongLongToDWord LongLongToULong + +// +// LONGLONG -> DWORD_PTR conversion +// +#define LongLongToDWordPtr LongLongToULongPtr + +// +// LONGLONG -> ULONGLONG conversion +// +__inline +HRESULT +LongLongToULongLong( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (llOperand >= 0) + { + *pullResult = (ULONGLONG)llOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> DWORDLONG conversion +// +#define LongLongToDWordLong LongLongToULongLong + +// +// LONGLONG -> ULONG64 conversion +// +#define LongLongToULong64 LongLongToULongLong + +// +// LONGLONG -> DWORD64 conversion +// +#define LongLongToDWord64 LongLongToULongLong + +// +// LONGLONG -> UINT64 conversion +// +#define LongLongToUInt64 LongLongToULongLong + +// +// LONGLONG -> ptrdiff_t conversion +// +#define LongLongToPtrdiffT LongLongToIntPtr + +// +// LONGLONG -> size_t conversion +// +#define LongLongToSizeT LongLongToUIntPtr + +// +// LONGLONG -> SSIZE_T conversion +// +#define LongLongToSSIZET LongLongToLongPtr + +// +// LONGLONG -> SIZE_T conversion +// +#define LongLongToSIZET LongLongToULongPtr + +// +// LONG64 -> CHAR conversion +// +#define Long64ToChar LongLongToChar + +// +// LONG64 -> INT8 conversion +// +#define Long64ToInt8 LongLongToInt8 + +// +// LONG64 -> UCHAR conversion +// +#define Long64ToUChar LongLongToUChar + +// +// LONG64 -> UINT8 conversion +// +#define Long64ToUInt8 LongLongToUInt8 + +// +// LONG64 -> BYTE conversion +// +#define Long64ToByte LongLongToUInt8 + +// +// LONG64 -> SHORT conversion +// +#define Long64ToShort LongLongToShort + +// +// LONG64 -> INT16 conversion +// +#define Long64ToInt16 LongLongToShort + +// +// LONG64 -> USHORT conversion +// +#define Long64ToUShort LongLongToUShort + +// +// LONG64 -> UINT16 conversion +// +#define Long64ToUInt16 LongLongToUShort + +// +// LONG64 -> WORD conversion +// +#define Long64ToWord LongLongToUShort + +// +// LONG64 -> INT conversion +// +#define Long64ToInt LongLongToInt + +// +// LONG64 -> INT32 conversion +// +#define Long64ToInt32 LongLongToInt + +// +// LONG64 -> INT_PTR conversion +// +#define Long64ToIntPtr LongLongToIntPtr + +// +// LONG64 -> UINT conversion +// +#define Long64ToUInt LongLongToUInt + +// +// LONG64 -> UINT32 conversion +// +#define Long64ToUInt32 LongLongToUInt + +// +// LONG64 -> UINT_PTR conversion +// +#define Long64ToUIntPtr LongLongToUIntPtr + +// +// LONG64 -> LONG conversion +// +#define Long64ToLong LongLongToLong + +// +// LONG64 -> LONG_PTR conversion +// +#define Long64ToLongPtr LongLongToLongPtr + +// +// LONG64 -> ULONG conversion +// +#define Long64ToULong LongLongToULong + +// +// LONG64 -> ULONG_PTR conversion +// +#define Long64ToULongPtr LongLongToULongPtr + +// +// LONG64 -> DWORD conversion +// +#define Long64ToDWord LongLongToULong + +// +// LONG64 -> DWORD_PTR conversion +// +#define Long64ToDWordPtr LongLongToULongPtr + +// +// LONG64 -> ULONGLONG conversion +// +#define Long64ToULongLong LongLongToULongLong + +// +// LONG64 -> ptrdiff_t conversion +// +#define Long64ToPtrdiffT LongLongToIntPtr + +// +// LONG64 -> size_t conversion +// +#define Long64ToSizeT LongLongToUIntPtr + +// +// LONG64 -> SSIZE_T conversion +// +#define Long64ToSSIZET LongLongToLongPtr + +// +// LONG64 -> SIZE_T conversion +// +#define Long64ToSIZET LongLongToULongPtr + +// +// INT64 -> CHAR conversion +// +#define Int64ToChar LongLongToChar + +// +// INT64 -> INT8 conversion +// +#define Int64ToInt8 LongLongToInt8 + +// +// INT64 -> UCHAR conversion +// +#define Int64ToUChar LongLongToUChar + +// +// INT64 -> UINT8 conversion +// +#define Int64ToUInt8 LongLongToUInt8 + +// +// INT64 -> BYTE conversion +// +#define Int64ToByte LongLongToUInt8 + +// +// INT64 -> SHORT conversion +// +#define Int64ToShort LongLongToShort + +// +// INT64 -> INT16 conversion +// +#define Int64ToInt16 LongLongToShort + +// +// INT64 -> USHORT conversion +// +#define Int64ToUShort LongLongToUShort + +// +// INT64 -> UINT16 conversion +// +#define Int64ToUInt16 LongLongToUShort + +// +// INT64 -> WORD conversion +// +#define Int64ToWord LongLongToUShort + +// +// INT64 -> INT conversion +// +#define Int64ToInt LongLongToInt + +// +// INT64 -> INT32 conversion +// +#define Int64ToInt32 LongLongToInt + +// +// INT64 -> INT_PTR conversion +// +#define Int64ToIntPtr LongLongToIntPtr + +// +// INT64 -> UINT conversion +// +#define Int64ToUInt LongLongToUInt + +// +// INT64 -> UINT32 conversion +// +#define Int64ToUInt32 LongLongToUInt + +// +// INT64 -> UINT_PTR conversion +// +#define Int64ToUIntPtr LongLongToUIntPtr + +// +// INT64 -> LONG conversion +// +#define Int64ToLong LongLongToLong + +// +// INT64 -> LONG_PTR conversion +// +#define Int64ToLongPtr LongLongToLongPtr + +// +// INT64 -> ULONG conversion +// +#define Int64ToULong LongLongToULong + +// +// INT64 -> ULONG_PTR conversion +// +#define Int64ToULongPtr LongLongToULongPtr + +// +// INT64 -> DWORD conversion +// +#define Int64ToDWord LongLongToULong + +// +// INT64 -> DWORD_PTR conversion +// +#define Int64ToDWordPtr LongLongToULongPtr + +// +// INT64 -> ULONGLONG conversion +// +#define Int64ToULongLong LongLongToULongLong + +// +// INT64 -> DWORDLONG conversion +// +#define Int64ToDWordLong LongLongToULongLong + +// +// INT64 -> ULONG64 conversion +// +#define Int64ToULong64 LongLongToULongLong + +// +// INT64 -> DWORD64 conversion +// +#define Int64ToDWord64 LongLongToULongLong + +// +// INT64 -> UINT64 conversion +// +#define Int64ToUInt64 LongLongToULongLong + +// +// INT64 -> ptrdiff_t conversion +// +#define Int64ToPtrdiffT LongLongToIntPtr + +// +// INT64 -> size_t conversion +// +#define Int64ToSizeT LongLongToUIntPtr + +// +// INT64 -> SSIZE_T conversion +// +#define Int64ToSSIZET LongLongToLongPtr + +// +// INT64 -> SIZE_T conversion +// +#define Int64ToSIZET LongLongToULongPtr + +// +// ULONGLONG -> INT8 conversion +// +__inline +HRESULT +ULongLongToInt8( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (ullOperand <= INT8_MAX) + { + *pi8Result = (INT8)ullOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> UCHAR conversion +// +__inline +HRESULT +ULongLongToUChar( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) UCHAR* pch) +{ + HRESULT hr; + + if (ullOperand <= 255) + { + *pch = (UCHAR)ullOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> CHAR conversion +// +__forceinline +HRESULT +ULongLongToChar( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return ULongLongToUChar(ullOperand, (UCHAR*)pch); +#else + return ULongLongToInt8(ullOperand, (INT8*)pch); +#endif +} + +// +// ULONGLONG -> UINT8 conversion +// +__inline +HRESULT +ULongLongToUInt8( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) UINT8* pu8Result) +{ + HRESULT hr; + + if (ullOperand <= UINT8_MAX) + { + *pu8Result = (UINT8)ullOperand; + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> BYTE conversion +// +#define ULongLongToByte ULongLongToUInt8 + +// +// ULONGLONG -> SHORT conversion +// +__inline +HRESULT +ULongLongToShort( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) SHORT* psResult) +{ + HRESULT hr; + + if (ullOperand <= SHORT_MAX) + { + *psResult = (SHORT)ullOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> INT16 conversion +// +#define ULongLongToInt16 ULongLongToShort + +// +// ULONGLONG -> USHORT conversion +// +__inline +HRESULT +ULongLongToUShort( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (ullOperand <= USHORT_MAX) + { + *pusResult = (USHORT)ullOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> UINT16 conversion +// +#define ULongLongToUInt16 ULongLongToUShort + +// +// ULONGLONG -> WORD conversion +// +#define ULongLongToWord ULongLongToUShort + +// +// ULONGLONG -> INT conversion +// +__inline +HRESULT +ULongLongToInt( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) INT* piResult) +{ + HRESULT hr; + + if (ullOperand <= INT_MAX) + { + *piResult = (INT)ullOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> INT32 conversion +// +#define ULongLongToInt32 ULongLongToInt + +// +// ULONGLONG -> INT_PTR conversion +// +#ifdef _WIN64 +#define ULongLongToIntPtr ULongLongToLongLong +#else +#define ULongLongToIntPtr ULongLongToInt +#endif + +// +// ULONGLONG -> UINT conversion +// +__inline +HRESULT +ULongLongToUInt( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) UINT* puResult) +{ + HRESULT hr; + + if (ullOperand <= UINT_MAX) + { + *puResult = (UINT)ullOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> UINT32 conversion +// +#define ULongLongToUInt32 ULongLongToUInt + +// +// ULONGLONG -> UINT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +ULongLongToUIntPtr( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) UINT_PTR* puResult) +{ + *puResult = ullOperand; + return S_OK; +} +#else +#define ULongLongToUIntPtr ULongLongToUInt +#endif + +// +// ULONGLONG -> LONG conversion +// +__inline +HRESULT +ULongLongToLong( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) LONG* plResult) +{ + HRESULT hr; + + if (ullOperand <= INT32_MAX) + { + *plResult = (LONG)ullOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> LONG_PTR conversion +// +__inline +HRESULT +ULongLongToLongPtr( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) LONG_PTR* plResult) +{ + HRESULT hr; + + if (ullOperand <= LONG_PTR_MAX) + { + *plResult = (LONG_PTR)ullOperand; + hr = S_OK; + } + else + { + *plResult = LONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> ULONG conversion +// +__inline +HRESULT +ULongLongToULong( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (ullOperand <= UINT32_MAX) + { + *pulResult = (ULONG)ullOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> ULONG_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +ULongLongToULongPtr( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) ULONG_PTR* pulResult) +{ + *pulResult = ullOperand; + return S_OK; +} +#else +#define ULongLongToULongPtr ULongLongToULong +#endif + +// +// ULONGLONG -> DWORD conversion +// +#define ULongLongToDWord ULongLongToULong + +// +// ULONGLONG -> DWORD_PTR conversion +// +#define ULongLongToDWordPtr ULongLongToULongPtr + +// +// ULONGLONG -> LONGLONG conversion +// +__inline +HRESULT +ULongLongToLongLong( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) LONGLONG* pllResult) +{ + HRESULT hr; + + if (ullOperand <= LONGLONG_MAX) + { + *pllResult = (LONGLONG)ullOperand; + hr = S_OK; + } + else + { + *pllResult = LONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> INT64 conversion +// +#define ULongLongToInt64 ULongLongToLongLong + +// +// ULONGLONG -> LONG64 conversion +// +#define ULongLongToLong64 ULongLongToLongLong + +// +// ULONGLONG -> ptrdiff_t conversion +// +#define ULongLongToPtrdiffT ULongLongToIntPtr + +// +// ULONGLONG -> size_t conversion +// +#define ULongLongToSizeT ULongLongToUIntPtr + +// +// ULONGLONG -> SSIZE_T conversion +// +#define ULongLongToSSIZET ULongLongToLongPtr + +// +// ULONGLONG -> SIZE_T conversion +// +#define ULongLongToSIZET ULongLongToULongPtr + +// +// DWORDLONG -> CHAR conversion +// +#define DWordLongToChar ULongLongToChar + +// +// DWORDLONG -> INT8 conversion +// +#define DWordLongToInt8 ULongLongToInt8 + +// +// DWORDLONG -> UCHAR conversion +// +#define DWordLongToUChar ULongLongToUChar + +// +// DWORDLONG -> UINT8 conversion +// +#define DWordLongToUInt8 ULongLongToUInt8 + +// +// DWORDLONG -> BYTE conversion +// +#define DWordLongToByte ULongLongToUInt8 + +// +// DWORDLONG -> SHORT conversion +// +#define DWordLongToShort ULongLongToShort + +// +// DWORDLONG -> INT16 conversion +// +#define DWordLongToInt16 ULongLongToShort + +// +// DWORDLONG -> USHORT conversion +// +#define DWordLongToUShort ULongLongToUShort + +// +// DWORDLONG -> UINT16 conversion +// +#define DWordLongToUInt16 ULongLongToUShort + +// +// DWORDLONG -> WORD conversion +// +#define DWordLongToWord ULongLongToUShort + +// +// DWORDLONG -> INT conversion +// +#define DWordLongToInt ULongLongToInt + +// +// DWORDLONG -> INT32 conversion +// +#define DWordLongToInt32 ULongLongToInt + +// +// DWORDLONG -> INT_PTR conversion +// +#define DWordLongToIntPtr ULongLongToIntPtr + +// +// DWORDLONG -> UINT conversion +// +#define DWordLongToUInt ULongLongToUInt + +// +// DWORDLONG -> UINT32 conversion +// +#define DWordLongToUInt32 ULongLongToUInt + +// +// DWORDLONG -> UINT_PTR conversion +// +#define DWordLongToUIntPtr ULongLongToUIntPtr + +// +// DWORDLONG -> LONG conversion +// +#define DWordLongToLong ULongLongToLong + +// +// DWORDLONG -> LONG_PTR conversion +// +#define DWordLongToLongPtr ULongLongToLongPtr + +// +// DWORDLONG -> ULONG conversion +// +#define DWordLongToULong ULongLongToULong + +// +// DWORDLONG -> ULONG_PTR conversion +// +#define DWordLongToULongPtr ULongLongToULongPtr + +// +// DWORDLONG -> DWORD conversion +// +#define DWordLongToDWord ULongLongToULong + +// +// DWORDLONG -> DWORD_PTR conversion +// +#define DWordLongToDWordPtr ULongLongToULongPtr + +// +// DWORDLONG -> LONGLONG conversion +// +#define DWordLongToLongLong ULongLongToLongLong + +// +// DWORDLONG -> LONG64 conversion +// +#define DWordLongToLong64 ULongLongToLongLong + +// +// DWORDLONG -> INT64 conversion +// +#define DWordLongToInt64 ULongLongToLongLong + +// +// DWORDLONG -> ptrdiff_t conversion +// +#define DWordLongToPtrdiffT ULongLongToIntPtr + +// +// DWORDLONG -> size_t conversion +// +#define DWordLongToSizeT ULongLongToUIntPtr + +// +// DWORDLONG -> SSIZE_T conversion +// +#define DWordLongToSSIZET ULongLongToLongPtr + +// +// DWORDLONG -> SIZE_T conversion +// +#define DWordLongToSIZET ULongLongToULongPtr + +// +// ULONG64 -> CHAR conversion +// +#define ULong64ToChar ULongLongToChar + +// +// ULONG64 -> INT8 conversion +// +#define ULong64ToInt8 ULongLongToInt8 + +// +// ULONG64 -> UCHAR conversion +// +#define ULong64ToUChar ULongLongToUChar + +// +// ULONG64 -> UINT8 conversion +// +#define ULong64ToUInt8 ULongLongToUInt8 + +// +// ULONG64 -> BYTE conversion +// +#define ULong64ToByte ULongLongToUInt8 + +// +// ULONG64 -> SHORT conversion +// +#define ULong64ToShort ULongLongToShort + +// +// ULONG64 -> INT16 conversion +// +#define ULong64ToInt16 ULongLongToShort + +// +// ULONG64 -> USHORT conversion +// +#define ULong64ToUShort ULongLongToUShort + +// +// ULONG64 -> UINT16 conversion +// +#define ULong64ToUInt16 ULongLongToUShort + +// +// ULONG64 -> WORD conversion +// +#define ULong64ToWord ULongLongToUShort + +// +// ULONG64 -> INT conversion +// +#define ULong64ToInt ULongLongToInt + +// +// ULONG64 -> INT32 conversion +// +#define ULong64ToInt32 ULongLongToInt + +// +// ULONG64 -> INT_PTR conversion +// +#define ULong64ToIntPtr ULongLongToIntPtr + +// +// ULONG64 -> UINT conversion +// +#define ULong64ToUInt ULongLongToUInt + +// +// ULONG64 -> UINT32 conversion +// +#define ULong64ToUInt32 ULongLongToUInt + +// +// ULONG64 -> UINT_PTR conversion +// +#define ULong64ToUIntPtr ULongLongToUIntPtr + +// +// ULONG64 -> LONG conversion +// +#define ULong64ToLong ULongLongToLong + +// +// ULONG64 -> LONG_PTR conversion +// +#define ULong64ToLongPtr ULongLongToLongPtr + +// +// ULONG64 -> ULONG conversion +// +#define ULong64ToULong ULongLongToULong + +// +// ULONG64 -> ULONG_PTR conversion +// +#define ULong64ToULongPtr ULongLongToULongPtr + +// +// ULONG64 -> DWORD conversion +// +#define ULong64ToDWord ULongLongToULong + +// +// ULONG64 -> DWORD_PTR conversion +// +#define ULong64ToDWordPtr ULongLongToULongPtr + +// +// ULONG64 -> LONGLONG conversion +// +#define ULong64ToLongLong ULongLongToLongLong + +// +// ULONG64 -> LONG64 conversion +// +#define ULong64ToLong64 ULongLongToLongLong + +// +// ULONG64 -> INT64 conversion +// +#define ULong64ToInt64 ULongLongToLongLong + +// +// ULONG64 -> ptrdiff_t conversion +// +#define ULong64ToPtrdiffT ULongLongToIntPtr + +// +// ULONG64 -> size_t conversion +// +#define ULong64ToSizeT ULongLongToUIntPtr + +// +// ULONG64 -> SSIZE_T conversion +// +#define ULong64ToSSIZET ULongLongToLongPtr + +// +// ULONG64 -> SIZE_T conversion +// +#define ULong64ToSIZET ULongLongToULongPtr + +// +// DWORD64 -> CHAR conversion +// +#define DWord64ToChar ULongLongToChar + +// +// DWORD64 -> INT8 conversion +// +#define DWord64ToInt8 ULongLongToInt8 + +// +// DWORD64 -> UCHAR conversion +// +#define DWord64ToUChar ULongLongToUChar + +// +// DWORD64 -> UINT8 conversion +// +#define DWord64ToUInt8 ULongLongToUInt8 + +// +// DWORD64 -> BYTE conversion +// +#define DWord64ToByte ULongLongToUInt8 + +// +// DWORD64 -> SHORT conversion +// +#define DWord64ToShort ULongLongToShort + +// +// DWORD64 -> INT16 conversion +// +#define DWord64ToInt16 ULongLongToShort + +// +// DWORD64 -> USHORT conversion +// +#define DWord64ToUShort ULongLongToUShort + +// +// DWORD64 -> UINT16 conversion +// +#define DWord64ToUInt16 ULongLongToUShort + +// +// DWORD64 -> WORD conversion +// +#define DWord64ToWord ULongLongToUShort + +// +// DWORD64 -> INT conversion +// +#define DWord64ToInt ULongLongToInt + +// +// DWORD64 -> INT32 conversion +// +#define DWord64ToInt32 ULongLongToInt + +// +// DWORD64 -> INT_PTR conversion +// +#define DWord64ToIntPtr ULongLongToIntPtr + +// +// DWORD64 -> UINT conversion +// +#define DWord64ToUInt ULongLongToUInt + +// +// DWORD64 -> UINT32 conversion +// +#define DWord64ToUInt32 ULongLongToUInt + +// +// DWORD64 -> UINT_PTR conversion +// +#define DWord64ToUIntPtr ULongLongToUIntPtr + +// +// DWORD64 -> LONG conversion +// +#define DWord64ToLong ULongLongToLong + +// +// DWORD64 -> LONG_PTR conversion +// +#define DWord64ToLongPtr ULongLongToLongPtr + +// +// DWORD64 -> ULONG conversion +// +#define DWord64ToULong ULongLongToULong + +// +// DWORD64 -> ULONG_PTR conversion +// +#define DWord64ToULongPtr ULongLongToULongPtr + +// +// DWORD64 -> DWORD conversion +// +#define DWord64ToDWord ULongLongToULong + +// +// DWORD64 -> DWORD_PTR conversion +// +#define DWord64ToDWordPtr ULongLongToULongPtr + +// +// DWORD64 -> LONGLONG conversion +// +#define DWord64ToLongLong ULongLongToLongLong + +// +// DWORD64 -> LONG64 conversion +// +#define DWord64ToLong64 ULongLongToLongLong + +// +// DWORD64 -> INT64 conversion +// +#define DWord64ToInt64 ULongLongToLongLong + +// +// DWORD64 -> ptrdiff_t conversion +// +#define DWord64ToPtrdiffT ULongLongToIntPtr + +// +// DWORD64 -> size_t conversion +// +#define DWord64ToSizeT ULongLongToUIntPtr + +// +// DWORD64 -> SSIZE_T conversion +// +#define DWord64ToSSIZET ULongLongToLongPtr + +// +// DWORD64 -> SIZE_T conversion +// +#define DWord64ToSIZET ULongLongToULongPtr + +// +// UINT64 -> CHAR conversion +// +#define UInt64ToChar ULongLongToChar + +// +// UINT64 -> INT8 conversion +// +#define UInt64ToInt8 ULongLongToInt8 + +// +// UINT64 -> UCHAR conversion +// +#define UInt64ToUChar ULongLongToUChar + +// +// UINT64 -> UINT8 conversion +// +#define UInt64ToUInt8 ULongLongToUInt8 + +// +// UINT64 -> BYTE conversion +// +#define UInt64ToByte ULongLongToUInt8 + +// +// UINT64 -> SHORT conversion +// +#define UInt64ToShort ULongLongToShort + +// +// UINT64 -> INT16 conversion +// +// +#define UInt64ToInt16 ULongLongToShort + +// +// UINT64 -> USHORT conversion +// +#define UInt64ToUShort ULongLongToUShort + +// +// UINT64 -> UINT16 conversion +// +#define UInt64ToUInt16 ULongLongToUShort + +// +// UINT64 -> WORD conversion +// +#define UInt64ToWord ULongLongToUShort + +// +// UINT64 -> INT conversion +// +#define UInt64ToInt ULongLongToInt + +// +// UINT64 -> INT32 conversion +// +#define UInt64ToInt32 ULongLongToInt + +// +// UINT64 -> INT_PTR conversion +// +#define UInt64ToIntPtr ULongLongToIntPtr + +// +// UINT64 -> UINT conversion +// +#define UInt64ToUInt ULongLongToUInt + +// +// UINT64 -> UINT32 conversion +// +#define UInt64ToUInt32 ULongLongToUInt + +// +// UINT64 -> UINT_PTR conversion +// +#define UInt64ToUIntPtr ULongLongToUIntPtr + +// +// UINT64 -> LONG conversion +// +#define UInt64ToLong ULongLongToLong + +// +// UINT64 -> LONG_PTR conversion +// +#define UInt64ToLongPtr ULongLongToLongPtr + +// +// UINT64 -> ULONG conversion +// +#define UInt64ToULong ULongLongToULong + +// +// UINT64 -> ULONG_PTR conversion +// +#define UInt64ToULongPtr ULongLongToULongPtr + +// +// UINT64 -> DWORD conversion +// +#define UInt64ToDWord ULongLongToULong + +// +// UINT64 -> DWORD_PTR conversion +// +#define UInt64ToDWordPtr ULongLongToULongPtr + +// +// UINT64 -> LONGLONG conversion +// +#define UInt64ToLongLong ULongLongToLongLong + +// +// UINT64 -> LONG64 conversion +// +#define UInt64ToLong64 ULongLongToLongLong + +// +// UINT64 -> INT64 conversion +// +#define UInt64ToInt64 ULongLongToLongLong + +// +// UINT64 -> ptrdiff_t conversion +// +#define UInt64ToPtrdiffT ULongLongToIntPtr + +// +// UINT64 -> size_t conversion +// +#define UInt64ToSizeT ULongLongToUIntPtr + +// +// UINT64 -> SSIZE_T conversion +// +#define UInt64ToSSIZET ULongLongToLongPtr + +// +// UINT64 -> SIZE_T conversion +// +#define UInt64ToSIZET ULongLongToULongPtr + +// +// ptrdiff_t -> CHAR conversion +// +#define PtrdiffTToChar IntPtrToChar + +// +// ptrdiff_t -> INT8 conversion +// +#define PtrdiffTToInt8 IntPtrToInt8 + +// +// ptrdiff_t -> UCHAR conversion +// +#define PtrdiffTToUChar IntPtrToUChar + +// +// ptrdiff_t -> UINT8 conversion +// +#define PtrdiffTToUInt8 IntPtrToUInt8 + +// +// ptrdiff_t -> BYTE conversion +// +#define PtrdiffTToByte IntPtrToUInt8 + +// +// ptrdiff_t -> SHORT conversion +// +#define PtrdiffTToShort IntPtrToShort + +// +// ptrdiff_t -> INT16 conversion +// +#define PtrdiffTToInt16 IntPtrToShort + +// +// ptrdiff_t -> USHORT conversion +// +#define PtrdiffTToUShort IntPtrToUShort + +// +// ptrdiff_t -> UINT16 conversion +// +#define PtrdiffTToUInt16 IntPtrToUShort + +// +// ptrdiff_t -> WORD conversion +// +#define PtrdiffTToWord IntPtrToUShort + +// +// ptrdiff_t -> INT conversion +// +#define PtrdiffTToInt IntPtrToInt + +// +// ptrdiff_t -> INT32 conversion +// +#define PtrdiffTToInt32 IntPtrToInt + +// +// ptrdiff_t -> UINT conversion +// +#define PtrdiffTToUInt IntPtrToUInt + +// +// ptrdiff_t -> UINT32 conversion +// +#define PtrdiffTToUInt32 IntPtrToUInt + +// +// ptrdiff_t -> UINT_PTR conversion +// +#define PtrdiffTToUIntPtr IntPtrToUIntPtr + +// +// ptrdiff_t -> LONG conversion +// +#define PtrdiffTToLong IntPtrToLong + +// +// ptrdiff_t -> LONG_PTR conversion +// +#define PtrdiffTToLongPtr IntPtrToLongPtr + +// +// ptrdiff_t -> ULONG conversion +// +#define PtrdiffTToULong IntPtrToULong + +// +// ptrdiff_t -> ULONG_PTR conversion +// +#define PtrdiffTToULongPtr IntPtrToULongPtr + +// +// ptrdiff_t -> DWORD conversion +// +#define PtrdiffTToDWord IntPtrToULong + +// +// ptrdiff_t -> DWORD_PTR conversion +// +#define PtrdiffTToDWordPtr IntPtrToULongPtr + +// +// ptrdiff_t -> ULONGLONG conversion +// +#define PtrdiffTToULongLong IntPtrToULongLong + +// +// ptrdiff_t -> DWORDLONG conversion +// +#define PtrdiffTToDWordLong IntPtrToULongLong + +// +// ptrdiff_t -> ULONG64 conversion +// +#define PtrdiffTToULong64 IntPtrToULongLong + +// +// ptrdiff_t -> DWORD64 conversion +// +#define PtrdiffTToDWord64 IntPtrToULongLong + +// +// ptrdiff_t -> UINT64 conversion +// +#define PtrdiffTToUInt64 IntPtrToULongLong + +// +// ptrdiff_t -> size_t conversion +// +#define PtrdiffTToSizeT IntPtrToUIntPtr + +// +// ptrdiff_t -> SIZE_T conversion +// +#define PtrdiffTToSIZET IntPtrToULongPtr + +// +// size_t -> INT8 conversion +// +#define SizeTToInt8 UIntPtrToInt8 + +// +// size_t -> UCHAR conversion +// +#define SizeTToUChar UIntPtrToUChar + +// +// size_t -> CHAR conversion +// +#define SizeTToChar UIntPtrToChar + +// +// size_t -> UINT8 conversion +// +#define SizeTToUInt8 UIntPtrToUInt8 + +// +// size_t -> BYTE conversion +// +#define SizeTToByte UIntPtrToUInt8 + +// +// size_t -> SHORT conversion +// +#define SizeTToShort UIntPtrToShort + +// +// size_t -> INT16 conversion +// +#define SizeTToInt16 UIntPtrToShort + +// +// size_t -> USHORT conversion +// +#define SizeTToUShort UIntPtrToUShort + +// +// size_t -> UINT16 conversion +// +#define SizeTToUInt16 UIntPtrToUShort + +// +// size_t -> WORD +// +#define SizeTToWord UIntPtrToUShort + +// +// size_t -> INT conversion +// +#define SizeTToInt UIntPtrToInt + +// +// size_t -> INT32 conversion +// +#define SizeTToInt32 UIntPtrToInt + +// +// size_t -> INT_PTR conversion +// +#define SizeTToIntPtr UIntPtrToIntPtr + +// +// size_t -> UINT conversion +// +#define SizeTToUInt UIntPtrToUInt + +// +// size_t -> UINT32 conversion +// +#define SizeTToUInt32 UIntPtrToUInt + +// +// size_t -> LONG conversion +// +#define SizeTToLong UIntPtrToLong + +// +// size_t -> LONG_PTR conversion +// +#define SizeTToLongPtr UIntPtrToLongPtr + +// +// size_t -> ULONG conversion +// +#define SizeTToULong UIntPtrToULong + +// +// size_t -> DWORD conversion +// +#define SizeTToDWord UIntPtrToULong + +// +// size_t -> LONGLONG conversion +// +#define SizeTToLongLong UIntPtrToLongLong + +// +// size_t -> LONG64 conversion +// +#define SizeTToLong64 UIntPtrToLongLong + +// +// size_t -> INT64 +// +#define SizeTToInt64 UIntPtrToLongLong + +// +// size_t -> ptrdiff_t conversion +// +#define SizeTToPtrdiffT UIntPtrToIntPtr + +// +// size_t -> SSIZE_T conversion +// +#define SizeTToSSIZET UIntPtrToLongPtr + +// +// SSIZE_T -> INT8 conversion +// +#define SSIZETToInt8 LongPtrToInt8 + +// +// SSIZE_T -> UCHAR conversion +// +#define SSIZETToUChar LongPtrToUChar + +// +// SSIZE_T -> CHAR conversion +// +#define SSIZETToChar LongPtrToChar + +// +// SSIZE_T -> UINT8 conversion +// +#define SSIZETToUInt8 LongPtrToUInt8 + +// +// SSIZE_T -> BYTE conversion +// +#define SSIZETToByte LongPtrToUInt8 + +// +// SSIZE_T -> SHORT conversion +// +#define SSIZETToShort LongPtrToShort + +// +// SSIZE_T -> INT16 conversion +// +#define SSIZETToInt16 LongPtrToShort + +// +// SSIZE_T -> USHORT conversion +// +#define SSIZETToUShort LongPtrToUShort + +// +// SSIZE_T -> UINT16 conversion +// +#define SSIZETToUInt16 LongPtrToUShort + +// +// SSIZE_T -> WORD conversion +// +#define SSIZETToWord LongPtrToUShort + +// +// SSIZE_T -> INT conversion +// +#define SSIZETToInt LongPtrToInt + +// +// SSIZE_T -> INT32 conversion +// +#define SSIZETToInt32 LongPtrToInt + +// +// SSIZE_T -> INT_PTR conversion +// +#define SSIZETToIntPtr LongPtrToIntPtr + +// +// SSIZE_T -> UINT conversion +// +#define SSIZETToUInt LongPtrToUInt + +// +// SSIZE_T -> UINT32 conversion +// +#define SSIZETToUInt32 LongPtrToUInt + +// +// SSIZE_T -> UINT_PTR conversion +// +#define SSIZETToUIntPtr LongPtrToUIntPtr + +// +// SSIZE_T -> LONG conversion +// +#define SSIZETToLong LongPtrToLong + +// +// SSIZE_T -> ULONG conversion +// +#define SSIZETToULong LongPtrToULong + +// +// SSIZE_T -> ULONG_PTR conversion +// +#define SSIZETToULongPtr LongPtrToULongPtr + +// +// SSIZE_T -> DWORD conversion +// +#define SSIZETToDWord LongPtrToULong + +// +// SSIZE_T -> DWORD_PTR conversion +// +#define SSIZETToDWordPtr LongPtrToULongPtr + +// +// SSIZE_T -> ULONGLONG conversion +// +#define SSIZETToULongLong LongPtrToULongLong + +// +// SSIZE_T -> DWORDLONG conversion +// +#define SSIZETToDWordLong LongPtrToULongLong + +// +// SSIZE_T -> ULONG64 conversion +// +#define SSIZETToULong64 LongPtrToULongLong + +// +// SSIZE_T -> DWORD64 conversion +// +#define SSIZETToDWord64 LongPtrToULongLong + +// +// SSIZE_T -> UINT64 conversion +// +#define SSIZETToUInt64 LongPtrToULongLong + +// +// SSIZE_T -> size_t conversion +// +#define SSIZETToSizeT LongPtrToUIntPtr + +// +// SSIZE_T -> SIZE_T conversion +// +#define SSIZETToSIZET LongPtrToULongPtr + +// +// SIZE_T -> INT8 conversion +// +#define SIZETToInt8 ULongPtrToInt8 + +// +// SIZE_T -> UCHAR conversion +// +#define SIZETToUChar ULongPtrToUChar + +// +// SIZE_T -> CHAR conversion +// +#define SIZETToChar ULongPtrToChar + +// +// SIZE_T -> UINT8 conversion +// +#define SIZETToUInt8 ULongPtrToUInt8 + +// +// SIZE_T -> BYTE conversion +// +#define SIZETToByte ULongPtrToUInt8 + +// +// SIZE_T -> SHORT conversion +// +#define SIZETToShort ULongPtrToShort + +// +// SIZE_T -> INT16 conversion +// +#define SIZETToInt16 ULongPtrToShort + +// +// SIZE_T -> USHORT conversion +// +#define SIZETToUShort ULongPtrToUShort + +// +// SIZE_T -> UINT16 conversion +// +#define SIZETToUInt16 ULongPtrToUShort + +// +// SIZE_T -> WORD +// +#define SIZETToWord ULongPtrToUShort + +// +// SIZE_T -> INT conversion +// +#define SIZETToInt ULongPtrToInt + +// +// SIZE_T -> INT32 conversion +// +#define SIZETToInt32 ULongPtrToInt + +// +// SIZE_T -> INT_PTR conversion +// +#define SIZETToIntPtr ULongPtrToIntPtr + +// +// SIZE_T -> UINT conversion +// +#define SIZETToUInt ULongPtrToUInt + +// +// SIZE_T -> UINT32 conversion +// +#define SIZETToUInt32 ULongPtrToUInt + +// +// SIZE_T -> UINT_PTR conversion +// +#define SIZETToUIntPtr ULongPtrToUIntPtr + +// +// SIZE_T -> LONG conversion +// +#define SIZETToLong ULongPtrToLong + +// +// SIZE_T -> LONG_PTR conversion +// +#define SIZETToLongPtr ULongPtrToLongPtr + +// +// SIZE_T -> ULONG conversion +// +#define SIZETToULong ULongPtrToULong + +// +// SIZE_T -> DWORD conversion +// +#define SIZETToDWord ULongPtrToULong + +// +// SIZE_T -> LONGLONG conversion +// +#define SIZETToLongLong ULongPtrToLongLong + +// +// SIZE_T -> LONG64 conversion +// +#define SIZETToLong64 ULongPtrToLongLong + +// +// SIZE_T -> INT64 +// +#define SIZETToInt64 ULongPtrToLongLong + +// +// SIZE_T -> ptrdiff_t conversion +// +#define SIZETToPtrdiffT ULongPtrToIntPtr + +// +// SIZE_T -> SSIZE_T conversion +// +#define SIZETToSSIZET ULongPtrToLongPtr + + +//============================================================================= +// Addition functions +//============================================================================= + +// +// UINT8 addition +// +__inline +HRESULT +UInt8Add( + __inn UINT8 u8Augend, + __inn UINT8 u8Addend, + __outt __deref_out_range(==,u8Augend + u8Addend) UINT8* pu8Result) +{ + HRESULT hr; + + if (((UINT8)(u8Augend + u8Addend)) >= u8Augend) + { + *pu8Result = (UINT8)(u8Augend + u8Addend); + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT addition +// +__inline +HRESULT +UShortAdd( + __inn USHORT usAugend, + __inn USHORT usAddend, + __outt __deref_out_range(==,usAugend + usAddend) USHORT* pusResult) +{ + HRESULT hr; + + if (((USHORT)(usAugend + usAddend)) >= usAugend) + { + *pusResult = (USHORT)(usAugend + usAddend); + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT16 addition +// +#define UInt16Add UShortAdd + +// +// WORD addtition +// +#define WordAdd UShortAdd + +// +// UINT addition +// +__inline +HRESULT +UIntAdd( + __inn UINT uAugend, + __inn UINT uAddend, + __outt __deref_out_range(==,uAugend + uAddend) UINT* puResult) +{ + HRESULT hr; + + if ((uAugend + uAddend) >= uAugend) + { + *puResult = (uAugend + uAddend); + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT32 addition +// +#define UInt32Add UIntAdd + +// +// UINT_PTR addition +// +#ifdef _WIN64 +#define UIntPtrAdd ULongLongAdd +#else +__inline +HRESULT +UIntPtrAdd( + __inn UINT_PTR uAugend, + __inn UINT_PTR uAddend, + __outt __deref_out_range(==,uAugend + uAddend) UINT_PTR* puResult) +{ + HRESULT hr; + + if ((uAugend + uAddend) >= uAugend) + { + *puResult = (uAugend + uAddend); + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// ULONG addition +// +__inline +HRESULT +ULongAdd( + __inn ULONG ulAugend, + __inn ULONG ulAddend, + __outt __deref_out_range(==,ulAugend + ulAddend) ULONG* pulResult) +{ + HRESULT hr; + + if ((ulAugend + ulAddend) >= ulAugend) + { + *pulResult = (ulAugend + ulAddend); + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR addition +// +#ifdef _WIN64 +#define ULongPtrAdd ULongLongAdd +#else +__inline +HRESULT +ULongPtrAdd( + __inn ULONG_PTR ulAugend, + __inn ULONG_PTR ulAddend, + __outt __deref_out_range(==,ulAugend + ulAddend) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if ((ulAugend + ulAddend) >= ulAugend) + { + *pulResult = (ulAugend + ulAddend); + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// DWORD addition +// +#define DWordAdd ULongAdd + +// +// DWORD_PTR addition +// +#ifdef _WIN64 +#define DWordPtrAdd ULongLongAdd +#else +__inline +HRESULT +DWordPtrAdd( + __inn DWORD_PTR dwAugend, + __inn DWORD_PTR dwAddend, + __outt __deref_out_range(==,dwAugend + dwAddend) DWORD_PTR* pdwResult) +{ + HRESULT hr; + + if ((dwAugend + dwAddend) >= dwAugend) + { + *pdwResult = (dwAugend + dwAddend); + hr = S_OK; + } + else + { + *pdwResult = DWORD_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// size_t addition +// +__inline +HRESULT +SizeTAdd( + __inn size_t Augend, + __inn size_t Addend, + __outt __deref_out_range(==,Augend + Addend) size_t* pResult) +{ + HRESULT hr; + + if ((Augend + Addend) >= Augend) + { + *pResult = (Augend + Addend); + hr = S_OK; + } + else + { + *pResult = SIZE_T_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SIZE_T addition +// +#ifdef _WIN64 +#define SIZETAdd ULongLongAdd +#else +__inline +HRESULT +SIZETAdd( + __inn SIZE_T Augend, + __inn SIZE_T Addend, + __outt __deref_out_range(==,Augend + Addend) SIZE_T* pResult) +{ + HRESULT hr; + + if ((Augend + Addend) >= Augend) + { + *pResult = (Augend + Addend); + hr = S_OK; + } + else + { + *pResult = _SIZE_T_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// ULONGLONG addition +// +__inline +HRESULT +ULongLongAdd( + __inn ULONGLONG ullAugend, + __inn ULONGLONG ullAddend, + __outt __deref_out_range(==,ullAugend + ullAddend) ULONGLONG* pullResult) +{ + HRESULT hr; + + if ((ullAugend + ullAddend) >= ullAugend) + { + *pullResult = (ullAugend + ullAddend); + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// DWORDLONG addition +// +#define DWordLongAdd ULongLongAdd + +// +// ULONG64 addition +// +#define ULong64Add ULongLongAdd + +// +// DWORD64 addition +// +#define DWord64Add ULongLongAdd + +// +// UINT64 addition +// +#define UInt64Add ULongLongAdd + + +//============================================================================= +// Subtraction functions +//============================================================================= + +// +// UINT8 subtraction +// +__inline +HRESULT +UInt8Sub( + __inn UINT8 u8Minuend, + __inn UINT8 u8Subtrahend, + __outt __deref_out_range(==,u8Minuend - u8Subtrahend) UINT8* pu8Result) +{ + HRESULT hr; + + if (u8Minuend >= u8Subtrahend) + { + *pu8Result = (UINT8)(u8Minuend - u8Subtrahend); + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT subtraction +// +__inline +HRESULT +UShortSub( + __inn USHORT usMinuend, + __inn USHORT usSubtrahend, + __outt __deref_out_range(==,usMinuend - usSubtrahend) USHORT* pusResult) +{ + HRESULT hr; + + if (usMinuend >= usSubtrahend) + { + *pusResult = (USHORT)(usMinuend - usSubtrahend); + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT16 subtraction +// +#define UInt16Sub UShortSub + +// +// WORD subtraction +// +#define WordSub UShortSub + + +// +// UINT subtraction +// +__inline +HRESULT +UIntSub( + __inn UINT uMinuend, + __inn UINT uSubtrahend, + __outt __deref_out_range(==,uMinuend - uSubtrahend) UINT* puResult) +{ + HRESULT hr; + + if (uMinuend >= uSubtrahend) + { + *puResult = (uMinuend - uSubtrahend); + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT32 subtraction +// +#define UInt32Sub UIntSub + +// +// UINT_PTR subtraction +// +#ifdef _WIN64 +#define UIntPtrSub ULongLongSub +#else +__inline +HRESULT +UIntPtrSub( + __inn UINT_PTR uMinuend, + __inn UINT_PTR uSubtrahend, + __outt __deref_out_range(==,uMinuend - uSubtrahend) UINT_PTR* puResult) +{ + HRESULT hr; + + if (uMinuend >= uSubtrahend) + { + *puResult = (uMinuend - uSubtrahend); + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// ULONG subtraction +// +__inline +HRESULT +ULongSub( + __inn ULONG ulMinuend, + __inn ULONG ulSubtrahend, + __outt __deref_out_range(==,ulMinuend - ulSubtrahend) ULONG* pulResult) +{ + HRESULT hr; + + if (ulMinuend >= ulSubtrahend) + { + *pulResult = (ulMinuend - ulSubtrahend); + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR subtraction +// +#ifdef _WIN64 +#define ULongPtrSub ULongLongSub +#else +__inline +HRESULT +ULongPtrSub( + __inn ULONG_PTR ulMinuend, + __inn ULONG_PTR ulSubtrahend, + __outt __deref_out_range(==,ulMinuend - ulSubtrahend) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (ulMinuend >= ulSubtrahend) + { + *pulResult = (ulMinuend - ulSubtrahend); + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + + +// +// DWORD subtraction +// +#define DWordSub ULongSub + +// +// DWORD_PTR subtraction +// +#ifdef _WIN64 +#define DWordPtrSub ULongLongSub +#else +__inline +HRESULT +DWordPtrSub( + __inn DWORD_PTR dwMinuend, + __inn DWORD_PTR dwSubtrahend, + __outt __deref_out_range(==,dwMinuend - dwSubtrahend) DWORD_PTR* pdwResult) +{ + HRESULT hr; + + if (dwMinuend >= dwSubtrahend) + { + *pdwResult = (dwMinuend - dwSubtrahend); + hr = S_OK; + } + else + { + *pdwResult = DWORD_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// size_t subtraction +// +__inline +HRESULT +SizeTSub( + __inn size_t Minuend, + __inn size_t Subtrahend, + __outt __deref_out_range(==,Minuend - Subtrahend) size_t* pResult) +{ + HRESULT hr; + + if (Minuend >= Subtrahend) + { + *pResult = (Minuend - Subtrahend); + hr = S_OK; + } + else + { + *pResult = SIZE_T_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SIZE_T subtraction +// +#ifdef _WIN64 +#define SIZETSub ULongLongSub +#else +__inline +HRESULT +SIZETSub( + __inn SIZE_T Minuend, + __inn SIZE_T Subtrahend, + __outt __deref_out_range(==,Minuend - Subtrahend) SIZE_T* pResult) +{ + HRESULT hr; + + if (Minuend >= Subtrahend) + { + *pResult = (Minuend - Subtrahend); + hr = S_OK; + } + else + { + *pResult = _SIZE_T_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// ULONGLONG subtraction +// +__inline +HRESULT +ULongLongSub( + __inn ULONGLONG ullMinuend, + __inn ULONGLONG ullSubtrahend, + __outt __deref_out_range(==,ullMinuend - ullSubtrahend) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (ullMinuend >= ullSubtrahend) + { + *pullResult = (ullMinuend - ullSubtrahend); + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// DWORDLONG subtraction +// +#define DWordLongSub ULongLongSub + +// +// ULONG64 subtraction +// +#define ULong64Sub ULongLongSub + +// +// DWORD64 subtraction +// +#define DWord64Sub ULongLongSub + +// +// UINT64 subtraction +// +#define UInt64Sub ULongLongSub + + +//============================================================================= +// Multiplication functions +//============================================================================= + +// +// UINT8 multiplication +// +__inline +HRESULT +UInt8Mult( + __inn UINT8 u8Multiplicand, + __inn UINT8 u8Multiplier, + __outt __deref_out_range(==,u8Multiplier * u8Multiplicand) UINT8* pu8Result) +{ + UINT uResult = ((UINT)u8Multiplicand) * ((UINT)u8Multiplier); + + return UIntToUInt8(uResult, pu8Result); +} + +// +// USHORT multiplication +// +__inline +HRESULT +UShortMult( + __inn USHORT usMultiplicand, + __inn USHORT usMultiplier, + __outt __deref_out_range(==,usMultiplier * usMultiplicand)USHORT* pusResult) +{ + ULONG ulResult = ((ULONG)usMultiplicand) * ((ULONG)usMultiplier); + + return ULongToUShort(ulResult, pusResult); +} + +// +// UINT16 multiplication +// +#define UInt16Mult UShortMult + +// +// WORD multiplication +// +#define WordMult UShortMult + +// +// UINT multiplication +// +__inline +HRESULT +UIntMult( + __inn UINT uMultiplicand, + __inn UINT uMultiplier, + __outt __deref_out_range(==,uMultiplier * uMultiplicand) UINT* puResult) +{ + ULONGLONG ull64Result = UInt32x32To64(uMultiplicand, uMultiplier); + + return ULongLongToUInt(ull64Result, puResult); +} + +// +// UINT32 multiplication +// +#define UInt32Mult UIntMult + +// +// UINT_PTR multiplication +// +#ifdef _WIN64 +#define UIntPtrMult ULongLongMult +#else +__inline +HRESULT +UIntPtrMult( + __inn UINT_PTR uMultiplicand, + __inn UINT_PTR uMultiplier, + __outt __deref_out_range(==,uMultiplier * uMultiplicand) UINT_PTR* puResult) +{ + ULONGLONG ull64Result = UInt32x32To64(uMultiplicand, uMultiplier); + + return ULongLongToUIntPtr(ull64Result, puResult); +} +#endif // _WIN64 + +// +// ULONG multiplication +// +__inline +HRESULT +ULongMult( + __inn ULONG ulMultiplicand, + __inn ULONG ulMultiplier, + __outt __deref_out_range(==,ulMultiplier * ulMultiplicand) ULONG* pulResult) +{ + ULONGLONG ull64Result = UInt32x32To64(ulMultiplicand, ulMultiplier); + + return ULongLongToULong(ull64Result, pulResult); +} + +// +// ULONG_PTR multiplication +// +#ifdef _WIN64 +#define ULongPtrMult ULongLongMult +#else +__inline +HRESULT +ULongPtrMult( + __inn ULONG_PTR ulMultiplicand, + __inn ULONG_PTR ulMultiplier, + __outt __deref_out_range(==,ulMultiplier * ulMultiplicand) ULONG_PTR* pulResult) +{ + ULONGLONG ull64Result = UInt32x32To64(ulMultiplicand, ulMultiplier); + + return ULongLongToULongPtr(ull64Result, (unsigned long *)pulResult); +} +#endif // _WIN64 + +// +// DWORD multiplication +// +#define DWordMult ULongMult + +// +// DWORD_PTR multiplication +// +#ifdef _WIN64 +#define DWordPtrMult ULongLongMult +#else +__inline +HRESULT +DWordPtrMult( + __inn DWORD_PTR dwMultiplicand, + __inn DWORD_PTR dwMultiplier, + __outt __deref_out_range(==,dwMultiplier * dwMultiplicand) DWORD_PTR* pdwResult) +{ + ULONGLONG ull64Result = UInt32x32To64(dwMultiplicand, dwMultiplier); + + return ULongLongToDWordPtr(ull64Result, (unsigned long *)pdwResult); +} +#endif // _WIN64 + +// +// size_t multiplication +// + +#ifdef _WIN64 +#define SizeTMult ULongLongMult +#else +__inline +HRESULT +SizeTMult( + __inn size_t Multiplicand, + __inn size_t Multiplier, + __outt __deref_out_range(==,Multiplier * Multiplicand) UINT* pResult) +{ + ULONGLONG ull64Result = UInt32x32To64(Multiplicand, Multiplier); + + return ULongLongToSizeT(ull64Result, pResult); +} +#endif // _WIN64 + +// +// SIZE_T multiplication +// +#ifdef _WIN64 +#define SIZETMult ULongLongMult +#else +__inline +HRESULT +SIZETMult( + __inn SIZE_T Multiplicand, + __inn SIZE_T Multiplier, + __outt __deref_out_range(==,Multiplier * Multiplicand) SIZE_T* pResult) +{ + ULONGLONG ull64Result = UInt32x32To64(Multiplicand, Multiplier); + + return ULongLongToSIZET(ull64Result, (unsigned long *)pResult); +} +#endif // _WIN64 + +// +// ULONGLONG multiplication +// +__inline +HRESULT +ULongLongMult( + __inn ULONGLONG ullMultiplicand, + __inn ULONGLONG ullMultiplier, + __outt __deref_out_range(==,ullMultiplier * ullMultiplicand) ULONGLONG* pullResult) +{ + HRESULT hr; +#ifdef _AMD64_ + ULONGLONG u64ResultHigh; + ULONGLONG u64ResultLow; + + u64ResultLow = UnsignedMultiply128(ullMultiplicand, ullMultiplier, &u64ResultHigh); + if (u64ResultHigh == 0) + { + *pullResult = u64ResultLow; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } +#else + // 64x64 into 128 is like 32.32 x 32.32. + // + // a.b * c.d = a*(c.d) + .b*(c.d) = a*c + a*.d + .b*c + .b*.d + // back in non-decimal notation where A=a*2^32 and C=c*2^32: + // A*C + A*d + b*C + b*d + // So there are four components to add together. + // result = (a*c*2^64) + (a*d*2^32) + (b*c*2^32) + (b*d) + // + // a * c must be 0 or there would be bits in the high 64-bits + // a * d must be less than 2^32 or there would be bits in the high 64-bits + // b * c must be less than 2^32 or there would be bits in the high 64-bits + // then there must be no overflow of the resulting values summed up. + + ULONG dw_a; + ULONG dw_b; + ULONG dw_c; + ULONG dw_d; + ULONGLONG ad = 0; + ULONGLONG bc = 0; + ULONGLONG bd = 0; + ULONGLONG ullResult = 0; + + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + + dw_a = (ULONG)(ullMultiplicand >> 32); + dw_c = (ULONG)(ullMultiplier >> 32); + + // common case -- if high dwords are both zero, no chance for overflow + if ((dw_a == 0) && (dw_c == 0)) + { + dw_b = (DWORD)ullMultiplicand; + dw_d = (DWORD)ullMultiplier; + + *pullResult = (((ULONGLONG)dw_b) * (ULONGLONG)dw_d); + hr = S_OK; + } + else + { + // a * c must be 0 or there would be bits set in the high 64-bits + if ((dw_a == 0) || + (dw_c == 0)) + { + dw_d = (DWORD)ullMultiplier; + + // a * d must be less than 2^32 or there would be bits set in the high 64-bits + ad = (((ULONGLONG)dw_a) * (ULONGLONG)dw_d); + if ((ad & 0xffffffff00000000LL) == 0) + { + dw_b = (DWORD)ullMultiplicand; + + // b * c must be less than 2^32 or there would be bits set in the high 64-bits + bc = (((ULONGLONG)dw_b) * (ULONGLONG)dw_c); + if ((bc & 0xffffffff00000000LL) == 0) + { + // now sum them all up checking for overflow. + // shifting is safe because we already checked for overflow above + if (SUCCEEDED(ULongLongAdd(bc << 32, ad << 32, &ullResult))) + { + // b * d + bd = (((ULONGLONG)dw_b) * (ULONGLONG)dw_d); + + if (SUCCEEDED(ULongLongAdd(ullResult, bd, &ullResult))) + { + *pullResult = ullResult; + hr = S_OK; + } + } + } + } + } + } + + if (FAILED(hr)) + { + *pullResult = ULONGLONG_ERROR; + } +#endif // _AMD64_ + + return hr; +} + +// +// DWORDLONG multiplication +// +#define DWordLongMult ULongLongMult + +// +// ULONG64 multiplication +// +#define ULong64Mult ULongLongMult + +// +// DWORD64 multiplication +// +#define DWord64Mult ULongLongMult + +// +// UINT64 multiplication +// +#define UInt64Mult ULongLongMult + +// +// Macros that are no longer used in this header but which clients may +// depend on being defined here. +// +#define LOWORD(_dw) ((WORD)(((DWORD_PTR)(_dw)) & 0xffff)) +#define HIWORD(_dw) ((WORD)((((DWORD_PTR)(_dw)) >> 16) & 0xffff)) +#define LODWORD(_qw) ((DWORD)(_qw)) +#define HIDWORD(_qw) ((DWORD)(((_qw) >> 32) & 0xffffffff)) + +#endif // XPLAT_INTSAFE_H diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h new file mode 100644 index 00000000..f7cd1716 --- /dev/null +++ b/source/shared/xplat_winerror.h @@ -0,0 +1,132 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: xplat_winerror.h +// +// Contents: Contains the minimal definitions to build on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef XPLAT_WINERROR_H +#define XPLAT_WINERROR_H + +#define NOERROR 0 +#define WAIT_TIMEOUT 258L // dderror +#define S_OK ((HRESULT)0L) +#define S_FALSE ((HRESULT)1L) +#define E_NOTIMPL ((HRESULT) 0x80004001L) +#define E_FAIL ((HRESULT) 0x80004005L) +#define E_ABORT _HRESULT_TYPEDEF_(0x80004004L) +#define ERROR_HANDLE_EOF 38L +#define E_UNEXPECTED ((HRESULT) 0x8000FFFFL) +#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0) +#define FAILED(hr) (((HRESULT)(hr)) < 0) +#define ERROR_SUCCESS 0L +#define ERROR_ACCESS_DENIED 5L +#define ERROR_TIMEOUT 1460L +#define E_POINTER _HRESULT_TYPEDEF_(0x80004003L) +#define _HRESULT_TYPEDEF_(_sc) ((HRESULT)_sc) +#define E_OUTOFMEMORY _HRESULT_TYPEDEF_(0x8007000EL) +#define NO_ERROR 0L +#define ERROR_CANCELLED 1223L +#define E_INVALIDARG _HRESULT_TYPEDEF_(0x80070057L) +#define DISP_E_TYPEMISMATCH _HRESULT_TYPEDEF_(0x80020005L) +#define DISP_E_OVERFLOW _HRESULT_TYPEDEF_(0x8002000AL) +#define ERROR_INSUFFICIENT_BUFFER 122L // dderror +#define FACILITY_WIN32 7 +#define __HRESULT_FROM_WIN32(x) ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : ((HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000))) +#define HRESULT_FROM_WIN32(x) __HRESULT_FROM_WIN32(x) +#define SEVERITY_ERROR 1 +#define FACILITY_ITF 4 +#define MAKE_HRESULT(sev,fac,code) \ + ((HRESULT) (((windowsULong_t)(sev)<<31) | ((windowsULong_t)(fac)<<16) | ((windowsULong_t)(code))) ) +#define ERROR_INVALID_DATA 13L +#define ERROR_INVALID_PARAMETER 87L // dderror +#define ERROR_POSSIBLE_DEADLOCK 1131L +#define ERROR_INVALID_FLAGS 1004L +#define ERROR_NO_UNICODE_TRANSLATION 1113L + +#define ERROR_FILE_NOT_FOUND 2L +#define ERROR_PATH_NOT_FOUND 3L +#define ERROR_TOO_MANY_OPEN_FILES 4L +#define E_NOINTERFACE _HRESULT_TYPEDEF_(0x80004002L) + +#define ERROR_MOD_NOT_FOUND 126L +#define ERROR_NO_MORE_FILES 18L +#define ERROR_FILE_EXISTS 80L +#define ERROR_ALREADY_EXISTS 183L +#define ERROR_SHARING_VIOLATION 32L +#define SCODE_CODE(sc) ((sc) & 0xFFFF) +#define ERROR_READ_FAULT 30L +#define ERROR_INTERNAL_ERROR 1359L +//---------------------------------------------------------------------------- +// Error codes used by SNI +// +#define ERROR_NOT_ENOUGH_MEMORY 8L // dderror +#define ERROR_IO_PENDING 997L +#define WSA_IO_PENDING (ERROR_IO_PENDING) +#define WSAHOST_NOT_FOUND 11001L +#define WSATRY_AGAIN 11002L +#define WSANO_RECOVERY 11003L +#define WSANO_DATA 11004L +#define WSATYPE_NOT_FOUND 10109L +#define WSA_NOT_ENOUGH_MEMORY 8L +#define WSAEINTR 10004L +#define WSAEACCES 10013L +#define WSAEFAULT 10014L +#define WSAEINVAL 10022L +#define WSAEMFILE 10024L +#define WSAEWOULDBLOCK 10035L +#define WSAEALREADY 10037L +#define WSAENOTSOCK 10038L +#define WSAEMSGSIZE 10040L +#define WSAENOPROTOOPT 10042L +#define WSAEPROTONOSUPPORT 10043L +#define WSAESOCKTNOSUPPORT 10044L +#define WSAEOPNOTSUPP 10045L +#define WSAEAFNOSUPPORT 10047L +#define WSAEADDRINUSE 10048L +#define WSAEADDRNOTAVAIL 10049L +#define WSAENETUNREACH 10051L +#define WSAECONNRESET 10054L +#define WSAENOBUFS 10055L +#define WSAEISCONN 10056L +#define WSAENOTCONN 10057L +#define WSAETIMEDOUT 10060L +#define WSAECONNREFUSED 10061L +#define WSANOTINITIALISED 10093L +#define ERROR_OUTOFMEMORY 14L +#define ERROR_NOT_SUPPORTED 50L +#define ERROR_BUFFER_OVERFLOW 111L +#define ERROR_MAX_THRDS_REACHED 164L +#define ERROR_INVALID_OPERATION 4317L +#define ERROR_INVALID_STATE 5023L +#define SEC_E_BAD_BINDINGS _HRESULT_TYPEDEF_(0x80090346L) +#define ERROR_MORE_DATA 234L // dderror +#define ERROR_ARITHMETIC_OVERFLOW 534L +#define SEC_E_INCOMPLETE_MESSAGE _HRESULT_TYPEDEF_(0x80090318L) +#define ERROR_OPERATION_ABORTED 995L +#define ERROR_CONNECTION_REFUSED 1225L +#define SEC_E_OK ((HRESULT)0x00000000L) +#define SEC_E_UNSUPPORTED_FUNCTION _HRESULT_TYPEDEF_(0x80090302L) +#define SEC_E_TARGET_UNKNOWN _HRESULT_TYPEDEF_(0x80090303L) +#define SEC_E_OUT_OF_SEQUENCE _HRESULT_TYPEDEF_(0x80090310L) +#define SEC_E_INVALID_TOKEN _HRESULT_TYPEDEF_(0x80090308L) +#define SEC_I_CONTINUE_NEEDED _HRESULT_TYPEDEF_(0x00090312L) +#define ERROR_INVALID_FUNCTION 1L // dderror +#define TRUST_E_TIME_STAMP _HRESULT_TYPEDEF_(0x80096005L) +#define CRYPT_E_NOT_FOUND _HRESULT_TYPEDEF_(0x80092004L) +#define WAIT_TIMEOUT 258L // dderror + + +#endif // XPLAT_WINERROR_H diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h new file mode 100644 index 00000000..0f21a44a --- /dev/null +++ b/source/shared/xplat_winnls.h @@ -0,0 +1,144 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: xplat_winnls.h +// +// Contents: Contains the minimal definitions to build on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef XPLAT_WINNLS_H +#define XPLAT_WINNLS_H + +#include +#include "typedefs_for_linux.h" + +struct threadlocaleinfostruct; +struct threadmbcinfostruct; +typedef struct threadlocaleinfostruct * pthreadlocinfo; +typedef struct threadmbcinfostruct * pthreadmbcinfo; + +typedef struct localeinfo_struct +{ + pthreadlocinfo locinfo; + pthreadmbcinfo mbcinfo; +} _locale_tstruct, *_locale_t; + +#define LOCALE_SDECIMAL 0x0000000E // decimal separator +#define LOCALE_SCURRENCY 0x00000014 // local monetary symbol +#define LOCALE_SMONDECIMALSEP 0x00000016 // monetary decimal separator +#define LOCALE_SMONTHOUSANDSEP 0x00000017 // monetary thousand separator +#define LOCALE_SMONGROUPING 0x00000018 // monetary grouping +#define LOCALE_ILDATE 0x00000022 // long date format ordering (derived from LOCALE_SLONGDATE, use that instead) +#define LOCALE_ITIME 0x00000023 // time format specifier (derived from LOCALE_STIMEFORMAT, use that instead) +#define LOCALE_SABBREVMONTHNAME1 0x00000044 // abbreviated name for January + +#define LOCALE_IDEFAULTLANGUAGE 0x00000009 // default language id +#define LOCALE_IDEFAULTCOUNTRY 0x0000000A // default country/region code, deprecated +#define LOCALE_IDEFAULTCODEPAGE 0x0000000B // default oem code page +#define LOCALE_IDEFAULTANSICODEPAGE 0x00001004 // default ansi code page +#define LOCALE_IDEFAULTMACCODEPAGE 0x00001011 // default mac code page + +#define LOCALE_STIMEFORMAT 0x00001003 // time format string, eg "HH:mm:ss" + +typedef DWORD LCTYPE; + +#define NORM_IGNORECASE 0x00000001 // ignore case +#define NORM_IGNORENONSPACE 0x00000002 // ignore nonspacing chars +#define NORM_IGNORESYMBOLS 0x00000004 // ignore symbols + +#define LINGUISTIC_IGNORECASE 0x00000010 // linguistically appropriate 'ignore case' +#define LINGUISTIC_IGNOREDIACRITIC 0x00000020 // linguistically appropriate 'ignore nonspace' + +#define NORM_IGNOREKANATYPE 0x00010000 // ignore kanatype +#define NORM_IGNOREWIDTH 0x00020000 // ignore width +#define NORM_LINGUISTIC_CASING 0x08000000 // use linguistic rules for casing + + +#define NORM_IGNORECASE 0x00000001 // ignore case + +#define MB_PRECOMPOSED 0x00000001 // use precomposed chars +#define MB_COMPOSITE 0x00000002 // use composite chars +#define MB_USEGLYPHCHARS 0x00000004 // use glyph chars, not ctrl chars +#define MB_ERR_INVALID_CHARS 0x00000008 // error for invalid chars + +#define WC_COMPOSITECHECK 0x00000200 // convert composite to precomposed +#define WC_DISCARDNS 0x00000010 // discard non-spacing chars +#define WC_SEPCHARS 0x00000020 // generate separate chars +#define WC_DEFAULTCHAR 0x00000040 // replace w/ default char + + +typedef WORD LANGID; + + + + +#define NLS_VALID_LOCALE_MASK 0x000fffff + +#define MAKELANGID(p, s) ((((WORD )(s)) << 10) | (WORD )(p)) +#define MAKELCID(lgid, srtid) ((DWORD)((((DWORD)((WORD )(srtid))) << 16) | \ + ((DWORD)((WORD )(lgid))))) +#define LANG_NEUTRAL 0x00 +#define SUBLANG_DEFAULT 0x01 // user default +#define SUBLANG_SYS_DEFAULT 0x02 // system default +#define SORT_DEFAULT 0x0 // sorting default +#define LANG_USER_DEFAULT (MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)) +#define LOCALE_USER_DEFAULT (MAKELCID(LANG_USER_DEFAULT, SORT_DEFAULT)) +#define SUBLANG_ENGLISH_US 0x01 // English (USA) +#define LANG_ENGLISH 0x09 +#define LOCALE_ENGLISH_US MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) +#define LANG_SYSTEM_DEFAULT (MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT)) +#define LOCALE_SYSTEM_DEFAULT (MAKELCID(LANG_SYSTEM_DEFAULT, SORT_DEFAULT)) + +BOOL +WINAPI +IsDBCSLeadByte( + __inn BYTE TestChar); + + +#ifdef MPLAT_UNIX +// XPLAT_ODBC_TODO: VSTS 718708 Localization +// Find way to remove this +LCID GetUserDefaultLCID(); +#endif + + +BOOL IsValidCodePage(UINT CodePage); + +#define HIGH_SURROGATE_START 0xd800 +#define HIGH_SURROGATE_END 0xdbff +#define LOW_SURROGATE_START 0xdc00 +#define LOW_SURROGATE_END 0xdfff +#define IS_HIGH_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && ((wch) <= HIGH_SURROGATE_END)) +#define IS_LOW_SURROGATE(wch) (((wch) >= LOW_SURROGATE_START) && ((wch) <= LOW_SURROGATE_END)) + +int +GetLocaleInfoA( + __inn LCID Locale, + __inn LCTYPE LCType, + __out_ecount_opt(cchData) LPSTR lpLCData, + __inn int cchData); +int +GetLocaleInfoW( + __inn LCID Locale, + __inn LCTYPE LCType, + __out_ecount_opt(cchData) LPWSTR lpLCData, + __inn int cchData); +#ifdef UNICODE +#define GetLocaleInfo GetLocaleInfoW +#else +#define GetLocaleInfo GetLocaleInfoA +#endif // !UNICODE + + +#endif // XPLAT_WINNLS_H diff --git a/source/sqlsrv/CREDITS b/source/sqlsrv/CREDITS new file mode 100644 index 00000000..44211664 --- /dev/null +++ b/source/sqlsrv/CREDITS @@ -0,0 +1 @@ +Microsoft Drivers for PHP for SQL Server diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 new file mode 100644 index 00000000..255470d4 --- /dev/null +++ b/source/sqlsrv/config.m4 @@ -0,0 +1,42 @@ +PHP_ARG_ENABLE(sqlsrv, whether to enable sqlsrv functions, +[ --disable-sqlsrv Disable sqlsrv functions], yes) + +if test "$PHP_SQLSRV" != "no"; then + sqlsrv_src_class="\ + conn.cpp \ + util.cpp \ + init.cpp \ + stmt.cpp \ + " + shared_src_class="\ + shared/core_conn.cpp \ + shared/core_results.cpp \ + shared/core_stream.cpp \ + shared/core_init.cpp \ + shared/core_stmt.cpp \ + shared/core_util.cpp \ + shared/FormattedPrint.cpp \ + shared/localizationimpl.cpp \ + shared/StringFunctions.cpp \ + " + AC_MSG_CHECKING([for SQLSRV headers]) + if test -f $srcdir/ext/sqlsrv/shared/core_sqlsrv.h; then + sqlsrv_inc_path=$srcdir/ext/sqlsrv/shared/ + elif test -f $srcdir/shared/core_sqlsrv.h; then + sqlsrv_inc_path=$srcdir/shared/ + else + AC_MSG_ERROR([Cannot find SQLSRV headers]) + fi + AC_MSG_RESULT($sqlsrv_inc_path) + + CXXFLAGS="$CXXFLAGS -std=c++11" + PHP_REQUIRE_CXX() + PHP_ADD_LIBRARY(stdc++, 1, SQLSRV_SHARED_LIBADD) + PHP_ADD_LIBRARY(odbc, 1, SQLSRV_SHARED_LIBADD) + PHP_ADD_LIBRARY(odbcinst, 1, SQLSRV_SHARED_LIBADD) + PHP_SUBST(SQLSRV_SHARED_LIBADD) + AC_DEFINE(HAVE_SQLSRV, 1, [ ]) + PHP_ADD_INCLUDE([$sqlsrv_inc_path]) + PHP_NEW_EXTENSION(sqlsrv, $sqlsrv_src_class $shared_src_class, $ext_shared,,-std=c++11) + PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1) +fi diff --git a/sqlsrv/config.w32 b/source/sqlsrv/config.w32 similarity index 59% rename from sqlsrv/config.w32 rename to source/sqlsrv/config.w32 index 1fb9fa5b..2b534aef 100644 --- a/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -1,39 +1,42 @@ -//---------------------------------------------------------------------------------------------------------------------------------- -// File: config.w32 -// -// Contents: JScript build configuration used by buildconf.bat -// -// Microsoft Drivers 4.0 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -ARG_ENABLE("sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (SQLSRV driver)", "no"); - -if( PHP_SQLSRV != "no" ) { - - sqlsrv_src = "conn.cpp init.cpp stmt.cpp util.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp"; - - if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") && - CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")) { - - EXTENSION("sqlsrv", sqlsrv_src, PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1") - - CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_SQLSRV_ODBC'); - CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_SQLSRV_ODBC'); - ADD_FLAG( 'LDFLAGS_SQLSRV', '/NXCOMPAT /DYNAMICBASE /debug' ); - ADD_FLAG( 'CFLAGS_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' ); - ADD_FLAG( 'CFLAGS_SQLSRV', '/EHsc' ); - ADD_FLAG( 'CFLAGS_SQLSRV', '/GS' ); - ADD_FLAG( 'CFLAGS_SQLSRV', '/Zi' ); - } -} +//---------------------------------------------------------------------------------------------------------------------------------- +// File: config.w32 +// +// Contents: JScript build configuration used by buildconf.bat +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +ARG_ENABLE("sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (SQLSRV driver)", "no"); + +if( PHP_SQLSRV != "no" ) { + + sqlsrv_src_class = " conn.cpp init.cpp stmt.cpp util.cpp "; + shared_src_class = " core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp "; + + if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") && + CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")&& + CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_SQLSRV", configure_module_dirname + "\\shared")) { + ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "sqlsrv" ); + CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_SQLSRV_ODBC"); + CHECK_HEADER_ADD_INCLUDE("sqlext.h", "CFLAGS_SQLSRV_ODBC"); + ADD_FLAG( "LDFLAGS_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug" ); + ADD_FLAG( "CFLAGS_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" ); + ADD_FLAG( "CFLAGS_SQLSRV", "/EHsc" ); + ADD_FLAG( "CFLAGS_SQLSRV", "/GS" ); + ADD_FLAG( "CFLAGS_SQLSRV", "/Zi" ); + EXTENSION("sqlsrv", sqlsrv_src_class , PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); + } else { + WARNING("sqlsrv not enabled; libraries and headers not found"); + } +} diff --git a/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp similarity index 95% rename from sqlsrv/conn.cpp rename to source/sqlsrv/conn.cpp index 2e7f1b2b..59a2479f 100644 --- a/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -1,1303 +1,1299 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: conn.cpp -// -// Contents: Routines that use connection handles -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "php_sqlsrv.h" -#include -#include -#include - -#include -#include - -// *** internal variables and constants *** - -namespace { - -// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros -unsigned int current_log_subsystem = LOG_CONN; - -struct date_as_string_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - TSRMLS_C; // show as used to avoid a warning - - ss_sqlsrv_conn* ss_conn = static_cast( conn ); - - if( zend_is_true( value )) { - ss_conn->date_as_string = true; - } - else { - ss_conn->date_as_string = false; - } - } -}; - -struct conn_char_set_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - convert_to_string( value ); - const char* encoding = Z_STRVAL_P( value ); - size_t encoding_len = Z_STRLEN_P( value ); - - zend_ulong index = -1; - zend_string* key = NULL; - void* ss_encoding_temp = NULL; - - ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, ss_encoding_temp ) { - sqlsrv_encoding* ss_encoding = reinterpret_cast( ss_encoding_temp ); - ss_encoding_temp = NULL; - if (!strnicmp( encoding, ss_encoding->iana, encoding_len )) { - - if ( ss_encoding->not_for_connection ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); - } - - conn->set_encoding( static_cast(ss_encoding->code_page )); - return; - } - } ZEND_HASH_FOREACH_END(); - - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); - } -}; - -struct bool_conn_str_func { - - static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ) - { - TSRMLS_C; - char const* val_str; - if( zend_is_true( value )) { - val_str = "yes"; - } - else { - val_str = "no"; - } - conn_str += option->odbc_name; - conn_str += "={"; - conn_str += val_str; - conn_str += "};"; - } -}; - -template -struct int_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_LVAL_P( value )), SQL_IS_UINTEGER TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } - } -}; - -template -struct bool_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - core::SQLSetConnectAttr(conn, Attr, reinterpret_cast((zend_long)zend_is_true(value)), - SQL_IS_UINTEGER TSRMLS_CC); - - } - catch( core::CoreException& ) { - throw; - } - - } -}; - -//// *** internal functions *** - -void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ); -void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ char** uid, _Out_ char** pwd, - _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ); -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ); -void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, - HashTable* options_ht, zval* data TSRMLS_DC ); -void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, HashTable* options_ht, zval* data TSRMLS_DC ); -int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC ); -int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ); - -} - -// constants for parameters used by process_params function(s) -int ss_sqlsrv_conn::descriptor; -char* ss_sqlsrv_conn::resource_name = "ss_sqlsrv_conn"; - -// connection specific parameter proccessing. Use the generic function specialised to return a connection -// resource. -#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \ - rsrc = process_params( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, __VA_ARGS__ ); \ - if( rsrc == NULL ) { \ - RETURN_FALSE; \ - } - -namespace SSStmtOptionNames { - const char QUERY_TIMEOUT[]= "QueryTimeout"; - const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec"; - const char SCROLLABLE[] = "Scrollable"; - const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT; -} - -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 ApplicationIntent[] = "ApplicationIntent"; -const char AttachDBFileName[] = "AttachDbFileName"; -const char CharacterSet[] = "CharacterSet"; -const char ConnectionPooling[] = "ConnectionPooling"; -const char Database[] = "Database"; -const char DateAsString[] = "ReturnDatesAsStrings"; -const char Encrypt[] = "Encrypt"; -const char Failover_Partner[] = "Failover_Partner"; -const char LoginTimeout[] = "LoginTimeout"; -const char MARS_Option[] = "MultipleActiveResultSets"; -const char MultiSubnetFailover[] = "MultiSubnetFailover"; -const char PWD[] = "PWD"; -const char QuotedId[] = "QuotedId"; -const char TraceFile[] = "TraceFile"; -const char TraceOn[] = "TraceOn"; -const char TrustServerCertificate[] = "TrustServerCertificate"; -const char TransactionIsolation[] = "TransactionIsolation"; -const char UID[] = "UID"; -const char WSID[] = "WSID"; - -} - -enum SS_CONN_OPTIONS { - - SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC, -}; - -//List of all statement options supported by this driver -const stmt_option SS_STMT_OPTS[] = { - { - SSStmtOptionNames::QUERY_TIMEOUT, - sizeof( SSStmtOptionNames::QUERY_TIMEOUT ), - SQLSRV_STMT_OPTION_QUERY_TIMEOUT, - std::unique_ptr( new stmt_option_query_timeout ) - }, - { - SSStmtOptionNames::SEND_STREAMS_AT_EXEC, - sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ), - SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, - std::unique_ptr( new stmt_option_send_at_exec ) - }, - { - SSStmtOptionNames::SCROLLABLE, - sizeof( SSStmtOptionNames::SCROLLABLE ), - SQLSRV_STMT_OPTION_SCROLLABLE, - std::unique_ptr( new stmt_option_scrollable ) - }, - { - SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE, - sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ), - SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, - std::unique_ptr( new stmt_option_buffered_query_limit ) - }, - { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, -}; - - -// List of all connection options supported by this driver. -const connection_option SS_CONN_OPTS[] = { - { - SSConnOptionNames::APP, - sizeof( SSConnOptionNames::APP ), - SQLSRV_CONN_OPTION_APP, - ODBCConnOptions::APP, - sizeof( ODBCConnOptions::APP ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::ApplicationIntent, - sizeof( SSConnOptionNames::ApplicationIntent ), - SQLSRV_CONN_OPTION_APPLICATION_INTENT, - ODBCConnOptions::ApplicationIntent, - sizeof( ODBCConnOptions::ApplicationIntent ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::AttachDBFileName, - sizeof( SSConnOptionNames::AttachDBFileName ), - SQLSRV_CONN_OPTION_ATTACHDBFILENAME, - ODBCConnOptions::AttachDBFileName, - sizeof( ODBCConnOptions::AttachDBFileName ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::CharacterSet, - sizeof( SSConnOptionNames::CharacterSet ), - SQLSRV_CONN_OPTION_CHARACTERSET, - ODBCConnOptions::CharacterSet, - sizeof( ODBCConnOptions::CharacterSet ), - CONN_ATTR_STRING, - conn_char_set_func::func - }, - { - SSConnOptionNames::ConnectionPooling, - sizeof( SSConnOptionNames::ConnectionPooling ), - SQLSRV_CONN_OPTION_CONN_POOLING, - ODBCConnOptions::ConnectionPooling, - sizeof( ODBCConnOptions::ConnectionPooling ), - CONN_ATTR_BOOL, - conn_null_func::func - }, - { - SSConnOptionNames::Database, - sizeof( SSConnOptionNames::Database ), - SQLSRV_CONN_OPTION_DATABASE, - ODBCConnOptions::Database, - sizeof( ODBCConnOptions::Database ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::Encrypt, - sizeof( SSConnOptionNames::Encrypt ), - SQLSRV_CONN_OPTION_ENCRYPT, - ODBCConnOptions::Encrypt, - sizeof( ODBCConnOptions::Encrypt ), - CONN_ATTR_BOOL, - bool_conn_str_func::func - }, - { - SSConnOptionNames::Failover_Partner, - sizeof( SSConnOptionNames::Failover_Partner ), - SQLSRV_CONN_OPTION_FAILOVER_PARTNER, - ODBCConnOptions::Failover_Partner, - sizeof( ODBCConnOptions::Failover_Partner ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::LoginTimeout, - sizeof( SSConnOptionNames::LoginTimeout ), - SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, - ODBCConnOptions::LoginTimeout, - sizeof( ODBCConnOptions::LoginTimeout ), - CONN_ATTR_INT, - int_conn_attr_func::func - }, - { - SSConnOptionNames::MARS_Option, - sizeof( SSConnOptionNames::MARS_Option ), - SQLSRV_CONN_OPTION_MARS, - ODBCConnOptions::MARS_ODBC, - sizeof( ODBCConnOptions::MARS_ODBC ), - CONN_ATTR_BOOL, - bool_conn_str_func::func - }, - { - SSConnOptionNames::MultiSubnetFailover, - sizeof( SSConnOptionNames::MultiSubnetFailover ), - SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, - ODBCConnOptions::MultiSubnetFailover, - sizeof( ODBCConnOptions::MultiSubnetFailover ), - CONN_ATTR_BOOL, - bool_conn_str_func::func - }, - { - SSConnOptionNames::QuotedId, - sizeof( SSConnOptionNames::QuotedId ), - SQLSRV_CONN_OPTION_QUOTED_ID, - ODBCConnOptions::QuotedId, - sizeof( ODBCConnOptions::QuotedId ), - CONN_ATTR_BOOL, - bool_conn_str_func::func - }, - { - SSConnOptionNames::TraceFile, - sizeof( SSConnOptionNames::TraceFile ), - SQLSRV_CONN_OPTION_TRACE_FILE, - ODBCConnOptions::TraceFile, - sizeof( ODBCConnOptions::TraceFile ), - CONN_ATTR_STRING, - str_conn_attr_func::func - }, - { - SSConnOptionNames::TraceOn, - sizeof( SSConnOptionNames::TraceOn ), - SQLSRV_CONN_OPTION_TRACE_ON, - ODBCConnOptions::TraceOn, - sizeof( ODBCConnOptions::TraceOn ), - CONN_ATTR_BOOL, - bool_conn_attr_func::func - }, - { - SSConnOptionNames::TransactionIsolation, - sizeof( SSConnOptionNames::TransactionIsolation ), - SQLSRV_CONN_OPTION_TRANS_ISOLATION, - ODBCConnOptions::TransactionIsolation, - sizeof( ODBCConnOptions::TransactionIsolation ), - CONN_ATTR_INT, - int_conn_attr_func::func - }, - { - SSConnOptionNames::TrustServerCertificate, - sizeof( SSConnOptionNames::TrustServerCertificate ), - SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, - ODBCConnOptions::TrustServerCertificate, - sizeof( ODBCConnOptions::TrustServerCertificate ), - CONN_ATTR_BOOL, - bool_conn_str_func::func - }, - { - SSConnOptionNames::WSID, - sizeof( SSConnOptionNames::WSID ), - SQLSRV_CONN_OPTION_WSID, - ODBCConnOptions::WSID, - sizeof( ODBCConnOptions::WSID ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::DateAsString, - sizeof( SSConnOptionNames::DateAsString ), - SS_CONN_OPTION_DATE_AS_STRING, - SSConnOptionNames::DateAsString, - sizeof( SSConnOptionNames::DateAsString ), - CONN_ATTR_BOOL, - date_as_string_func::func - }, - { NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table -}; - -// sqlsrv_connect( string $serverName [, array $connectionInfo]) -// -// Creates a connection resource and opens a connection. By default, the -// connection is attempted using Windows Authentication. -// -// Parameters -// $serverName: A string specifying the name of the server to which a connection -// is being established. An instance name (for example, "myServer\instanceName") -// or port number (for example, "myServer, 1521") can be included as part of -// this string. For a complete description of the options available for this -// parameter, see the Server keyword in the ODBC Driver Connection String -// Keywords section of Using Connection String Keywords with ODBC Driver 11 for SQL Server. -// -// $connectionInfo [OPTIONAL]: An associative array that contains connection -// attributes (for example, array("Database" => "AdventureWorks")). -// -// Return Value -// A PHP connection resource. If a connection cannot be successfully created and -// opened, false is returned - -PHP_FUNCTION ( sqlsrv_connect ) -{ - - LOG_FUNCTION( "sqlsrv_connect" ); - SET_FUNCTION_NAME( *g_henv_cp ); - SET_FUNCTION_NAME( *g_henv_ncp ); - - reset_errors( TSRMLS_C ); - - const char* server = NULL; - zval* options_z = NULL; - char* uid = NULL; - char* pwd = NULL; - size_t server_len = 0; - zval conn_z; - ZVAL_UNDEF(&conn_z); - // get the server name and connection options - int result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &server, &server_len, &options_z ); - - CHECK_CUSTOM_ERROR(( result == FAILURE ), *g_henv_cp, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, "sqlsrv_connect" ) { - RETURN_FALSE; - } - - hash_auto_ptr ss_conn_options_ht; - hash_auto_ptr stmts; - ss_sqlsrv_conn* conn = NULL; - - try { - - // Initialize the options array to be passed to the core layer - ALLOC_HASHTABLE( ss_conn_options_ht ); - - core::sqlsrv_zend_hash_init( *g_henv_cp, ss_conn_options_ht, 10 /* # of buckets */, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); - - // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. - ::validate_conn_options( *g_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht TSRMLS_CC ); - - // call the core connect function - conn = static_cast( core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, &core::allocate_conn, - server, uid, pwd, ss_conn_options_ht, ss_error_handler, - SS_CONN_OPTS, NULL, "sqlsrv_connect" TSRMLS_CC )); - - SQLSRV_ASSERT( conn != NULL, "sqlsrv_connect: Invalid connection returned. Exception should have been thrown." ); - - // create a bunch of statements - ALLOC_HASHTABLE( stmts ); - - core::sqlsrv_zend_hash_init( *g_henv_cp, stmts, 5, NULL /* dtor */, 0 /* persistent */ TSRMLS_CC ); - - // register the connection with the PHP runtime - - ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC); - - conn->stmts = stmts; - stmts.transferred(); - RETURN_RES( Z_RES(conn_z) ); - } - - catch( core::CoreException& ) { - - if( conn != NULL ) { - - conn->invalidate(); - } - - RETURN_FALSE; - } - - catch( ... ) { - - DIE("sqlsrv_connect: Unknown exception caught."); - } -} - -// sqlsrv_begin_transaction( resource $conn ) -// -// Begins a transaction on a specified connection. The current transaction -// includes all statements on the specified connection that were executed after -// the call to sqlsrv_begin_transaction and before any calls to sqlsrv_rollback -// or sqlsrv_commit. -// -// The SQLSRV driver is in auto-commit mode by default. This means that all -// queries are automatically committed upon success unless they have been -// designated as part of an explicit transaction by using -// sqlsrv_begin_transaction. -// -// If sqlsrv_begin_transaction is called after a transaction has already been -// initiated on the connection but not completed by calling either sqlsrv_commit -// or sqlsrv_rollback, the call returns false and an Already in Transaction -// error is added to the error collection. -// -// Parameters -// $conn: The connection with which the transaction is associated. -// -// Return Value -// A Boolean value: true if the transaction was successfully begun. Otherwise, false. - -PHP_FUNCTION( sqlsrv_begin_transaction ) -{ - LOG_FUNCTION( "sqlsrv_begin_transaction" ); - - - ss_sqlsrv_conn* conn = NULL; - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - // Return false if already in transaction - CHECK_CUSTOM_ERROR(( conn->in_transaction == true ), *conn, SS_SQLSRV_ERROR_ALREADY_IN_TXN ) { - RETURN_FALSE; - } - - try { - - core_sqlsrv_begin_transaction( conn TSRMLS_CC ); - conn->in_transaction = true; - RETURN_TRUE; - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE("sqlsrv_begin_transaction: Unknown exception caught."); - } -} - - -// sqlsrv_close( resource $conn ) -// Closes the specified connection and releases associated resources. -// -// Parameters -// $conn: The connection to be closed. Null is a valid value parameter for this -// parameter. This allows the function to be called multiple times in a -// script. For example, if you close a connection in an error condition and -// close it again at the end of the script, the second call to sqlsrv_close will -// return true because the first call to sqlsrv_close (in the error condition) -// sets the connection resource to null. -// -// Return Value -// The Boolean value true unless the function is called with an invalid -// parameter. If the function is called with an invalid parameter, false is -// returned. - -PHP_FUNCTION( sqlsrv_close ) -{ - LOG_FUNCTION( "sqlsrv_close" ); - - zval* conn_r = NULL; - ss_sqlsrv_conn* conn = NULL; - sqlsrv_context_auto_ptr error_ctx; - - reset_errors( TSRMLS_C ); - - try { - - // dummy context to pass to the error handler - error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); - - if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r) == FAILURE ) { - - // Check if it was a zval - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &conn_r ); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - throw ss::SSException(); - } - - // if sqlsrv_close was called on a non-existent connection then we just return success. - if( Z_TYPE_P( conn_r ) == IS_NULL ) { - RETURN_TRUE; - } - else { - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - } - - conn = static_cast( zend_fetch_resource( Z_RES_P( conn_r ) TSRMLS_CC, ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor )); - - // if sqlsrv_close was called on an already closed connection then we just return success. - if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) { - RETURN_TRUE; - } - - CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - SET_FUNCTION_NAME( *conn ); - - // cause any variables still holding a reference to this to be invalid so they cause - // an error when passed to a sqlsrv function. There's nothing we can do if the - // removal fails, so we just log it and move on. - if( zend_list_close( Z_RES_P( conn_r ) ) == FAILURE ) { - LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P( conn_r )); - } - - ZVAL_NULL( conn_r ); - - RETURN_TRUE; - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_close: Unknown exception caught." ); - } -} - -void __cdecl sqlsrv_conn_dtor( zend_resource *rsrc TSRMLS_DC ) -{ - LOG_FUNCTION( "sqlsrv_conn_dtor" ); - - // get the structure - ss_sqlsrv_conn *conn = static_cast( rsrc->ptr ); - SQLSRV_ASSERT( conn != NULL, "sqlsrv_conn_dtor: connection was null"); - - SET_FUNCTION_NAME( *conn ); - - // close all statements associated with the connection. - sqlsrv_conn_close_stmts( conn TSRMLS_CC ); - - // close the connection itself. - core_sqlsrv_close( conn TSRMLS_CC ); - - rsrc->ptr = NULL; -} - -// sqlsrv_commit( resource $conn ) -// -// Commits the current transaction on the specified connection and returns the -// connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or -// sqlsrv_commit. - -// The SQLSRV driver is in auto-commit mode by -// default. This means that all queries are automatically committed upon success -// unless they have been designated as part of an explicit transaction by using -// sqlsrv_begin_transaction. If sqlsrv_commit is called on a connection that is -// not in an active transaction and that was initiated with -// sqlsrv_begin_transaction, the call returns false and a Not in Transaction -// error is added to the error collection. -// -// Parameters -// $conn: The connection on which the transaction is active. -// -// Return Value -// A Boolean value: true if the transaction was successfully committed. Otherwise, false. - -PHP_FUNCTION( sqlsrv_commit ) -{ - LOG_FUNCTION( "sqlsrv_commit" ); - - ss_sqlsrv_conn* conn = NULL; - - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - // Return false if not in transaction - CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) { - RETURN_FALSE; - } - - try { - - conn->in_transaction = false; - core_sqlsrv_commit( conn TSRMLS_CC ); - RETURN_TRUE; - - } - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_commit: Unknown exception caught." ); - } -} - -// sqlsrv_rollback( resource $conn ) -// -// Rolls back the current transaction on the specified connection and returns -// the connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or -// sqlsrv_commit. -// -// The SQLSRV driver is in auto-commit mode by default. This -// means that all queries are automatically committed upon success unless they -// have been designated as part of an explicit transaction by using -// sqlsrv_begin_transaction. -// -// If sqlsrv_rollback is called on a connection that is not in an active -// transaction that was initiated with sqlsrv_begin_transaction, the call -// returns false and a Not in Transaction error is added to the error -// collection. -// -// Parameters -// $conn: The connection on which the transaction is active. -// -// Return Value -// A Boolean value: true if the transaction was successfully rolled back. Otherwise, false. - -PHP_FUNCTION( sqlsrv_rollback ) -{ - LOG_FUNCTION( "sqlsrv_rollback" ); - - - ss_sqlsrv_conn* conn = NULL; - - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - // Return false if not in transaction - CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) { - RETURN_FALSE; - } - - try { - - conn->in_transaction = false; - core_sqlsrv_rollback( conn TSRMLS_CC ); - RETURN_TRUE; - } - catch( core::CoreException& ){ - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_rollback: Unknown exception caught." ); - } -} - -// sqlsrv_client_info -// Returns the ODBC driver's dll name, version and the ODBC version. Also returns -// the version of this extension. -// Parameters: -// $conn - The connection resource by which the client and server are connected. - -PHP_FUNCTION( sqlsrv_client_info ) -{ - - LOG_FUNCTION( "sqlsrv_client_info" ); - ss_sqlsrv_conn* conn = NULL; - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - try { - - core_sqlsrv_get_client_info( conn, return_value TSRMLS_CC ); - - // Add the sqlsrv driver's file version - core::sqlsrv_add_assoc_string( *conn, return_value, "ExtensionVer", VER_FILEVERSION_STR, 1 /*duplicate*/ TSRMLS_CC ); - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_client_info: Unknown exception caught." ); - } -} - -// sqlsrv_server_info( resource $conn ) -// -// Returns information about the server. -// -// Parameters -// $conn: The connection resource by which the client and server are connected. -// -// Return Value -// An associative array with the following keys: -// CurrentDatabase -// The database currently being targeted. -// SQLServerVersion -// The version of SQL Server. -// SQLServerName -// The name of the server. - -PHP_FUNCTION( sqlsrv_server_info ) -{ - try { - - LOG_FUNCTION( "sqlsrv_server_info" ); - ss_sqlsrv_conn* conn = NULL; - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - core_sqlsrv_get_server_info( conn, return_value TSRMLS_CC ); - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_server_info: Unknown exception caught." ); - } -} - - -// sqlsrv_prepare( resource $conn, string $tsql [, array $params [, array $options]]) -// -// Creates a statement resource associated with the specified connection. A statement -// resource returned by sqlsrv_prepare may be executed multiple times by sqlsrv_execute. -// In between each execution, the values may be updated by changing the value of the -// variables bound. Output parameters cannot be relied upon to contain their results until -// all rows are processed. -// -// Parameters -// $conn: The connection resource associated with the created statement. -// -// $tsql: The Transact-SQL expression that corresponds to the created statement. -// -// $params [OPTIONAL]: An array of values that correspond to parameters in a -// parameterized query. Each parameter may be specified as: -// $value | array($value [, $direction [, $phpType [, $sqlType]]]) -// When given just a $value, the direction is default input, and phptype is the value -// given, with the sql type inferred from the php type. -// -// $options [OPTIONAL]: An associative array that sets query properties. The -// table below lists the supported keys and corresponding values: -// QueryTimeout -// Sets the query timeout in seconds. By default, the driver will wait -// indefinitely for results. -// SendStreamParamsAtExec -// Configures the driver to send all stream data at execution (true), or to -// send stream data in chunks (false). By default, the value is set to -// true. For more information, see sqlsrv_send_stream_data. -// -// Return Value -// A statement resource. If the statement resource cannot be created, false is returned. - -PHP_FUNCTION( sqlsrv_prepare ) -{ - - LOG_FUNCTION( "sqlsrv_prepare" ); - - sqlsrv_malloc_auto_ptr stmt; - ss_sqlsrv_conn* conn = NULL; - char *sql = NULL; - zend_long sql_len = 0; - zval* params_z = NULL; - zval* options_z = NULL; - hash_auto_ptr ss_stmt_options_ht; - zval stmt_z; - ZVAL_UNDEF(&stmt_z); - - PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z ); - - try { - - if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) { - - // Initialize the options array to be passed to the core layer - ALLOC_HASHTABLE( ss_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); - - validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); - - } - - if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - if( sql == NULL ) { - - DIE( "sqlsrv_prepare: sql string was null." ); - } - - stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, - ss_stmt_options_ht, SS_STMT_OPTS, - ss_error_handler, NULL TSRMLS_CC ) ); - - core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC ); - - //mark_params_by_reference( stmt, params_z TSRMLS_CC ); - if (params_z) { - stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); - ZVAL_COPY(stmt->params_z, params_z); - } - - stmt->prepared = true; - - // register the statement with the PHP runtime - ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC ); - - // store the resource id with the connection so the connection - // can release this statement when it closes. - zend_long next_index = zend_hash_next_free_element( conn->stmts ); - - core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); - - stmt->conn_index = next_index; - - // the statement is now registered with EG( regular_list ) - stmt.transferred(); - - RETURN_RES(Z_RES(stmt_z)); - - } - catch( core::CoreException& ) { - - if( stmt ) { - - stmt->conn = NULL; - stmt->~ss_sqlsrv_stmt(); - } - if (!Z_ISUNDEF(stmt_z)) { - free_stmt_resource(&stmt_z TSRMLS_CC); - } - - RETURN_FALSE; - } - - catch( ... ) { - - DIE( "sqlsrv_prepare: Unknown exception caught." ); - } -} - -// sqlsrv_query( resource $conn, string $tsql [, array $params [, array $options]]) -// -// Creates a statement resource associated with the specified connection. The statement -// is immediately executed and may not be executed again using sqlsrv_execute. -// -// Parameters -// $conn: The connection resource associated with the created statement. -// -// $tsql: The Transact-SQL expression that corresponds to the created statement. -// -// $params [OPTIONAL]: An array of values that correspond to parameters in a -// parameterized query. Each parameter may be specified as: -// $value | array($value [, $direction [, $phpType [, $sqlType]]]) -// When given just a $value, the direction is default input, and phptype is the value -// given, with the sql type inferred from the php type. -// -// $options [OPTIONAL]: An associative array that sets query properties. The -// table below lists the supported keys and corresponding values: -// QueryTimeout -// Sets the query timeout in seconds. By default, the driver will wait -// indefinitely for results. -// SendStreamParamsAtExec -// Configures the driver to send all stream data at execution (true), or to -// send stream data in chunks (false). By default, the value is set to -// true. For more information, see sqlsrv_send_stream_data. -// -// Return Value -// A statement resource. If the statement resource cannot be created, false is returned. - -PHP_FUNCTION( sqlsrv_query ) -{ - - LOG_FUNCTION( "sqlsrv_query" ); - - ss_sqlsrv_conn* conn = NULL; - sqlsrv_malloc_auto_ptr stmt; - char* sql = NULL; - hash_auto_ptr ss_stmt_options_ht; - int sql_len = 0; - zval* options_z = NULL; - zval* params_z = NULL; - zval stmt_z; - ZVAL_UNDEF(&stmt_z); - - PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z ); - - try { - - // check for statement options - if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) { - - // Initialize the options array to be passed to the core layer - ALLOC_HASHTABLE( ss_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, ZVAL_PTR_DTOR, - 0 /*persistent*/ TSRMLS_CC ); - - validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); - } - - if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - if( sql == NULL ) { - - DIE( "sqlsrv_query: sql string was null." ); - } - - stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, - ss_stmt_options_ht, SS_STMT_OPTS, - ss_error_handler, NULL TSRMLS_CC ) ); - - if( params_z ) { - stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); - ZVAL_COPY(stmt->params_z, params_z); - } - - stmt->set_func( "sqlsrv_query" ); - - bind_params( stmt TSRMLS_CC ); - - // execute the statement - core_sqlsrv_execute( stmt TSRMLS_CC, sql, sql_len ); - - // register the statement with the PHP runtime - ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC); - // store the resource id with the connection so the connection - // can release this statement when it closes. - zend_ulong next_index = zend_hash_next_free_element( conn->stmts ); - - core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); - stmt->conn_index = next_index; - stmt.transferred(); - - RETURN_RES(Z_RES(stmt_z)); - } - - catch( core::CoreException& ) { - - if( stmt ) { - - stmt->conn = NULL; // tell the statement that it isn't part of the connection so it doesn't try to remove itself - stmt->~ss_sqlsrv_stmt(); - } - if (!Z_ISUNDEF(stmt_z)) { - free_stmt_resource(&stmt_z TSRMLS_CC); - } - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_query: Unknown exception caught." ); - } -} - -void free_stmt_resource( zval* stmt_z TSRMLS_DC ) -{ - if( FAILURE == zend_list_close( Z_RES_P( stmt_z ))) { - LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z)); - } - ZVAL_NULL( stmt_z ); - zval_ptr_dtor(stmt_z); -} - -// internal connection functions - -namespace { - -// must close all statement handles opened by this connection before closing the connection -// no errors are returned, since close should always succeed - -void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ) -{ - //pre-condition check - SQLSRV_ASSERT(( conn->handle() != NULL ), "sqlsrv_conn_close_stmts: Connection handle is NULL. Trying to destroy an " - "already destroyed connection."); - SQLSRV_ASSERT(( conn->stmts ), "sqlsrv_conn_close_stmts: Connection doesn't contain a statement array." ); - - // loop through the stmts hash table and destroy each stmt resource so we can close the - // ODBC connection - - zval* rsrc_ptr = NULL; - ZEND_HASH_FOREACH_VAL( conn->stmts, rsrc_ptr ) { - try { - int zr = ( rsrc_ptr ) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, *conn, SQLSRV_ERROR_ZEND_HASH ) { - throw core::CoreException(); - } - } - catch( core::CoreException& ) { - DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" ); - } - // see if the statement is still valid, and if not skip to the next one - // presumably this should never happen because if it's in the list, it should still be valid - // by virtue that a statement resource should remove itself from its connection when it is - // destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource - // and move to the next one. - ss_sqlsrv_stmt* stmt = NULL; - stmt = static_cast( Z_RES_VAL_P( rsrc_ptr )); - if( stmt == NULL || Z_RES_TYPE_P( rsrc_ptr ) != ss_sqlsrv_stmt::descriptor ) { - LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves" - " from the connection so this shouldn't be out of sync." ); - continue; - } - // delete the statement by deleting it from Zend's resource list, which will force its destruction - stmt->conn = NULL; - - try { - // this would call the destructor on the statement. - int zr = zend_list_close(Z_RES_P(rsrc_ptr)); - } - catch( core::CoreException& ) { - LOG(SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", Z_RES_HANDLE_P(rsrc_ptr)); - } - } ZEND_HASH_FOREACH_END(); - - zend_hash_destroy( conn->stmts ); - FREE_HASHTABLE( conn->stmts ); - conn->stmts = NULL; -} - -int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC ) -{ - for( int i=0; SS_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) - { - if( key_len == SS_CONN_OPTS[ i ].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[ i ].sqlsrv_name )) { - - - switch( SS_CONN_OPTS[ i ].value_type ) { - - case CONN_ATTR_BOOL: - // bool attributes can be either strings to be appended to the connection string - // as yes or no or integral connection attributes. This will have to be reworked - // if we ever introduce a boolean connection option that maps to a string connection - // attribute. - break; - case CONN_ATTR_INT: - { - CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - SS_CONN_OPTS[ i ].sqlsrv_name ) - { - throw ss::SSException(); - } - break; - } - case CONN_ATTR_STRING: - { - CHECK_CUSTOM_ERROR( Z_TYPE_P( value_z ) != IS_STRING, ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - SS_CONN_OPTS[ i ].sqlsrv_name ) { - - throw ss::SSException(); - } - - char* value = Z_STRVAL_P( value_z ); - size_t value_len = Z_STRLEN_P( value_z ); - bool escaped = core_is_conn_opt_value_escaped( value, value_len ); - - CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[ i ].sqlsrv_name ) { - - throw ss::SSException(); - } - break; - } - } - - return SS_CONN_OPTS[ i ].conn_option_key; - } - } - return SQLSRV_CONN_OPTION_INVALID; -} - -int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ) -{ - for( int i = 0; SS_STMT_OPTS[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) - { - if( key_len == SS_STMT_OPTS[ i ].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[ i ].name )) { - return SS_STMT_OPTS[ i ].key; - } - } - return SQLSRV_STMT_OPTION_INVALID; -} - -void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, - HashTable* options_ht, zval* data TSRMLS_DC ) -{ - int option_key = ::get_stmt_option_key( key, key_len TSRMLS_CC ); - - CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, ZSTR_VAL( key ) ) { - - throw ss::SSException(); - } - - Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. - core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); -} - -void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, - HashTable* options_ht, zval* data TSRMLS_DC ) -{ - int option_key = ::get_conn_option_key( ctx, key, key_len, data TSRMLS_CC ); - CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key ) { - - throw ss::SSException(); - } - - Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. - core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); -} - -// Iterates through the list of statement options provided by the user and validates them -// against the list of supported statement options by this driver. After validation -// creates a Hashtable of statement options to be sent to the core layer for processing. - -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ) -{ - try { - if( stmt_options ) { - - HashTable* options_ht = Z_ARRVAL_P( stmt_options ); - zend_ulong int_key = -1; - zend_string *key = NULL; - zval* data = NULL; - ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - size_t key_len = 0; - zval* conn_opt = NULL; - int result = 0; - - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - if (type != HASH_KEY_IS_STRING) { - CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) { - throw core::CoreException(); - } - } - key_len = ZSTR_LEN(key) + 1; - add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - } - } - catch( core::CoreException& ) { - - throw; - } -} - -// Iterates through the list of connection options provided by the user and validates them -// against the predefined list of supported connection options by this driver. After validation -// creates a Hashtable of connection options to be sent to the core layer for processing. - -void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ char** uid, _Out_ char** pwd, _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ) -{ - try { - - if( user_options_z ) { - - HashTable* options_ht = Z_ARRVAL_P( user_options_z ); - zend_ulong int_key = -1; - zend_string *key = NULL; - zval* data = NULL; - ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - CHECK_CUSTOM_ERROR(( Z_TYPE_P( data ) == IS_NULL || Z_TYPE_P( data ) == IS_UNDEF ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key) { - throw ss::SSException(); - } - - CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) { - throw ss::SSException(); - } - - // Length of the key string does not include the null terminator in PHP7, +1 has to be added - size_t key_len = ZSTR_LEN(key) + 1; - if( key_len == sizeof(SSConnOptionNames::UID) && !stricmp(ZSTR_VAL(key), SSConnOptionNames::UID )) { - - *uid = Z_STRVAL_P( data ); - } - - else if( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::PWD )) { - - *pwd = Z_STRVAL_P( data ); - } - else { - - ::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC ); - } - } ZEND_HASH_FOREACH_END(); - } - } - catch( core::CoreException& ) { - throw; - } -} - -} // namespace +//--------------------------------------------------------------------------------------------------------------------------------- +// File: conn.cpp +// +// Contents: Routines that use connection handles +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_sqlsrv.h" + +#include +#include + +// *** internal variables and constants *** + +namespace { + +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros +unsigned int current_log_subsystem = LOG_CONN; + +struct date_as_string_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + TSRMLS_C; // show as used to avoid a warning + + ss_sqlsrv_conn* ss_conn = static_cast( conn ); + + if( zend_is_true( value )) { + ss_conn->date_as_string = true; + } + else { + ss_conn->date_as_string = false; + } + } +}; + +struct conn_char_set_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + convert_to_string( value ); + const char* encoding = Z_STRVAL_P( value ); + size_t encoding_len = Z_STRLEN_P( value ); + + zend_ulong index = -1; + zend_string* key = NULL; + void* ss_encoding_temp = NULL; + + ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, ss_encoding_temp ) { + sqlsrv_encoding* ss_encoding = reinterpret_cast( ss_encoding_temp ); + ss_encoding_temp = NULL; + if (!strnicmp( encoding, ss_encoding->iana, encoding_len )) { + + if ( ss_encoding->not_for_connection ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); + } + + conn->set_encoding( static_cast(ss_encoding->code_page )); + return; + } + } ZEND_HASH_FOREACH_END(); + + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); + } +}; + +struct bool_conn_str_func { + + static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ) + { + TSRMLS_C; + char const* val_str; + if( zend_is_true( value )) { + val_str = "yes"; + } + else { + val_str = "no"; + } + conn_str += option->odbc_name; + conn_str += "={"; + conn_str += val_str; + conn_str += "};"; + } +}; + +template +struct int_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_LVAL_P( value )), SQL_IS_UINTEGER TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +template +struct bool_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + core::SQLSetConnectAttr(conn, Attr, reinterpret_cast((zend_long)zend_is_true(value)), + SQL_IS_UINTEGER TSRMLS_CC); + + } + catch( core::CoreException& ) { + throw; + } + + } +}; + +//// *** internal functions *** + +void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ); +void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ char** uid, _Out_ char** pwd, + _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ); +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ); +void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, + HashTable* options_ht, zval* data TSRMLS_DC ); +void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, HashTable* options_ht, zval* data TSRMLS_DC ); +int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC ); +int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ); + +} + +// constants for parameters used by process_params function(s) +int ss_sqlsrv_conn::descriptor; +char* ss_sqlsrv_conn::resource_name = static_cast("ss_sqlsrv_conn"); + +// connection specific parameter proccessing. Use the generic function specialised to return a connection +// resource. +#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \ + rsrc = process_params( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, ##__VA_ARGS__ );\ + if( rsrc == NULL ) { \ + RETURN_FALSE; \ + } + +namespace SSStmtOptionNames { + const char QUERY_TIMEOUT[]= "QueryTimeout"; + const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec"; + const char SCROLLABLE[] = "Scrollable"; + const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT; +} + +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 ApplicationIntent[] = "ApplicationIntent"; +const char AttachDBFileName[] = "AttachDbFileName"; +const char CharacterSet[] = "CharacterSet"; +const char ConnectionPooling[] = "ConnectionPooling"; +const char Database[] = "Database"; +const char DateAsString[] = "ReturnDatesAsStrings"; +const char Encrypt[] = "Encrypt"; +const char Failover_Partner[] = "Failover_Partner"; +const char LoginTimeout[] = "LoginTimeout"; +const char MARS_Option[] = "MultipleActiveResultSets"; +const char MultiSubnetFailover[] = "MultiSubnetFailover"; +const char PWD[] = "PWD"; +const char QuotedId[] = "QuotedId"; +const char TraceFile[] = "TraceFile"; +const char TraceOn[] = "TraceOn"; +const char TrustServerCertificate[] = "TrustServerCertificate"; +const char TransactionIsolation[] = "TransactionIsolation"; +const char UID[] = "UID"; +const char WSID[] = "WSID"; + +} + +enum SS_CONN_OPTIONS { + + SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC, +}; + +//List of all statement options supported by this driver +const stmt_option SS_STMT_OPTS[] = { + { + SSStmtOptionNames::QUERY_TIMEOUT, + sizeof( SSStmtOptionNames::QUERY_TIMEOUT ), + SQLSRV_STMT_OPTION_QUERY_TIMEOUT, + std::unique_ptr( new stmt_option_query_timeout ) + }, + { + SSStmtOptionNames::SEND_STREAMS_AT_EXEC, + sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ), + SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, + std::unique_ptr( new stmt_option_send_at_exec ) + }, + { + SSStmtOptionNames::SCROLLABLE, + sizeof( SSStmtOptionNames::SCROLLABLE ), + SQLSRV_STMT_OPTION_SCROLLABLE, + std::unique_ptr( new stmt_option_scrollable ) + }, + { + SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE, + sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ), + SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, + std::unique_ptr( new stmt_option_buffered_query_limit ) + }, + { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, +}; + + +// List of all connection options supported by this driver. +const connection_option SS_CONN_OPTS[] = { + { + SSConnOptionNames::APP, + sizeof( SSConnOptionNames::APP ), + SQLSRV_CONN_OPTION_APP, + ODBCConnOptions::APP, + sizeof( ODBCConnOptions::APP ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::ApplicationIntent, + sizeof( SSConnOptionNames::ApplicationIntent ), + SQLSRV_CONN_OPTION_APPLICATION_INTENT, + ODBCConnOptions::ApplicationIntent, + sizeof( ODBCConnOptions::ApplicationIntent ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::AttachDBFileName, + sizeof( SSConnOptionNames::AttachDBFileName ), + SQLSRV_CONN_OPTION_ATTACHDBFILENAME, + ODBCConnOptions::AttachDBFileName, + sizeof( ODBCConnOptions::AttachDBFileName ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::CharacterSet, + sizeof( SSConnOptionNames::CharacterSet ), + SQLSRV_CONN_OPTION_CHARACTERSET, + ODBCConnOptions::CharacterSet, + sizeof( ODBCConnOptions::CharacterSet ), + CONN_ATTR_STRING, + conn_char_set_func::func + }, + { + SSConnOptionNames::ConnectionPooling, + sizeof( SSConnOptionNames::ConnectionPooling ), + SQLSRV_CONN_OPTION_CONN_POOLING, + ODBCConnOptions::ConnectionPooling, + sizeof( ODBCConnOptions::ConnectionPooling ), + CONN_ATTR_BOOL, + conn_null_func::func + }, + { + SSConnOptionNames::Database, + sizeof( SSConnOptionNames::Database ), + SQLSRV_CONN_OPTION_DATABASE, + ODBCConnOptions::Database, + sizeof( ODBCConnOptions::Database ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::Encrypt, + sizeof( SSConnOptionNames::Encrypt ), + SQLSRV_CONN_OPTION_ENCRYPT, + ODBCConnOptions::Encrypt, + sizeof( ODBCConnOptions::Encrypt ), + CONN_ATTR_BOOL, + bool_conn_str_func::func + }, + { + SSConnOptionNames::Failover_Partner, + sizeof( SSConnOptionNames::Failover_Partner ), + SQLSRV_CONN_OPTION_FAILOVER_PARTNER, + ODBCConnOptions::Failover_Partner, + sizeof( ODBCConnOptions::Failover_Partner ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::LoginTimeout, + sizeof( SSConnOptionNames::LoginTimeout ), + SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, + ODBCConnOptions::LoginTimeout, + sizeof( ODBCConnOptions::LoginTimeout ), + CONN_ATTR_INT, + int_conn_attr_func::func + }, + { + SSConnOptionNames::MARS_Option, + sizeof( SSConnOptionNames::MARS_Option ), + SQLSRV_CONN_OPTION_MARS, + ODBCConnOptions::MARS_ODBC, + sizeof( ODBCConnOptions::MARS_ODBC ), + CONN_ATTR_BOOL, + bool_conn_str_func::func + }, + { + SSConnOptionNames::MultiSubnetFailover, + sizeof( SSConnOptionNames::MultiSubnetFailover ), + SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, + ODBCConnOptions::MultiSubnetFailover, + sizeof( ODBCConnOptions::MultiSubnetFailover ), + CONN_ATTR_BOOL, + bool_conn_str_func::func + }, + { + SSConnOptionNames::QuotedId, + sizeof( SSConnOptionNames::QuotedId ), + SQLSRV_CONN_OPTION_QUOTED_ID, + ODBCConnOptions::QuotedId, + sizeof( ODBCConnOptions::QuotedId ), + CONN_ATTR_BOOL, + bool_conn_str_func::func + }, + { + SSConnOptionNames::TraceFile, + sizeof( SSConnOptionNames::TraceFile ), + SQLSRV_CONN_OPTION_TRACE_FILE, + ODBCConnOptions::TraceFile, + sizeof( ODBCConnOptions::TraceFile ), + CONN_ATTR_STRING, + str_conn_attr_func::func + }, + { + SSConnOptionNames::TraceOn, + sizeof( SSConnOptionNames::TraceOn ), + SQLSRV_CONN_OPTION_TRACE_ON, + ODBCConnOptions::TraceOn, + sizeof( ODBCConnOptions::TraceOn ), + CONN_ATTR_BOOL, + bool_conn_attr_func::func + }, + { + SSConnOptionNames::TransactionIsolation, + sizeof( SSConnOptionNames::TransactionIsolation ), + SQLSRV_CONN_OPTION_TRANS_ISOLATION, + ODBCConnOptions::TransactionIsolation, + sizeof( ODBCConnOptions::TransactionIsolation ), + CONN_ATTR_INT, + int_conn_attr_func::func + }, + { + SSConnOptionNames::TrustServerCertificate, + sizeof( SSConnOptionNames::TrustServerCertificate ), + SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, + ODBCConnOptions::TrustServerCertificate, + sizeof( ODBCConnOptions::TrustServerCertificate ), + CONN_ATTR_BOOL, + bool_conn_str_func::func + }, + { + SSConnOptionNames::WSID, + sizeof( SSConnOptionNames::WSID ), + SQLSRV_CONN_OPTION_WSID, + ODBCConnOptions::WSID, + sizeof( ODBCConnOptions::WSID ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::DateAsString, + sizeof( SSConnOptionNames::DateAsString ), + SS_CONN_OPTION_DATE_AS_STRING, + SSConnOptionNames::DateAsString, + sizeof( SSConnOptionNames::DateAsString ), + CONN_ATTR_BOOL, + date_as_string_func::func + }, + { NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table +}; + +// sqlsrv_connect( string $serverName [, array $connectionInfo]) +// +// Creates a connection resource and opens a connection. By default, the +// connection is attempted using Windows Authentication. +// +// Parameters +// $serverName: A string specifying the name of the server to which a connection +// is being established. An instance name (for example, "myServer\instanceName") +// or port number (for example, "myServer, 1521") can be included as part of +// this string. For a complete description of the options available for this +// parameter, see the Server keyword in the ODBC Driver Connection String +// Keywords section of Using Connection String Keywords with ODBC Driver 11 for SQL Server. +// +// $connectionInfo [OPTIONAL]: An associative array that contains connection +// attributes (for example, array("Database" => "AdventureWorks")). +// +// Return Value +// A PHP connection resource. If a connection cannot be successfully created and +// opened, false is returned + +PHP_FUNCTION ( sqlsrv_connect ) +{ + + LOG_FUNCTION( "sqlsrv_connect" ); + SET_FUNCTION_NAME( *g_henv_cp ); + SET_FUNCTION_NAME( *g_henv_ncp ); + + reset_errors( TSRMLS_C ); + + const char* server = NULL; + zval* options_z = NULL; + char* uid = NULL; + char* pwd = NULL; + size_t server_len = 0; + zval conn_z; + ZVAL_UNDEF(&conn_z); + // get the server name and connection options + int result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &server, &server_len, &options_z ); + + CHECK_CUSTOM_ERROR(( result == FAILURE ), *g_henv_cp, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, "sqlsrv_connect" ) { + RETURN_FALSE; + } + + hash_auto_ptr ss_conn_options_ht; + hash_auto_ptr stmts; + ss_sqlsrv_conn* conn = NULL; + + try { + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( ss_conn_options_ht ); + + core::sqlsrv_zend_hash_init( *g_henv_cp, ss_conn_options_ht, 10 /* # of buckets */, + ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + + // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. + ::validate_conn_options( *g_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht TSRMLS_CC ); + + // call the core connect function + conn = static_cast( core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, &core::allocate_conn, + server, uid, pwd, ss_conn_options_ht, ss_error_handler, + SS_CONN_OPTS, NULL, "sqlsrv_connect" TSRMLS_CC )); + + SQLSRV_ASSERT( conn != NULL, "sqlsrv_connect: Invalid connection returned. Exception should have been thrown." ); + + // create a bunch of statements + ALLOC_HASHTABLE( stmts ); + + core::sqlsrv_zend_hash_init( *g_henv_cp, stmts, 5, NULL /* dtor */, 0 /* persistent */ TSRMLS_CC ); + + // register the connection with the PHP runtime + + ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC); + + conn->stmts = stmts; + stmts.transferred(); + RETURN_RES( Z_RES(conn_z) ); + } + + catch( core::CoreException& ) { + + if( conn != NULL ) { + + conn->invalidate(); + } + + RETURN_FALSE; + } + + catch( ... ) { + + DIE("sqlsrv_connect: Unknown exception caught."); + } +} + +// sqlsrv_begin_transaction( resource $conn ) +// +// Begins a transaction on a specified connection. The current transaction +// includes all statements on the specified connection that were executed after +// the call to sqlsrv_begin_transaction and before any calls to sqlsrv_rollback +// or sqlsrv_commit. +// +// The SQLSRV driver is in auto-commit mode by default. This means that all +// queries are automatically committed upon success unless they have been +// designated as part of an explicit transaction by using +// sqlsrv_begin_transaction. +// +// If sqlsrv_begin_transaction is called after a transaction has already been +// initiated on the connection but not completed by calling either sqlsrv_commit +// or sqlsrv_rollback, the call returns false and an Already in Transaction +// error is added to the error collection. +// +// Parameters +// $conn: The connection with which the transaction is associated. +// +// Return Value +// A Boolean value: true if the transaction was successfully begun. Otherwise, false. + +PHP_FUNCTION( sqlsrv_begin_transaction ) +{ + LOG_FUNCTION( "sqlsrv_begin_transaction" ); + + + ss_sqlsrv_conn* conn = NULL; + PROCESS_PARAMS( conn, "r", _FN_, 0 ); + + // Return false if already in transaction + CHECK_CUSTOM_ERROR(( conn->in_transaction == true ), *conn, SS_SQLSRV_ERROR_ALREADY_IN_TXN ) { + RETURN_FALSE; + } + + try { + + core_sqlsrv_begin_transaction( conn TSRMLS_CC ); + conn->in_transaction = true; + RETURN_TRUE; + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE("sqlsrv_begin_transaction: Unknown exception caught."); + } +} + + +// sqlsrv_close( resource $conn ) +// Closes the specified connection and releases associated resources. +// +// Parameters +// $conn: The connection to be closed. Null is a valid value parameter for this +// parameter. This allows the function to be called multiple times in a +// script. For example, if you close a connection in an error condition and +// close it again at the end of the script, the second call to sqlsrv_close will +// return true because the first call to sqlsrv_close (in the error condition) +// sets the connection resource to null. +// +// Return Value +// The Boolean value true unless the function is called with an invalid +// parameter. If the function is called with an invalid parameter, false is +// returned. + +PHP_FUNCTION( sqlsrv_close ) +{ + LOG_FUNCTION( "sqlsrv_close" ); + + zval* conn_r = NULL; + ss_sqlsrv_conn* conn = NULL; + sqlsrv_context_auto_ptr error_ctx; + + reset_errors( TSRMLS_C ); + + try { + + // dummy context to pass to the error handler + error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); + SET_FUNCTION_NAME( *error_ctx ); + + if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r) == FAILURE ) { + + // Check if it was a zval + int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &conn_r ); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + throw ss::SSException(); + } + + // if sqlsrv_close was called on a non-existent connection then we just return success. + if( Z_TYPE_P( conn_r ) == IS_NULL ) { + RETURN_TRUE; + } + else { + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + } + + conn = static_cast( zend_fetch_resource( Z_RES_P( conn_r ) TSRMLS_CC, ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor )); + + // if sqlsrv_close was called on an already closed connection then we just return success. + if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) { + RETURN_TRUE; + } + + CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + SET_FUNCTION_NAME( *conn ); + + // cause any variables still holding a reference to this to be invalid so they cause + // an error when passed to a sqlsrv function. There's nothing we can do if the + // removal fails, so we just log it and move on. + if( zend_list_close( Z_RES_P( conn_r ) ) == FAILURE ) { + LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P( conn_r )); + } + + ZVAL_NULL( conn_r ); + + RETURN_TRUE; + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_close: Unknown exception caught." ); + } +} + +void __cdecl sqlsrv_conn_dtor( zend_resource *rsrc TSRMLS_DC ) +{ + LOG_FUNCTION( "sqlsrv_conn_dtor" ); + + // get the structure + ss_sqlsrv_conn *conn = static_cast( rsrc->ptr ); + SQLSRV_ASSERT( conn != NULL, "sqlsrv_conn_dtor: connection was null"); + + SET_FUNCTION_NAME( *conn ); + + // close all statements associated with the connection. + sqlsrv_conn_close_stmts( conn TSRMLS_CC ); + + // close the connection itself. + core_sqlsrv_close( conn TSRMLS_CC ); + + rsrc->ptr = NULL; +} + +// sqlsrv_commit( resource $conn ) +// +// Commits the current transaction on the specified connection and returns the +// connection to the auto-commit mode. The current transaction includes all +// statements on the specified connection that were executed after the call to +// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or +// sqlsrv_commit. + +// The SQLSRV driver is in auto-commit mode by +// default. This means that all queries are automatically committed upon success +// unless they have been designated as part of an explicit transaction by using +// sqlsrv_begin_transaction. If sqlsrv_commit is called on a connection that is +// not in an active transaction and that was initiated with +// sqlsrv_begin_transaction, the call returns false and a Not in Transaction +// error is added to the error collection. +// +// Parameters +// $conn: The connection on which the transaction is active. +// +// Return Value +// A Boolean value: true if the transaction was successfully committed. Otherwise, false. + +PHP_FUNCTION( sqlsrv_commit ) +{ + LOG_FUNCTION( "sqlsrv_commit" ); + + ss_sqlsrv_conn* conn = NULL; + + PROCESS_PARAMS( conn, "r", _FN_, 0 ); + + // Return false if not in transaction + CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) { + RETURN_FALSE; + } + + try { + + conn->in_transaction = false; + core_sqlsrv_commit( conn TSRMLS_CC ); + RETURN_TRUE; + + } + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_commit: Unknown exception caught." ); + } +} + +// sqlsrv_rollback( resource $conn ) +// +// Rolls back the current transaction on the specified connection and returns +// the connection to the auto-commit mode. The current transaction includes all +// statements on the specified connection that were executed after the call to +// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or +// sqlsrv_commit. +// +// The SQLSRV driver is in auto-commit mode by default. This +// means that all queries are automatically committed upon success unless they +// have been designated as part of an explicit transaction by using +// sqlsrv_begin_transaction. +// +// If sqlsrv_rollback is called on a connection that is not in an active +// transaction that was initiated with sqlsrv_begin_transaction, the call +// returns false and a Not in Transaction error is added to the error +// collection. +// +// Parameters +// $conn: The connection on which the transaction is active. +// +// Return Value +// A Boolean value: true if the transaction was successfully rolled back. Otherwise, false. + +PHP_FUNCTION( sqlsrv_rollback ) +{ + LOG_FUNCTION( "sqlsrv_rollback" ); + + + ss_sqlsrv_conn* conn = NULL; + + PROCESS_PARAMS( conn, "r", _FN_, 0 ); + + // Return false if not in transaction + CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) { + RETURN_FALSE; + } + + try { + + conn->in_transaction = false; + core_sqlsrv_rollback( conn TSRMLS_CC ); + RETURN_TRUE; + } + catch( core::CoreException& ){ + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_rollback: Unknown exception caught." ); + } +} + +// sqlsrv_client_info +// Returns the ODBC driver's dll name, version and the ODBC version. Also returns +// the version of this extension. +// Parameters: +// $conn - The connection resource by which the client and server are connected. + +PHP_FUNCTION( sqlsrv_client_info ) +{ + + LOG_FUNCTION( "sqlsrv_client_info" ); + ss_sqlsrv_conn* conn = NULL; + PROCESS_PARAMS( conn, "r", _FN_, 0 ); + + try { + + core_sqlsrv_get_client_info( conn, return_value TSRMLS_CC ); + + // Add the sqlsrv driver's file version + //Declarations below eliminate compiler warnings about string constant to char* conversions + const char* extver = "ExtensionVer"; + std::string filever = VER_FILEVERSION_STR; + core::sqlsrv_add_assoc_string( *conn, return_value, extver, &filever[0], 1 /*duplicate*/ TSRMLS_CC ); + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_client_info: Unknown exception caught." ); + } +} + +// sqlsrv_server_info( resource $conn ) +// +// Returns information about the server. +// +// Parameters +// $conn: The connection resource by which the client and server are connected. +// +// Return Value +// An associative array with the following keys: +// CurrentDatabase +// The database currently being targeted. +// SQLServerVersion +// The version of SQL Server. +// SQLServerName +// The name of the server. + +PHP_FUNCTION( sqlsrv_server_info ) +{ + try { + + LOG_FUNCTION( "sqlsrv_server_info" ); + ss_sqlsrv_conn* conn = NULL; + PROCESS_PARAMS( conn, "r", _FN_, 0 ); + + core_sqlsrv_get_server_info( conn, return_value TSRMLS_CC ); + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_server_info: Unknown exception caught." ); + } +} + + +// sqlsrv_prepare( resource $conn, string $tsql [, array $params [, array $options]]) +// +// Creates a statement resource associated with the specified connection. A statement +// resource returned by sqlsrv_prepare may be executed multiple times by sqlsrv_execute. +// In between each execution, the values may be updated by changing the value of the +// variables bound. Output parameters cannot be relied upon to contain their results until +// all rows are processed. +// +// Parameters +// $conn: The connection resource associated with the created statement. +// +// $tsql: The Transact-SQL expression that corresponds to the created statement. +// +// $params [OPTIONAL]: An array of values that correspond to parameters in a +// parameterized query. Each parameter may be specified as: +// $value | array($value [, $direction [, $phpType [, $sqlType]]]) +// When given just a $value, the direction is default input, and phptype is the value +// given, with the sql type inferred from the php type. +// +// $options [OPTIONAL]: An associative array that sets query properties. The +// table below lists the supported keys and corresponding values: +// QueryTimeout +// Sets the query timeout in seconds. By default, the driver will wait +// indefinitely for results. +// SendStreamParamsAtExec +// Configures the driver to send all stream data at execution (true), or to +// send stream data in chunks (false). By default, the value is set to +// true. For more information, see sqlsrv_send_stream_data. +// +// Return Value +// A statement resource. If the statement resource cannot be created, false is returned. + +PHP_FUNCTION( sqlsrv_prepare ) +{ + + LOG_FUNCTION( "sqlsrv_prepare" ); + + sqlsrv_malloc_auto_ptr stmt; + ss_sqlsrv_conn* conn = NULL; + char *sql = NULL; + zend_long sql_len = 0; + zval* params_z = NULL; + zval* options_z = NULL; + hash_auto_ptr ss_stmt_options_ht; + zval stmt_z; + ZVAL_UNDEF(&stmt_z); + + PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z ); + + try { + + if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) { + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( ss_stmt_options_ht ); + core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, + ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + + validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); + + } + + if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + if( sql == NULL ) { + + DIE( "sqlsrv_prepare: sql string was null." ); + } + + stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, + ss_stmt_options_ht, SS_STMT_OPTS, + ss_error_handler, NULL TSRMLS_CC ) ); + + core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC ); + + //mark_params_by_reference( stmt, params_z TSRMLS_CC ); + if (params_z) { + stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); + ZVAL_COPY(stmt->params_z, params_z); + } + + stmt->prepared = true; + + // register the statement with the PHP runtime + ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC ); + + // store the resource id with the connection so the connection + // can release this statement when it closes. + zend_long next_index = zend_hash_next_free_element( conn->stmts ); + + core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); + + stmt->conn_index = next_index; + + // the statement is now registered with EG( regular_list ) + stmt.transferred(); + + RETURN_RES(Z_RES(stmt_z)); + + } + catch( core::CoreException& ) { + + if( stmt ) { + + stmt->conn = NULL; + stmt->~ss_sqlsrv_stmt(); + } + if (!Z_ISUNDEF(stmt_z)) { + free_stmt_resource(&stmt_z TSRMLS_CC); + } + + RETURN_FALSE; + } + + catch( ... ) { + + DIE( "sqlsrv_prepare: Unknown exception caught." ); + } +} + +// sqlsrv_query( resource $conn, string $tsql [, array $params [, array $options]]) +// +// Creates a statement resource associated with the specified connection. The statement +// is immediately executed and may not be executed again using sqlsrv_execute. +// +// Parameters +// $conn: The connection resource associated with the created statement. +// +// $tsql: The Transact-SQL expression that corresponds to the created statement. +// +// $params [OPTIONAL]: An array of values that correspond to parameters in a +// parameterized query. Each parameter may be specified as: +// $value | array($value [, $direction [, $phpType [, $sqlType]]]) +// When given just a $value, the direction is default input, and phptype is the value +// given, with the sql type inferred from the php type. +// +// $options [OPTIONAL]: An associative array that sets query properties. The +// table below lists the supported keys and corresponding values: +// QueryTimeout +// Sets the query timeout in seconds. By default, the driver will wait +// indefinitely for results. +// SendStreamParamsAtExec +// Configures the driver to send all stream data at execution (true), or to +// send stream data in chunks (false). By default, the value is set to +// true. For more information, see sqlsrv_send_stream_data. +// +// Return Value +// A statement resource. If the statement resource cannot be created, false is returned. + +PHP_FUNCTION( sqlsrv_query ) +{ + + LOG_FUNCTION( "sqlsrv_query" ); + + ss_sqlsrv_conn* conn = NULL; + sqlsrv_malloc_auto_ptr stmt; + char* sql = NULL; + hash_auto_ptr ss_stmt_options_ht; + size_t sql_len = 0; + zval* options_z = NULL; + zval* params_z = NULL; + zval stmt_z; + ZVAL_UNDEF(&stmt_z); + + PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z ); + + try { + + // check for statement options + if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) { + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( ss_stmt_options_ht ); + core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, ZVAL_PTR_DTOR, + 0 /*persistent*/ TSRMLS_CC ); + + validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); + } + + if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + if( sql == NULL ) { + + DIE( "sqlsrv_query: sql string was null." ); + } + + stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, + ss_stmt_options_ht, SS_STMT_OPTS, + ss_error_handler, NULL TSRMLS_CC ) ); + + if( params_z ) { + stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); + ZVAL_COPY(stmt->params_z, params_z); + } + + stmt->set_func( "sqlsrv_query" ); + + bind_params( stmt TSRMLS_CC ); + + // execute the statement + core_sqlsrv_execute( stmt TSRMLS_CC, sql, static_cast( sql_len ) ); + + // register the statement with the PHP runtime + ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC); + // store the resource id with the connection so the connection + // can release this statement when it closes. + zend_ulong next_index = zend_hash_next_free_element( conn->stmts ); + + core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); + stmt->conn_index = next_index; + stmt.transferred(); + + RETURN_RES(Z_RES(stmt_z)); + } + + catch( core::CoreException& ) { + + if( stmt ) { + + stmt->conn = NULL; // tell the statement that it isn't part of the connection so it doesn't try to remove itself + stmt->~ss_sqlsrv_stmt(); + } + if (!Z_ISUNDEF(stmt_z)) { + free_stmt_resource(&stmt_z TSRMLS_CC); + } + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_query: Unknown exception caught." ); + } +} + +void free_stmt_resource( zval* stmt_z TSRMLS_DC ) +{ + if( FAILURE == zend_list_close( Z_RES_P( stmt_z ))) { + LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z)); + } + ZVAL_NULL( stmt_z ); + zval_ptr_dtor(stmt_z); +} + +// internal connection functions + +namespace { + +// must close all statement handles opened by this connection before closing the connection +// no errors are returned, since close should always succeed + +void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ) +{ + //pre-condition check + SQLSRV_ASSERT(( conn->handle() != NULL ), "sqlsrv_conn_close_stmts: Connection handle is NULL. Trying to destroy an " + "already destroyed connection."); + SQLSRV_ASSERT(( conn->stmts ), "sqlsrv_conn_close_stmts: Connection doesn't contain a statement array." ); + + // loop through the stmts hash table and destroy each stmt resource so we can close the + // ODBC connection + + zval* rsrc_ptr = NULL; + ZEND_HASH_FOREACH_VAL( conn->stmts, rsrc_ptr ) { + try { + int zr = ( rsrc_ptr ) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, *conn, SQLSRV_ERROR_ZEND_HASH ) { + throw core::CoreException(); + } + } + catch( core::CoreException& ) { + DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" ); + } + // see if the statement is still valid, and if not skip to the next one + // presumably this should never happen because if it's in the list, it should still be valid + // by virtue that a statement resource should remove itself from its connection when it is + // destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource + // and move to the next one. + ss_sqlsrv_stmt* stmt = NULL; + stmt = static_cast( Z_RES_VAL_P( rsrc_ptr )); + if( stmt == NULL || Z_RES_TYPE_P( rsrc_ptr ) != ss_sqlsrv_stmt::descriptor ) { + LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves" + " from the connection so this shouldn't be out of sync." ); + continue; + } + // delete the statement by deleting it from Zend's resource list, which will force its destruction + stmt->conn = NULL; + + // this would call the destructor on the statement. + // There's nothing we can do if the removal fails, so we just log it and move on. + if( zend_list_close( Z_RES_P(rsrc_ptr) ) == FAILURE ) { + LOG(SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", Z_RES_HANDLE_P(rsrc_ptr)); + } + } ZEND_HASH_FOREACH_END(); + + zend_hash_destroy( conn->stmts ); + FREE_HASHTABLE( conn->stmts ); + conn->stmts = NULL; +} + +int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC ) +{ + for( int i=0; SS_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) + { + if( key_len == SS_CONN_OPTS[ i ].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[ i ].sqlsrv_name )) { + + + switch( SS_CONN_OPTS[ i ].value_type ) { + + case CONN_ATTR_BOOL: + // bool attributes can be either strings to be appended to the connection string + // as yes or no or integral connection attributes. This will have to be reworked + // if we ever introduce a boolean connection option that maps to a string connection + // attribute. + break; + case CONN_ATTR_INT: + { + CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, + SS_CONN_OPTS[ i ].sqlsrv_name ) + { + throw ss::SSException(); + } + break; + } + case CONN_ATTR_STRING: + { + CHECK_CUSTOM_ERROR( Z_TYPE_P( value_z ) != IS_STRING, ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, + SS_CONN_OPTS[ i ].sqlsrv_name ) { + + throw ss::SSException(); + } + + char* value = Z_STRVAL_P( value_z ); + size_t value_len = Z_STRLEN_P( value_z ); + bool escaped = core_is_conn_opt_value_escaped( value, value_len ); + + CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[ i ].sqlsrv_name ) { + + throw ss::SSException(); + } + break; + } + } + + return SS_CONN_OPTS[ i ].conn_option_key; + } + } + return SQLSRV_CONN_OPTION_INVALID; +} + +int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ) +{ + for( int i = 0; SS_STMT_OPTS[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) + { + if( key_len == SS_STMT_OPTS[ i ].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[ i ].name )) { + return SS_STMT_OPTS[ i ].key; + } + } + return SQLSRV_STMT_OPTION_INVALID; +} + +void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, + HashTable* options_ht, zval* data TSRMLS_DC ) +{ + int option_key = ::get_stmt_option_key( key, key_len TSRMLS_CC ); + + CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, ZSTR_VAL( key ) ) { + + throw ss::SSException(); + } + + Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. + core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); +} + +void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, + HashTable* options_ht, zval* data TSRMLS_DC ) +{ + int option_key = ::get_conn_option_key( ctx, key, key_len, data TSRMLS_CC ); + CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, ZSTR_VAL( key ) ) { + + throw ss::SSException(); + } + + Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. + core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); +} + +// Iterates through the list of statement options provided by the user and validates them +// against the list of supported statement options by this driver. After validation +// creates a Hashtable of statement options to be sent to the core layer for processing. + +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ) +{ + try { + if( stmt_options ) { + + HashTable* options_ht = Z_ARRVAL_P( stmt_options ); + zend_ulong int_key = -1; + zend_string *key = NULL; + zval* data = NULL; + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + size_t key_len = 0; + + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + if (type != HASH_KEY_IS_STRING) { + CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) { + throw core::CoreException(); + } + } + key_len = ZSTR_LEN(key) + 1; + add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); + } + } + catch( core::CoreException& ) { + + throw; + } +} + +// Iterates through the list of connection options provided by the user and validates them +// against the predefined list of supported connection options by this driver. After validation +// creates a Hashtable of connection options to be sent to the core layer for processing. + +void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ char** uid, _Out_ char** pwd, _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ) +{ + try { + + if( user_options_z ) { + + HashTable* options_ht = Z_ARRVAL_P( user_options_z ); + zend_ulong int_key = -1; + zend_string *key = NULL; + zval* data = NULL; + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + CHECK_CUSTOM_ERROR(( Z_TYPE_P( data ) == IS_NULL || Z_TYPE_P( data ) == IS_UNDEF ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key) { + throw ss::SSException(); + } + + CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) { + throw ss::SSException(); + } + + // Length of the key string does not include the null terminator in PHP7, +1 has to be added + size_t key_len = ZSTR_LEN(key) + 1; + if( key_len == sizeof(SSConnOptionNames::UID) && !stricmp(ZSTR_VAL(key), SSConnOptionNames::UID )) { + + *uid = Z_STRVAL_P( data ); + } + + else if( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::PWD )) { + + *pwd = Z_STRVAL_P( data ); + } + else { + + ::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC ); + } + } ZEND_HASH_FOREACH_END(); + } + } + catch( core::CoreException& ) { + throw; + } +} + +} // namespace diff --git a/sqlsrv/init.cpp b/source/sqlsrv/init.cpp similarity index 93% rename from sqlsrv/init.cpp rename to source/sqlsrv/init.cpp index e9c0473b..5f047f4b 100644 --- a/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -1,660 +1,680 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: init.cpp -// Contents: initialization routines for the extension -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "php_sqlsrv.h" - -#ifdef ZTS -ZEND_TSRMLS_CACHE_DEFINE(); -#endif -ZEND_GET_MODULE(g_sqlsrv) - -extern "C" { - -ZEND_DECLARE_MODULE_GLOBALS(sqlsrv); - -} - -// module global variables (initialized in minit and freed in mshutdown) -HashTable* g_ss_errors_ht = NULL; -// special list of warnings to ignore even if warnings are treated as errors -HashTable* g_ss_warnings_to_ignore_ht = NULL; -// encodings we understand -HashTable* g_ss_encodings_ht = NULL; - -// Destructors called by Zend for each element in the hashtable -void sqlsrv_error_const_dtor( zval* element ); -void sqlsrv_encoding_dtor( zval* element ); - -// henv context for creating connections -sqlsrv_context* g_henv_cp; -sqlsrv_context* g_henv_ncp; - -namespace { - -// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros -unsigned int current_log_subsystem = LOG_INIT; -} - -// argument info structures for functions, arranged alphabetically. -// see zend_API.h in the PHP sources for more information about these macros -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_begin_transaction_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, conn ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_cancel_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_close_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, conn ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_client_info_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, conn ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_commit_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, conn ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_configure_arginfo, 0, 0, 2 ) - ZEND_ARG_INFO( 0, setting ) - ZEND_ARG_INFO( 0, value ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_connect_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, server_name ) - ZEND_ARG_ARRAY_INFO( 0, connection_info, 0 ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_errors_arginfo, 0, 1, 0 ) - ZEND_ARG_INFO( 0, errors_and_or_warnings ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_execute_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 1, 1 ) - ZEND_ARG_INFO( 0, stmt ) - ZEND_ARG_INFO( 0, fetch_type ) - ZEND_ARG_INFO( 0, row ) - ZEND_ARG_INFO( 0, offset ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 1, 1 ) - ZEND_ARG_INFO( 0, stmt ) - ZEND_ARG_INFO( 0, class_name ) - ZEND_ARG_INFO( 0, ctor_params ) - ZEND_ARG_INFO( 0, row ) - ZEND_ARG_INFO( 0, offset ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_field_metadata_arginfo, 0, 1, 1 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_free_stmt_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_config_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, setting ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 1, 2 ) - ZEND_ARG_INFO( 0, stmt ) - ZEND_ARG_INFO( 0, field_index ) - ZEND_ARG_INFO( 0, get_as_type ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_has_rows_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_next_result_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_num_fields_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_num_rows_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 1, 2 ) - ZEND_ARG_INFO( 0, conn ) - ZEND_ARG_INFO( 0, tsql ) - ZEND_ARG_INFO( 0, params ) - ZEND_ARG_INFO( 0, options ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_query_arginfo, 0, 1, 2 ) - ZEND_ARG_INFO( 0, conn ) - ZEND_ARG_INFO( 0, tsql ) - ZEND_ARG_INFO( 0, params ) - ZEND_ARG_INFO( 0, options ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_rollback_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, conn ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_rows_affected_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_send_stream_data_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_server_info_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_size_arginfo, 0 ) - ZEND_ARG_INFO( 0, size ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_precision_scale_arginfo, 0 ) - ZEND_ARG_INFO( 0, precision ) - ZEND_ARG_INFO( 0, scale ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_phptype_encoding_arginfo, 0 ) - ZEND_ARG_INFO( 0, encoding ) -ZEND_END_ARG_INFO() - -// function table with associated arginfo structures -zend_function_entry sqlsrv_functions[] = { - PHP_FE( sqlsrv_connect, sqlsrv_connect_arginfo ) - PHP_FE( sqlsrv_close, sqlsrv_close_arginfo ) - PHP_FE( sqlsrv_commit, sqlsrv_commit_arginfo ) - PHP_FE( sqlsrv_begin_transaction, sqlsrv_begin_transaction_arginfo ) - PHP_FE( sqlsrv_rollback, sqlsrv_rollback_arginfo ) - PHP_FE( sqlsrv_errors, sqlsrv_errors_arginfo ) - PHP_FE( sqlsrv_configure, sqlsrv_configure_arginfo ) - PHP_FE( sqlsrv_get_config, sqlsrv_get_config_arginfo ) - PHP_FE( sqlsrv_prepare, sqlsrv_prepare_arginfo ) - PHP_FE( sqlsrv_execute, sqlsrv_execute_arginfo ) - PHP_FE( sqlsrv_query, sqlsrv_query_arginfo ) - PHP_FE( sqlsrv_fetch, sqlsrv_fetch_arginfo ) - PHP_FE( sqlsrv_get_field, sqlsrv_get_field_arginfo ) - PHP_FE( sqlsrv_fetch_array, sqlsrv_fetch_array_arginfo ) - PHP_FE( sqlsrv_fetch_object, sqlsrv_fetch_object_arginfo ) - PHP_FE( sqlsrv_has_rows, sqlsrv_has_rows_arginfo ) - PHP_FE( sqlsrv_num_fields, sqlsrv_num_fields_arginfo ) - PHP_FE( sqlsrv_next_result, sqlsrv_next_result_arginfo ) - PHP_FE( sqlsrv_num_rows, sqlsrv_num_rows_arginfo ) - PHP_FE( sqlsrv_rows_affected, sqlsrv_rows_affected_arginfo ) - PHP_FE( SQLSRV_PHPTYPE_STREAM, sqlsrv_phptype_encoding_arginfo ) - PHP_FE( SQLSRV_PHPTYPE_STRING, sqlsrv_phptype_encoding_arginfo ) - PHP_FE( sqlsrv_client_info, sqlsrv_client_info_arginfo ) - PHP_FE( sqlsrv_server_info, sqlsrv_server_info_arginfo ) - PHP_FE( sqlsrv_cancel, sqlsrv_cancel_arginfo ) - PHP_FE( sqlsrv_free_stmt, sqlsrv_close_arginfo ) - PHP_FE( sqlsrv_field_metadata, sqlsrv_field_metadata_arginfo ) - PHP_FE( sqlsrv_send_stream_data, sqlsrv_send_stream_data_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_BINARY, sqlsrv_sqltype_size_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_CHAR, sqlsrv_sqltype_size_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_DECIMAL, sqlsrv_sqltype_precision_scale_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_NCHAR, sqlsrv_sqltype_size_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_NUMERIC, sqlsrv_sqltype_precision_scale_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_NVARCHAR, sqlsrv_sqltype_size_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_VARBINARY, sqlsrv_sqltype_size_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_VARCHAR, sqlsrv_sqltype_size_arginfo ) - - {NULL, NULL, NULL} // end of the table -}; - -// the structure returned to Zend that exposes the extension to the Zend engine. -// this structure is defined in zend_modules.h in the PHP sources - -zend_module_entry g_sqlsrv_module_entry = -{ - STANDARD_MODULE_HEADER, - "sqlsrv", - sqlsrv_functions, // exported function table - // initialization and shutdown functions - PHP_MINIT(sqlsrv), - PHP_MSHUTDOWN(sqlsrv), - PHP_RINIT(sqlsrv), - PHP_RSHUTDOWN(sqlsrv), - PHP_MINFO(sqlsrv), - // version of the extension. Matches the version resource of the extension dll - VER_FILEVERSION_STR, - PHP_MODULE_GLOBALS(sqlsrv), - NULL, - NULL, - NULL, - STANDARD_MODULE_PROPERTIES_EX -}; - -// Module initialization -// This function is called once per execution of the Zend engine -// We use it to: -// 1) Register our constants. See MSDN or the function below for the exact constants -// we register. -// 2) Register our resource types (connection, statement, and stream types) -// 3) Allocate the environment handles for ODBC connections (1 for non pooled -// connections and 1 for pooled connections) -// 4) Register our INI entries. See MSDN or php_sqlsrv.h for our supported INI entries - -PHP_MINIT_FUNCTION(sqlsrv) -{ - SQLSRV_UNUSED( type ); - - core_sqlsrv_register_logger( ss_sqlsrv_log ); - - // our global variables are initialized in the RINIT function -#if defined(ZTS) - if( ts_allocate_id( &sqlsrv_globals_id, - sizeof( zend_sqlsrv_globals ), - (ts_allocate_ctor) NULL, - (ts_allocate_dtor) NULL ) == 0 ) - return FAILURE; - ZEND_TSRMLS_CACHE_UPDATE(); -#endif - - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long )); - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); - - REGISTER_INI_ENTRIES(); - - LOG_FUNCTION( "PHP_MINIT_FUNCTION for php_sqlsrv" ); - - REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ERRORS", SQLSRV_ERR_ERRORS, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_ERR_WARNINGS", SQLSRV_ERR_WARNINGS, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ALL", SQLSRV_ERR_ALL, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_OFF", 0, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_INIT", LOG_INIT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_CONN", LOG_CONN, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_STMT", LOG_STMT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_UTIL", LOG_UTIL, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set - - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ERROR", SEV_ERROR, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_WARNING", SEV_WARNING, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_NOTICE", SEV_NOTICE, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set - - // register connection resource - ss_sqlsrv_conn::descriptor = zend_register_list_destructors_ex( sqlsrv_conn_dtor, NULL, "SQL Server Connection", - module_number ); - - if( ss_sqlsrv_conn::descriptor == FAILURE ) { - LOG( SEV_ERROR, "%1!s!: connection resource registration failed", _FN_ ); - return FAILURE; - } - - // register statement resources - ss_sqlsrv_stmt::descriptor = zend_register_list_destructors_ex( sqlsrv_stmt_dtor, NULL, "SQL Server Statement", module_number ); - - if( ss_sqlsrv_stmt::descriptor == FAILURE ) { - LOG( SEV_ERROR, "%1!s!: statement resource regisration failed", _FN_ ); - return FAILURE; - } - - sqlsrv_sqltype constant_type; - - REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_NUMERIC", SQLSRV_FETCH_NUMERIC, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_ASSOC", SQLSRV_FETCH_ASSOC, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_BOTH", SQLSRV_FETCH_BOTH, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_NULL", SQLSRV_PHPTYPE_NULL, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_INT", SQLSRV_PHPTYPE_INT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_FLOAT", SQLSRV_PHPTYPE_FLOAT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_DATETIME", SQLSRV_PHPTYPE_DATETIME, CONST_PERSISTENT | CONST_CS ); - - REGISTER_STRING_CONSTANT( "SQLSRV_ENC_BINARY", "binary", CONST_PERSISTENT | CONST_CS ); - REGISTER_STRING_CONSTANT( "SQLSRV_ENC_CHAR", "char", CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_NO", 0, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_YES", 1, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_UNKNOWN", 2, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIGINT", SQL_BIGINT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIT", SQL_BIT, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; - constant_type.typeinfo.size = 23; - constant_type.typeinfo.scale = 3; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_FLOAT", SQL_FLOAT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_IMAGE", SQL_LONGVARBINARY, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_INT", SQL_INTEGER, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_DECIMAL; - constant_type.typeinfo.size = 19; - constant_type.typeinfo.scale = 4; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_MONEY", constant_type.value, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_NTEXT", SQL_WLONGVARCHAR, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TEXT", SQL_LONGVARCHAR, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_REAL", SQL_REAL, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; - constant_type.typeinfo.size = 16; - constant_type.typeinfo.scale = 0; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLDATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLINT", SQL_SMALLINT, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_DECIMAL; - constant_type.typeinfo.size = 10; - constant_type.typeinfo.scale = 4; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLMONEY", constant_type.value, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_BINARY; - constant_type.typeinfo.size = 8; - constant_type.typeinfo.scale = 0; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIMESTAMP", constant_type.value, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TINYINT", SQL_TINYINT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UDT", SQL_SS_UDT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UNIQUEIDENTIFIER", SQL_GUID, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_XML", SQL_SS_XML, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_TYPE_DATE; - constant_type.typeinfo.size = 10; - constant_type.typeinfo.scale = 0; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATE", constant_type.value, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_SS_TIME2; - constant_type.typeinfo.size = 16; - constant_type.typeinfo.scale = 7; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_SS_TIMESTAMPOFFSET; - constant_type.typeinfo.size = 34; - constant_type.typeinfo.scale = 7; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIMEOFFSET", constant_type.value, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; - constant_type.typeinfo.size = 27; - constant_type.typeinfo.scale = 7; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME2", constant_type.value, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_IN", SQL_PARAM_INPUT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_OUT", SQL_PARAM_OUTPUT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_INOUT", SQL_PARAM_INPUT_OUTPUT, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_UNCOMMITTED", SQL_TXN_READ_UNCOMMITTED, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_COMMITTED", SQL_TXN_READ_COMMITTED, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_TXN_REPEATABLE_READ", SQL_TXN_REPEATABLE_READ, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SERIALIZABLE", SQL_TXN_SERIALIZABLE, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SNAPSHOT", SQL_TXN_SS_SNAPSHOT, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_NEXT", SQL_FETCH_NEXT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_PRIOR", SQL_FETCH_PRIOR, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_FIRST", SQL_FETCH_FIRST, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_LAST", SQL_FETCH_LAST, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_ABSOLUTE", SQL_FETCH_ABSOLUTE, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_RELATIVE", SQL_FETCH_RELATIVE, CONST_PERSISTENT | CONST_CS ); - - REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_FORWARD", "forward", CONST_PERSISTENT | CONST_CS ); - REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_STATIC", "static", CONST_PERSISTENT | CONST_CS ); - REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_DYNAMIC", "dynamic", CONST_PERSISTENT | CONST_CS ); - REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_KEYSET", "keyset", CONST_PERSISTENT | CONST_CS ); - REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_CLIENT_BUFFERED", "buffered", CONST_PERSISTENT | CONST_CS ); - - try { - - // initialize list of warnings to ignore - g_ss_warnings_to_ignore_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - zend_hash_init( g_ss_warnings_to_ignore_ht, 6, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); - - sqlsrv_error_const error_to_ignore; - - // changed database warning - error_to_ignore.sqlstate = (SQLCHAR*)"01000"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = 5701; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // changed language warning - error_to_ignore.sqlstate = (SQLCHAR*)"01000"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = 5703; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // option value changed - error_to_ignore.sqlstate = (SQLCHAR*)"01S02"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = -1; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // cursor operation conflict - error_to_ignore.sqlstate = (SQLCHAR*)"01001"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = -1; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // null value eliminated in set function - error_to_ignore.sqlstate = (SQLCHAR*)"01003"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = -1; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // SQL Azure warning: This session has been assigned a tracing id of .. - error_to_ignore.sqlstate = (SQLCHAR*)"01000"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = 40608; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // Full-text search condition contained noise words warning - error_to_ignore.sqlstate = (SQLCHAR*)"01000"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = 9927; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem(g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - } - catch( ss::SSException& ) { - - LOG( SEV_ERROR, "PHP_MINIT: warnings hash table failure" ); - return FAILURE; - } - - try { - - // supported encodings - g_ss_encodings_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - zend_hash_init( g_ss_encodings_ht, 3, NULL /*use standard hash function*/, sqlsrv_encoding_dtor /*resource destructor*/, 1 /*persistent*/ ); - - sqlsrv_encoding sql_enc_char( "char", SQLSRV_ENCODING_CHAR ); - if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_char, sizeof( sqlsrv_encoding ))) { - throw ss::SSException(); - } - - sqlsrv_encoding sql_enc_bin( "binary", SQLSRV_ENCODING_BINARY, true ); - if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_bin, sizeof( sqlsrv_encoding ))) { - throw ss::SSException(); - } - - sqlsrv_encoding sql_enc_utf8( "utf-8", CP_UTF8 ); - if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_utf8, sizeof( sqlsrv_encoding ))) { - throw ss::SSException(); - } - } - catch( ss::SSException& ) { - - LOG( SEV_ERROR, "PHP_RINIT: encodings hash table failure" ); - return FAILURE; - } - - // initialize list of sqlsrv errors - g_ss_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - ::zend_hash_init( g_ss_errors_ht, 50, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); - - for( int i = 0; SS_ERRORS[ i ].error_code != UINT_MAX; ++i ) { - if (NULL == ::zend_hash_index_update_mem( g_ss_errors_ht, SS_ERRORS[ i ].error_code, - &( SS_ERRORS[ i ].sqlsrv_error ), sizeof( SS_ERRORS[ i ].sqlsrv_error ))) { - LOG( SEV_ERROR, "%1!s!: Failed to insert data into sqlsrv errors hashtable.", _FN_ ); - return FAILURE; - } - } - - if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper TSRMLS_CC ) == FAILURE ) { - LOG( SEV_ERROR, "%1!s!: stream registration failed", _FN_ ); - return FAILURE; - } - - try { - // retrieve the handles for the environments - core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, ss_error_handler, "PHP_MINIT_FUNCTION for sqlsrv" TSRMLS_CC ); - } - - catch( core::CoreException& ) { - return FAILURE; - } - - catch( ... ) { - - LOG( SEV_ERROR, "PHP_RINIT: Unknown exception caught." ); - return FAILURE; - } - - return SUCCESS; -} - -// called by Zend for each parameter in the g_ss_warnings_to_ignore_ht and g_ss_errors_ht hash table when it is destroyed -void sqlsrv_error_const_dtor( zval* elem ) { - sqlsrv_error_const* error_to_ignore = static_cast( Z_PTR_P(elem) ); - pefree(error_to_ignore, 1); -} - -// called by Zend for each parameter in the g_ss_encodings_ht hash table when it is destroyed -void sqlsrv_encoding_dtor( zval* elem ) { - sqlsrv_encoding* sql_enc = static_cast( Z_PTR_P(elem) ); - pefree(sql_enc, 1); -} - - -// Module shutdown function -// Free the environment handles allocated in MINIT and unregister our stream wrapper. -// Resource types and constants are automatically released since we don't flag them as -// persistent when they are registered. - -PHP_MSHUTDOWN_FUNCTION(sqlsrv) -{ - SQLSRV_UNUSED( type ); - - UNREGISTER_INI_ENTRIES(); - - // clean up the list of sqlsrv errors - zend_hash_destroy( g_ss_errors_ht ); - pefree( g_ss_errors_ht, 1 /*persistent*/ ); - - zend_hash_destroy( g_ss_warnings_to_ignore_ht ); - pefree( g_ss_warnings_to_ignore_ht, 1 ); - - zend_hash_destroy( g_ss_encodings_ht ); - pefree( g_ss_encodings_ht, 1 ); - - core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp ); - - if( php_unregister_url_stream_wrapper( SQLSRV_STREAM_WRAPPER TSRMLS_CC ) == FAILURE ) { - return FAILURE; - } - - return SUCCESS; -} - - -// Request initialization function -// This function is called once per PHP script execution -// Initialize request globals used in the request, including those that correspond to INI entries. -// Also, we allocate a list of warnings "to ignore", meaning that they are warnings that do not -// trigger errors when WarningsReturnAsErrors is true. If you have warnings that you want ignored -// (such as return values from stored procedures), add them to this collection and they won't be -// returned as errors. Or you could just set WarningsReturnAsErrors to false. - -PHP_RINIT_FUNCTION(sqlsrv) -{ - SQLSRV_UNUSED( module_number ); - SQLSRV_UNUSED( type ); - -#if defined(ZTS) - ZEND_TSRMLS_CACHE_UPDATE(); -#endif - - SQLSRV_G( warnings_return_as_errors ) = true; - ZVAL_NULL( &SQLSRV_G( errors )); - ZVAL_NULL( &SQLSRV_G( warnings )); - - LOG_FUNCTION( "PHP_RINIT for php_sqlsrv" ); - - // read INI settings - SQLSRV_G( warnings_return_as_errors ) = INI_BOOL( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS ); - SQLSRV_G( log_severity ) = INI_INT( INI_PREFIX INI_LOG_SEVERITY ); - SQLSRV_G( log_subsystems ) = INI_INT( INI_PREFIX INI_LOG_SUBSYSTEMS ); - SQLSRV_G( buffered_query_limit ) = INI_INT( INI_PREFIX INI_BUFFERED_QUERY_LIMIT ); - - LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); - LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); - LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); - LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit )); - - return SUCCESS; -} - - -// Request shutdown -// Called at the end of a script's execution -// Simply releases the variables allocated during request initialization. - -PHP_RSHUTDOWN_FUNCTION(sqlsrv) -{ - SQLSRV_UNUSED( module_number ); - SQLSRV_UNUSED( type ); - - LOG_FUNCTION( "PHP_RSHUTDOWN for php_sqlsrv" ); - reset_errors( TSRMLS_C ); - - // TODO - destruction - zval_ptr_dtor( &SQLSRV_G( errors )); - zval_ptr_dtor( &SQLSRV_G( warnings )); - - return SUCCESS; -} - -// Called for php_info(); Displays the INI settings registered and their current values -PHP_MINFO_FUNCTION(sqlsrv) -{ - php_info_print_table_start(); - php_info_print_table_header(2, "sqlsrv support", "enabled"); - php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); - php_info_print_table_end(); - DISPLAY_INI_ENTRIES(); -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: init.cpp +// Contents: initialization routines for the extension +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_sqlsrv.h" + +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE(); +#endif +ZEND_GET_MODULE(g_sqlsrv) + +extern "C" { + +ZEND_DECLARE_MODULE_GLOBALS(sqlsrv); + +} + +// module global variables (initialized in minit and freed in mshutdown) +HashTable* g_ss_errors_ht = NULL; +// special list of warnings to ignore even if warnings are treated as errors +HashTable* g_ss_warnings_to_ignore_ht = NULL; +// encodings we understand +HashTable* g_ss_encodings_ht = NULL; + +// Destructors called by Zend for each element in the hashtable +void sqlsrv_error_const_dtor( zval* element ); +void sqlsrv_encoding_dtor( zval* element ); + +// henv context for creating connections +sqlsrv_context* g_henv_cp; +sqlsrv_context* g_henv_ncp; + +namespace { + +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros +unsigned int current_log_subsystem = LOG_INIT; +} + +// argument info structures for functions, arranged alphabetically. +// see zend_API.h in the PHP sources for more information about these macros +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_begin_transaction_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, conn ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_cancel_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_close_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, conn ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_client_info_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, conn ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_commit_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, conn ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_configure_arginfo, 0, 0, 2 ) + ZEND_ARG_INFO( 0, setting ) + ZEND_ARG_INFO( 0, value ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_connect_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, server_name ) + ZEND_ARG_ARRAY_INFO( 0, connection_info, 0 ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_errors_arginfo, 0, 1, 0 ) + ZEND_ARG_INFO( 0, errors_and_or_warnings ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_execute_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 1, 1 ) + ZEND_ARG_INFO( 0, stmt ) + ZEND_ARG_INFO( 0, fetch_type ) + ZEND_ARG_INFO( 0, row ) + ZEND_ARG_INFO( 0, offset ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 1, 1 ) + ZEND_ARG_INFO( 0, stmt ) + ZEND_ARG_INFO( 0, class_name ) + ZEND_ARG_INFO( 0, ctor_params ) + ZEND_ARG_INFO( 0, row ) + ZEND_ARG_INFO( 0, offset ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_field_metadata_arginfo, 0, 1, 1 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_free_stmt_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_config_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, setting ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 1, 2 ) + ZEND_ARG_INFO( 0, stmt ) + ZEND_ARG_INFO( 0, field_index ) + ZEND_ARG_INFO( 0, get_as_type ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_has_rows_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_next_result_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_num_fields_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_num_rows_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 1, 2 ) + ZEND_ARG_INFO( 0, conn ) + ZEND_ARG_INFO( 0, tsql ) + ZEND_ARG_INFO( 0, params ) + ZEND_ARG_INFO( 0, options ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_query_arginfo, 0, 1, 2 ) + ZEND_ARG_INFO( 0, conn ) + ZEND_ARG_INFO( 0, tsql ) + ZEND_ARG_INFO( 0, params ) + ZEND_ARG_INFO( 0, options ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_rollback_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, conn ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_rows_affected_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_send_stream_data_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_server_info_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_size_arginfo, 0 ) + ZEND_ARG_INFO( 0, size ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_precision_scale_arginfo, 0 ) + ZEND_ARG_INFO( 0, precision ) + ZEND_ARG_INFO( 0, scale ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_phptype_encoding_arginfo, 0 ) + ZEND_ARG_INFO( 0, encoding ) +ZEND_END_ARG_INFO() + +// function table with associated arginfo structures +zend_function_entry sqlsrv_functions[] = { + PHP_FE( sqlsrv_connect, sqlsrv_connect_arginfo ) + PHP_FE( sqlsrv_close, sqlsrv_close_arginfo ) + PHP_FE( sqlsrv_commit, sqlsrv_commit_arginfo ) + PHP_FE( sqlsrv_begin_transaction, sqlsrv_begin_transaction_arginfo ) + PHP_FE( sqlsrv_rollback, sqlsrv_rollback_arginfo ) + PHP_FE( sqlsrv_errors, sqlsrv_errors_arginfo ) + PHP_FE( sqlsrv_configure, sqlsrv_configure_arginfo ) + PHP_FE( sqlsrv_get_config, sqlsrv_get_config_arginfo ) + PHP_FE( sqlsrv_prepare, sqlsrv_prepare_arginfo ) + PHP_FE( sqlsrv_execute, sqlsrv_execute_arginfo ) + PHP_FE( sqlsrv_query, sqlsrv_query_arginfo ) + PHP_FE( sqlsrv_fetch, sqlsrv_fetch_arginfo ) + PHP_FE( sqlsrv_get_field, sqlsrv_get_field_arginfo ) + PHP_FE( sqlsrv_fetch_array, sqlsrv_fetch_array_arginfo ) + PHP_FE( sqlsrv_fetch_object, sqlsrv_fetch_object_arginfo ) + PHP_FE( sqlsrv_has_rows, sqlsrv_has_rows_arginfo ) + PHP_FE( sqlsrv_num_fields, sqlsrv_num_fields_arginfo ) + PHP_FE( sqlsrv_next_result, sqlsrv_next_result_arginfo ) + PHP_FE( sqlsrv_num_rows, sqlsrv_num_rows_arginfo ) + PHP_FE( sqlsrv_rows_affected, sqlsrv_rows_affected_arginfo ) + PHP_FE( SQLSRV_PHPTYPE_STREAM, sqlsrv_phptype_encoding_arginfo ) + PHP_FE( SQLSRV_PHPTYPE_STRING, sqlsrv_phptype_encoding_arginfo ) + PHP_FE( sqlsrv_client_info, sqlsrv_client_info_arginfo ) + PHP_FE( sqlsrv_server_info, sqlsrv_server_info_arginfo ) + PHP_FE( sqlsrv_cancel, sqlsrv_cancel_arginfo ) + PHP_FE( sqlsrv_free_stmt, sqlsrv_close_arginfo ) + PHP_FE( sqlsrv_field_metadata, sqlsrv_field_metadata_arginfo ) + PHP_FE( sqlsrv_send_stream_data, sqlsrv_send_stream_data_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_BINARY, sqlsrv_sqltype_size_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_CHAR, sqlsrv_sqltype_size_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_DECIMAL, sqlsrv_sqltype_precision_scale_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_NCHAR, sqlsrv_sqltype_size_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_NUMERIC, sqlsrv_sqltype_precision_scale_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_NVARCHAR, sqlsrv_sqltype_size_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_VARBINARY, sqlsrv_sqltype_size_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_VARCHAR, sqlsrv_sqltype_size_arginfo ) + + {NULL, NULL, NULL} // end of the table +}; + +// the structure returned to Zend that exposes the extension to the Zend engine. +// this structure is defined in zend_modules.h in the PHP sources + +zend_module_entry g_sqlsrv_module_entry = +{ + STANDARD_MODULE_HEADER, + "sqlsrv", + sqlsrv_functions, // exported function table + // initialization and shutdown functions + PHP_MINIT(sqlsrv), + PHP_MSHUTDOWN(sqlsrv), + PHP_RINIT(sqlsrv), + PHP_RSHUTDOWN(sqlsrv), + PHP_MINFO(sqlsrv), + // version of the extension. Matches the version resource of the extension dll + VER_FILEVERSION_STR, + PHP_MODULE_GLOBALS(sqlsrv), + NULL, + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +// Module initialization +// This function is called once per execution of the Zend engine +// We use it to: +// 1) Register our constants. See MSDN or the function below for the exact constants +// we register. +// 2) Register our resource types (connection, statement, and stream types) +// 3) Allocate the environment handles for ODBC connections (1 for non pooled +// connections and 1 for pooled connections) +// 4) Register our INI entries. See MSDN or php_sqlsrv.h for our supported INI entries + +PHP_MINIT_FUNCTION(sqlsrv) +{ + SQLSRV_UNUSED( type ); + + core_sqlsrv_register_logger( ss_sqlsrv_log ); + + // our global variables are initialized in the RINIT function +#if defined(ZTS) + if( ts_allocate_id( &sqlsrv_globals_id, + sizeof( zend_sqlsrv_globals ), + (ts_allocate_ctor) NULL, + (ts_allocate_dtor) NULL ) == 0 ) + return FAILURE; + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long )); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); + + REGISTER_INI_ENTRIES(); + + LOG_FUNCTION( "PHP_MINIT_FUNCTION for php_sqlsrv" ); + + REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ERRORS", SQLSRV_ERR_ERRORS, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_ERR_WARNINGS", SQLSRV_ERR_WARNINGS, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ALL", SQLSRV_ERR_ALL, CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_OFF", 0, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_INIT", LOG_INIT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_CONN", LOG_CONN, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_STMT", LOG_STMT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_UTIL", LOG_UTIL, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set + + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ERROR", SEV_ERROR, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_WARNING", SEV_WARNING, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_NOTICE", SEV_NOTICE, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set + + // register connection resource + ss_sqlsrv_conn::descriptor = zend_register_list_destructors_ex( sqlsrv_conn_dtor, NULL, "SQL Server Connection", + module_number ); + + if( ss_sqlsrv_conn::descriptor == FAILURE ) { + LOG( SEV_ERROR, "%1!s!: connection resource registration failed", _FN_ ); + return FAILURE; + } + + // register statement resources + ss_sqlsrv_stmt::descriptor = zend_register_list_destructors_ex( sqlsrv_stmt_dtor, NULL, "SQL Server Statement", module_number ); + + if( ss_sqlsrv_stmt::descriptor == FAILURE ) { + LOG( SEV_ERROR, "%1!s!: statement resource regisration failed", _FN_ ); + return FAILURE; + } + + sqlsrv_sqltype constant_type; + + REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_NUMERIC", SQLSRV_FETCH_NUMERIC, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_ASSOC", SQLSRV_FETCH_ASSOC, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_BOTH", SQLSRV_FETCH_BOTH, CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_NULL", SQLSRV_PHPTYPE_NULL, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_INT", SQLSRV_PHPTYPE_INT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_FLOAT", SQLSRV_PHPTYPE_FLOAT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_DATETIME", SQLSRV_PHPTYPE_DATETIME, CONST_PERSISTENT | CONST_CS ); + + std::string bin = "binary"; + std::string chr = "char"; + + REGISTER_STRING_CONSTANT( "SQLSRV_ENC_BINARY", &bin[0], CONST_PERSISTENT | CONST_CS ); + REGISTER_STRING_CONSTANT( "SQLSRV_ENC_CHAR", &chr[0], CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_NO", 0, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_YES", 1, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_UNKNOWN", 2, CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIGINT", SQL_BIGINT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIT", SQL_BIT, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; + constant_type.typeinfo.size = 23; + constant_type.typeinfo.scale = 3; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_FLOAT", SQL_FLOAT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_IMAGE", SQL_LONGVARBINARY, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_INT", SQL_INTEGER, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_DECIMAL; + constant_type.typeinfo.size = 19; + constant_type.typeinfo.scale = 4; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_MONEY", constant_type.value, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_NTEXT", SQL_WLONGVARCHAR, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TEXT", SQL_LONGVARCHAR, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_REAL", SQL_REAL, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; + constant_type.typeinfo.size = 16; + constant_type.typeinfo.scale = 0; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLDATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLINT", SQL_SMALLINT, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_DECIMAL; + constant_type.typeinfo.size = 10; + constant_type.typeinfo.scale = 4; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLMONEY", constant_type.value, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_BINARY; + constant_type.typeinfo.size = 8; + constant_type.typeinfo.scale = 0; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIMESTAMP", constant_type.value, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TINYINT", SQL_TINYINT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UDT", SQL_SS_UDT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UNIQUEIDENTIFIER", SQL_GUID, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_XML", SQL_SS_XML, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_TYPE_DATE; + constant_type.typeinfo.size = 10; + constant_type.typeinfo.scale = 0; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATE", constant_type.value, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_SS_TIME2; + constant_type.typeinfo.size = 16; + constant_type.typeinfo.scale = 7; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_SS_TIMESTAMPOFFSET; + constant_type.typeinfo.size = 34; + constant_type.typeinfo.scale = 7; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIMEOFFSET", constant_type.value, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; + constant_type.typeinfo.size = 27; + constant_type.typeinfo.scale = 7; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME2", constant_type.value, CONST_PERSISTENT | CONST_CS ); + + // These constant are defined to provide type checking (type ==SQLSRV_SQLTYPE_DECIMAL). + // There are functions with the same name which accept parameters and is used in binding paramters. + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_DECIMAL", SQL_DECIMAL, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NUMERIC", SQL_NUMERIC, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_CHAR", SQL_CHAR, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NCHAR", SQL_WCHAR, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_VARCHAR", SQL_VARCHAR, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NVARCHAR", SQL_WVARCHAR, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_BINARY", SQL_BINARY, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_VARBINARY", SQL_VARBINARY, CONST_PERSISTENT | CONST_CS); + + REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_IN", SQL_PARAM_INPUT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_OUT", SQL_PARAM_OUTPUT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_INOUT", SQL_PARAM_INPUT_OUTPUT, CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_UNCOMMITTED", SQL_TXN_READ_UNCOMMITTED, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_COMMITTED", SQL_TXN_READ_COMMITTED, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_TXN_REPEATABLE_READ", SQL_TXN_REPEATABLE_READ, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SERIALIZABLE", SQL_TXN_SERIALIZABLE, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SNAPSHOT", SQL_TXN_SS_SNAPSHOT, CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_NEXT", SQL_FETCH_NEXT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_PRIOR", SQL_FETCH_PRIOR, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_FIRST", SQL_FETCH_FIRST, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_LAST", SQL_FETCH_LAST, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_ABSOLUTE", SQL_FETCH_ABSOLUTE, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_RELATIVE", SQL_FETCH_RELATIVE, CONST_PERSISTENT | CONST_CS ); + + std::string fwd = "forward"; + std::string stc = "static"; + std::string dyn = "dynamic"; + std::string key = "keyset"; + std::string buf = "buffered"; + + REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_FORWARD", &fwd[0], CONST_PERSISTENT | CONST_CS ); + REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_STATIC", &stc[0], CONST_PERSISTENT | CONST_CS ); + REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_DYNAMIC", &dyn[0], CONST_PERSISTENT | CONST_CS ); + REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_KEYSET", &key[0], CONST_PERSISTENT | CONST_CS ); + REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_CLIENT_BUFFERED", &buf[0], CONST_PERSISTENT | CONST_CS ); + + try { + + // initialize list of warnings to ignore + g_ss_warnings_to_ignore_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); + zend_hash_init( g_ss_warnings_to_ignore_ht, 6, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); + + sqlsrv_error_const error_to_ignore; + + // changed database warning + error_to_ignore.sqlstate = (SQLCHAR*)"01000"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = 5701; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // changed language warning + error_to_ignore.sqlstate = (SQLCHAR*)"01000"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = 5703; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // option value changed + error_to_ignore.sqlstate = (SQLCHAR*)"01S02"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = -1; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // cursor operation conflict + error_to_ignore.sqlstate = (SQLCHAR*)"01001"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = -1; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // null value eliminated in set function + error_to_ignore.sqlstate = (SQLCHAR*)"01003"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = -1; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // SQL Azure warning: This session has been assigned a tracing id of .. + error_to_ignore.sqlstate = (SQLCHAR*)"01000"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = 40608; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // Full-text search condition contained noise words warning + error_to_ignore.sqlstate = (SQLCHAR*)"01000"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = 9927; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem(g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + } + catch( ss::SSException& ) { + + LOG( SEV_ERROR, "PHP_MINIT: warnings hash table failure" ); + return FAILURE; + } + + try { + + // supported encodings + g_ss_encodings_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); + zend_hash_init( g_ss_encodings_ht, 3, NULL /*use standard hash function*/, sqlsrv_encoding_dtor /*resource destructor*/, 1 /*persistent*/ ); + + sqlsrv_encoding sql_enc_char( "char", SQLSRV_ENCODING_CHAR ); + if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_char, sizeof( sqlsrv_encoding ))) { + throw ss::SSException(); + } + + sqlsrv_encoding sql_enc_bin( "binary", SQLSRV_ENCODING_BINARY, true ); + if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_bin, sizeof( sqlsrv_encoding ))) { + throw ss::SSException(); + } + + sqlsrv_encoding sql_enc_utf8( "utf-8", CP_UTF8 ); + if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_utf8, sizeof( sqlsrv_encoding ))) { + throw ss::SSException(); + } + } + catch( ss::SSException& ) { + + LOG( SEV_ERROR, "PHP_RINIT: encodings hash table failure" ); + return FAILURE; + } + + // initialize list of sqlsrv errors + g_ss_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); + ::zend_hash_init( g_ss_errors_ht, 50, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); + + for( int i = 0; SS_ERRORS[ i ].error_code != UINT_MAX; ++i ) { + if (NULL == ::zend_hash_index_update_mem( g_ss_errors_ht, SS_ERRORS[ i ].error_code, + &( SS_ERRORS[ i ].sqlsrv_error ), sizeof( SS_ERRORS[ i ].sqlsrv_error ))) { + LOG( SEV_ERROR, "%1!s!: Failed to insert data into sqlsrv errors hashtable.", _FN_ ); + return FAILURE; + } + } + + if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper TSRMLS_CC ) == FAILURE ) { + LOG( SEV_ERROR, "%1!s!: stream registration failed", _FN_ ); + return FAILURE; + } + + try { + // retrieve the handles for the environments + core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, ss_error_handler, "PHP_MINIT_FUNCTION for sqlsrv" TSRMLS_CC ); + } + + catch( core::CoreException& ) { + return FAILURE; + } + + catch( ... ) { + + LOG( SEV_ERROR, "PHP_RINIT: Unknown exception caught." ); + return FAILURE; + } + + return SUCCESS; +} + +// called by Zend for each parameter in the g_ss_warnings_to_ignore_ht and g_ss_errors_ht hash table when it is destroyed +void sqlsrv_error_const_dtor( zval* elem ) { + sqlsrv_error_const* error_to_ignore = static_cast( Z_PTR_P(elem) ); + pefree(error_to_ignore, 1); +} + +// called by Zend for each parameter in the g_ss_encodings_ht hash table when it is destroyed +void sqlsrv_encoding_dtor( zval* elem ) { + sqlsrv_encoding* sql_enc = static_cast( Z_PTR_P(elem) ); + pefree(sql_enc, 1); +} + + +// Module shutdown function +// Free the environment handles allocated in MINIT and unregister our stream wrapper. +// Resource types and constants are automatically released since we don't flag them as +// persistent when they are registered. + +PHP_MSHUTDOWN_FUNCTION(sqlsrv) +{ + SQLSRV_UNUSED( type ); + + UNREGISTER_INI_ENTRIES(); + + // clean up the list of sqlsrv errors + zend_hash_destroy( g_ss_errors_ht ); + pefree( g_ss_errors_ht, 1 /*persistent*/ ); + + zend_hash_destroy( g_ss_warnings_to_ignore_ht ); + pefree( g_ss_warnings_to_ignore_ht, 1 ); + + zend_hash_destroy( g_ss_encodings_ht ); + pefree( g_ss_encodings_ht, 1 ); + + core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp ); + + if( php_unregister_url_stream_wrapper( SQLSRV_STREAM_WRAPPER TSRMLS_CC ) == FAILURE ) { + return FAILURE; + } + + return SUCCESS; +} + + +// Request initialization function +// This function is called once per PHP script execution +// Initialize request globals used in the request, including those that correspond to INI entries. +// Also, we allocate a list of warnings "to ignore", meaning that they are warnings that do not +// trigger errors when WarningsReturnAsErrors is true. If you have warnings that you want ignored +// (such as return values from stored procedures), add them to this collection and they won't be +// returned as errors. Or you could just set WarningsReturnAsErrors to false. + +PHP_RINIT_FUNCTION(sqlsrv) +{ + SQLSRV_UNUSED( module_number ); + SQLSRV_UNUSED( type ); + +#if defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + SQLSRV_G( warnings_return_as_errors ) = true; + ZVAL_NULL( &SQLSRV_G( errors )); + ZVAL_NULL( &SQLSRV_G( warnings )); + + LOG_FUNCTION( "PHP_RINIT for php_sqlsrv" ); + + // read INI settings + SQLSRV_G( warnings_return_as_errors ) = INI_BOOL( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS ); + SQLSRV_G( log_severity ) = INI_INT( INI_PREFIX INI_LOG_SEVERITY ); + SQLSRV_G( log_subsystems ) = INI_INT( INI_PREFIX INI_LOG_SUBSYSTEMS ); + SQLSRV_G( buffered_query_limit ) = INI_INT( INI_PREFIX INI_BUFFERED_QUERY_LIMIT ); + + LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); + LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); + LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); + LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit )); + + return SUCCESS; +} + + +// Request shutdown +// Called at the end of a script's execution +// Simply releases the variables allocated during request initialization. + +PHP_RSHUTDOWN_FUNCTION(sqlsrv) +{ + SQLSRV_UNUSED( module_number ); + SQLSRV_UNUSED( type ); + + LOG_FUNCTION( "PHP_RSHUTDOWN for php_sqlsrv" ); + reset_errors( TSRMLS_C ); + + // TODO - destruction + zval_ptr_dtor( &SQLSRV_G( errors )); + zval_ptr_dtor( &SQLSRV_G( warnings )); + + return SUCCESS; +} + +// Called for php_info(); Displays the INI settings registered and their current values +PHP_MINFO_FUNCTION(sqlsrv) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "sqlsrv support", "enabled"); + php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} diff --git a/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h similarity index 95% rename from sqlsrv/php_sqlsrv.h rename to source/sqlsrv/php_sqlsrv.h index 2f5be3cc..043a108b 100644 --- a/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -1,618 +1,620 @@ -#ifndef PHP_SQLSRV_H -#define PHP_SQLSRV_H - -//--------------------------------------------------------------------------------------------------------------------------------- -// File: php_sqlsrv.h -// -// Contents: Declarations for the extension -// -// Comments: Also contains "internal" declarations shared across source files. -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" -#include "version.h" - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef PHP_WIN32 -#define PHP_SQLSRV_API __declspec(dllexport) -#else -#define PHP_SQLSRV_API -#endif - -// OACR is an internal Microsoft static code analysis tool -#if defined(OACR) -#include -OACR_WARNING_PUSH -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) -OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) -#endif - -extern "C" { - -#pragma warning(push) -#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) - -#ifdef ZTS -#include "TSRM.h" -#endif - -#if _MSC_VER >= 1400 -// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. -// php.h defines this constant as unsigned int which causes a compile error -// in ws2tcpip.h. Fortunately php.h allows an override by defining -// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define -// socklen_t here and override the php.h version. -typedef int socklen_t; -#define HAVE_SOCKLEN_T -#endif - -#include "php.h" -#include "php_globals.h" -#include "php_ini.h" -#include "ext/standard/php_standard.h" -#include "ext/standard/info.h" - -#pragma warning(pop) - -#if PHP_MAJOR_VERSION < 7 -#error Trying to compile "Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)" with an unsupported version of PHP -#endif - -#if ZEND_DEBUG -// debug build causes warning C4505 to pop up from the Zend header files -#pragma warning( disable: 4505 ) -#endif - -} // extern "C" - -//********************************************************************************************************************************* -// Initialization Functions -//********************************************************************************************************************************* - -// module global variables (initialized in minit and freed in mshutdown) -extern HashTable* g_ss_errors_ht; -extern HashTable* g_ss_encodings_ht; -extern HashTable* g_ss_warnings_to_ignore_ht; - -// variables set during initialization -extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP -extern HMODULE g_sqlsrv_hmodule; // used for getting the version information - -// henv context for creating connections -extern sqlsrv_context* g_henv_cp; -extern sqlsrv_context* g_henv_ncp; - -extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in - -// module initialization -PHP_MINIT_FUNCTION(sqlsrv); -// module shutdown function -PHP_MSHUTDOWN_FUNCTION(sqlsrv); -// request initialization function -PHP_RINIT_FUNCTION(sqlsrv); -// request shutdown function -PHP_RSHUTDOWN_FUNCTION(sqlsrv); -// module info function (info returned by phpinfo()) -PHP_MINFO_FUNCTION(sqlsrv); - -//********************************************************************************************************************************* -// Connection -//********************************************************************************************************************************* -PHP_FUNCTION(sqlsrv_connect); -PHP_FUNCTION(sqlsrv_begin_transaction); -PHP_FUNCTION(sqlsrv_client_info); -PHP_FUNCTION(sqlsrv_close); -PHP_FUNCTION(sqlsrv_commit); -PHP_FUNCTION(sqlsrv_query); -PHP_FUNCTION(sqlsrv_prepare); -PHP_FUNCTION(sqlsrv_rollback); -PHP_FUNCTION(sqlsrv_server_info); - -struct ss_sqlsrv_conn : sqlsrv_conn -{ - HashTable* stmts; - bool date_as_string; - bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls - - // static variables used in process_params - static char* resource_name; // char because const char forces casting all over the place. Just easier to leave it char here. - static int descriptor; - - // initialize with default values - ss_sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ) : - sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ), - stmts( NULL ), - date_as_string( false ), - in_transaction( false ) - { - } -}; - -// resource destructor -void __cdecl sqlsrv_conn_dtor( zend_resource *rsrc TSRMLS_DC ); - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -// holds the field names for reuse by sqlsrv_fetch_array/object as keys -struct sqlsrv_fetch_field_name { - char* name; - unsigned int len; -}; - -struct stmt_option_scrollable : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -// This object inherits and overrides the callbacks necessary -struct ss_sqlsrv_stmt : public sqlsrv_stmt { - - ss_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); - - virtual ~ss_sqlsrv_stmt( void ); - - void new_result_set( TSRMLS_D ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ); - - bool prepared; // whether the statement has been prepared yet (used for error messages) - zend_ulong conn_index; // index into the connection hash that contains this statement structure - zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute - sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys - int fetch_fields_count; - - // static variables used in process_params - static char* resource_name; // char because const char forces casting all over the place in ODBC functions - static int descriptor; - -}; - -// holds the field names for reuse by sqlsrv_fetch_array/object as keys -struct sqlsrv_fetch_field { - char* name; - unsigned int len; -}; - -// holds the stream param and the encoding that it was assigned -struct sqlsrv_stream_encoding { - zval* stream_z; - unsigned int encoding; - - sqlsrv_stream_encoding( zval* str_z, unsigned int enc ) : - stream_z( str_z ), encoding( enc ) - { - } -}; - -// *** statement functions *** -PHP_FUNCTION(sqlsrv_cancel); -PHP_FUNCTION(sqlsrv_execute); -PHP_FUNCTION(sqlsrv_fetch); -PHP_FUNCTION(sqlsrv_fetch_array); -PHP_FUNCTION(sqlsrv_fetch_object); -PHP_FUNCTION(sqlsrv_field_metadata); -PHP_FUNCTION(sqlsrv_free_stmt); -PHP_FUNCTION(sqlsrv_get_field); -PHP_FUNCTION(sqlsrv_has_rows); -PHP_FUNCTION(sqlsrv_next_result); -PHP_FUNCTION(sqlsrv_num_fields); -PHP_FUNCTION(sqlsrv_num_rows); -PHP_FUNCTION(sqlsrv_rows_affected); -PHP_FUNCTION(sqlsrv_send_stream_data); - -// resource destructor -void __cdecl sqlsrv_stmt_dtor( zend_resource *rsrc TSRMLS_DC ); - -// "internal" statement functions shared by functions in conn.cpp and stmt.cpp -void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC ); -void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC ); -bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function - TSRMLS_DC ); -void free_odbc_resources( ss_sqlsrv_stmt* stmt TSRMLS_DC ); -void free_stmt_resource( zval* stmt_z TSRMLS_DC ); - -//********************************************************************************************************************************* -// Type Functions -//********************************************************************************************************************************* - -// type functions for SQL types. -// to expose SQL Server paramterized types, we use functions that return encoded integers that contain the size/precision etc. -// for example, SQLSRV_SQLTYPE_VARCHAR(4000) matches the usage of SQLSRV_SQLTYPE_INT with the size added. -PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY); -PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR); -PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL); -PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR); -PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC); -PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR); -PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY); -PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR); - -// PHP type functions -// strings and streams may have an encoding parameterized, so we use the functions -// the valid encodings are SQLSRV_ENC_BINARY and SQLSRV_ENC_CHAR. -PHP_FUNCTION(SQLSRV_PHPTYPE_STREAM); -PHP_FUNCTION(SQLSRV_PHPTYPE_STRING); - -//********************************************************************************************************************************* -// Global variables -//********************************************************************************************************************************* - -extern "C" { - -// request level variables -ZEND_BEGIN_MODULE_GLOBALS(sqlsrv) - -// global objects for errors and warnings. These are returned by sqlsrv_errors. -zval errors; -zval warnings; - -// flags for error handling and logging (set via sqlsrv_configure or php.ini) -zend_long log_severity; -zend_long log_subsystems; -zend_long current_subsystem; -zend_bool warnings_return_as_errors; -zend_long buffered_query_limit; - -ZEND_END_MODULE_GLOBALS(sqlsrv) - -ZEND_EXTERN_MODULE_GLOBALS(sqlsrv); - -} - -// macro used to access the global variables. Use it to make global variable access agnostic to threads -#define SQLSRV_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(sqlsrv, v) - -#if defined(ZTS) -ZEND_TSRMLS_CACHE_EXTERN(); -#endif - -// INI settings and constants -// (these are defined as macros to allow concatenation as we do below) -#define INI_WARNINGS_RETURN_AS_ERRORS "WarningsReturnAsErrors" -#define INI_LOG_SEVERITY "LogSeverity" -#define INI_LOG_SUBSYSTEMS "LogSubsystems" -#define INI_BUFFERED_QUERY_LIMIT "ClientBufferMaxKBSize" -#define INI_PREFIX "sqlsrv." - -PHP_INI_BEGIN() - STD_PHP_INI_BOOLEAN( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS , "1", PHP_INI_ALL, OnUpdateBool, warnings_return_as_errors, - zend_sqlsrv_globals, sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SEVERITY, "0", PHP_INI_ALL, OnUpdateLong, log_severity, zend_sqlsrv_globals, - sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SUBSYSTEMS, "0", PHP_INI_ALL, OnUpdateLong, log_subsystems, zend_sqlsrv_globals, - sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_BUFFERED_QUERY_LIMIT, INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, buffered_query_limit, - zend_sqlsrv_globals, sqlsrv_globals ) -PHP_INI_END() - -//********************************************************************************************************************************* -// Configuration -//********************************************************************************************************************************* -// These functions set and retrieve configuration settings. Configuration settings defined are: -// WarningsReturnAsErrors - treat all ODBC warnings as errors and return false from sqlsrv APIs. -// LogSeverity - combination of severity of messages to log (see Logging) -// LogSubsystems - subsystems within sqlsrv to log messages (see Logging) - -PHP_FUNCTION(sqlsrv_configure); -PHP_FUNCTION(sqlsrv_get_config); - -//********************************************************************************************************************************* -// Errors -//********************************************************************************************************************************* - -// represents the mapping between an error_code and the corresponding error message. -struct ss_error { - - unsigned int error_code; - sqlsrv_error_const sqlsrv_error; -}; - -// List of all driver specific error codes. -enum SS_ERROR_CODES { - - SS_SQLSRV_ERROR_ALREADY_IN_TXN = SQLSRV_ERROR_DRIVER_SPECIFIC, - SS_SQLSRV_ERROR_NOT_IN_TXN, - SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, - SS_SQLSRV_ERROR_REGISTER_RESOURCE, - SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY, - SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED, - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE, - SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, - SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, - SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, - SS_SQLSRV_ERROR_ZEND_BAD_CLASS, - SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE, - SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, - SS_SQLSRV_ERROR_INVALID_OPTION, - SS_SQLSRV_ERROR_PARAM_INVALID_INDEX, - SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, - SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, - SS_SQLSRV_ERROR_VAR_REQUIRED, - SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, - SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, - SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF -}; - -extern ss_error SS_ERRORS[]; - -bool ss_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args ); - -// *** extension error functions *** -PHP_FUNCTION(sqlsrv_errors); - -// convert from the default encoding specified by the "CharacterSet" -// connection option to UTF-16. mbcs_len and utf16_len are sizes in -// bytes. The return is the number of UTF-16 characters in the string -// returned in utf16_out_string. -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, char const* mbcs_in_string, - unsigned int mbcs_len, _Out_ wchar_t* utf16_out_string, - unsigned int utf16_len ); -// create a wide char string from the passed in mbcs string. NULL is returned if the string -// could not be created. No error is posted by this function. utf16_len is the number of -// wchar_t characters, not the number of bytes. -wchar_t* utf16_string_from_mbcs_string( unsigned int php_encoding, const char* mbcs_string, - unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); - -// *** internal error macros and functions *** -bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, - sqlsrv_error const* ssphp TSRMLS_DC, ... ); -void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function, - sqlsrv_error const* ssphp TSRMLS_DC, ... ); -void __cdecl sqlsrv_error_dtor( zend_resource *rsrc TSRMLS_DC ); - -// release current error lists and set to NULL -inline void reset_errors( TSRMLS_D ) -{ - if( Z_TYPE( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE( SQLSRV_G( errors )) != IS_NULL ) { - DIE( "sqlsrv_errors contains an invalid type" ); - } - if( Z_TYPE( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE( SQLSRV_G( warnings )) != IS_NULL ) { - DIE( "sqlsrv_warnings contains an invalid type" ); - } - - if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY ) { - zend_hash_destroy( Z_ARRVAL( SQLSRV_G( errors ))); - FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( errors ))); - } - if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY ) { - zend_hash_destroy( Z_ARRVAL( SQLSRV_G( warnings ))); - FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( warnings ))); - } - - ZVAL_NULL( &SQLSRV_G( errors )); - ZVAL_NULL( &SQLSRV_G( warnings )); -} - -#define THROW_SS_ERROR( ctx, error_code, ... ) \ - (void)call_error_handler( ctx, error_code TSRMLS_CC, false /*warning*/, __VA_ARGS__ ); \ - throw ss::SSException(); - - -class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr > { - -public: - - sqlsrv_context_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - sqlsrv_context_auto_ptr( const sqlsrv_context_auto_ptr& src ) : - sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >( src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( sqlsrv_context* ptr = NULL ) - { - if( _ptr ) { - _ptr->~sqlsrv_context(); - sqlsrv_free( (void*) _ptr ); - } - _ptr = ptr; - } - - sqlsrv_context* operator=( sqlsrv_context* ptr ) - { - return sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >::operator=( ptr ); - } - - void operator=( sqlsrv_context_auto_ptr& src ) - { - sqlsrv_context* p = src.get(); - src.transferred(); - this->_ptr = p; - } -}; - - - -//********************************************************************************************************************************* -// Logging -//********************************************************************************************************************************* -#define LOG_FUNCTION( function_name ) \ - const char* _FN_ = function_name; \ - SQLSRV_G( current_subsystem ) = current_log_subsystem; \ - LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); - -#define SET_FUNCTION_NAME( context ) \ -{ \ - (context).set_func( _FN_ ); \ -} - -// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro -void ss_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); - -// subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise. -enum logging_subsystems { - LOG_INIT = 0x01, - LOG_CONN = 0x02, - LOG_STMT = 0x04, - LOG_UTIL = 0x08, - LOG_ALL = -1, -}; - - -//********************************************************************************************************************************* -// Utility Functions -//********************************************************************************************************************************* - -// generic function used to validate parameters to a PHP function. -// Register an invalid parameter error and returns NULL when parameters don't match the spec given. -template -inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, char const* param_spec, const char* calling_func, size_t param_count, ... ) -{ - SQLSRV_UNUSED( return_value ); - - zval* rsrc; - H* h; - - // reset the errors from the previous API call - reset_errors( TSRMLS_C ); - - if( ZEND_NUM_ARGS() > param_count + 1 ) { - DIE( "Param count and argument count don't match." ); - return NULL; // for static analysis tools - } - - try { - - if( param_count > 6 ) { - DIE( "Param count cannot exceed 6" ); - return NULL; // for static analysis tools - } - - void* arr[6]; - va_list vaList; - va_start(vaList, param_count); //set the pointer to first argument - - for(size_t i = 0; i < param_count; ++i) { - - arr[i] = va_arg(vaList, void*); - } - - va_end(vaList); - - int result = SUCCESS; - - // dummy context to pass to the error handler - sqlsrv_context error_ctx( 0, ss_error_handler, NULL );; - error_ctx.set_func( calling_func ); - - switch( param_count ) { - - case 0: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc ); - break; - - case 1: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0] ); - break; - - case 2: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1] ); - break; - - case 3: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2] ); - break; - - case 4: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3] ); - break; - - case 5: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3], arr[4] ); - break; - - case 6: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3], arr[4], arr[5] ); - break; - - default: - { - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ); - break; - } - } - - CHECK_CUSTOM_ERROR(( result == FAILURE ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { - - throw ss::SSException(); - } - - // get the resource registered - h = static_cast( zend_fetch_resource(Z_RES_P(rsrc) TSRMLS_CC, H::resource_name, H::descriptor )); - - CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { - - throw ss::SSException(); - } - - h->set_func( calling_func ); - - return h; - } - - catch( core::CoreException& ) { - - return NULL; - } - catch ( ... ) { - - DIE( "%1!s!: Unknown exception caught in process_params.", calling_func ); - } -} -//********************************************************************************************************************************* -// Common function wrappers -//********************************************************************************************************************************* -namespace ss { - - // an error which occurred in our SQLSRV driver - struct SSException : public core::CoreException { - - SSException() - { - } - }; - - inline void zend_register_resource(_Out_ zval& rsrc_result, void* rsrc_pointer, int rsrc_type, char* rsrc_name TSRMLS_DC) - { - int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE, - rsrc_name ) { - throw ss::SSException(); - } - Z_TYPE_INFO(rsrc_result) = IS_RESOURCE_EX; - } -} // namespace ss - -#endif /* PHP_SQLSRV_H */ +#ifndef PHP_SQLSRV_H +#define PHP_SQLSRV_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: php_sqlsrv.h +// +// Contents: Declarations for the extension +// +// Comments: Also contains "internal" declarations shared across source files. +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" +#include "version.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef PHP_WIN32 +#define PHP_SQLSRV_API __declspec(dllexport) +#else +#define PHP_SQLSRV_API +#endif + +// OACR is an internal Microsoft static code analysis tool +#if defined(OACR) +#include +OACR_WARNING_PUSH +OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) +OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) +OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) +OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) +OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) +#endif + +extern "C" { + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#if _MSC_VER >= 1400 +// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. +// php.h defines this constant as unsigned int which causes a compile error +// in ws2tcpip.h. Fortunately php.h allows an override by defining +// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define +// socklen_t here and override the php.h version. +typedef int socklen_t; +#define HAVE_SOCKLEN_T +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if PHP_MAJOR_VERSION < 7 +#error Trying to compile "Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)" with an unsupported version of PHP +#endif + +#if ZEND_DEBUG +// debug build causes warning C4505 to pop up from the Zend header files +#pragma warning( disable: 4505 ) +#endif + +} // extern "C" + +//********************************************************************************************************************************* +// Initialization Functions +//********************************************************************************************************************************* + +// module global variables (initialized in minit and freed in mshutdown) +extern HashTable* g_ss_errors_ht; +extern HashTable* g_ss_encodings_ht; +extern HashTable* g_ss_warnings_to_ignore_ht; + +// variables set during initialization +extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP +extern HMODULE g_sqlsrv_hmodule; // used for getting the version information + +// henv context for creating connections +extern sqlsrv_context* g_henv_cp; +extern sqlsrv_context* g_henv_ncp; + +extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in + +#define phpext_sqlsrv_ptr &g_sqlsrv_module_entry + +// module initialization +PHP_MINIT_FUNCTION(sqlsrv); +// module shutdown function +PHP_MSHUTDOWN_FUNCTION(sqlsrv); +// request initialization function +PHP_RINIT_FUNCTION(sqlsrv); +// request shutdown function +PHP_RSHUTDOWN_FUNCTION(sqlsrv); +// module info function (info returned by phpinfo()) +PHP_MINFO_FUNCTION(sqlsrv); + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* +PHP_FUNCTION(sqlsrv_connect); +PHP_FUNCTION(sqlsrv_begin_transaction); +PHP_FUNCTION(sqlsrv_client_info); +PHP_FUNCTION(sqlsrv_close); +PHP_FUNCTION(sqlsrv_commit); +PHP_FUNCTION(sqlsrv_query); +PHP_FUNCTION(sqlsrv_prepare); +PHP_FUNCTION(sqlsrv_rollback); +PHP_FUNCTION(sqlsrv_server_info); + +struct ss_sqlsrv_conn : sqlsrv_conn +{ + HashTable* stmts; + bool date_as_string; + bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls + + // static variables used in process_params + static char* resource_name; // char because const char forces casting all over the place. Just easier to leave it char here. + static int descriptor; + + // initialize with default values + ss_sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ) : + sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ), + stmts( NULL ), + date_as_string( false ), + in_transaction( false ) + { + } +}; + +// resource destructor +void __cdecl sqlsrv_conn_dtor( zend_resource *rsrc TSRMLS_DC ); + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +// holds the field names for reuse by sqlsrv_fetch_array/object as keys +struct sqlsrv_fetch_field_name { + char* name; + unsigned int len; +}; + +struct stmt_option_scrollable : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +// This object inherits and overrides the callbacks necessary +struct ss_sqlsrv_stmt : public sqlsrv_stmt { + + ss_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); + + virtual ~ss_sqlsrv_stmt( void ); + + void new_result_set( TSRMLS_D ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ); + + bool prepared; // whether the statement has been prepared yet (used for error messages) + zend_ulong conn_index; // index into the connection hash that contains this statement structure + zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute + sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys + int fetch_fields_count; + + // static variables used in process_params + static char* resource_name; // char because const char forces casting all over the place in ODBC functions + static int descriptor; + +}; + +// holds the field names for reuse by sqlsrv_fetch_array/object as keys +struct sqlsrv_fetch_field { + char* name; + unsigned int len; +}; + +// holds the stream param and the encoding that it was assigned +struct sqlsrv_stream_encoding { + zval* stream_z; + unsigned int encoding; + + sqlsrv_stream_encoding( zval* str_z, unsigned int enc ) : + stream_z( str_z ), encoding( enc ) + { + } +}; + +// *** statement functions *** +PHP_FUNCTION(sqlsrv_cancel); +PHP_FUNCTION(sqlsrv_execute); +PHP_FUNCTION(sqlsrv_fetch); +PHP_FUNCTION(sqlsrv_fetch_array); +PHP_FUNCTION(sqlsrv_fetch_object); +PHP_FUNCTION(sqlsrv_field_metadata); +PHP_FUNCTION(sqlsrv_free_stmt); +PHP_FUNCTION(sqlsrv_get_field); +PHP_FUNCTION(sqlsrv_has_rows); +PHP_FUNCTION(sqlsrv_next_result); +PHP_FUNCTION(sqlsrv_num_fields); +PHP_FUNCTION(sqlsrv_num_rows); +PHP_FUNCTION(sqlsrv_rows_affected); +PHP_FUNCTION(sqlsrv_send_stream_data); + +// resource destructor +void __cdecl sqlsrv_stmt_dtor( zend_resource *rsrc TSRMLS_DC ); + +// "internal" statement functions shared by functions in conn.cpp and stmt.cpp +void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC ); +void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC ); +bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function + TSRMLS_DC ); +void free_odbc_resources( ss_sqlsrv_stmt* stmt TSRMLS_DC ); +void free_stmt_resource( zval* stmt_z TSRMLS_DC ); + +//********************************************************************************************************************************* +// Type Functions +//********************************************************************************************************************************* + +// type functions for SQL types. +// to expose SQL Server paramterized types, we use functions that return encoded integers that contain the size/precision etc. +// for example, SQLSRV_SQLTYPE_VARCHAR(4000) matches the usage of SQLSRV_SQLTYPE_INT with the size added. +PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY); +PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR); +PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL); +PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR); +PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC); +PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR); +PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY); +PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR); + +// PHP type functions +// strings and streams may have an encoding parameterized, so we use the functions +// the valid encodings are SQLSRV_ENC_BINARY and SQLSRV_ENC_CHAR. +PHP_FUNCTION(SQLSRV_PHPTYPE_STREAM); +PHP_FUNCTION(SQLSRV_PHPTYPE_STRING); + +//********************************************************************************************************************************* +// Global variables +//********************************************************************************************************************************* + +extern "C" { + +// request level variables +ZEND_BEGIN_MODULE_GLOBALS(sqlsrv) + +// global objects for errors and warnings. These are returned by sqlsrv_errors. +zval errors; +zval warnings; + +// flags for error handling and logging (set via sqlsrv_configure or php.ini) +zend_long log_severity; +zend_long log_subsystems; +zend_long current_subsystem; +zend_bool warnings_return_as_errors; +zend_long buffered_query_limit; + +ZEND_END_MODULE_GLOBALS(sqlsrv) + +ZEND_EXTERN_MODULE_GLOBALS(sqlsrv); + +} + +// macro used to access the global variables. Use it to make global variable access agnostic to threads +#define SQLSRV_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(sqlsrv, v) + +#if defined(ZTS) +ZEND_TSRMLS_CACHE_EXTERN(); +#endif + +// INI settings and constants +// (these are defined as macros to allow concatenation as we do below) +#define INI_WARNINGS_RETURN_AS_ERRORS "WarningsReturnAsErrors" +#define INI_LOG_SEVERITY "LogSeverity" +#define INI_LOG_SUBSYSTEMS "LogSubsystems" +#define INI_BUFFERED_QUERY_LIMIT "ClientBufferMaxKBSize" +#define INI_PREFIX "sqlsrv." + +PHP_INI_BEGIN() + STD_PHP_INI_BOOLEAN( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS , "1", PHP_INI_ALL, OnUpdateBool, warnings_return_as_errors, + zend_sqlsrv_globals, sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SEVERITY, "0", PHP_INI_ALL, OnUpdateLong, log_severity, zend_sqlsrv_globals, + sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SUBSYSTEMS, "0", PHP_INI_ALL, OnUpdateLong, log_subsystems, zend_sqlsrv_globals, + sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_BUFFERED_QUERY_LIMIT, INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, buffered_query_limit, + zend_sqlsrv_globals, sqlsrv_globals ) +PHP_INI_END() + +//********************************************************************************************************************************* +// Configuration +//********************************************************************************************************************************* +// These functions set and retrieve configuration settings. Configuration settings defined are: +// WarningsReturnAsErrors - treat all ODBC warnings as errors and return false from sqlsrv APIs. +// LogSeverity - combination of severity of messages to log (see Logging) +// LogSubsystems - subsystems within sqlsrv to log messages (see Logging) + +PHP_FUNCTION(sqlsrv_configure); +PHP_FUNCTION(sqlsrv_get_config); + +//********************************************************************************************************************************* +// Errors +//********************************************************************************************************************************* + +// represents the mapping between an error_code and the corresponding error message. +struct ss_error { + + unsigned int error_code; + sqlsrv_error_const sqlsrv_error; +}; + +// List of all driver specific error codes. +enum SS_ERROR_CODES { + + SS_SQLSRV_ERROR_ALREADY_IN_TXN = SQLSRV_ERROR_DRIVER_SPECIFIC, + SS_SQLSRV_ERROR_NOT_IN_TXN, + SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, + SS_SQLSRV_ERROR_REGISTER_RESOURCE, + SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY, + SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED, + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE, + SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, + SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, + SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, + SS_SQLSRV_ERROR_ZEND_BAD_CLASS, + SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE, + SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, + SS_SQLSRV_ERROR_INVALID_OPTION, + SS_SQLSRV_ERROR_PARAM_INVALID_INDEX, + SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, + SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, + SS_SQLSRV_ERROR_VAR_REQUIRED, + SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, + SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, + SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF +}; + +extern ss_error SS_ERRORS[]; + +bool ss_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args ); + +// *** extension error functions *** +PHP_FUNCTION(sqlsrv_errors); + +// convert from the default encoding specified by the "CharacterSet" +// connection option to UTF-16. mbcs_len and utf16_len are sizes in +// bytes. The return is the number of UTF-16 characters in the string +// returned in utf16_out_string. +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, char const* mbcs_in_string, + unsigned int mbcs_len, _Out_ wchar_t* utf16_out_string, + unsigned int utf16_len ); +// create a wide char string from the passed in mbcs string. NULL is returned if the string +// could not be created. No error is posted by this function. utf16_len is the number of +// wchar_t characters, not the number of bytes. +SQLWCHAR* utf16_string_from_mbcs_string( unsigned int php_encoding, const char* mbcs_string, + unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); + +// *** internal error macros and functions *** +bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, + sqlsrv_error const* ssphp TSRMLS_DC, ... ); +void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function, + sqlsrv_error const* ssphp TSRMLS_DC, ... ); +void __cdecl sqlsrv_error_dtor( zend_resource *rsrc TSRMLS_DC ); + +// release current error lists and set to NULL +inline void reset_errors( TSRMLS_D ) +{ + if( Z_TYPE( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE( SQLSRV_G( errors )) != IS_NULL ) { + DIE( "sqlsrv_errors contains an invalid type" ); + } + if( Z_TYPE( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE( SQLSRV_G( warnings )) != IS_NULL ) { + DIE( "sqlsrv_warnings contains an invalid type" ); + } + + if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( SQLSRV_G( errors ))); + FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( errors ))); + } + if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( SQLSRV_G( warnings ))); + FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( warnings ))); + } + + ZVAL_NULL( &SQLSRV_G( errors )); + ZVAL_NULL( &SQLSRV_G( warnings )); +} + +#define THROW_SS_ERROR( ctx, error_code, ... ) \ + (void)call_error_handler( ctx, error_code TSRMLS_CC, false /*warning*/, ## __VA_ARGS__ ); \ + throw ss::SSException(); + + +class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr > { + +public: + + sqlsrv_context_auto_ptr( void ) : + sqlsrv_auto_ptr( NULL ) + { + } + + sqlsrv_context_auto_ptr( const sqlsrv_context_auto_ptr& src ) : + sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >( src ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( sqlsrv_context* ptr = NULL ) + { + if( _ptr ) { + _ptr->~sqlsrv_context(); + sqlsrv_free( (void*) _ptr ); + } + _ptr = ptr; + } + + sqlsrv_context* operator=( sqlsrv_context* ptr ) + { + return sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >::operator=( ptr ); + } + + void operator=( sqlsrv_context_auto_ptr& src ) + { + sqlsrv_context* p = src.get(); + src.transferred(); + this->_ptr = p; + } +}; + + + +//********************************************************************************************************************************* +// Logging +//********************************************************************************************************************************* +#define LOG_FUNCTION( function_name ) \ + const char* _FN_ = function_name; \ + SQLSRV_G( current_subsystem ) = current_log_subsystem; \ + LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); + +#define SET_FUNCTION_NAME( context ) \ +{ \ + (context).set_func( _FN_ ); \ +} + +// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro +void ss_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); + +// subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise. +enum logging_subsystems { + LOG_INIT = 0x01, + LOG_CONN = 0x02, + LOG_STMT = 0x04, + LOG_UTIL = 0x08, + LOG_ALL = -1, +}; + +//********************************************************************************************************************************* +// Common function wrappers +// have to place this namespace before the utility functions +// otherwise can't compile in Linux because 'ss' not defined +//********************************************************************************************************************************* +namespace ss { + + // an error which occurred in our SQLSRV driver + struct SSException : public core::CoreException { + + SSException() + { + } + }; + + inline void zend_register_resource(_Out_ zval& rsrc_result, void* rsrc_pointer, int rsrc_type, char* rsrc_name TSRMLS_DC) + { + int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE, + rsrc_name ) { + throw ss::SSException(); + } + Z_TYPE_INFO(rsrc_result) = IS_RESOURCE_EX; + } +} // namespace ss + +//********************************************************************************************************************************* +// Utility Functions +//********************************************************************************************************************************* + +// generic function used to validate parameters to a PHP function. +// Register an invalid parameter error and returns NULL when parameters don't match the spec given. +template +inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, char const* param_spec, const char* calling_func, size_t param_count, ... ) +{ + SQLSRV_UNUSED( return_value ); + + zval* rsrc; + H* h; + + // reset the errors from the previous API call + reset_errors( TSRMLS_C ); + + if( ZEND_NUM_ARGS() > param_count + 1 ) { + DIE( "Param count and argument count don't match." ); + return NULL; // for static analysis tools + } + + try { + + if( param_count > 6 ) { + DIE( "Param count cannot exceed 6" ); + return NULL; // for static analysis tools + } + + void* arr[6]; + va_list vaList; + va_start(vaList, param_count); //set the pointer to first argument + + for(size_t i = 0; i < param_count; ++i) { + + arr[i] = va_arg(vaList, void*); + } + + va_end(vaList); + + int result = SUCCESS; + + // dummy context to pass to the error handler + sqlsrv_context error_ctx( 0, ss_error_handler, NULL ); + error_ctx.set_func( calling_func ); + + switch( param_count ) { + + case 0: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc ); + break; + + case 1: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0] ); + break; + + case 2: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1] ); + break; + + case 3: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2] ); + break; + + case 4: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3] ); + break; + + case 5: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3], arr[4] ); + break; + + case 6: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3], arr[4], arr[5] ); + break; + + default: + { + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ); + break; + } + } + + CHECK_CUSTOM_ERROR(( result == FAILURE ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { + + throw ss::SSException(); + } + + // get the resource registered + h = static_cast( zend_fetch_resource(Z_RES_P(rsrc) TSRMLS_CC, H::resource_name, H::descriptor )); + + CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { + + throw ss::SSException(); + } + + h->set_func( calling_func ); + } + + catch( core::CoreException& ) { + + return NULL; + } + catch ( ... ) { + + DIE( "%1!s!: Unknown exception caught in process_params.", calling_func ); + } + + return h; +} + +#endif /* PHP_SQLSRV_H */ diff --git a/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp similarity index 93% rename from sqlsrv/stmt.cpp rename to source/sqlsrv/stmt.cpp index 40eb589f..7834aa83 100644 --- a/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1,2247 +1,2265 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: stmt.cpp -// -// Contents: Routines that use statement handles -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -// *** header files *** -#include "php_sqlsrv.h" -#include - -// -// *** internal variables and constants *** -// -// our resource descriptor assigned in minit -int ss_sqlsrv_stmt::descriptor = 0; -char* ss_sqlsrv_stmt::resource_name = "ss_sqlsrv_stmt"; // not const for a reason. see sqlsrv_stmt in php_sqlsrv.h - -namespace { - -// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros -unsigned int current_log_subsystem = LOG_STMT; - -// constants used as invalid types for type errors -const zend_uchar PHPTYPE_INVALID = SQLSRV_PHPTYPE_INVALID; -const int SQLTYPE_INVALID = 0; -const int SQLSRV_INVALID_PRECISION = -1; -const SQLUINTEGER SQLSRV_INVALID_SIZE = (~1U); -const int SQLSRV_INVALID_SCALE = -1; -const int SQLSRV_SIZE_MAX_TYPE = -1; - -// constants for maximums in SQL Server -const int SQL_SERVER_MAX_FIELD_SIZE = 8000; -const int SQL_SERVER_MAX_PRECISION = 38; -const int SQL_SERVER_DEFAULT_PRECISION = 18; -const int SQL_SERVER_DEFAULT_SCALE = 0; - -// default class used when no class is specified by sqlsrv_fetch_object -const char STDCLASS_NAME[] = "stdclass"; -const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1; - -// map a Zend PHP type constant to our constant type -enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = { - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_NULL, - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_INT, - SQLSRV_PHPTYPE_FLOAT, - SQLSRV_PHPTYPE_STRING, - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_DATETIME, - SQLSRV_PHPTYPE_STREAM, - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_INVALID -}; - -// constant strings used for the field metadata results -// (char to avoid having to cast them where they are used) -namespace FieldMetaData { - -char* NAME = "Name"; -char* TYPE = "Type"; -char* SIZE = "Size"; -char* PREC = "Precision"; -char* SCALE = "Scale"; -char* NULLABLE = "Nullable"; - -} - -// warning message printed when a parameter variable is not passed by reference -const char SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF[] = "Variable parameter %d not passed by reference (prefaced with an &). " - "Variable parameters passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value. " - "For more information, see sqlsrv_prepare or sqlsrv_query in the API Reference section of the product documentation."; - -/* internal functions */ - -void convert_to_zval( sqlsrv_stmt* stmt, SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len, zval& out_zval ); - -void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, zend_long fetch_type, _Out_ zval& fields, bool allow_empty_field_names - TSRMLS_DC ); -bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, _Out_ SQLULEN* column_size, - _Out_ SQLSMALLINT* decimal_digits ); -sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ); -void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC ); -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); -bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype type ); -void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, - _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); -void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ); -void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); -void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); -bool verify_and_set_encoding( const char* encoding_string, _Out_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ); - -} - -// query options for cursor types -namespace SSCursorTypes { - - const char QUERY_OPTION_SCROLLABLE_STATIC[] = "static"; - const char QUERY_OPTION_SCROLLABLE_DYNAMIC[] = "dynamic"; - const char QUERY_OPTION_SCROLLABLE_KEYSET[] = "keyset"; - const char QUERY_OPTION_SCROLLABLE_FORWARD[] = "forward"; - const char QUERY_OPTION_SCROLLABLE_BUFFERED[] = "buffered"; -} - -ss_sqlsrv_stmt::ss_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : - sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), - prepared( false ), - conn_index( -1 ), - params_z( NULL ), - fetch_field_names( NULL ), - fetch_fields_count ( 0 ) -{ - core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC ); -} - -ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) -{ - if( fetch_field_names != NULL ) { - - for( int i=0; i < fetch_fields_count; ++i ) { - - sqlsrv_free( fetch_field_names[ i ].name ); - } - sqlsrv_free( fetch_field_names ); - } - if( params_z ) { - zval_ptr_dtor( params_z ); - sqlsrv_free(params_z); - } -} - -// to be called whenever a new result set is created, such as after an -// execute or next_result. Resets the state variables and calls the subclass. -void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) -{ - if( fetch_field_names != NULL ) { - - for( int i=0; i < fetch_fields_count; ++i ) { - - sqlsrv_free( fetch_field_names[ i ].name ); - } - sqlsrv_free( fetch_field_names ); - } - - fetch_field_names = NULL; - fetch_fields_count = 0; - sqlsrv_stmt::new_result_set( TSRMLS_C ); -} - -// Returns a php type for a given sql type. Also sets the encoding wherever applicable. -sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ) -{ - sqlsrv_phptype ss_phptype; - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; - - switch( sql_type ) { - - case SQL_BIGINT: - case SQL_CHAR: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - case SQL_WCHAR: - case SQL_SS_VARIANT: - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - ss_phptype.typeinfo.encoding = this->conn->encoding(); - break; - - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - if( prefer_string_to_stream || size != SQL_SS_LENGTH_UNLIMITED ) { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - ss_phptype.typeinfo.encoding = this->conn->encoding(); - } - else { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; - ss_phptype.typeinfo.encoding = this->conn->encoding(); - } - break; - - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; - break; - - case SQL_BINARY: - case SQL_LONGVARBINARY: - case SQL_VARBINARY: - case SQL_SS_UDT: - if( prefer_string_to_stream ) { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; - } - else { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; - ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; - } - break; - - case SQL_FLOAT: - case SQL_REAL: - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; - break; - - case SQL_TYPE_DATE: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_TIME2: - case SQL_TYPE_TIMESTAMP: - if( reinterpret_cast( this->conn )->date_as_string ) { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - ss_phptype.typeinfo.encoding = this->conn->encoding(); - } - else { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; - } - break; - - default: - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - break; - } - - return ss_phptype; -} - -// statement specific parameter proccessing. Uses the generic function specialised to return a statement -// resource. -#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \ - rsrc = process_params( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, __VA_ARGS__ );\ - if( rsrc == NULL ) { \ - RETURN_FALSE; \ - } - -// sqlsrv_execute( resource $stmt ) -// -// Executes a previously prepared statement. See sqlsrv_prepare for information -// on preparing a statement for execution. -// -// This function is ideal for executing a prepared statement multiple times with -// different parameter values. See the MSDN documentation -// -// Parameters -// $stmt: A resource specifying the statement to be executed. For more -// information about how to create a statement resource, see sqlsrv_prepare. -// -// Return Value -// A Boolean value: true if the statement was successfully executed. Otherwise, false. - -PHP_FUNCTION( sqlsrv_execute ) -{ - LOG_FUNCTION( "sqlsrv_execute" ); - - ss_sqlsrv_stmt* stmt = NULL; - - try { - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - CHECK_CUSTOM_ERROR(( !stmt->prepared ), stmt, SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED ) { - throw ss::SSException(); - } - - // prepare for the next execution by flushing anything remaining in the result set - if( stmt->executed ) { - - // to prepare to execute the next statement, we skip any remaining results (and skip parameter finalization too) - while( stmt->past_next_result_end == false ) { - - core_sqlsrv_next_result( stmt TSRMLS_CC, false, false ); - } - } - - // bind parameters before executing - bind_params( stmt TSRMLS_CC ); - - core_sqlsrv_execute( stmt TSRMLS_CC ); - - RETURN_TRUE; - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_execute: Unknown exception caught." ); - } -} - - -// sqlsrv_fetch( resource $stmt ) -// -// Makes the next row of a result set available for reading. Use -// sqlsrv_get_field to read fields of the row. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. A -// statement must be executed before results can be retrieved. For information -// on executing a statement, see sqlsrv_query and sqlsrv_execute. -// -// Return Value -// If the next row of the result set was successfully retrieved, true is -// returned. If there are no more results in the result set, null is -// returned. If an error occured, false is returned - -PHP_FUNCTION( sqlsrv_fetch ) -{ - LOG_FUNCTION( "sqlsrv_fetch" ); - - ss_sqlsrv_stmt* stmt = NULL; - // NOTE: zend_parse_parameter expect zend_long when the type spec is 'l',and core_sqlsrv_fetch expect short int - int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied - zend_long fetch_offset = 0; // default value for parameter if one isn't supplied - - // take only the statement resource - PROCESS_PARAMS( stmt, "r|ll", _FN_, 2, &fetch_style, &fetch_offset ); - - try { - - CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { - throw ss::SSException(); - } - - bool result = core_sqlsrv_fetch( stmt, fetch_style, fetch_offset TSRMLS_CC ); - if( !result ) { - RETURN_NULL(); - } - - RETURN_TRUE; - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_fetch: Unknown exception caught." ); - } -} - -// sqlsrv_fetch_array( resource $stmt [, int $fetchType] ) -// -// Retrieves the next row of data as an array. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. -// $fetchType [OPTIONAL]: A predefined constant. See SQLSRV_FETCH_TYPE in php_sqlsrv.h -// -// Return Value -// If a row of data is retrieved, an array is returned. If there are no more -// rows to retrieve, null is returned. If an error occurs, false is returned. -// Based on the value of the $fetchType parameter, the returned array can be a -// numerically indexed array, an associative array, or both. By default, an -// array with both numeric and associative keys is returned. The data type of a -// value in the returned array will be the default PHP data type. For -// information about default PHP data types, see Default PHP Data Types. - -PHP_FUNCTION( sqlsrv_fetch_array ) -{ - LOG_FUNCTION( "sqlsrv_fetch_array" ); - - ss_sqlsrv_stmt* stmt = NULL; - zend_long fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied - int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied - zend_long fetch_offset = 0; // default value for parameter if one isn't supplied - - // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), - // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset - PROCESS_PARAMS( stmt, "r|lll", _FN_, 3, &fetch_type, &fetch_style, &fetch_offset ); - - try { - - CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt, - SS_SQLSRV_ERROR_INVALID_FETCH_TYPE ) { - throw ss::SSException(); - } - - CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { - throw ss::SSException(); - } - - bool result = core_sqlsrv_fetch( stmt, fetch_style, fetch_offset TSRMLS_CC ); - if( !result ) { - RETURN_NULL(); - } - zval fields; - ZVAL_UNDEF( &fields ); - fetch_fields_common( stmt, fetch_type, fields, true /*allow_empty_field_names*/ TSRMLS_CC ); - RETURN_ARR( Z_ARRVAL( fields )); - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_fetch_array: Unknown exception caught." ); - } -} - -// sqlsrv_field_metadata( resource $stmt ) -// -// Retrieves metadata for the fields of a prepared statement. For information -// about preparing a statement, see sqlsrv_query or sqlsrv_prepare. Note that -// sqlsrv_field_metadata can be called on any prepared statement, pre- or -// post-execution. -// -// Parameters -// $stmt: A statement resource for which field metadata is sought. -// -// Return Value -// retrieve an array of metadata for the current result set on a statement. Each element of the -// array is a sub-array containing 5 elements accessed by key: -// name - name of the field. -// type - integer of the type. Can be compared against the SQLSRV_SQLTYPE constants. -// size - length of the field. null if the field uses precision and scale instead. -// precision - number of digits in a numeric field. null if the field uses size. -// scale - number of decimal digits in a numeric field. null if the field uses sizes. -// is_nullable - if the field may contain a NULL instead of a value -// false is returned if an error occurs retrieving the metadata - -PHP_FUNCTION( sqlsrv_field_metadata ) -{ - sqlsrv_stmt* stmt = NULL; - SQLSMALLINT num_cols = -1; - - LOG_FUNCTION( "sqlsrv_field_metadata" ); - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // get the number of fields in the resultset - num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - - zval result_meta_data; - ZVAL_UNDEF( &result_meta_data ); - core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC ); - - for( SQLSMALLINT f = 0; f < num_cols; ++f ) { - - sqlsrv_malloc_auto_ptr core_meta_data; - core_meta_data = core_sqlsrv_field_metadata( stmt, f TSRMLS_CC ); - - // initialize the array - zval field_array; - ZVAL_UNDEF( &field_array ); - core::sqlsrv_array_init( *stmt, &field_array TSRMLS_CC ); - - core::sqlsrv_add_assoc_string( *stmt, &field_array, FieldMetaData::NAME, - reinterpret_cast( core_meta_data->field_name.get() ), 0 TSRMLS_CC ); - - core_meta_data->field_name.transferred(); - - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC ); - - switch( core_meta_data->field_type ) { - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SCALE, core_meta_data->field_scale TSRMLS_CC ); - break; - case SQL_BIT: - case SQL_TINYINT: - case SQL_SMALLINT: - case SQL_INTEGER: - case SQL_BIGINT: - case SQL_REAL: - case SQL_FLOAT: - case SQL_DOUBLE: - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); - break; - default: - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SIZE, core_meta_data->field_size TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::PREC TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); - break; - } - - // add the nullability to the array - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable - TSRMLS_CC ); - - // add this field's meta data to the result set meta data - core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC ); - - // always good to call destructor for allocations done through placement new operator. - core_meta_data->~field_meta_data(); - } - - // return our built collection and transfer ownership - RETURN_ZVAL(&result_meta_data, 1, 1); - - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_field_metadata: Unknown exception caught." ); - } -} - - -// sqlsrv_next_result( resource $stmt ) -// -// Makes the next result (result set, row count, or output parameter) of the -// specified statement active. The first (or only) result returned by a batch -// query or stored procedure is active without a call to sqlsrv_next_result. -// Any output parameters bound are only available after sqlsrv_next_result returns -// null as per ODBC Driver 11 for SQL Server specs: http://msdn.microsoft.com/en-us/library/ms403283.aspx -// -// Parameters -// $stmt: The executed statement on which the next result is made active. -// -// Return Value -// If the next result was successfully made active, the Boolean value true is -// returned. If an error occurred in making the next result active, false is -// returned. If no more results are available, null is returned. - -PHP_FUNCTION( sqlsrv_next_result ) -{ - LOG_FUNCTION( "sqlsrv_next_result" ); - - SQLRETURN r = SQL_SUCCESS; - ss_sqlsrv_stmt* stmt = NULL; - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - core_sqlsrv_next_result( stmt TSRMLS_CC, true ); - - if( stmt->past_next_result_end ) { - - RETURN_NULL(); - } - - RETURN_TRUE; - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_next_result: Unknown exception caught." ); - } -} - -// sqlsrv_rows_affected( resource $stmt ) -// -// Returns the number of rows modified by the last statement executed. This -// function does not return the number of rows returned by a SELECT statement. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. -// -// Return Value -// An integer indicating the number of rows modified by the last executed -// statement. If no rows were modified, zero (0) is returned. If no information -// about the number of modified rows is available, negative one (-1) is -// returned. If an error occurred in retrieving the number of modified rows, -// false is returned. See SQLRowCount in the MSDN ODBC documentation. - -PHP_FUNCTION( sqlsrv_rows_affected ) -{ - LOG_FUNCTION( "sqlsrv_rows_affected" ); - SQLRETURN r = SQL_SUCCESS; - ss_sqlsrv_stmt* stmt = NULL; - SQLLEN rows = -1; - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // make sure that the statement has already been executed. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw ss::SSException(); - } - - // make sure it is not scrollable. This function should only work for inserts, updates, and deletes, - // but this is the best we can do to enforce that. - CHECK_CUSTOM_ERROR( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY, stmt, SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE ) { - throw ss::SSException(); - } - - rows = stmt->current_results->row_count( TSRMLS_C ); - RETURN_LONG( rows ); - } - - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_rows_affected: Unknown exception caught." ); - } -} - -// sqlsrv_num_rows( resource $stmt ) -// -// Retrieves the number of rows in an active result set. The statement must -// have been created with the Scrollable attribute set to 'static'. -// -// Parameters -// $stmt: The statement on which the targeted result set is active. -// -// Return Value -// An integer value that represents the number of rows in the active result -// set. If an error occurs, the boolean value false is returned. - -PHP_FUNCTION( sqlsrv_num_rows ) -{ - LOG_FUNCTION( "sqlsrv_num_rows" ); - - ss_sqlsrv_stmt* stmt = NULL; - SQLLEN rows = -1; - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // make sure that the statement has already been executed. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw ss::SSException(); - } - - // make sure that the statement is scrollable and the cursor is not dynamic. - // if the cursor is dynamic, then the number of rows returned is always -1. - CHECK_CUSTOM_ERROR( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY || stmt->cursor_type == SQL_CURSOR_DYNAMIC, stmt, - SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE ) { - throw ss::SSException(); - } - - rows = stmt->current_results->row_count( TSRMLS_C ); - RETURN_LONG( rows ); - } - - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_num_rows: Unknown exception caught." ); - } -} - -// sqlsrv_num_fields( resource $stmt ) -// -// Retrieves the number of fields in an active result set. Note that -// sqlsrv_num_fields can be called on any prepared statement, before or after -// execution. -// -// Parameters -// $stmt: The statement on which the targeted result set is active. -// -// Return Value -// An integer value that represents the number of fields in the active result -// set. If an error occurs, the boolean value false is returned. - -PHP_FUNCTION( sqlsrv_num_fields ) -{ - LOG_FUNCTION( "sqlsrv_num_fields" ); - - ss_sqlsrv_stmt* stmt = NULL; - SQLSMALLINT fields = -1; - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // retrieve the number of columns from ODBC - fields = core::SQLNumResultCols( stmt TSRMLS_CC ); - - RETURN_LONG( fields ); - } - - catch( ss::SSException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_num_fields: Unknown exception caught." ); - } -} - -// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams ]]) -// -// Retrieves the next row of data as a PHP object. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. -// -// $className [OPTIONAL]: A string specifying the name of the class to -// instantiate. If a value for the $className parameter is not specified, an -// instance of the PHP stdClass is instantiated. -// -// $ctorParams [OPTIONAL]: An array that contains values passed to the -// constructor of the class specified with the $className parameter. If the -// constructor of the specified class accepts parameter values, the $ctorParams -// parameter must be used when calling sqlsrv_fetch_object. -// -// Return Value -// A PHP object with properties that correspond to result set field -// names. Property values are populated with the corresponding result set field -// values. If the class specified with the optional $className parameter does -// not exist or if there is no active result set associated with the specified -// statement, false is returned. -// The data type of a value in the returned object will be the default PHP data -// type. For information on default PHP data types, see Default PHP Data Types. -// -// Remarks -// If a class name is specified with the optional $className parameter, an -// object of this class type is instantiated. If the class has properties whose -// names match the result set field names, the corresponding result set values -// are applied to the properties. If a result set field name does not match a -// class property, a property with the result set field name is added to the -// object and the result set value is applied to the property. For more -// information about calling sqlsrv_fetch_object with the $className parameter, -// see How to: Retrieve Data as an Object (Microsoft Drivers for PHP for SQL Server). -// -// If a field with no name is returned, sqlsrv_fetch_object will discard the -// field value and issue a warning. - -PHP_FUNCTION( sqlsrv_fetch_object ) -{ - LOG_FUNCTION( "sqlsrv_fetch_object" ); - - ss_sqlsrv_stmt* stmt = NULL; - zval* class_name_z = NULL; - zval* ctor_params_z = NULL; - int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied - zend_long fetch_offset = 0; // default value for parameter if one isn't supplied - - // stdClass is the name of the system's default base class in PHP - char* class_name = const_cast( STDCLASS_NAME ); - std::size_t class_name_len = STDCLASS_NAME_LEN; - HashTable* properties_ht = NULL; - zval retval_z; - ZVAL_UNDEF( &retval_z ); - - // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), - // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset - // we also use z! instead of s and a so that null may be passed in as valid values for - // the class name and ctor params - PROCESS_PARAMS( stmt, "r|z!z!ll", _FN_, 4, &class_name_z, &ctor_params_z, &fetch_style, &fetch_offset ); - - try { - - CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { - throw ss::SSException(); - } - - if( class_name_z ) { - - CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - throw ss::SSException(); - } - class_name = Z_STRVAL( *class_name_z ); - class_name_len = Z_STRLEN( *class_name_z ); - zend_str_tolower( class_name, class_name_len ); - } - - if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) { - THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - // fetch the data - bool result = core_sqlsrv_fetch( stmt, fetch_style, fetch_offset TSRMLS_CC ); - if( !result ) { - RETURN_NULL(); - } - - fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ TSRMLS_CC ); - properties_ht = Z_ARRVAL( retval_z ); - - // find the zend_class_entry of the class the user requested (stdClass by default) for use below - zend_class_entry* class_entry = NULL; - zend_string* class_name_str_z = zend_string_init( class_name, class_name_len, 0 ); - int zr = ( NULL != ( class_entry = zend_lookup_class( class_name_str_z TSRMLS_CC ))) ? SUCCESS : FAILURE; - zend_string_release( class_name_str_z ); - CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_BAD_CLASS, class_name ) { - throw ss::SSException(); - } - - // create an instance of the object with its default properties - // we pass NULL for the properties so that the object will be populated by its default properties - zr = object_and_properties_init( &retval_z, class_entry, NULL /*properties*/ ); - CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { - throw ss::SSException(); - } - - // merge in the "properties" (associative array) returned from the fetch doing this vice versa - // since putting properties_ht into object_and_properties_init and merging the default properties - // causes duplicate properties when the visibilities are different and also references the - // default parameters directly in the object, meaning the default property value is changed when - // the object's property is changed. - zend_merge_properties( &retval_z, properties_ht TSRMLS_CC ); - zend_hash_destroy( properties_ht ); - FREE_HASHTABLE( properties_ht ); - - // find and call the object's constructor - - // The header files (zend.h and zend_API.h) declare - // these functions and structures, so by working with those, we were able to - // develop this as a suitable snippet for calling constructors. Some observations: - // params must be an array of zval**, not a zval** to an array as we originally - // thought. Also, a constructor doesn't show up in the function table, but - // is put into the "magic methods" section of the class entry. - // - // The default values of the fci and fcic structures were determined by - // calling zend_fcall_info_init with a test callable. - - // if there is a constructor (e.g., stdClass doesn't have one) - if( class_entry->constructor ) { - - // take the parameters given as our last argument and put them into a sequential array - sqlsrv_malloc_auto_ptr params_m; - zval ctor_retval_z; - ZVAL_UNDEF( &ctor_retval_z ); - int num_params = 0; - - if ( ctor_params_z ) { - HashTable* ctor_params_ht = Z_ARRVAL( *ctor_params_z ); - num_params = zend_hash_num_elements( ctor_params_ht ); - params_m = reinterpret_cast( sqlsrv_malloc( num_params * sizeof( zval ) )); - - int i = 0; - zval* value_z = NULL; - ZEND_HASH_FOREACH_VAL( ctor_params_ht, value_z ) { - ZVAL_COPY_VALUE( ¶ms_m[i], value_z ); - zr = ( NULL != ¶ms_m[i] ) ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { - throw ss::SSException(); - } - i++; - } ZEND_HASH_FOREACH_END(); - } //if( !Z_ISUNDEF( ctor_params_z )) - - // call the constructor function itself. - zend_fcall_info fci; - zend_fcall_info_cache fcic; - - memset( &fci, 0, sizeof( fci )); - fci.size = sizeof( fci ); - fci.function_table = &( class_entry )->function_table; - ZVAL_UNDEF( &( fci.function_name ) ); - fci.retval = &ctor_retval_z; - fci.param_count = num_params; - fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred. - - fci.object = reinterpret_cast( &retval_z ); - - memset( &fcic, 0, sizeof( fcic )); - fcic.initialized = 1; - fcic.function_handler = class_entry->constructor; - fcic.calling_scope = class_entry; - - fci.object = reinterpret_cast( &retval_z ); - - zr = zend_call_function( &fci, &fcic TSRMLS_CC ); - CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { - throw ss::SSException(); - } - - } //if( class_entry->constructor ) - RETURN_ZVAL( &retval_z, 1, 1 ); - } - - catch( core::CoreException& ) { - - if( properties_ht != NULL ) { - - zend_hash_destroy( properties_ht ); - FREE_HASHTABLE( properties_ht ); - } - else if ( Z_TYPE( retval_z ) == IS_ARRAY ) { - zend_hash_destroy( Z_ARRVAL( retval_z )); - FREE_HASHTABLE( Z_ARRVAL( retval_z )); - } - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_fetch_object: Unknown exception caught." ); - } -} - - -// sqlsrv_has_rows( resource $stmt ) -// -// Parameters -// $stmt: The statement on which the targeted result set is active. -// -// Return Value -// Returns whether or not there are rows waiting to be processed. There are two scenarios -// for using a function like this: -// 1) To know if there are any actual rows, not just a result set (empty or not). Use sqlsrv_has_rows to determine this. -// The guarantee is that if sqlsrv_has_rows returns true immediately after a query, that sqlsrv_fetch_* will return at least -// one row of data. -// 2) To know if there is any sort of result set, empty or not, that has to be bypassed to get to something else, such as -// output parameters being returned. Use sqlsrv_num_fields > 0 to check if there is any result set that must be bypassed -// until sqlsrv_fetch returns NULL. -// The last caveat is that this function can still return FALSE if there is an error, which is fine since an error -// most likely means that there is no result data anyways. -// If this functions returs true one time, then it will return true even after the result set is exhausted -// (sqlsrv_fetch returns null) - -PHP_FUNCTION( sqlsrv_has_rows ) -{ - LOG_FUNCTION( "sqlsrv_has_rows" ); - ss_sqlsrv_stmt* stmt = NULL; - - try { - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw ss::SSException(); - } - - if( !stmt->fetch_called ) { - - determine_stmt_has_rows( stmt TSRMLS_CC ); - } - - if( stmt->has_rows ) { - - RETURN_TRUE; - } - } - catch( core::CoreException& ) { - } - catch( ... ) { - - DIE( "sqlsrv_has_rows: Unknown exception caught." ); - } - - RETURN_FALSE; -} - - -// sqlsrv_send_stream_data( resource $stmt ) -// -// Sends data from parameter streams to the server. Up to eight kilobytes (8K) -// of data is sent with each call to sqlsrv_send_stream_data. -// By default, all stream data is sent to the server when a query is -// executed. If this default behavior is not changed, you do not have to use -// sqlsrv_send_stream_data to send stream data to the server. For information -// about changing the default behavior, see the Parameters section of -// sqlsrv_query or sqlsrv_prepare. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. -// -// Return Value -// true if there is more data to be sent. null, if all the data has been sent, -// and false if an error occurred - -PHP_FUNCTION( sqlsrv_send_stream_data ) -{ - sqlsrv_stmt* stmt = NULL; - - LOG_FUNCTION( "sqlsrv_send_stream_data" ); - - // get the statement resource that we've bound streams to - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // if everything was sent at execute time, just return that there is nothing more to send. - if( stmt->send_streams_at_exec ) { - RETURN_NULL(); - } - - // send the next packet - bool more = core_sqlsrv_send_stream_packet( stmt TSRMLS_CC ); - - // if more to send, return true - if( more ) { - RETURN_TRUE; - } - // otherwise we're done, so return null - else { - RETURN_NULL(); - } - } - catch( core::CoreException& ) { - - // return false if an error occurred - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_send_stream_data: Unknown exception caught." ); - } -} - - -// sqlsrv_get_field( resource $stmt, int $fieldIndex [, int $getAsType] ) -// -// Retrieves data from the specified field of the current row. Field data must -// be accessed in order. For example, data from the first field cannot be -// accessed after data from the second field has been accessed. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. -// $fieldIndex: The index of the field to be retrieved. Indexes begin at zero. -// $getAsType [OPTIONAL]: A SQLSRV constant (SQLSRV_PHPTYPE) that determines -// the PHP data type for the returned data. For information about supported data -// types, see SQLSRV Constants (Microsoft Drivers for PHP for SQL Server). If no return -// type is specified, a default PHP type will be returned. For information about -// default PHP types, see Default PHP Data Types. For information about -// specifying PHP data types, see How to: Specify PHP Data Types. -// -// Return Value -// The field data. You can specify the PHP data type of the returned data by -// using the $getAsType parameter. If no return data type is specified, the -// default PHP data type will be returned. For information about default PHP -// types, see Default PHP Data Types. For information about specifying PHP data -// types, see How to: Specify PHP Data Types. - -PHP_FUNCTION( sqlsrv_get_field ) -{ - LOG_FUNCTION( "sqlsrv_get_field" ); - - ss_sqlsrv_stmt* stmt = NULL; - sqlsrv_phptype sqlsrv_php_type; - sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID; - void* field_value = NULL; - int field_index = -1; - SQLLEN field_len = -1; - zval retval_z; - ZVAL_UNDEF(&retval_z); - - // get the statement, the field index and the optional type - PROCESS_PARAMS( stmt, "rl|l", _FN_, 2, &field_index, &sqlsrv_php_type ); - - try { - - // validate that the field index is within range - int num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - - if( field_index < 0 || field_index >= num_cols ) { - THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - core_sqlsrv_get_field( stmt, field_index, sqlsrv_php_type, false, field_value, &field_len, false/*cache_field*/, - &sqlsrv_php_type_out TSRMLS_CC ); - convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z ); - sqlsrv_free( field_value ); - RETURN_ZVAL( &retval_z, 1, 1 ); - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_get_field: Unknown exception caught." ); - } -} - - -// ** type functions. ** -// When specifying PHP and SQL Server types that take parameters, such as VARCHAR(2000), we use functions -// to match that notation and return a specially encoded integer that tells us what type and size/precision -// are. For PHP types specifically we munge the type and encoding into the integer. -// As is easily seen, since they are so similar, we delegate the actual encoding to helper methods defined -// below. - -// takes an encoding of the stream -PHP_FUNCTION( SQLSRV_PHPTYPE_STREAM ) -{ - type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STREAM ); -} - -// takes an encoding of the string -PHP_FUNCTION( SQLSRV_PHPTYPE_STRING ) -{ - type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STRING ); -} - -// takes the size of the binary field -PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_BINARY ); -} - -// takes the size of the char field -PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_CHAR ); -} - -// takes the precision and scale of the decimal field -PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL) -{ - type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_DECIMAL ); -} - -// takes the size of the nchar field -PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WCHAR ); -} - -// takes the precision and scale of the numeric field -PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC) -{ - type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_NUMERIC ); -} - -// takes the size (in characters, not bytes) of the nvarchar field -PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WVARCHAR ); -} - -// takes the size of the varbinary field -PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARBINARY ); -} - -// takes the size of the varchar field -PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARCHAR ); -} - -// mark parameters passed into sqlsrv_prepare as reference parameters so that they may be updated later in the -// script and subsequent sqlsrv_execute calls will use the new values. Marking them as references "pins" them -// to their memory location so that the buffer we give to ODBC can be relied on to be there. - -void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC ) -{ - SQLSRV_ASSERT( stmt->params_z == NULL, "mark_params_by_reference: parameters list shouldn't be present" ); - - if( params_z == NULL ) { - return; - } - - HashTable* params_ht = Z_ARRVAL_P( params_z ); - - zend_ulong index; - zend_string* key = NULL; - zval* value_z = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, value_z ) { - - // make sure it's an integer index - int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { - throw ss::SSException(); - } - - // This code turns parameters into references. Since the function declaration cannot - // pass array elements as references (without requiring & in front of each variable), - // we have to set the reference in each of the zvals ourselves. In the event of a - // parameter array (or sub array if you will) being passed in, we set the zval of the - // parameter array's first element. - - // if it's a sole variable - if ( Z_TYPE_P( value_z ) != IS_ARRAY ) { - ZVAL_MAKE_REF( value_z ); - } - else { - zval* var = NULL; - int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( value_z ), 0 ))) ? SUCCESS : FAILURE; - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { - throw ss::SSException(); - } - ZVAL_MAKE_REF( var ); - } - } ZEND_HASH_FOREACH_END(); - - // save our parameters for later. - Z_TRY_ADDREF_P( params_z ); - stmt->params_z = params_z; -} - -void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC ) -{ - // if there's nothing to do, just return - if( stmt->params_z == NULL ) { - return; - } - - try { - - stmt->free_param_data( TSRMLS_C ); - - stmt->executed = false; - - zval* params_z = stmt->params_z; - - HashTable* params_ht = Z_ARRVAL_P( params_z ); - - zend_ulong index = -1; - zend_string *key = NULL; - zval* param_z = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) { - zval* value_z = NULL; - SQLSMALLINT direction = SQL_PARAM_INPUT; - SQLSRV_ENCODING encoding = stmt->encoding(); - if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) { - encoding = stmt->conn->encoding(); - } - SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE; - SQLULEN column_size = SQLSRV_UNKNOWN_SIZE; - SQLSMALLINT decimal_digits = 0; - SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; - - // make sure it's an integer index - int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { - throw ss::SSException(); - } - - // if it's a parameter array - if( Z_TYPE_P( param_z ) == IS_ARRAY ) { - - zval* var = NULL; - int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( param_z ), 0 ))) ? SUCCESS : FAILURE; - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { - throw ss::SSException(); - } - - // parse the parameter array that the user gave - parse_param_array( stmt, param_z, index, direction, php_out_type, encoding, sql_type, column_size, - decimal_digits TSRMLS_CC ); - value_z = var; - } - else { - value_z = param_z; - } - // bind the parameter - core_sqlsrv_bind_param( stmt, index, direction, value_z, php_out_type, encoding, sql_type, column_size, - decimal_digits TSRMLS_CC ); - - } ZEND_HASH_FOREACH_END(); - } - catch( core::CoreException& ) { - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - zval_ptr_dtor( stmt->params_z ); - sqlsrv_free( stmt->params_z ); - stmt->params_z = NULL; - throw; - } -} - -// sqlsrv_cancel( resource $stmt ) -// -// Cancels a statement. This means that any pending results for the statement -// are discarded. After this function is called, the statement can be -// re-executed if it was prepared with sqlsrv_prepare. Calling this function is -// not necessary if all the results associated with the statement have been -// consumed. -// -// Parameters -// $stmt: The statement to be canceled. -// -// Return Value -// A Boolean value: true if the operation was successful. Otherwise, false. - -PHP_FUNCTION( sqlsrv_cancel ) -{ - - LOG_FUNCTION( "sqlsrv_cancel" ); - ss_sqlsrv_stmt* stmt = NULL; - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - SQLRETURN r = SQLCancel( stmt->handle() ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw ss::SSException(); - } - - RETURN_TRUE; - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_cancel: Unknown exception caught." ); - } -} - -void __cdecl sqlsrv_stmt_dtor(zend_resource *rsrc TSRMLS_DC) -{ - LOG_FUNCTION( "sqlsrv_stmt_dtor" ); - - // get the structure - ss_sqlsrv_stmt *stmt = static_cast( rsrc->ptr ); - if( stmt->conn ) { - int zr = zend_hash_index_del( static_cast( stmt->conn )->stmts, stmt->conn_index ); - if( zr == FAILURE ) { - LOG( SEV_ERROR, "Failed to remove statement reference from the connection" ); - } - } - - stmt->~ss_sqlsrv_stmt(); - sqlsrv_free( stmt ); - rsrc->ptr = NULL; -} - -// sqlsrv_free_stmt( resource $stmt ) -// -// Frees all resources associated with the specified statement. The statement -// cannot be used again after this function has been called. -// -// Parameters -// $stmt: The statement to be closed. -// -// Return Value -// The Boolean value true unless the function is called with an invalid -// parameter. If the function is called with an invalid parameter, false is -// returned. -// -// Null is a valid parameter for this function. This allows the function to be -// called multiple times in a script. For example, if you free a statement in an -// error condition and free it again at the end of the script, the second call -// to sqlsrv_free_stmt will return true because the first call to -// sqlsrv_free_stmt (in the error condition) sets the statement resource to -// null. - -PHP_FUNCTION( sqlsrv_free_stmt ) -{ - - LOG_FUNCTION( "sqlsrv_free_stmt" ); - - zval* stmt_r = NULL; - ss_sqlsrv_stmt* stmt = NULL; - sqlsrv_context_auto_ptr error_ctx; - - reset_errors( TSRMLS_C ); - - try { - - // dummy context to pass to the error handler - error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); - - // take only the statement resource - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &stmt_r ) == FAILURE ) { - - // Check if it was a zval - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &stmt_r ); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - if( Z_TYPE_P( stmt_r ) == IS_NULL ) { - - RETURN_TRUE; - } - else { - - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - } - - // verify the resource so we know we're deleting a statement - stmt = static_cast(zend_fetch_resource_ex(stmt_r TSRMLS_CC, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor)); - - // if sqlsrv_free_stmt was called on an already closed statment then we just return success. - // zend_list_close sets the type of the closed statment to -1. - if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) { - RETURN_TRUE; - } - - if( stmt == NULL ) { - - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - // delete the resource from Zend's master list, which will trigger the statement's destructor - if( zend_list_close( Z_RES_P(stmt_r) ) == FAILURE ) { - LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P( stmt_r )->handle); - } - - ZVAL_NULL( stmt_r ); - - RETURN_TRUE; - - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - - catch( ... ) { - - DIE( "sqlsrv_free_stmt: Unknown exception caught." ); - } -} - -void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_STRING ), stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) { - throw ss::SSException(); - } - - const char* scroll_type = Z_STRVAL_P( value_z ); - unsigned long cursor_type = -1; - - // find which cursor type they would like and set the ODBC statement attribute as such - if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) { - - cursor_type = SQL_CURSOR_STATIC; - } - - else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_DYNAMIC )) { - - cursor_type = SQL_CURSOR_DYNAMIC; - } - - else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_KEYSET )) { - - cursor_type = SQL_CURSOR_KEYSET_DRIVEN; - } - - else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_FORWARD )) { - - cursor_type = SQL_CURSOR_FORWARD_ONLY; - } - - else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_BUFFERED )) { - - cursor_type = SQLSRV_CURSOR_BUFFERED; - } - - else { - - THROW_SS_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); - } - - core_sqlsrv_set_scrollable( stmt, cursor_type TSRMLS_CC ); - -} - -namespace { - -void convert_to_zval(sqlsrv_stmt* stmt, SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len, zval& out_zval) -{ - if ( in_val == NULL ) { - ZVAL_NULL( &out_zval); - return; - } - - switch (sqlsrv_php_type) { - - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - { - if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) { - ZVAL_LONG( &out_zval, *(static_cast( in_val ))); - } - else { - ZVAL_DOUBLE( &out_zval, *(static_cast( in_val ))); - } - break; - } - - case SQLSRV_PHPTYPE_STRING: - { - ZVAL_STRINGL( &out_zval, static_cast( in_val ), field_len); - break; - } - - case SQLSRV_PHPTYPE_STREAM: - { - out_zval = *( static_cast( in_val )); - stmt->active_stream = out_zval; - //addref here because deleting out_zval later will decrement the refcount - Z_TRY_ADDREF( out_zval ); - break; - } - case SQLSRV_PHPTYPE_DATETIME: - { - out_zval = *( static_cast( in_val )); - break; - } - - case SQLSRV_PHPTYPE_NULL: - ZVAL_NULL(&out_zval); - break; - - default: - DIE("Unknown php type"); - break; - } - return; -} - - -// put in the column size and scale/decimal digits of the sql server type -// these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx -bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, _Out_ SQLULEN* column_size, - _Out_ SQLSMALLINT* decimal_digits ) -{ - *decimal_digits = 0; - - switch( sqlsrv_type.typeinfo.type ) { - case SQL_BIGINT: - *column_size = 19; - break; - case SQL_BIT: - *column_size = 1; - break; - case SQL_INTEGER: - *column_size = 10; - break; - case SQL_SMALLINT: - *column_size = 5; - break; - case SQL_TINYINT: - *column_size = 3; - break; - case SQL_GUID: - *column_size = 36; - break; - case SQL_FLOAT: - *column_size = 53; - break; - case SQL_REAL: - *column_size = 24; - break; - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - *column_size = LONG_MAX; - break; - case SQL_WLONGVARCHAR: - *column_size = LONG_MAX >> 1; - break; - case SQL_SS_XML: - *column_size = SQL_SS_LENGTH_UNLIMITED; - break; - case SQL_BINARY: - case SQL_CHAR: - case SQL_VARBINARY: - case SQL_VARCHAR: - *column_size = sqlsrv_type.typeinfo.size; - if( *column_size == SQLSRV_SIZE_MAX_TYPE ) { - *column_size = SQL_SS_LENGTH_UNLIMITED; - } - else if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) { - *column_size = SQLSRV_INVALID_SIZE; - return false; - } - break; - case SQL_WCHAR: - case SQL_WVARCHAR: - *column_size = sqlsrv_type.typeinfo.size; - if( *column_size == SQLSRV_SIZE_MAX_TYPE ) { - *column_size = SQL_SS_LENGTH_UNLIMITED; - break; - } - if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) { - *column_size = SQLSRV_INVALID_SIZE; - return false; - } - break; - case SQL_DECIMAL: - case SQL_NUMERIC: - *column_size = sqlsrv_type.typeinfo.size; - *decimal_digits = sqlsrv_type.typeinfo.scale; - // if there was something wrong with the values given on type_and_precision_calc, these are set to invalid precision - if( *column_size == SQLSRV_INVALID_PRECISION || *decimal_digits == SQLSRV_INVALID_PRECISION ) { - *column_size = SQLSRV_INVALID_SIZE; - return false; - } - break; - // this can represent one of three data types: smalldatetime, datetime, and datetime2 - // we present the largest for the version and let SQL Server downsize it - case SQL_TYPE_TIMESTAMP: - *column_size = sqlsrv_type.typeinfo.size; - *decimal_digits = sqlsrv_type.typeinfo.scale; - break; - case SQL_SS_TIMESTAMPOFFSET: - *column_size = 34; - *decimal_digits = 7; - break; - case SQL_TYPE_DATE: - *column_size = 10; - *decimal_digits = 0; - break; - case SQL_SS_TIME2: - *column_size = 16; - *decimal_digits = 7; - break; - default: - // an invalid sql type should have already been dealt with, so we assert here. - DIE( "Trying to determine column size for an invalid type. Type should have already been verified." ); - return false; - } - - return true; -} - - -// given a SQL Server type, return a sqlsrv php type -sqlsrv_phptype determine_sqlsrv_php_type( ss_sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ) -{ - sqlsrv_phptype sqlsrv_phptype; - sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; - - switch( sql_type ) { - case SQL_BIGINT: - case SQL_CHAR: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - case SQL_WCHAR: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - break; - case SQL_VARCHAR: - case SQL_WVARCHAR: - if( prefer_string || size != SQL_SS_LENGTH_UNLIMITED ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - } - break; - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; - break; - case SQL_BINARY: - case SQL_LONGVARBINARY: - case SQL_VARBINARY: - case SQL_SS_UDT: - if( prefer_string ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; - } - break; - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - if( prefer_string ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - } - break; - case SQL_FLOAT: - case SQL_REAL: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; - break; - case SQL_TYPE_DATE: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_TIME2: - case SQL_TYPE_TIMESTAMP: - { - ss_sqlsrv_conn* c = static_cast( stmt->conn ); - if( c->date_as_string ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; - } - break; - } - default: - sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID; - break; - } - - // if an encoding hasn't been set for the statement, then use the connection's encoding - if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - sqlsrv_phptype.typeinfo.encoding = stmt->conn->encoding(); - } - - return sqlsrv_phptype; -} - - -// determine if a query returned any rows of data. It does this by actually fetching the first row -// (though not retrieving the data) and setting the has_rows flag in the stmt the fetch was successful. -// The return value simply states whether or not if an error occurred during the determination. -// (All errors are posted here before returning.) - -void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC ) -{ - SQLRETURN r = SQL_SUCCESS; - - if( stmt->fetch_called ) { - - return; - } - - // default condition - stmt->has_rows = false; - - // if there are no columns then there are no rows - if( core::SQLNumResultCols( stmt TSRMLS_CC ) == 0 ) { - - return; - } - - // if the statement is scrollable, our work is easier though less performant. We simply - // fetch the first row, and then roll the cursor back to be prior to the first row - if( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { - - r = stmt->current_results->fetch( SQL_FETCH_FIRST, 0 TSRMLS_CC ); - if( SQL_SUCCEEDED( r )) { - - stmt->has_rows = true; - CHECK_SQL_WARNING( r, stmt ); - // restore the cursor to its original position. - r = stmt->current_results->fetch( SQL_FETCH_ABSOLUTE, 0 TSRMLS_CC ); - SQLSRV_ASSERT(( r == SQL_NO_DATA ), "core_sqlsrv_has_rows: Should have scrolled the cursor to the beginning " - "of the result set." ); - } - } - else { - - // otherwise, we fetch the first row, but record that we did. sqlsrv_fetch checks this - // flag and simply skips the first fetch, knowing it was already done. It records its own - // flags to know if it should fetch on subsequent calls. - - r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); - if( SQL_SUCCEEDED( r )) { - - stmt->has_rows = true; - CHECK_SQL_WARNING( r, stmt ); - return; - } - } -} - -void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, zend_long fetch_type, _Out_ zval& fields, bool allow_empty_field_names - TSRMLS_DC ) -{ - void* field_value = NULL; - sqlsrv_phptype sqlsrv_php_type; - sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID; - - // make sure that the fetch type is legal - CHECK_CUSTOM_ERROR((fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, stmt->func()) { - throw ss::SSException(); - } - - // get the numer of columns in the result set - SQLSMALLINT num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); - - // if this is the first fetch in a new result set, then get the field names and - // store them off for successive fetches. - if(( fetch_type & SQLSRV_FETCH_ASSOC ) && stmt->fetch_field_names == NULL) { - - SQLSMALLINT field_name_len; - char field_name_temp[SS_MAXCOLNAMELEN + 1]; - sqlsrv_malloc_auto_ptr field_names; - field_names = static_cast( sqlsrv_malloc( num_cols * sizeof(sqlsrv_fetch_field_name))); - - for(int i = 0; i < num_cols; ++i) { - - core::SQLColAttribute(stmt, i + 1, SQL_DESC_NAME, field_name_temp, SS_MAXCOLNAMELEN + 1, &field_name_len, NULL - TSRMLS_CC); - field_names[i].name = static_cast( sqlsrv_malloc( field_name_len, sizeof(char), 1 )); - memcpy_s(( void* )field_names[i].name, ( field_name_len * sizeof( char )) ,field_name_temp, field_name_len); - field_names[i].name[field_name_len] = '\0'; // null terminate the field name since SQLColAttribute doesn't. - field_names[i].len = field_name_len + 1; - } - - stmt->fetch_field_names = field_names; - stmt->fetch_fields_count = num_cols; - field_names.transferred(); - } - - int zr = array_init( &fields ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } - - for( int i = 0; i < num_cols; ++i ) { - SQLLEN field_len = -1; - - core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/, - field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out TSRMLS_CC ); - - zval field; - ZVAL_UNDEF( &field ); - convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, field ); - sqlsrv_free( field_value ); - if( fetch_type & SQLSRV_FETCH_NUMERIC ) { - - zr = add_next_index_zval( &fields, &field ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } - } - - if( fetch_type & SQLSRV_FETCH_ASSOC ) { - - CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 1 && !allow_empty_field_names ), stmt, - SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) { - throw ss::SSException(); - } - - 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 ) { - throw ss::SSException(); - } - } - } - //only addref when the fetch_type is BOTH because this is the only case when fields(hashtable) - //has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because - //fields now only has 1 element pointing to field and we want the ref count to be only 1 - if (fetch_type == SQLSRV_FETCH_BOTH) { - Z_TRY_ADDREF(field); - } - } //for loop - -} - -void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, - _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) - -{ - zval* var_or_val = NULL; - zval* temp = NULL; - HashTable* param_ht = Z_ARRVAL_P( param_array ); - sqlsrv_sqltype sqlsrv_sql_type; - - try { - - bool php_type_param_was_null = true; - bool sql_type_param_was_null = true; - - php_out_type = SQLSRV_PHPTYPE_INVALID; - encoding = SQLSRV_ENCODING_INVALID; - - // handle the array parameters that contain the value/var, direction, php_type, sql_type - zend_hash_internal_pointer_reset( param_ht ); - if( zend_hash_has_more_elements( param_ht ) == FAILURE || - (var_or_val = zend_hash_get_current_data(param_ht)) == NULL) { - - THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ); - } - - // if the direction is included, then use what they gave, otherwise INPUT is assumed - if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && - Z_TYPE_P( temp ) != IS_NULL ) { - - CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { - - throw ss::SSException(); - } - direction = static_cast(Z_LVAL_P( temp )); - CHECK_CUSTOM_ERROR( direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT, - stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { - throw ss::SSException(); - } - - CHECK_CUSTOM_ERROR(!Z_ISREF_P(var_or_val) && (direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1) { - throw ss::SSException(); - } - - } - else { - direction = SQL_PARAM_INPUT; - } - - // extract the php type and encoding from the 3rd parameter - if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && - Z_TYPE_P( temp ) != IS_NULL ) { - - php_type_param_was_null = false; - sqlsrv_phptype sqlsrv_phptype; - - CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1 ) { - - throw ss::SSException(); - } - - sqlsrv_phptype.value = Z_LVAL_P( temp ); - - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_phptype ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, - index + 1 ) { - - throw ss::SSException(); - } - - php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); - encoding = (SQLSRV_ENCODING) sqlsrv_phptype.typeinfo.encoding; - // if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established - // by the connection - if( encoding == SQLSRV_ENCODING_DEFAULT ) { - encoding = stmt->conn->encoding(); - } - } - // set default for php type and encoding if not supplied - else { - - php_type_param_was_null = true; - - if (Z_ISREF_P(var_or_val)){ - php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(Z_REFVAL_P(var_or_val))]; - } - else{ - php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(var_or_val)]; - } - encoding = stmt->encoding(); - if( encoding == SQLSRV_ENCODING_DEFAULT ) { - encoding = stmt->conn->encoding(); - } - } - - // get the server type, column size/precision and the decimal digits if provided - if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && - Z_TYPE_P( temp ) != IS_NULL ) { - - sql_type_param_was_null = false; - - CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1 ) { - - throw ss::SSException(); - } - - sqlsrv_sql_type.value = Z_LVAL_P( temp ); - - // since the user supplied this type, make sure it's valid - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_sqltype( sqlsrv_sql_type ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, - index + 1 ) { - - throw ss::SSException(); - } - - bool size_okay = determine_column_size_or_precision(stmt, sqlsrv_sql_type, &column_size, &decimal_digits); - - CHECK_CUSTOM_ERROR( !size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1 ) { - - throw ss::SSException(); - } - - sql_type = sqlsrv_sql_type.typeinfo.type; - } - // else the sql type and size are uknown, so tell the core layer to use its defaults - else { - - sql_type_param_was_null = true; - - sql_type = SQL_UNKNOWN_TYPE; - column_size = SQLSRV_UNKNOWN_SIZE; - decimal_digits = 0; - } - - // if the user for some reason provides an output parameter with a null phptype and a specified - // sql server type, infer the php type from the sql server type. - if( direction == SQL_PARAM_OUTPUT && php_type_param_was_null && !sql_type_param_was_null ) { - - int encoding; - sqlsrv_phptype sqlsrv_phptype; - - sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, (SQLUINTEGER)column_size, true ); - - // we DIE here since everything should have been validated already and to return the user an error - // for our own logic error would be confusing/misleading. - SQLSRV_ASSERT( sqlsrv_phptype.typeinfo.type != PHPTYPE_INVALID, "An invalid php type was returned with (supposed) " - "validated sql type and column_size" ); - - php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); - encoding = sqlsrv_phptype.typeinfo.encoding; - } - - // verify that the parameter is a valid output param type - if( direction == SQL_PARAM_OUTPUT ) { - - switch( php_out_type ) { - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_DATETIME: - case SQLSRV_PHPTYPE_STREAM: - THROW_CORE_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE ); - break; - default: - break; - } - - } - - } - catch( core::CoreException& ) { - - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - throw; - } -} - -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) -{ - switch( type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - case SQLSRV_PHPTYPE_DATETIME: - return true; - case SQLSRV_PHPTYPE_STRING: - case SQLSRV_PHPTYPE_STREAM: - { - if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR - || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - return true; - } - break; - } - } - - return false; -} - -// return if the type is a valid sql server type not including -// size, precision or scale. Use determine_precision_and_scale for that. -bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype sql_type ) -{ - switch( sql_type.typeinfo.type ) { - case SQL_BIGINT: - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - case SQL_GUID: - case SQL_FLOAT: - case SQL_REAL: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - case SQL_BINARY: - case SQL_CHAR: - case SQL_WCHAR: - case SQL_WVARCHAR: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - break; - default: - return false; - } - - return true; -} - -// verify an encoding given to type_and_encoding by looking through the list -// of standard encodings created at module initialization time -bool verify_and_set_encoding( const char* encoding_string, _Out_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ) -{ - void* encoding_temp = NULL; - zend_ulong index = -1; - zend_string* key = NULL; - ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, encoding_temp ) { - if ( !encoding_temp ) { - DIE( "Fatal: Error retrieving encoding from encoding hash table." ); - } - sqlsrv_encoding* encoding = reinterpret_cast( encoding_temp ); - encoding_temp = NULL; - if( !stricmp( encoding_string, encoding->iana )) { - phptype_encoding.typeinfo.encoding = encoding->code_page; - return true; - } - } ZEND_HASH_FOREACH_END(); - - return false; -} - -// called when one of the SQLSRV_SQLTYPE type functions is called. Encodes the type and size -// into a sqlsrv_sqltype bit fields (see php_sqlsrv.h). -void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) -{ - char* size_p = NULL; - size_t size_len = 0; - long size = 0; - - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &size_p, &size_len ) == FAILURE ) { - - return; - } - - if( !strnicmp( "max", size_p, sizeof( "max" ) / sizeof(char)) ) { - size = SQLSRV_SIZE_MAX_TYPE; - } - else { - _set_errno( 0 ); // reset errno for atol - size = atol( size_p ); - if( errno != 0 ) { - size = SQLSRV_INVALID_SIZE; - } - } - - int max_size = SQL_SERVER_MAX_FIELD_SIZE; - // size is actually the number of characters, not the number of bytes, so if they ask for a - // 2 byte per character type, then we half the maximum size allowed. - if( type == SQL_WVARCHAR || type == SQL_WCHAR ) { - max_size >>= 1; - } - - if( size > max_size || size < SQLSRV_SIZE_MAX_TYPE || size == 0 ) { - LOG( SEV_ERROR, "invalid size. size must be > 0 and <= %1!d! characters or 'max'", max_size ); - size = SQLSRV_INVALID_SIZE; - } - - sqlsrv_sqltype sql_type; - sql_type.typeinfo.type = type; - sql_type.typeinfo.size = size; - sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE; - - ZVAL_LONG( return_value, sql_type.value ); -} - -// called when the user gives SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC sql types as the type of the -// field. encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h) -void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) -{ - zend_long prec = SQLSRV_INVALID_PRECISION; - zend_long scale = SQLSRV_INVALID_SCALE; - - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &prec, &scale ) == FAILURE ) { - - return; - } - - if( prec > SQL_SERVER_MAX_PRECISION ) { - LOG( SEV_ERROR, "Invalid precision. Precision can't be > 38" ); - prec = SQLSRV_INVALID_PRECISION; - } - - if( prec < 0 ) { - LOG( SEV_ERROR, "Invalid precision. Precision can't be negative" ); - prec = SQLSRV_INVALID_PRECISION; - } - - if( scale > prec ) { - LOG( SEV_ERROR, "Invalid scale. Scale can't be > precision" ); - scale = SQLSRV_INVALID_SCALE; - } - - sqlsrv_sqltype sql_type; - sql_type.typeinfo.type = type; - sql_type.typeinfo.size = prec; - sql_type.typeinfo.scale = scale; - - ZVAL_LONG( return_value, sql_type.value ); -} - -// common code for SQLSRV_PHPTYPE_STREAM and SQLSRV_PHPTYPE_STRING php types given as parameters. -// encodes the type and encoding into a sqlsrv_phptype structure (see php_sqlsrv.h) -void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ) -{ - - SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." ); - - char* encoding_param; - size_t encoding_param_len = 0; - - // set the default encoding values to invalid so that - // if the encoding isn't validated, it will return the invalid setting. - sqlsrv_phptype sqlsrv_php_type; - sqlsrv_php_type.typeinfo.type = type; - sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID; - - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &encoding_param, &encoding_param_len ) == FAILURE ) { - - ZVAL_LONG( return_value, sqlsrv_php_type.value ); - } - - if( !verify_and_set_encoding( encoding_param, sqlsrv_php_type TSRMLS_CC )) { - LOG( SEV_ERROR, "Invalid encoding for php type." ); - } - - ZVAL_LONG( return_value, sqlsrv_php_type.value ); -} - -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: stmt.cpp +// +// Contents: Routines that use statement handles +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +// *** header files *** +#include "php_sqlsrv.h" +#ifdef _WIN32 +#include +#endif // _WIN32 + +// +// *** internal variables and constants *** +// +// our resource descriptor assigned in minit +int ss_sqlsrv_stmt::descriptor = 0; +char* ss_sqlsrv_stmt::resource_name = static_cast("ss_sqlsrv_stmt"); // not const for a reason. see sqlsrv_stmt in php_sqlsrv.h + +namespace { + +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros +unsigned int current_log_subsystem = LOG_STMT; + +// constants used as invalid types for type errors +const zend_uchar PHPTYPE_INVALID = SQLSRV_PHPTYPE_INVALID; +const int SQLTYPE_INVALID = 0; +const int SQLSRV_INVALID_PRECISION = -1; +const SQLUINTEGER SQLSRV_INVALID_SIZE = (~1U); +const int SQLSRV_INVALID_SCALE = -1; +const int SQLSRV_SIZE_MAX_TYPE = -1; + +// constants for maximums in SQL Server +const int SQL_SERVER_MAX_FIELD_SIZE = 8000; +const int SQL_SERVER_MAX_PRECISION = 38; +const int SQL_SERVER_DEFAULT_PRECISION = 18; +const int SQL_SERVER_DEFAULT_SCALE = 0; + +// default class used when no class is specified by sqlsrv_fetch_object +const char STDCLASS_NAME[] = "stdclass"; +const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1; + +// map a Zend PHP type constant to our constant type +enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = { + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_NULL, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_INT, + SQLSRV_PHPTYPE_FLOAT, + SQLSRV_PHPTYPE_STRING, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_DATETIME, + SQLSRV_PHPTYPE_STREAM, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_INVALID +}; + +// constant strings used for the field metadata results +// (char to avoid having to cast them where they are used) +namespace FieldMetaData { + +const char* NAME = "Name"; +const char* TYPE = "Type"; +const char* SIZE = "Size"; +const char* PREC = "Precision"; +const char* SCALE = "Scale"; +const char* NULLABLE = "Nullable"; + +} + +// warning message printed when a parameter variable is not passed by reference +const char SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF[] = "Variable parameter %d not passed by reference (prefaced with an &). " + "Variable parameters passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value. " + "For more information, see sqlsrv_prepare or sqlsrv_query in the API Reference section of the product documentation."; + +/* internal functions */ + +void convert_to_zval( sqlsrv_stmt* stmt, SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len, zval& out_zval ); + +void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, zend_long fetch_type, _Out_ zval& fields, bool allow_empty_field_names + TSRMLS_DC ); +bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, _Out_ SQLULEN* column_size, + _Out_ SQLSMALLINT* decimal_digits ); +sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ); +void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC ); +bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); +bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype type ); +void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, + _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); +void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ); +void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); +void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); +bool verify_and_set_encoding( const char* encoding_string, _Out_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ); + +} + +// query options for cursor types +namespace SSCursorTypes { + + const char QUERY_OPTION_SCROLLABLE_STATIC[] = "static"; + const char QUERY_OPTION_SCROLLABLE_DYNAMIC[] = "dynamic"; + const char QUERY_OPTION_SCROLLABLE_KEYSET[] = "keyset"; + const char QUERY_OPTION_SCROLLABLE_FORWARD[] = "forward"; + const char QUERY_OPTION_SCROLLABLE_BUFFERED[] = "buffered"; +} + +ss_sqlsrv_stmt::ss_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : + sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), + prepared( false ), + conn_index( -1 ), + params_z( NULL ), + fetch_field_names( NULL ), + fetch_fields_count ( 0 ) +{ + core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC ); +} + +ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) +{ + if( fetch_field_names != NULL ) { + + for( int i=0; i < fetch_fields_count; ++i ) { + + sqlsrv_free( fetch_field_names[ i ].name ); + } + sqlsrv_free( fetch_field_names ); + } + if( params_z ) { + zval_ptr_dtor( params_z ); + sqlsrv_free(params_z); + } +} + +// to be called whenever a new result set is created, such as after an +// execute or next_result. Resets the state variables and calls the subclass. +void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) +{ + if( fetch_field_names != NULL ) { + + for( int i=0; i < fetch_fields_count; ++i ) { + + sqlsrv_free( fetch_field_names[ i ].name ); + } + sqlsrv_free( fetch_field_names ); + } + + fetch_field_names = NULL; + fetch_fields_count = 0; + sqlsrv_stmt::new_result_set( TSRMLS_C ); +} + +// Returns a php type for a given sql type. Also sets the encoding wherever applicable. +sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) +{ + sqlsrv_phptype ss_phptype; + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + + switch( sql_type ) { + + case SQL_BIGINT: + case SQL_CHAR: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + case SQL_WCHAR: + case SQL_SS_VARIANT: + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + ss_phptype.typeinfo.encoding = this->conn->encoding(); + break; + + case SQL_VARCHAR: + case SQL_WVARCHAR: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + if( prefer_string_to_stream || size != SQL_SS_LENGTH_UNLIMITED ) { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + ss_phptype.typeinfo.encoding = this->conn->encoding(); + } + else { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + ss_phptype.typeinfo.encoding = this->conn->encoding(); + } + break; + + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + break; + + case SQL_BINARY: + case SQL_LONGVARBINARY: + case SQL_VARBINARY: + case SQL_SS_UDT: + if( prefer_string_to_stream ) { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + } + else { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + } + break; + + case SQL_FLOAT: + case SQL_REAL: + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + break; + + case SQL_TYPE_DATE: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_TIME2: + case SQL_TYPE_TIMESTAMP: + if( reinterpret_cast( this->conn )->date_as_string ) { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + ss_phptype.typeinfo.encoding = this->conn->encoding(); + } + else { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; + } + break; + + default: + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + break; + } + + return ss_phptype; +} + +// statement specific parameter proccessing. Uses the generic function specialised to return a statement +// resource. +#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \ + rsrc = process_params( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, ## __VA_ARGS__ );\ + if( rsrc == NULL ) { \ + RETURN_FALSE; \ + } + +// sqlsrv_execute( resource $stmt ) +// +// Executes a previously prepared statement. See sqlsrv_prepare for information +// on preparing a statement for execution. +// +// This function is ideal for executing a prepared statement multiple times with +// different parameter values. See the MSDN documentation +// +// Parameters +// $stmt: A resource specifying the statement to be executed. For more +// information about how to create a statement resource, see sqlsrv_prepare. +// +// Return Value +// A Boolean value: true if the statement was successfully executed. Otherwise, false. + +PHP_FUNCTION( sqlsrv_execute ) +{ + LOG_FUNCTION( "sqlsrv_execute" ); + + ss_sqlsrv_stmt* stmt = NULL; + + try { + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + CHECK_CUSTOM_ERROR(( !stmt->prepared ), stmt, SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED ) { + throw ss::SSException(); + } + + // prepare for the next execution by flushing anything remaining in the result set + if( stmt->executed ) { + + // to prepare to execute the next statement, we skip any remaining results (and skip parameter finalization too) + while( stmt->past_next_result_end == false ) { + + core_sqlsrv_next_result( stmt TSRMLS_CC, false, false ); + } + } + + // bind parameters before executing + bind_params( stmt TSRMLS_CC ); + + core_sqlsrv_execute( stmt TSRMLS_CC ); + + RETURN_TRUE; + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_execute: Unknown exception caught." ); + } +} + + +// sqlsrv_fetch( resource $stmt ) +// +// Makes the next row of a result set available for reading. Use +// sqlsrv_get_field to read fields of the row. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. A +// statement must be executed before results can be retrieved. For information +// on executing a statement, see sqlsrv_query and sqlsrv_execute. +// +// Return Value +// If the next row of the result set was successfully retrieved, true is +// returned. If there are no more results in the result set, null is +// returned. If an error occured, false is returned + +PHP_FUNCTION( sqlsrv_fetch ) +{ + LOG_FUNCTION( "sqlsrv_fetch" ); + + ss_sqlsrv_stmt* stmt = NULL; + // NOTE: zend_parse_parameter expect zend_long when the type spec is 'l',and core_sqlsrv_fetch expect short int + zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied + zend_long fetch_offset = 0; // default value for parameter if one isn't supplied + + // take only the statement resource + PROCESS_PARAMS( stmt, "r|ll", _FN_, 2, &fetch_style, &fetch_offset ); + + try { + + CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { + throw ss::SSException(); + } + + bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset TSRMLS_CC ); + if( !result ) { + RETURN_NULL(); + } + + RETURN_TRUE; + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_fetch: Unknown exception caught." ); + } +} + +// sqlsrv_fetch_array( resource $stmt [, int $fetchType] ) +// +// Retrieves the next row of data as an array. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. +// $fetchType [OPTIONAL]: A predefined constant. See SQLSRV_FETCH_TYPE in php_sqlsrv.h +// +// Return Value +// If a row of data is retrieved, an array is returned. If there are no more +// rows to retrieve, null is returned. If an error occurs, false is returned. +// Based on the value of the $fetchType parameter, the returned array can be a +// numerically indexed array, an associative array, or both. By default, an +// array with both numeric and associative keys is returned. The data type of a +// value in the returned array will be the default PHP data type. For +// information about default PHP data types, see Default PHP Data Types. + +PHP_FUNCTION( sqlsrv_fetch_array ) +{ + LOG_FUNCTION( "sqlsrv_fetch_array" ); + + ss_sqlsrv_stmt* stmt = NULL; + zend_long fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied + zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied + zend_long fetch_offset = 0; // default value for parameter if one isn't supplied + + // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), + // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset + PROCESS_PARAMS( stmt, "r|lll", _FN_, 3, &fetch_type, &fetch_style, &fetch_offset ); + + try { + + CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt, + SS_SQLSRV_ERROR_INVALID_FETCH_TYPE ) { + throw ss::SSException(); + } + + CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { + throw ss::SSException(); + } + + bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset TSRMLS_CC ); + if( !result ) { + RETURN_NULL(); + } + zval fields; + ZVAL_UNDEF( &fields ); + fetch_fields_common( stmt, fetch_type, fields, true /*allow_empty_field_names*/ TSRMLS_CC ); + RETURN_ARR( Z_ARRVAL( fields )); + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_fetch_array: Unknown exception caught." ); + } +} + +// sqlsrv_field_metadata( resource $stmt ) +// +// Retrieves metadata for the fields of a prepared statement. For information +// about preparing a statement, see sqlsrv_query or sqlsrv_prepare. Note that +// sqlsrv_field_metadata can be called on any prepared statement, pre- or +// post-execution. +// +// Parameters +// $stmt: A statement resource for which field metadata is sought. +// +// Return Value +// retrieve an array of metadata for the current result set on a statement. Each element of the +// array is a sub-array containing 5 elements accessed by key: +// name - name of the field. +// type - integer of the type. Can be compared against the SQLSRV_SQLTYPE constants. +// size - length of the field. null if the field uses precision and scale instead. +// precision - number of digits in a numeric field. null if the field uses size. +// scale - number of decimal digits in a numeric field. null if the field uses sizes. +// is_nullable - if the field may contain a NULL instead of a value +// false is returned if an error occurs retrieving the metadata + +PHP_FUNCTION( sqlsrv_field_metadata ) +{ + sqlsrv_stmt* stmt = NULL; + SQLSMALLINT num_cols = -1; + + LOG_FUNCTION( "sqlsrv_field_metadata" ); + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // get the number of fields in the resultset + num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + + zval result_meta_data; + ZVAL_UNDEF( &result_meta_data ); + core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC ); + + for( SQLSMALLINT f = 0; f < num_cols; ++f ) { + + sqlsrv_malloc_auto_ptr core_meta_data; + core_meta_data = core_sqlsrv_field_metadata( stmt, f TSRMLS_CC ); + + // initialize the array + zval field_array; + ZVAL_UNDEF( &field_array ); + core::sqlsrv_array_init( *stmt, &field_array TSRMLS_CC ); + + core::sqlsrv_add_assoc_string( *stmt, &field_array, FieldMetaData::NAME, + reinterpret_cast( core_meta_data->field_name.get() ), 0 TSRMLS_CC ); + + core_meta_data->field_name.transferred(); + + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC ); + + switch( core_meta_data->field_type ) { + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TYPE_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SCALE, core_meta_data->field_scale TSRMLS_CC ); + break; + case SQL_BIT: + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIGINT: + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); + break; + default: + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SIZE, core_meta_data->field_size TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::PREC TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); + break; + } + + // add the nullability to the array + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable + TSRMLS_CC ); + + // add this field's meta data to the result set meta data + core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC ); + + // always good to call destructor for allocations done through placement new operator. + core_meta_data->~field_meta_data(); + } + + // return our built collection and transfer ownership + RETURN_ZVAL(&result_meta_data, 1, 1); + + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_field_metadata: Unknown exception caught." ); + } +} + + +// sqlsrv_next_result( resource $stmt ) +// +// Makes the next result (result set, row count, or output parameter) of the +// specified statement active. The first (or only) result returned by a batch +// query or stored procedure is active without a call to sqlsrv_next_result. +// Any output parameters bound are only available after sqlsrv_next_result returns +// null as per ODBC Driver 11 for SQL Server specs: http://msdn.microsoft.com/en-us/library/ms403283.aspx +// +// Parameters +// $stmt: The executed statement on which the next result is made active. +// +// Return Value +// If the next result was successfully made active, the Boolean value true is +// returned. If an error occurred in making the next result active, false is +// returned. If no more results are available, null is returned. + +PHP_FUNCTION( sqlsrv_next_result ) +{ + LOG_FUNCTION( "sqlsrv_next_result" ); + + ss_sqlsrv_stmt* stmt = NULL; + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + core_sqlsrv_next_result( stmt TSRMLS_CC, true ); + + if( stmt->past_next_result_end ) { + + RETURN_NULL(); + } + + RETURN_TRUE; + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_next_result: Unknown exception caught." ); + } +} + +// sqlsrv_rows_affected( resource $stmt ) +// +// Returns the number of rows modified by the last statement executed. This +// function does not return the number of rows returned by a SELECT statement. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. +// +// Return Value +// An integer indicating the number of rows modified by the last executed +// statement. If no rows were modified, zero (0) is returned. If no information +// about the number of modified rows is available, negative one (-1) is +// returned. If an error occurred in retrieving the number of modified rows, +// false is returned. See SQLRowCount in the MSDN ODBC documentation. + +PHP_FUNCTION( sqlsrv_rows_affected ) +{ + LOG_FUNCTION( "sqlsrv_rows_affected" ); + ss_sqlsrv_stmt* stmt = NULL; + SQLLEN rows = -1; + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // make sure that the statement has already been executed. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw ss::SSException(); + } + + // make sure it is not scrollable. This function should only work for inserts, updates, and deletes, + // but this is the best we can do to enforce that. + CHECK_CUSTOM_ERROR( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY, stmt, SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE ) { + throw ss::SSException(); + } + + rows = stmt->current_results->row_count( TSRMLS_C ); + RETURN_LONG( rows ); + } + + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_rows_affected: Unknown exception caught." ); + } +} + +// sqlsrv_num_rows( resource $stmt ) +// +// Retrieves the number of rows in an active result set. The statement must +// have been created with the Scrollable attribute set to 'static'. +// +// Parameters +// $stmt: The statement on which the targeted result set is active. +// +// Return Value +// An integer value that represents the number of rows in the active result +// set. If an error occurs, the boolean value false is returned. + +PHP_FUNCTION( sqlsrv_num_rows ) +{ + LOG_FUNCTION( "sqlsrv_num_rows" ); + + ss_sqlsrv_stmt* stmt = NULL; + SQLLEN rows = -1; + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // make sure that the statement has already been executed. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw ss::SSException(); + } + + // make sure that the statement is scrollable and the cursor is not dynamic. + // if the cursor is dynamic, then the number of rows returned is always -1. + CHECK_CUSTOM_ERROR( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY || stmt->cursor_type == SQL_CURSOR_DYNAMIC, stmt, + SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE ) { + throw ss::SSException(); + } + + rows = stmt->current_results->row_count( TSRMLS_C ); + RETURN_LONG( rows ); + } + + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_num_rows: Unknown exception caught." ); + } +} + +// sqlsrv_num_fields( resource $stmt ) +// +// Retrieves the number of fields in an active result set. Note that +// sqlsrv_num_fields can be called on any prepared statement, before or after +// execution. +// +// Parameters +// $stmt: The statement on which the targeted result set is active. +// +// Return Value +// An integer value that represents the number of fields in the active result +// set. If an error occurs, the boolean value false is returned. + +PHP_FUNCTION( sqlsrv_num_fields ) +{ + LOG_FUNCTION( "sqlsrv_num_fields" ); + + ss_sqlsrv_stmt* stmt = NULL; + SQLSMALLINT fields = -1; + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // retrieve the number of columns from ODBC + fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + + RETURN_LONG( fields ); + } + + catch( ss::SSException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_num_fields: Unknown exception caught." ); + } +} + +// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams ]]) +// +// Retrieves the next row of data as a PHP object. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. +// +// $className [OPTIONAL]: A string specifying the name of the class to +// instantiate. If a value for the $className parameter is not specified, an +// instance of the PHP stdClass is instantiated. +// +// $ctorParams [OPTIONAL]: An array that contains values passed to the +// constructor of the class specified with the $className parameter. If the +// constructor of the specified class accepts parameter values, the $ctorParams +// parameter must be used when calling sqlsrv_fetch_object. +// +// Return Value +// A PHP object with properties that correspond to result set field +// names. Property values are populated with the corresponding result set field +// values. If the class specified with the optional $className parameter does +// not exist or if there is no active result set associated with the specified +// statement, false is returned. +// The data type of a value in the returned object will be the default PHP data +// type. For information on default PHP data types, see Default PHP Data Types. +// +// Remarks +// If a class name is specified with the optional $className parameter, an +// object of this class type is instantiated. If the class has properties whose +// names match the result set field names, the corresponding result set values +// are applied to the properties. If a result set field name does not match a +// class property, a property with the result set field name is added to the +// object and the result set value is applied to the property. For more +// information about calling sqlsrv_fetch_object with the $className parameter, +// see How to: Retrieve Data as an Object (Microsoft Drivers for PHP for SQL Server). +// +// If a field with no name is returned, sqlsrv_fetch_object will discard the +// field value and issue a warning. + +PHP_FUNCTION( sqlsrv_fetch_object ) +{ + LOG_FUNCTION( "sqlsrv_fetch_object" ); + + ss_sqlsrv_stmt* stmt = NULL; + zval* class_name_z = NULL; + zval* ctor_params_z = NULL; + zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied + zend_long fetch_offset = 0; // default value for parameter if one isn't supplied + + // stdClass is the name of the system's default base class in PHP + char* class_name = const_cast( STDCLASS_NAME ); + std::size_t class_name_len = STDCLASS_NAME_LEN; + HashTable* properties_ht = NULL; + zval retval_z; + ZVAL_UNDEF( &retval_z ); + + // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), + // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset + // we also use z! instead of s and a so that null may be passed in as valid values for + // the class name and ctor params + PROCESS_PARAMS( stmt, "r|z!z!ll", _FN_, 4, &class_name_z, &ctor_params_z, &fetch_style, &fetch_offset ); + + try { + + CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { + throw ss::SSException(); + } + + if( class_name_z ) { + + CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + throw ss::SSException(); + } + class_name = Z_STRVAL( *class_name_z ); + class_name_len = Z_STRLEN( *class_name_z ); + } + + if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) { + THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + // fetch the data + bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset TSRMLS_CC ); + if( !result ) { + RETURN_NULL(); + } + + fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ TSRMLS_CC ); + properties_ht = Z_ARRVAL( retval_z ); + + // find the zend_class_entry of the class the user requested (stdClass by default) for use below + zend_class_entry* class_entry = NULL; + zend_string* class_name_str_z = zend_string_init( class_name, class_name_len, 0 ); + int zr = ( NULL != ( class_entry = zend_lookup_class( class_name_str_z TSRMLS_CC ))) ? SUCCESS : FAILURE; + zend_string_release( class_name_str_z ); + CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_BAD_CLASS, class_name ) { + throw ss::SSException(); + } + + // create an instance of the object with its default properties + // we pass NULL for the properties so that the object will be populated by its default properties + zr = object_and_properties_init( &retval_z, class_entry, NULL /*properties*/ ); + CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { + throw ss::SSException(); + } + + // merge in the "properties" (associative array) returned from the fetch doing this vice versa + // since putting properties_ht into object_and_properties_init and merging the default properties + // causes duplicate properties when the visibilities are different and also references the + // default parameters directly in the object, meaning the default property value is changed when + // the object's property is changed. + zend_merge_properties( &retval_z, properties_ht TSRMLS_CC ); + zend_hash_destroy( properties_ht ); + FREE_HASHTABLE( properties_ht ); + + // find and call the object's constructor + + // The header files (zend.h and zend_API.h) declare + // these functions and structures, so by working with those, we were able to + // develop this as a suitable snippet for calling constructors. Some observations: + // params must be an array of zval**, not a zval** to an array as we originally + // thought. Also, a constructor doesn't show up in the function table, but + // is put into the "magic methods" section of the class entry. + // + // The default values of the fci and fcic structures were determined by + // calling zend_fcall_info_init with a test callable. + + // if there is a constructor (e.g., stdClass doesn't have one) + if( class_entry->constructor ) { + + // take the parameters given as our last argument and put them into a sequential array + sqlsrv_malloc_auto_ptr params_m; + zval ctor_retval_z; + ZVAL_UNDEF( &ctor_retval_z ); + int num_params = 0; + + if ( ctor_params_z ) { + HashTable* ctor_params_ht = Z_ARRVAL( *ctor_params_z ); + num_params = zend_hash_num_elements( ctor_params_ht ); + params_m = reinterpret_cast( sqlsrv_malloc( num_params * sizeof( zval ) )); + + int i = 0; + zval* value_z = NULL; + ZEND_HASH_FOREACH_VAL( ctor_params_ht, value_z ) { + zr = ( value_z ) ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { + throw ss::SSException(); + } + ZVAL_COPY_VALUE(¶ms_m[i], value_z); + i++; + } ZEND_HASH_FOREACH_END(); + } //if( !Z_ISUNDEF( ctor_params_z )) + + // call the constructor function itself. + zend_fcall_info fci; + zend_fcall_info_cache fcic; + + memset( &fci, 0, sizeof( fci )); + fci.size = sizeof( fci ); +#if PHP_VERSION_ID < 70100 + fci.function_table = &( class_entry )->function_table; +#endif + ZVAL_UNDEF( &( fci.function_name ) ); + fci.retval = &ctor_retval_z; + fci.param_count = num_params; + fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred. + + fci.object = Z_OBJ_P( &retval_z ); + + memset( &fcic, 0, sizeof( fcic )); + fcic.initialized = 1; + fcic.function_handler = class_entry->constructor; + fcic.calling_scope = class_entry; + + fcic.object = Z_OBJ_P( &retval_z ); + + zr = zend_call_function( &fci, &fcic TSRMLS_CC ); + CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { + throw ss::SSException(); + } + + } //if( class_entry->constructor ) + RETURN_ZVAL( &retval_z, 1, 1 ); + } + + catch( core::CoreException& ) { + + if( properties_ht != NULL ) { + + zend_hash_destroy( properties_ht ); + FREE_HASHTABLE( properties_ht ); + } + else if ( Z_TYPE( retval_z ) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( retval_z )); + FREE_HASHTABLE( Z_ARRVAL( retval_z )); + } + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_fetch_object: Unknown exception caught." ); + } +} + + +// sqlsrv_has_rows( resource $stmt ) +// +// Parameters +// $stmt: The statement on which the targeted result set is active. +// +// Return Value +// Returns whether or not there are rows waiting to be processed. There are two scenarios +// for using a function like this: +// 1) To know if there are any actual rows, not just a result set (empty or not). Use sqlsrv_has_rows to determine this. +// The guarantee is that if sqlsrv_has_rows returns true immediately after a query, that sqlsrv_fetch_* will return at least +// one row of data. +// 2) To know if there is any sort of result set, empty or not, that has to be bypassed to get to something else, such as +// output parameters being returned. Use sqlsrv_num_fields > 0 to check if there is any result set that must be bypassed +// until sqlsrv_fetch returns NULL. +// The last caveat is that this function can still return FALSE if there is an error, which is fine since an error +// most likely means that there is no result data anyways. +// If this functions returs true one time, then it will return true even after the result set is exhausted +// (sqlsrv_fetch returns null) + +PHP_FUNCTION( sqlsrv_has_rows ) +{ + LOG_FUNCTION( "sqlsrv_has_rows" ); + ss_sqlsrv_stmt* stmt = NULL; + + try { + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw ss::SSException(); + } + + if( !stmt->has_rows && !stmt->fetch_called ) { + + determine_stmt_has_rows( stmt TSRMLS_CC ); + } + + if( stmt->has_rows ) { + + RETURN_TRUE; + } + } + catch( core::CoreException& ) { + } + catch( ... ) { + + DIE( "sqlsrv_has_rows: Unknown exception caught." ); + } + + RETURN_FALSE; +} + + +// sqlsrv_send_stream_data( resource $stmt ) +// +// Sends data from parameter streams to the server. Up to eight kilobytes (8K) +// of data is sent with each call to sqlsrv_send_stream_data. +// By default, all stream data is sent to the server when a query is +// executed. If this default behavior is not changed, you do not have to use +// sqlsrv_send_stream_data to send stream data to the server. For information +// about changing the default behavior, see the Parameters section of +// sqlsrv_query or sqlsrv_prepare. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. +// +// Return Value +// true if there is more data to be sent. null, if all the data has been sent, +// and false if an error occurred + +PHP_FUNCTION( sqlsrv_send_stream_data ) +{ + sqlsrv_stmt* stmt = NULL; + + LOG_FUNCTION( "sqlsrv_send_stream_data" ); + + // get the statement resource that we've bound streams to + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // if everything was sent at execute time, just return that there is nothing more to send. + if( stmt->send_streams_at_exec ) { + RETURN_NULL(); + } + + // send the next packet + bool more = core_sqlsrv_send_stream_packet( stmt TSRMLS_CC ); + + // if more to send, return true + if( more ) { + RETURN_TRUE; + } + // otherwise we're done, so return null + else { + RETURN_NULL(); + } + } + catch( core::CoreException& ) { + + // return false if an error occurred + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_send_stream_data: Unknown exception caught." ); + } +} + + +// sqlsrv_get_field( resource $stmt, int $fieldIndex [, int $getAsType] ) +// +// Retrieves data from the specified field of the current row. Field data must +// be accessed in order. For example, data from the first field cannot be +// accessed after data from the second field has been accessed. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. +// $fieldIndex: The index of the field to be retrieved. Indexes begin at zero. +// $getAsType [OPTIONAL]: A SQLSRV constant (SQLSRV_PHPTYPE) that determines +// the PHP data type for the returned data. For information about supported data +// types, see SQLSRV Constants (Microsoft Drivers for PHP for SQL Server). If no return +// type is specified, a default PHP type will be returned. For information about +// default PHP types, see Default PHP Data Types. For information about +// specifying PHP data types, see How to: Specify PHP Data Types. +// +// Return Value +// The field data. You can specify the PHP data type of the returned data by +// using the $getAsType parameter. If no return data type is specified, the +// default PHP data type will be returned. For information about default PHP +// types, see Default PHP Data Types. For information about specifying PHP data +// types, see How to: Specify PHP Data Types. + +PHP_FUNCTION( sqlsrv_get_field ) +{ + LOG_FUNCTION( "sqlsrv_get_field" ); + + ss_sqlsrv_stmt* stmt = NULL; + sqlsrv_phptype sqlsrv_php_type; + sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID; + void* field_value = NULL; + zend_long field_index = -1; + SQLLEN field_len = -1; + zval retval_z; + ZVAL_UNDEF(&retval_z); + + // get the statement, the field index and the optional type + PROCESS_PARAMS( stmt, "rl|l", _FN_, 2, &field_index, &sqlsrv_php_type ); + + try { + + // validate that the field index is within range + int num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + + if( field_index < 0 || field_index >= num_cols ) { + THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + core_sqlsrv_get_field( stmt, static_cast( field_index ), sqlsrv_php_type, false, field_value, &field_len, false/*cache_field*/, + &sqlsrv_php_type_out TSRMLS_CC ); + convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z ); + sqlsrv_free( field_value ); + RETURN_ZVAL( &retval_z, 1, 1 ); + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_get_field: Unknown exception caught." ); + } +} + + +// ** type functions. ** +// When specifying PHP and SQL Server types that take parameters, such as VARCHAR(2000), we use functions +// to match that notation and return a specially encoded integer that tells us what type and size/precision +// are. For PHP types specifically we munge the type and encoding into the integer. +// As is easily seen, since they are so similar, we delegate the actual encoding to helper methods defined +// below. + +// takes an encoding of the stream +PHP_FUNCTION( SQLSRV_PHPTYPE_STREAM ) +{ + type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STREAM ); +} + +// takes an encoding of the string +PHP_FUNCTION( SQLSRV_PHPTYPE_STRING ) +{ + type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STRING ); +} + +// takes the size of the binary field +PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_BINARY ); +} + +// takes the size of the char field +PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_CHAR ); +} + +// takes the precision and scale of the decimal field +PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL) +{ + type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_DECIMAL ); +} + +// takes the size of the nchar field +PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WCHAR ); +} + +// takes the precision and scale of the numeric field +PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC) +{ + type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_NUMERIC ); +} + +// takes the size (in characters, not bytes) of the nvarchar field +PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WVARCHAR ); +} + +// takes the size of the varbinary field +PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARBINARY ); +} + +// takes the size of the varchar field +PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARCHAR ); +} + +// mark parameters passed into sqlsrv_prepare as reference parameters so that they may be updated later in the +// script and subsequent sqlsrv_execute calls will use the new values. Marking them as references "pins" them +// to their memory location so that the buffer we give to ODBC can be relied on to be there. + +void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC ) +{ + SQLSRV_ASSERT( stmt->params_z == NULL, "mark_params_by_reference: parameters list shouldn't be present" ); + + if( params_z == NULL ) { + return; + } + + HashTable* params_ht = Z_ARRVAL_P( params_z ); + + zend_ulong index; + zend_string* key = NULL; + zval* value_z = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, value_z ) { + + // make sure it's an integer index + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { + throw ss::SSException(); + } + + // This code turns parameters into references. Since the function declaration cannot + // pass array elements as references (without requiring & in front of each variable), + // we have to set the reference in each of the zvals ourselves. In the event of a + // parameter array (or sub array if you will) being passed in, we set the zval of the + // parameter array's first element. + + // if it's a sole variable + if ( Z_TYPE_P( value_z ) != IS_ARRAY ) { + ZVAL_MAKE_REF( value_z ); + } + else { + zval* var = NULL; + int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( value_z ), 0 ))) ? SUCCESS : FAILURE; + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { + throw ss::SSException(); + } + ZVAL_MAKE_REF( var ); + } + } ZEND_HASH_FOREACH_END(); + + // save our parameters for later. + Z_TRY_ADDREF_P( params_z ); + stmt->params_z = params_z; +} + +void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC ) +{ + // if there's nothing to do, just return + if( stmt->params_z == NULL ) { + return; + } + + try { + + stmt->free_param_data( TSRMLS_C ); + + stmt->executed = false; + + zval* params_z = stmt->params_z; + + HashTable* params_ht = Z_ARRVAL_P( params_z ); + + zend_ulong index = -1; + zend_string *key = NULL; + zval* param_z = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) { + zval* value_z = NULL; + SQLSMALLINT direction = SQL_PARAM_INPUT; + SQLSRV_ENCODING encoding = stmt->encoding(); + if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) { + encoding = stmt->conn->encoding(); + } + SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE; + SQLULEN column_size = SQLSRV_UNKNOWN_SIZE; + SQLSMALLINT decimal_digits = 0; + SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; + + // make sure it's an integer index + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { + throw ss::SSException(); + } + + // if it's a parameter array + if( Z_TYPE_P( param_z ) == IS_ARRAY ) { + + zval* var = NULL; + int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( param_z ), 0 ))) ? SUCCESS : FAILURE; + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { + throw ss::SSException(); + } + + // parse the parameter array that the user gave + parse_param_array( stmt, param_z, index, direction, php_out_type, encoding, sql_type, column_size, + decimal_digits TSRMLS_CC ); + value_z = var; + } + else { + value_z = param_z; + } + // bind the parameter + core_sqlsrv_bind_param( stmt, index, direction, value_z, php_out_type, encoding, sql_type, column_size, + decimal_digits TSRMLS_CC ); + + } ZEND_HASH_FOREACH_END(); + } + catch( core::CoreException& ) { + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); + zval_ptr_dtor( stmt->params_z ); + sqlsrv_free( stmt->params_z ); + stmt->params_z = NULL; + throw; + } +} + +// sqlsrv_cancel( resource $stmt ) +// +// Cancels a statement. This means that any pending results for the statement +// are discarded. After this function is called, the statement can be +// re-executed if it was prepared with sqlsrv_prepare. Calling this function is +// not necessary if all the results associated with the statement have been +// consumed. +// +// Parameters +// $stmt: The statement to be canceled. +// +// Return Value +// A Boolean value: true if the operation was successful. Otherwise, false. + +PHP_FUNCTION( sqlsrv_cancel ) +{ + + LOG_FUNCTION( "sqlsrv_cancel" ); + ss_sqlsrv_stmt* stmt = NULL; + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // close the stream to release the resource + close_active_stream( stmt TSRMLS_CC ); + + SQLRETURN r = SQLCancel( stmt->handle() ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw ss::SSException(); + } + + RETURN_TRUE; + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_cancel: Unknown exception caught." ); + } +} + +void __cdecl sqlsrv_stmt_dtor(zend_resource *rsrc TSRMLS_DC) +{ + LOG_FUNCTION( "sqlsrv_stmt_dtor" ); + + // get the structure + ss_sqlsrv_stmt *stmt = static_cast( rsrc->ptr ); + if( stmt->conn ) { + int zr = zend_hash_index_del( static_cast( stmt->conn )->stmts, stmt->conn_index ); + if( zr == FAILURE ) { + LOG( SEV_ERROR, "Failed to remove statement reference from the connection" ); + } + } + + stmt->~ss_sqlsrv_stmt(); + sqlsrv_free( stmt ); + rsrc->ptr = NULL; +} + +// sqlsrv_free_stmt( resource $stmt ) +// +// Frees all resources associated with the specified statement. The statement +// cannot be used again after this function has been called. +// +// Parameters +// $stmt: The statement to be closed. +// +// Return Value +// The Boolean value true unless the function is called with an invalid +// parameter. If the function is called with an invalid parameter, false is +// returned. +// +// Null is a valid parameter for this function. This allows the function to be +// called multiple times in a script. For example, if you free a statement in an +// error condition and free it again at the end of the script, the second call +// to sqlsrv_free_stmt will return true because the first call to +// sqlsrv_free_stmt (in the error condition) sets the statement resource to +// null. + +PHP_FUNCTION( sqlsrv_free_stmt ) +{ + + LOG_FUNCTION( "sqlsrv_free_stmt" ); + + zval* stmt_r = NULL; + ss_sqlsrv_stmt* stmt = NULL; + sqlsrv_context_auto_ptr error_ctx; + + reset_errors( TSRMLS_C ); + + try { + + // dummy context to pass to the error handler + error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); + SET_FUNCTION_NAME( *error_ctx ); + + // take only the statement resource + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &stmt_r ) == FAILURE ) { + + // Check if it was a zval + int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &stmt_r ); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + if( Z_TYPE_P( stmt_r ) == IS_NULL ) { + + RETURN_TRUE; + } + else { + + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + } + + // verify the resource so we know we're deleting a statement + stmt = static_cast(zend_fetch_resource_ex(stmt_r TSRMLS_CC, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor)); + + // if sqlsrv_free_stmt was called on an already closed statment then we just return success. + // zend_list_close sets the type of the closed statment to -1. + if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) { + RETURN_TRUE; + } + + if( stmt == NULL ) { + + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + // delete the resource from Zend's master list, which will trigger the statement's destructor + if( zend_list_close( Z_RES_P(stmt_r) ) == FAILURE ) { + LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P( stmt_r )->handle); + } + + ZVAL_NULL( stmt_r ); + + RETURN_TRUE; + + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + + catch( ... ) { + + DIE( "sqlsrv_free_stmt: Unknown exception caught." ); + } +} + +void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_STRING ), stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) { + throw ss::SSException(); + } + + const char* scroll_type = Z_STRVAL_P( value_z ); + unsigned long cursor_type = -1; + + // find which cursor type they would like and set the ODBC statement attribute as such + if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) { + + cursor_type = SQL_CURSOR_STATIC; + } + + else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_DYNAMIC )) { + + cursor_type = SQL_CURSOR_DYNAMIC; + } + + else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_KEYSET )) { + + cursor_type = SQL_CURSOR_KEYSET_DRIVEN; + } + + else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_FORWARD )) { + + cursor_type = SQL_CURSOR_FORWARD_ONLY; + } + + else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_BUFFERED )) { + + cursor_type = SQLSRV_CURSOR_BUFFERED; + } + + else { + + THROW_SS_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); + } + + core_sqlsrv_set_scrollable( stmt, cursor_type TSRMLS_CC ); + +} + +namespace { + +void convert_to_zval(sqlsrv_stmt* stmt, SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len, zval& out_zval) +{ + if ( in_val == NULL ) { + ZVAL_NULL( &out_zval); + return; + } + + switch (sqlsrv_php_type) { + + case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_FLOAT: + { + if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) { + ZVAL_LONG( &out_zval, *(static_cast( in_val ))); + } + else { + ZVAL_DOUBLE( &out_zval, *(static_cast( in_val ))); + } + break; + } + + case SQLSRV_PHPTYPE_STRING: + { + ZVAL_STRINGL( &out_zval, static_cast( in_val ), field_len); + break; + } + + case SQLSRV_PHPTYPE_STREAM: + { + out_zval = *( static_cast( in_val )); + stmt->active_stream = out_zval; + //addref here because deleting out_zval later will decrement the refcount + Z_TRY_ADDREF( out_zval ); + break; + } + case SQLSRV_PHPTYPE_DATETIME: + { + out_zval = *( static_cast( in_val )); + break; + } + + case SQLSRV_PHPTYPE_NULL: + ZVAL_NULL(&out_zval); + break; + + default: + DIE("Unknown php type"); + break; + } + return; +} + + +// put in the column size and scale/decimal digits of the sql server type +// these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx +// for SQL_VARBINARY, SQL_VARCHAR, and SQL_WLONGVARCHAR types, see https://msdn.microsoft.com/en-CA/library/ms187993.aspx +bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, _Out_ SQLULEN* column_size, + _Out_ SQLSMALLINT* decimal_digits ) +{ + *decimal_digits = 0; + + switch( sqlsrv_type.typeinfo.type ) { + case SQL_BIGINT: + *column_size = 19; + break; + case SQL_BIT: + *column_size = 1; + break; + case SQL_INTEGER: + *column_size = 10; + break; + case SQL_SMALLINT: + *column_size = 5; + break; + case SQL_TINYINT: + *column_size = 3; + break; + case SQL_GUID: + *column_size = 36; + break; + case SQL_FLOAT: + *column_size = 53; + break; + case SQL_REAL: + *column_size = 24; + break; + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + *column_size = INT_MAX; + break; + case SQL_WLONGVARCHAR: + *column_size = INT_MAX >> 1; + break; + case SQL_SS_XML: + *column_size = SQL_SS_LENGTH_UNLIMITED; + break; + case SQL_BINARY: + case SQL_CHAR: + case SQL_VARBINARY: + case SQL_VARCHAR: + *column_size = sqlsrv_type.typeinfo.size; + if( *column_size == SQLSRV_SIZE_MAX_TYPE ) { + *column_size = SQL_SS_LENGTH_UNLIMITED; + } + else if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) { + *column_size = SQLSRV_INVALID_SIZE; + return false; + } + break; + case SQL_WCHAR: + case SQL_WVARCHAR: + *column_size = sqlsrv_type.typeinfo.size; + if( *column_size == SQLSRV_SIZE_MAX_TYPE ) { + *column_size = SQL_SS_LENGTH_UNLIMITED; + break; + } + if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) { + *column_size = SQLSRV_INVALID_SIZE; + return false; + } + break; + case SQL_DECIMAL: + case SQL_NUMERIC: + *column_size = sqlsrv_type.typeinfo.size; + *decimal_digits = sqlsrv_type.typeinfo.scale; + // if there was something wrong with the values given on type_and_precision_calc, these are set to invalid precision + if( *column_size == SQLSRV_INVALID_PRECISION || *decimal_digits == SQLSRV_INVALID_PRECISION ) { + *column_size = SQLSRV_INVALID_SIZE; + return false; + } + break; + // this can represent one of three data types: smalldatetime, datetime, and datetime2 + // we present the largest for the version and let SQL Server downsize it + case SQL_TYPE_TIMESTAMP: + *column_size = sqlsrv_type.typeinfo.size; + *decimal_digits = sqlsrv_type.typeinfo.scale; + break; + case SQL_SS_TIMESTAMPOFFSET: + *column_size = 34; + *decimal_digits = 7; + break; + case SQL_TYPE_DATE: + *column_size = 10; + *decimal_digits = 0; + break; + case SQL_SS_TIME2: + *column_size = 16; + *decimal_digits = 7; + break; + default: + // an invalid sql type should have already been dealt with, so we assert here. + DIE( "Trying to determine column size for an invalid type. Type should have already been verified." ); + return false; + } + + return true; +} + + +// given a SQL Server type, return a sqlsrv php type +sqlsrv_phptype determine_sqlsrv_php_type( ss_sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ) +{ + sqlsrv_phptype sqlsrv_phptype; + sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + + switch( sql_type ) { + case SQL_BIGINT: + case SQL_CHAR: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + case SQL_WCHAR: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + break; + case SQL_VARCHAR: + case SQL_WVARCHAR: + if( prefer_string || size != SQL_SS_LENGTH_UNLIMITED ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + } + break; + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + break; + case SQL_BINARY: + case SQL_LONGVARBINARY: + case SQL_VARBINARY: + case SQL_SS_UDT: + if( prefer_string ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + } + break; + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + if( prefer_string ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + } + break; + case SQL_FLOAT: + case SQL_REAL: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + break; + case SQL_TYPE_DATE: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_TIME2: + case SQL_TYPE_TIMESTAMP: + { + ss_sqlsrv_conn* c = static_cast( stmt->conn ); + if( c->date_as_string ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; + } + break; + } + default: + sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID; + break; + } + + // if an encoding hasn't been set for the statement, then use the connection's encoding + if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + sqlsrv_phptype.typeinfo.encoding = stmt->conn->encoding(); + } + + return sqlsrv_phptype; +} + + +// determine if a query returned any rows of data. It does this by actually fetching the first row +// (though not retrieving the data) and setting the has_rows flag in the stmt the fetch was successful. +// The return value simply states whether or not if an error occurred during the determination. +// (All errors are posted here before returning.) + +void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC ) +{ + SQLRETURN r = SQL_SUCCESS; + + if( stmt->fetch_called ) { + + return; + } + + // default condition + stmt->has_rows = false; + + // if there are no columns then there are no rows + if( core::SQLNumResultCols( stmt TSRMLS_CC ) == 0 ) { + + return; + } + + // if the statement is scrollable, our work is easier though less performant. We simply + // fetch the first row, and then roll the cursor back to be prior to the first row + if( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { + + r = stmt->current_results->fetch( SQL_FETCH_FIRST, 0 TSRMLS_CC ); + if( SQL_SUCCEEDED( r )) { + + stmt->has_rows = true; + CHECK_SQL_WARNING( r, stmt ); + // restore the cursor to its original position. + r = stmt->current_results->fetch( SQL_FETCH_ABSOLUTE, 0 TSRMLS_CC ); + SQLSRV_ASSERT(( r == SQL_NO_DATA ), "core_sqlsrv_has_rows: Should have scrolled the cursor to the beginning " + "of the result set." ); + } + } + else { + + // otherwise, we fetch the first row, but record that we did. sqlsrv_fetch checks this + // flag and simply skips the first fetch, knowing it was already done. It records its own + // flags to know if it should fetch on subsequent calls. + + r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); + if( SQL_SUCCEEDED( r )) { + + stmt->has_rows = true; + CHECK_SQL_WARNING( r, stmt ); + return; + } + } +} + +void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, zend_long fetch_type, _Out_ zval& fields, bool allow_empty_field_names + TSRMLS_DC ) +{ + void* field_value = NULL; + sqlsrv_phptype sqlsrv_php_type; + sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID; + + // make sure that the fetch type is legal + CHECK_CUSTOM_ERROR((fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, stmt->func()) { + throw ss::SSException(); + } + + // get the numer of columns in the result set + SQLSMALLINT num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); + + // if this is the first fetch in a new result set, then get the field names and + // store them off for successive fetches. + if(( fetch_type & SQLSRV_FETCH_ASSOC ) && stmt->fetch_field_names == NULL ) { + + SQLLEN field_name_len = 0; + SQLSMALLINT field_name_len_w = 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 ))); + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); + for( int i = 0; i < num_cols; ++i ) { + + core::SQLColAttributeW ( stmt, i + 1, SQL_DESC_NAME, field_name_w, ( SS_MAXCOLNAMELEN + 1 ) * 2, &field_name_len_w, NULL TSRMLS_CC ); + + //Conversion function expects size in characters + field_name_len_w = field_name_len_w / sizeof ( SQLWCHAR ); + bool converted = convert_string_from_utf16( encoding, field_name_w, + field_name_len_w, ( char** ) &field_name, field_name_len ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { + throw core::CoreException(); + } + + field_names[i].name = static_cast( sqlsrv_malloc( field_name_len, sizeof( char ), 1 )); + memcpy_s(( void* )field_names[i].name, ( field_name_len * sizeof( char )) , ( void* ) field_name, field_name_len ); + field_names[i].name[field_name_len] = '\0'; // null terminate the field name since SQLColAttribute doesn't. + field_names[i].len = field_name_len + 1; + field_name.reset(); + } + + stmt->fetch_field_names = field_names; + stmt->fetch_fields_count = num_cols; + field_names.transferred(); + } + + int zr = array_init( &fields ); + CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { + throw ss::SSException(); + } + + for( int i = 0; i < num_cols; ++i ) { + SQLLEN field_len = -1; + + core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/, + field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out TSRMLS_CC ); + + zval field; + ZVAL_UNDEF( &field ); + convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, field ); + sqlsrv_free( field_value ); + if( fetch_type & SQLSRV_FETCH_NUMERIC ) { + + zr = add_next_index_zval( &fields, &field ); + CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { + throw ss::SSException(); + } + } + + if( fetch_type & SQLSRV_FETCH_ASSOC ) { + + CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 1 && !allow_empty_field_names ), stmt, + SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) { + throw ss::SSException(); + } + + 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 ) { + throw ss::SSException(); + } + } + } + //only addref when the fetch_type is BOTH because this is the only case when fields(hashtable) + //has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because + //fields now only has 1 element pointing to field and we want the ref count to be only 1 + if (fetch_type == SQLSRV_FETCH_BOTH) { + Z_TRY_ADDREF(field); + } + } //for loop + +} + +void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, + _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) + +{ + zval* var_or_val = NULL; + zval* temp = NULL; + HashTable* param_ht = Z_ARRVAL_P( param_array ); + sqlsrv_sqltype sqlsrv_sql_type; + + try { + + bool php_type_param_was_null = true; + bool sql_type_param_was_null = true; + + php_out_type = SQLSRV_PHPTYPE_INVALID; + encoding = SQLSRV_ENCODING_INVALID; + + // handle the array parameters that contain the value/var, direction, php_type, sql_type + zend_hash_internal_pointer_reset( param_ht ); + if( zend_hash_has_more_elements( param_ht ) == FAILURE || + (var_or_val = zend_hash_get_current_data(param_ht)) == NULL) { + + THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ); + } + + // if the direction is included, then use what they gave, otherwise INPUT is assumed + if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && + Z_TYPE_P( temp ) != IS_NULL ) { + + CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { + + throw ss::SSException(); + } + direction = static_cast(Z_LVAL_P( temp )); + CHECK_CUSTOM_ERROR( direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT, + stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { + throw ss::SSException(); + } + + CHECK_CUSTOM_ERROR(!Z_ISREF_P(var_or_val) && (direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1) { + throw ss::SSException(); + } + + } + else { + direction = SQL_PARAM_INPUT; + } + + // extract the php type and encoding from the 3rd parameter + if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && + Z_TYPE_P( temp ) != IS_NULL ) { + + php_type_param_was_null = false; + sqlsrv_phptype sqlsrv_phptype; + + CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1 ) { + + throw ss::SSException(); + } + + sqlsrv_phptype.value = Z_LVAL_P( temp ); + + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_phptype ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + index + 1 ) { + + throw ss::SSException(); + } + + php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); + encoding = (SQLSRV_ENCODING) sqlsrv_phptype.typeinfo.encoding; + // if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established + // by the connection + if( encoding == SQLSRV_ENCODING_DEFAULT ) { + encoding = stmt->conn->encoding(); + } + } + // set default for php type and encoding if not supplied + else { + + php_type_param_was_null = true; + + if (Z_ISREF_P(var_or_val)){ + php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(Z_REFVAL_P(var_or_val))]; + } + else{ + php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(var_or_val)]; + } + encoding = stmt->encoding(); + if( encoding == SQLSRV_ENCODING_DEFAULT ) { + encoding = stmt->conn->encoding(); + } + } + + // get the server type, column size/precision and the decimal digits if provided + if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && + Z_TYPE_P( temp ) != IS_NULL ) { + + sql_type_param_was_null = false; + + CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1 ) { + + throw ss::SSException(); + } + + sqlsrv_sql_type.value = Z_LVAL_P( temp ); + + // since the user supplied this type, make sure it's valid + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_sqltype( sqlsrv_sql_type ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + index + 1 ) { + + throw ss::SSException(); + } + + bool size_okay = determine_column_size_or_precision(stmt, sqlsrv_sql_type, &column_size, &decimal_digits); + + CHECK_CUSTOM_ERROR( !size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1 ) { + + throw ss::SSException(); + } + + sql_type = sqlsrv_sql_type.typeinfo.type; + } + // else the sql type and size are uknown, so tell the core layer to use its defaults + else { + + sql_type_param_was_null = true; + + sql_type = SQL_UNKNOWN_TYPE; + column_size = SQLSRV_UNKNOWN_SIZE; + decimal_digits = 0; + } + + // if the user for some reason provides an output parameter with a null phptype and a specified + // sql server type, infer the php type from the sql server type. + if( direction == SQL_PARAM_OUTPUT && php_type_param_was_null && !sql_type_param_was_null ) { + + int encoding; + sqlsrv_phptype sqlsrv_phptype; + + sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, (SQLUINTEGER)column_size, true ); + + // we DIE here since everything should have been validated already and to return the user an error + // for our own logic error would be confusing/misleading. + SQLSRV_ASSERT( sqlsrv_phptype.typeinfo.type != PHPTYPE_INVALID, "An invalid php type was returned with (supposed) " + "validated sql type and column_size" ); + + php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); + encoding = sqlsrv_phptype.typeinfo.encoding; + } + + // verify that the parameter is a valid output param type + if( direction == SQL_PARAM_OUTPUT ) { + + switch( php_out_type ) { + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_DATETIME: + case SQLSRV_PHPTYPE_STREAM: + THROW_CORE_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE ); + break; + default: + break; + } + + } + + } + catch( core::CoreException& ) { + + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); + throw; + } +} + +bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) +{ + switch( type.typeinfo.type ) { + + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_FLOAT: + case SQLSRV_PHPTYPE_DATETIME: + return true; + case SQLSRV_PHPTYPE_STRING: + case SQLSRV_PHPTYPE_STREAM: + { + if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR + || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + return true; + } + break; + } + } + + return false; +} + +// return if the type is a valid sql server type not including +// size, precision or scale. Use determine_precision_and_scale for that. +bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype sql_type ) +{ + switch( sql_type.typeinfo.type ) { + case SQL_BIGINT: + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + case SQL_GUID: + case SQL_FLOAT: + case SQL_REAL: + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + case SQL_BINARY: + case SQL_CHAR: + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TYPE_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + break; + default: + return false; + } + + return true; +} + +// verify an encoding given to type_and_encoding by looking through the list +// of standard encodings created at module initialization time +bool verify_and_set_encoding( const char* encoding_string, _Out_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ) +{ + void* encoding_temp = NULL; + zend_ulong index = -1; + zend_string* key = NULL; + ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, encoding_temp ) { + if ( !encoding_temp ) { + DIE( "Fatal: Error retrieving encoding from encoding hash table." ); + } + sqlsrv_encoding* encoding = reinterpret_cast( encoding_temp ); + encoding_temp = NULL; + if( !stricmp( encoding_string, encoding->iana )) { + phptype_encoding.typeinfo.encoding = encoding->code_page; + return true; + } + } ZEND_HASH_FOREACH_END(); + + return false; +} + +// called when one of the SQLSRV_SQLTYPE type functions is called. Encodes the type and size +// into a sqlsrv_sqltype bit fields (see php_sqlsrv.h). +void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) +{ + char* size_p = NULL; + size_t size_len = 0; + int size = 0; + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &size_p, &size_len ) == FAILURE ) { + + return; + } + + if( !strnicmp( "max", size_p, sizeof( "max" ) / sizeof(char)) ) { + size = SQLSRV_SIZE_MAX_TYPE; + } + else { +#ifndef _WIN32 + errno = 0; +#else + _set_errno(0); // reset errno for atol +#endif // !_WIN32 + size = atol( size_p ); + if( errno != 0 ) { + size = SQLSRV_INVALID_SIZE; + } + } + + int max_size = SQL_SERVER_MAX_FIELD_SIZE; + // size is actually the number of characters, not the number of bytes, so if they ask for a + // 2 byte per character type, then we half the maximum size allowed. + if( type == SQL_WVARCHAR || type == SQL_WCHAR ) { + max_size >>= 1; + } + + if( size > max_size || size < SQLSRV_SIZE_MAX_TYPE || size == 0 ) { + LOG( SEV_ERROR, "invalid size. size must be > 0 and <= %1!d! characters or 'max'", max_size ); + size = SQLSRV_INVALID_SIZE; + } + + sqlsrv_sqltype sql_type; + sql_type.typeinfo.type = type; + sql_type.typeinfo.size = size; + sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE; + + ZVAL_LONG( return_value, sql_type.value ); +} + +// called when the user gives SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC sql types as the type of the +// field. encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h) +void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) +{ + zend_long prec = SQLSRV_INVALID_PRECISION; + zend_long scale = SQLSRV_INVALID_SCALE; + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &prec, &scale ) == FAILURE ) { + + return; + } + + if( prec > SQL_SERVER_MAX_PRECISION ) { + LOG( SEV_ERROR, "Invalid precision. Precision can't be > 38" ); + prec = SQLSRV_INVALID_PRECISION; + } + + if( prec < 0 ) { + LOG( SEV_ERROR, "Invalid precision. Precision can't be negative" ); + prec = SQLSRV_INVALID_PRECISION; + } + + if( scale > prec ) { + LOG( SEV_ERROR, "Invalid scale. Scale can't be > precision" ); + scale = SQLSRV_INVALID_SCALE; + } + + sqlsrv_sqltype sql_type; + sql_type.typeinfo.type = type; + sql_type.typeinfo.size = prec; + sql_type.typeinfo.scale = scale; + + ZVAL_LONG( return_value, sql_type.value ); +} + +// common code for SQLSRV_PHPTYPE_STREAM and SQLSRV_PHPTYPE_STRING php types given as parameters. +// encodes the type and encoding into a sqlsrv_phptype structure (see php_sqlsrv.h) +void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ) +{ + + SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." ); + + char* encoding_param; + size_t encoding_param_len = 0; + + // set the default encoding values to invalid so that + // if the encoding isn't validated, it will return the invalid setting. + sqlsrv_phptype sqlsrv_php_type; + sqlsrv_php_type.typeinfo.type = type; + sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &encoding_param, &encoding_param_len ) == FAILURE ) { + + ZVAL_LONG( return_value, sqlsrv_php_type.value ); + } + + if( !verify_and_set_encoding( encoding_param, sqlsrv_php_type TSRMLS_CC )) { + LOG( SEV_ERROR, "Invalid encoding for php type." ); + } + + ZVAL_LONG( return_value, sqlsrv_php_type.value ); +} + +} diff --git a/sqlsrv/template.rc b/source/sqlsrv/template.rc similarity index 86% rename from sqlsrv/template.rc rename to source/sqlsrv/template.rc index c6a4f82a..5d148c59 100644 --- a/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -1,83 +1,83 @@ -//---------------------------------------------------------------------------------------------------------------------------------- -// File: template.rc -// -// Contents: Version resource -// -// Microsoft Drivers 4.0 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#ifdef APSTUDIO_INVOKED -# error dont edit with MSVC -#endif - -#include "winresrc.h" -#include "main/php_version.h" -#include "version.h" - -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -#ifndef THANKS_GUYS -# define THANKS_GUYS "" -#endif - -#ifdef WANT_LOGO -0 ICON win32\build\php.ico -#endif - -#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build -#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel -#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build) -#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel) - -//Version -VS_VERSION_INFO VERSIONINFO - FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION - PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_MMDD,0 - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_DLL - FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright 2001-2016 The PHP Group. All rights reserved.\0" - VALUE "CompanyName", "Microsoft Corp.\0" - VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0" - VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION) - VALUE "InternalName", FILE_NAME "\0" - VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" - VALUE "OriginalFilename", FILE_NAME "\0" - VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" - VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD) - VALUE "URL", "http://www.microsoft.com\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END - -#ifdef MC_INCLUDE -#include MC_INCLUDE -#endif - +//---------------------------------------------------------------------------------------------------------------------------------- +// File: template.rc +// +// Contents: Version resource +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifdef APSTUDIO_INVOKED +# error dont edit with MSVC +#endif + +#include "winresrc.h" +#include "main/php_version.h" +#include "shared/version.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifndef THANKS_GUYS +# define THANKS_GUYS "" +#endif + +#ifdef WANT_LOGO +0 ICON win32\build\php.ico +#endif + +#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build +#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel +#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build) +#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel) + +//Version +VS_VERSION_INFO VERSIONINFO + FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD + PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_RELEASE,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright © 2001-2016 The PHP Group. All rights reserved.\0" + VALUE "CompanyName", "Microsoft Corp.\0" + VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server\0" + VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD) + VALUE "InternalName", FILE_NAME "\0" + VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" + VALUE "OriginalFilename", FILE_NAME "\0" + VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" + VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE) + VALUE "URL", "http://www.microsoft.com\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#ifdef MC_INCLUDE +#include MC_INCLUDE +#endif + diff --git a/sqlsrv/util.cpp b/source/sqlsrv/util.cpp similarity index 96% rename from sqlsrv/util.cpp rename to source/sqlsrv/util.cpp index 43cc265f..28c06011 100644 --- a/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -1,923 +1,921 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: util.cpp -// -// Contents: Utility functions used by both connection or statement functions -// -// Comments: Mostly error handling and some type handling -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "php_sqlsrv.h" - -#include - -namespace { - -// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros -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 ]; - -// internal error that says that FormatMessage failed -SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; - -// *** internal functions *** -void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reported_chain, zval* ignored_chain, - bool warning TSRMLS_DC ); -bool ignore_warning( char* sql_state, int native_code TSRMLS_DC ); -bool handle_errors_and_warnings( sqlsrv_context& ctx, zval* reported_chain, zval* ignored_chain, logging_severity log_severity, - unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC ); - -int sqlsrv_merge_zend_hash_dtor( zval* dest TSRMLS_DC ); -bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ); - -} - -// List of all error messages -ss_error SS_ERRORS[] = { - - { - SS_SQLSRV_ERROR_INVALID_OPTION, - { IMSSP, (SQLCHAR*)"Invalid option %1!s! was passed to sqlsrv_connect.", -1, true } - }, - - // no equivalent to error 2 in 2.0 - // error 3 is superceded by -16 - - // these two share the same code since they are basically the same error. - { - SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, - { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be" - " escaped with another right brace (}}).", -4, false } - }, - - { - SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, - { IMSSP, (SQLCHAR*)"An unescaped right brace (}) was found in option %1!s!.", -4, true } - }, - - { - SQLSRV_ERROR_NO_DATA, - { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -5, true } - }, - - { - SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, - { IMSSP, (SQLCHAR*)"Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using " - "streams.", -6, false} - }, - - { - SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - { IMSSP, (SQLCHAR*)"An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be " - "specified as output parameters.", -7, false } - }, - - { - SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY, - { IMSSP, (SQLCHAR*)"An invalid connection option key type was received. Option key types must be strings.", -8, false } - }, - - { - SS_SQLSRV_ERROR_VAR_REQUIRED, - { IMSSP, (SQLCHAR*)"Parameter array %1!d! must have at least one value or variable.", -9, true } - }, - - { - SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, - { IMSSP, (SQLCHAR*)"An invalid fetch type was specified. SQLSRV_FETCH_NUMERIC, SQLSRV_FETCH_ARRAY and SQLSRV_FETCH_BOTH are acceptable values.", -10, false } - }, - - { - SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, - { IMSSP, (SQLCHAR*)"The statement must be executed before results can be retrieved.", -11, false } - }, - - { - SS_SQLSRV_ERROR_ALREADY_IN_TXN, - { IMSSP, (SQLCHAR*)"Cannot begin a transaction until the current transaction has been completed by calling either " - "sqlsrv_commit or sqlsrv_rollback.", -12, false } - }, - - { - SS_SQLSRV_ERROR_NOT_IN_TXN, - { IMSSP, (SQLCHAR*)"A transaction must be started by calling sqlsrv_begin_transaction before calling sqlsrv_commit or " - "sqlsrv_rollback.", -13, false } - }, - - { - SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, - { IMSSP, (SQLCHAR*)"An invalid parameter was passed to %1!s!.", -14, true } - }, - - { - SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, - { IMSSP, (SQLCHAR*)"An invalid direction for parameter %1!d! was specified. SQLSRV_PARAM_IN, SQLSRV_PARAM_OUT, and " - "SQLSRV_PARAM_INOUT are valid values.", -15, true } - }, - - { - SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, - { IMSSP, (SQLCHAR*)"An invalid PHP type for parameter %1!d! was specified.", -16, true } - }, - - { - SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, - { IMSSP, (SQLCHAR*)"An invalid SQL Server type for parameter %1!d! was specified.", -17, true } - }, - - { - SQLSRV_ERROR_FETCH_NOT_CALLED, - { IMSSP, (SQLCHAR*)"A row must be retrieved with sqlsrv_fetch before retrieving data with sqlsrv_get_field.", -18, false } - }, - - { - SQLSRV_ERROR_FIELD_INDEX_ERROR, - { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. " - "The sqlsrv_get_field function cannot retrieve field %1!d! because its index is less " - "than the index of a field that has already been retrieved (%2!d!).", -19, true } - }, - - { - SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, - { IMSSP, (SQLCHAR*)"The retrieval of the DateTime object failed.", -20, false } - }, - - // no equivalent to SQLSRV_ERROR_SERVER_INFO in 2.0 so -21 is skipped - - { - SQLSRV_ERROR_FETCH_PAST_END, - { IMSSP, (SQLCHAR*)"There are no more rows in the active result set. Since this result set is not scrollable, no more " - "data may be retrieved.", -22, false } - }, - - { - SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED, - { IMSSP, (SQLCHAR*)"A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute.", -23, false } - }, - - { - SQLSRV_ERROR_ZEND_HASH, - { IMSSP, (SQLCHAR*)"An error occurred while creating or accessing a Zend hash table.", -24, false } - }, - - { - SQLSRV_ERROR_ZEND_STREAM, - { IMSSP, (SQLCHAR*)"An error occurred while reading from a PHP stream.", -25, false } - }, - - { - SQLSRV_ERROR_NEXT_RESULT_PAST_END, - { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -26, false } - }, - - { - SQLSRV_ERROR_STREAM_CREATE, - { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -27, false } - }, - - { - SQLSRV_ERROR_NO_FIELDS, - { IMSSP, (SQLCHAR*)"The active result for the query contains no fields.", -28, false } - }, - - { - SS_SQLSRV_ERROR_ZEND_BAD_CLASS, - { IMSSP, (SQLCHAR*)"Failed to find class %1!s!.", -29, true } - }, - - { - SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, - { IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true } - }, - - { - SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, - { IMSSP, (SQLCHAR*)"An invalid size or precision for parameter %1!d! was specified.", -31, true } - }, - - { - SQLSRV_ERROR_INVALID_OPTION_KEY, - { IMSSP, (SQLCHAR*)"Option %1!s! is invalid.", -32, true } - }, - - // these three errors are returned for invalid options, so they are given the same number for compatibility with 1.1 - { - SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, - { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option SQLSRV_QUERY_TIMEOUT.", -33, true } - }, - - { - SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -33, true } - }, - - { - SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -33, true } - }, - - { - SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, - { IMSSP, (SQLCHAR*)"The type of output parameter %1!d! does not match the type specified by the SQLSRV_PHPTYPE_* constant." - " For output parameters, the type of the variable's current value must match the SQLSRV_PHPTYPE_* constant, or be NULL. " - "If the type is NULL, the PHP type of the output parameter is inferred from the SQLSRV_SQLTYPE_* constant.", -34, true } - }, - - { - SQLSRV_ERROR_INVALID_TYPE, - { IMSSP, (SQLCHAR*)"Invalid type", -35, false } - }, - - // 36-38 have no equivalent 2.0 errors - - { - SS_SQLSRV_ERROR_REGISTER_RESOURCE, - { IMSSP, (SQLCHAR*)"Registering the %1!s! resource failed.", -39, true } - }, - - { - SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*)"An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -40, true } - }, - - { - SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*)"An error occurred translating string for an output param to UTF-8: %1!s!", -41, true } - }, - - { - SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -42, true } - }, - - { - SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*)"An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -43, true } - }, - - { - SQLSRV_ERROR_MARS_OFF, - { IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. " - "To make the connection available for other queries, either fetch all results or cancel or free the statement. " - "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -44, false } - }, - - { - SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, - { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of " - "key/value pairs.", -45, false } - }, - - { - SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -46, true } - }, - - { - SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -47, true } - }, - - { - SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, - { IMSSP, (SQLCHAR*)"The encoding '%1!s!' is not a supported encoding for the CharacterSet connection option.", -48, true } - }, - - { - SQLSRV_ERROR_DRIVER_NOT_INSTALLED, - { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 or 13 for SQL Server. " - "Access the following URL to download the ODBC Driver 11 or 13 for SQL Server for %1!s!: " - "http://go.microsoft.com/fwlink/?LinkId=163712", -49, true } - }, - - { - SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, - { IMSSP, (SQLCHAR*)"This function only works with statements that have static or keyset scrollable cursors.", -50, false } - }, - - { - SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE, - { IMSSP, (SQLCHAR*)"This function only works with statements that are not scrollable.", -51, false } - }, - - // new error for 2.0, used here since 1.1 didn't have a -52 - { - SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, - { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -52, true } - }, - - { - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE, - { IMSSP, (SQLCHAR*)"The scroll type passed to sqlsrv_fetch, sqlsrv_fetch_array, or sqlsrv_fetch_object was not valid. " - "Please use one of the SQLSRV_SCROLL constants.", -53, false } - }, - - { - SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, - { IMSSP, (SQLCHAR*)"The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', " - "'keyset', 'forward', or 'buffered'.", -54, false } - }, - - { - SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, - { IMSSP, (SQLCHAR*)"Failed to retrieve the server version. Unable to continue.", -55, false } - }, - - { - SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, - { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -56, true } - }, - - { - SS_SQLSRV_ERROR_PARAM_INVALID_INDEX, - { IMSSP, (SQLCHAR*)"String keys are not allowed in parameters arrays.", -57, false } - }, - - { - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, - { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -58, true } - }, - { - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, - { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -59, true } - }, - { - SQLSRV_ERROR_INVALID_BUFFER_LIMIT, - { IMSSP, (SQLCHAR*) "Setting for " INI_BUFFERED_QUERY_LIMIT " was non-int or non-positive.", -60, false } - }, - { - SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, - { IMSSP, (SQLCHAR*)"Variable parameter %1!d! not passed by reference (prefaced with an &). " - "Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value." - , -61, true } - }, - - // internal warning definitions - { - SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, - { SSPWARN, (SQLCHAR*)"An empty field name was skipped by sqlsrv_fetch_object.", -100, false } - }, - - // terminate the list of errors/warnings - { UINT_MAX, {} } -}; - -sqlsrv_error_const* get_error_message( unsigned int sqlsrv_error_code ) { - - sqlsrv_error_const *error_message = NULL; - - int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr( g_ss_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; - if( zr == FAILURE ) { - DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); - } - - SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null"); - - return error_message; -} - -// Formats an error message and finally writes it to the php log. -void ss_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ) -{ - if(( severity & SQLSRV_G( log_severity )) && ( SQLSRV_G( current_subsystem ) & SQLSRV_G( log_subsystems ))) { - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); - - // if an error occurs for FormatMessage, we just output an internal error occurred. - if( rc == 0 ) { - SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); - std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); - } - - php_log_err( log_msg TSRMLS_CC ); - } -} - -bool ss_error_handler(sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args ) -{ - logging_severity severity = SEV_ERROR; - if( warning && !SQLSRV_G( warnings_return_as_errors )) { - severity = SEV_WARNING; - } - - return handle_errors_and_warnings( ctx, &SQLSRV_G( errors ), &SQLSRV_G( warnings ), severity, sqlsrv_error_code, warning, - print_args TSRMLS_CC ); -} - -// sqlsrv_errors( [int $errorsAndOrWarnings] ) -// -// Returns extended error and/or warning information about the last sqlsrv -// operation performed. -// -// The sqlsrv_errors function can return error and/or warning information by -// calling it with one of the following parameter values below. -// -// Parameters -// -// $errorsAndOrWarnings[OPTIONAL]: A predefined constant. This parameter can -// take one of the values listed: -// -// SQLSRV_ERR_ALL -// Errors and warnings generated on the last sqlsrv function call are returned. -// SQLSRV_ERR_ERRORS -// Errors generated on the last sqlsrv function call are returned. -// SQLSRV_ERR_WARNINGS -// Warnings generated on the last sqlsrv function call are returned. -// -// If no parameter value is supplied, SQLSRV_ERR_ALL is the default -// -// Return Value -// An array of arrays, or null. An example of an error returned: -// Array -// ( -// [0] => Array -// ( -// [0] => HYT00 -// [SQLSTATE] => HYT00 -// [1] => 0 -// [code] => 0 -// [2] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired -// [message] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired -// ) -// ) - -PHP_FUNCTION( sqlsrv_errors ) -{ - SQLSRV_UNUSED( execute_data ); - - zend_long flags = SQLSRV_ERR_ALL; - - LOG_FUNCTION( "sqlsrv_errors" ); - - if(( zend_parse_parameters( ZEND_NUM_ARGS() 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( 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); - RETURN_FALSE; - } - } - if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_WARNINGS ) { - if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( warnings ) TSRMLS_CC )) { - zval_ptr_dtor(&err_z); - RETURN_FALSE; - } - } - if( zend_hash_num_elements( Z_ARRVAL_P( &err_z )) == 0 ) { - zval_ptr_dtor(&err_z); - RETURN_NULL(); - } - RETURN_ZVAL( &err_z, 1, 1 ); -} - -// sqlsrv_configure( string $setting, mixed $value ) -// -// Changes the settings for error handling and logging options. -// -// Parameters -// $setting: The name of the setting to be configured. The possible implemented values are -// "WarningsReturnAsErrors", "LogSubsystems", and "LogSeverity". -// -// $value: The value to be applied to the setting specified in the $setting -// parameter. See MSDN or the MINIT function for possible values. -// -// Return Value -// If sqlsrv_configure is called with an unsupported setting or value, the -// function returns false. Otherwise, the function returns true. - -PHP_FUNCTION( sqlsrv_configure ) -{ - SQLSRV_UNUSED( execute_data ); - - LOG_FUNCTION( "sqlsrv_configure" ); - - char* option; - size_t option_len; - zval* value_z; - sqlsrv_context_auto_ptr error_ctx; - - RETVAL_FALSE; - - reset_errors( TSRMLS_C ); - - try { - - // dummy context to pass onto the error handler - error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); - - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "sz", &option, &option_len, &value_z ); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - // WarningsReturnAsErrors - if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { - - SQLSRV_G( warnings_return_as_errors ) = zend_is_true( value_z ) ? true : false; - LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); - RETURN_TRUE; - } - - // LogSeverity - else if( !stricmp( option, INI_LOG_SEVERITY )) { - - CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - zend_long severity_mask = Z_LVAL_P( value_z ); - // make sure they can't use 0 to shut off the masking in the severity - if( severity_mask < SEV_ALL || severity_mask == 0 || severity_mask > (SEV_NOTICE + SEV_ERROR + SEV_WARNING) ) { - RETURN_FALSE; - } - - SQLSRV_G( log_severity ) = static_cast( severity_mask ); - LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); - RETURN_TRUE; - } - - // LogSubsystems - else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { - - CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - zend_long subsystem_mask = Z_LVAL_P( value_z ); - - if( subsystem_mask < LOG_ALL || subsystem_mask > (LOG_INIT + LOG_CONN + LOG_STMT + LOG_UTIL) ) { - RETURN_FALSE; - } - - SQLSRV_G( log_subsystems ) = static_cast( subsystem_mask ); - LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); - RETURN_TRUE; - } - - else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) { - - CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) { - - throw ss::SSException(); - } - - zend_long buffered_query_limit = Z_LVAL_P( value_z ); - - CHECK_CUSTOM_ERROR( buffered_query_limit < 0, error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) { - - throw ss::SSException(); - } - - SQLSRV_G( buffered_query_limit ) = buffered_query_limit; - LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit )); - RETURN_TRUE; - } - - else { - - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_configure: Unknown exception caught." ); - } -} - - -// sqlsrv_get_config( string $setting ) -// -// Returns the current value of the specified configuration setting. -// -// Parameters -// $setting: The configuration setting for which the value is returned. For a -// list of configurable settings, see sqlsrv_configure. -// -// Return Value -// The value of the setting specified by the $setting parameter. If an invalid -// setting is specified, false is returned and an error is added to the error -// collection. Because false is a valid value for WarningsReturnAsErrors, to -// really determine if an error occurred, call sqlsrv_errors. - -PHP_FUNCTION( sqlsrv_get_config ) -{ - SQLSRV_UNUSED( execute_data ); - - char* option = NULL; - size_t option_len; - sqlsrv_context_auto_ptr error_ctx; - - LOG_FUNCTION( "sqlsrv_get_config" ); - - reset_errors( TSRMLS_C ); - - try { - - // dummy context to pass onto the error handler - error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); - - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &option, &option_len ); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { - - ZVAL_BOOL( return_value, SQLSRV_G( warnings_return_as_errors )); - return; - } - else if( !stricmp( option, INI_LOG_SEVERITY )) { - - ZVAL_LONG( return_value, SQLSRV_G( log_severity )); - return; - } - else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { - - ZVAL_LONG( return_value, SQLSRV_G( log_subsystems )); - return; - } - else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) { - - ZVAL_LONG( return_value, SQLSRV_G( buffered_query_limit )); - return; - } - else { - - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_get_config: Unknown exception caught." ); - } -} - - -namespace { - -void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reported_chain, zval* ignored_chain, - bool warning TSRMLS_DC ) -{ - - if( array_init( error_z ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - // sqlstate - zval temp; - ZVAL_UNDEF(&temp); - core::sqlsrv_zval_stringl( &temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE ); - //TODO: reference? - Z_TRY_ADDREF_P( &temp ); - if( add_next_index_zval( error_z, &temp ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - if( add_assoc_zval( error_z, "SQLSTATE", &temp ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - // native_code - if( add_next_index_long( error_z, error->native_code ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - if( add_assoc_long( error_z, "code", error->native_code ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - // native_message - ZVAL_UNDEF(&temp); - ZVAL_STRING( &temp, reinterpret_cast( error->native_message ) ); - //TODO: reference? - Z_TRY_ADDREF_P(&temp); - if( add_next_index_zval( error_z, &temp ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - if( add_assoc_zval( error_z, "message", &temp ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - // If it is an error or if warning_return_as_errors is true than - // add the error or warning to the reported_chain. - if( !warning || SQLSRV_G( warnings_return_as_errors ) ) - { - // if the warning is part of the ignored warning list than - // add to the ignored chain if the ignored chain is not null. - if( warning && ignore_warning( reinterpret_cast(error->sqlstate), error->native_code TSRMLS_CC ) && - ignored_chain != NULL ) { - - if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - } - else { - - // It is either an error or a warning which should not be ignored. - if( add_next_index_zval( reported_chain, error_z ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - } - } - else - { - // It is a warning with warning_return_as_errors as false, so simply add it to the ignored_chain list - if( ignored_chain != NULL ) { - - if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - } - } -} - -bool handle_errors_and_warnings( sqlsrv_context& ctx, zval* reported_chain, zval* ignored_chain, logging_severity log_severity, - unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC ) -{ - bool result = true; - bool errors_ignored = false; - size_t prev_reported_cnt = 0; - bool reported_chain_was_null = false; - bool ignored_chain_was_null = false; - int zr = SUCCESS; - zval error_z; - ZVAL_UNDEF(&error_z); - sqlsrv_error_auto_ptr error; - - // array of reported errors - if( Z_TYPE_P( reported_chain ) == IS_NULL ) { - - reported_chain_was_null = true; - zr = array_init( reported_chain ); - if( zr == FAILURE ) { - DIE( "Fatal error in handle_errors_and_warnings" ); - } - } - else { - prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_P( reported_chain )); - } - - // array of ignored errors - if( ignored_chain != NULL ) { - - if( Z_TYPE_P( ignored_chain ) == IS_NULL ) { - - ignored_chain_was_null = true; - zr = array_init( ignored_chain ); - if( zr == FAILURE ) { - DIE( "Fatal error in handle_errors_and_warnings" ); - } - } - } - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, log_severity TSRMLS_CC, print_args ); - copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC ); - } - - SQLSMALLINT record_number = 0; - do { - - result = core_sqlsrv_get_odbc_error( ctx, ++record_number, error, log_severity TSRMLS_CC ); - if( result ) { - copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC ); - } - } while( result ); - - // If it were a warning, we report that warnings where ignored except if warnings_return_as_errors - // was true and we added some warnings to the reported_chain. - if( warning ) { - - errors_ignored = true; - - if( SQLSRV_G( warnings_return_as_errors ) ) { - - if( zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) > prev_reported_cnt ) { - - // We actually added some errors - errors_ignored = false; - } - } - } - - // if the error array came in as NULL and didn't have anything added to it, return it as NULL - if( reported_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) == 0 ) { - zend_hash_destroy( Z_ARRVAL_P( reported_chain )); - FREE_HASHTABLE( Z_ARRVAL_P( reported_chain )); - ZVAL_NULL( reported_chain ); - } - if( ignored_chain != NULL && ignored_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( ignored_chain )) == 0 ) { - zend_hash_destroy( Z_ARRVAL_P( ignored_chain )); - FREE_HASHTABLE( Z_ARRVAL_P( ignored_chain )); - ZVAL_NULL( ignored_chain ); - } - - // If it was an error instead of a warning than we always return errors_ignored = false. - return errors_ignored; -} - -// return whether or not a warning should be ignored or returned as an error if WarningsReturnAsErrors is true -// see RINIT in init.cpp for information about which errors are ignored. -bool ignore_warning( char* sql_state, int native_code TSRMLS_DC ) -{ - zend_ulong index = -1; - zend_string* key = NULL; - void* error_temp = NULL; - - ZEND_HASH_FOREACH_KEY_PTR( g_ss_warnings_to_ignore_ht, index, key, error_temp ) { - sqlsrv_error* error = static_cast( error_temp ); - if (NULL == error) { - return false; - } - - if( !strncmp( reinterpret_cast( error->sqlstate ), sql_state, SQL_SQLSTATE_SIZE ) && - ( error->native_code == native_code || error->native_code == -1 )) { - return true; - } - } ZEND_HASH_FOREACH_END(); - - return false; -} - -int sqlsrv_merge_zend_hash_dtor( zval* dest TSRMLS_DC ) -{ - zval_ptr_dtor( dest ); - return ZEND_HASH_APPLY_REMOVE; -} - -// sqlsrv_merge_zend_hash -// merge a source hash into a dest hash table and return any errors. -bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ) -{ - if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" ); - if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" ); - - if( Z_TYPE_P( src_z ) == IS_NULL ) { - return true; - } - - HashTable* src_ht = Z_ARRVAL_P( src_z ); - zend_ulong index = -1; - zend_string* key = NULL; - zval* value_z = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( src_ht, index, key, value_z ) { - if ( !value_z ) { - zend_hash_apply( Z_ARRVAL_P(dest_z), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); - return false; - } - - int result = add_next_index_zval( dest_z, value_z ); - - if( result == FAILURE ) { - zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); - return false; - } - Z_TRY_ADDREF_P( value_z ); - } ZEND_HASH_FOREACH_END(); - - return true; -} - -} // namespace +//--------------------------------------------------------------------------------------------------------------------------------- +// File: util.cpp +// +// Contents: Utility functions used by both connection or statement functions +// +// Comments: Mostly error handling and some type handling +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_sqlsrv.h" + +namespace { + +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros +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 ]; + +// internal error that says that FormatMessage failed +SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; + +// *** internal functions *** +void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reported_chain, zval* ignored_chain, + bool warning TSRMLS_DC ); +bool ignore_warning( char* sql_state, int native_code TSRMLS_DC ); +bool handle_errors_and_warnings( sqlsrv_context& ctx, zval* reported_chain, zval* ignored_chain, logging_severity log_severity, + unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC ); + +int sqlsrv_merge_zend_hash_dtor( zval* dest TSRMLS_DC ); +bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ); + +} + +// List of all error messages +ss_error SS_ERRORS[] = { + + { + SS_SQLSRV_ERROR_INVALID_OPTION, + { IMSSP, (SQLCHAR*)"Invalid option %1!s! was passed to sqlsrv_connect.", -1, true } + }, + + // no equivalent to error 2 in 2.0 + // error 3 is superceded by -16 + + // these two share the same code since they are basically the same error. + { + SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, + { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be" + " escaped with another right brace (}}).", -4, false } + }, + + { + SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, + { IMSSP, (SQLCHAR*)"An unescaped right brace (}) was found in option %1!s!.", -4, true } + }, + + { + SQLSRV_ERROR_NO_DATA, + { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -5, true } + }, + + { + SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, + { IMSSP, (SQLCHAR*)"Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using " + "streams.", -6, false} + }, + + { + SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + { IMSSP, (SQLCHAR*)"An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be " + "specified as output parameters.", -7, false } + }, + + { + SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY, + { IMSSP, (SQLCHAR*)"An invalid connection option key type was received. Option key types must be strings.", -8, false } + }, + + { + SS_SQLSRV_ERROR_VAR_REQUIRED, + { IMSSP, (SQLCHAR*)"Parameter array %1!d! must have at least one value or variable.", -9, true } + }, + + { + SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, + { IMSSP, (SQLCHAR*)"An invalid fetch type was specified. SQLSRV_FETCH_NUMERIC, SQLSRV_FETCH_ARRAY and SQLSRV_FETCH_BOTH are acceptable values.", -10, false } + }, + + { + SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, + { IMSSP, (SQLCHAR*)"The statement must be executed before results can be retrieved.", -11, false } + }, + + { + SS_SQLSRV_ERROR_ALREADY_IN_TXN, + { IMSSP, (SQLCHAR*)"Cannot begin a transaction until the current transaction has been completed by calling either " + "sqlsrv_commit or sqlsrv_rollback.", -12, false } + }, + + { + SS_SQLSRV_ERROR_NOT_IN_TXN, + { IMSSP, (SQLCHAR*)"A transaction must be started by calling sqlsrv_begin_transaction before calling sqlsrv_commit or " + "sqlsrv_rollback.", -13, false } + }, + + { + SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, + { IMSSP, (SQLCHAR*)"An invalid parameter was passed to %1!s!.", -14, true } + }, + + { + SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, + { IMSSP, (SQLCHAR*)"An invalid direction for parameter %1!d! was specified. SQLSRV_PARAM_IN, SQLSRV_PARAM_OUT, and " + "SQLSRV_PARAM_INOUT are valid values.", -15, true } + }, + + { + SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + { IMSSP, (SQLCHAR*)"An invalid PHP type for parameter %1!d! was specified.", -16, true } + }, + + { + SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + { IMSSP, (SQLCHAR*)"An invalid SQL Server type for parameter %1!d! was specified.", -17, true } + }, + + { + SQLSRV_ERROR_FETCH_NOT_CALLED, + { IMSSP, (SQLCHAR*)"A row must be retrieved with sqlsrv_fetch before retrieving data with sqlsrv_get_field.", -18, false } + }, + + { + SQLSRV_ERROR_FIELD_INDEX_ERROR, + { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. " + "The sqlsrv_get_field function cannot retrieve field %1!d! because its index is less " + "than the index of a field that has already been retrieved (%2!d!).", -19, true } + }, + + { + SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, + { IMSSP, (SQLCHAR*)"The retrieval of the DateTime object failed.", -20, false } + }, + + // no equivalent to SQLSRV_ERROR_SERVER_INFO in 2.0 so -21 is skipped + + { + SQLSRV_ERROR_FETCH_PAST_END, + { IMSSP, (SQLCHAR*)"There are no more rows in the active result set. Since this result set is not scrollable, no more " + "data may be retrieved.", -22, false } + }, + + { + SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED, + { IMSSP, (SQLCHAR*)"A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute.", -23, false } + }, + + { + SQLSRV_ERROR_ZEND_HASH, + { IMSSP, (SQLCHAR*)"An error occurred while creating or accessing a Zend hash table.", -24, false } + }, + + { + SQLSRV_ERROR_ZEND_STREAM, + { IMSSP, (SQLCHAR*)"An error occurred while reading from a PHP stream.", -25, false } + }, + + { + SQLSRV_ERROR_NEXT_RESULT_PAST_END, + { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -26, false } + }, + + { + SQLSRV_ERROR_STREAM_CREATE, + { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -27, false } + }, + + { + SQLSRV_ERROR_NO_FIELDS, + { IMSSP, (SQLCHAR*)"The active result for the query contains no fields.", -28, false } + }, + + { + SS_SQLSRV_ERROR_ZEND_BAD_CLASS, + { IMSSP, (SQLCHAR*)"Failed to find class %1!s!.", -29, true } + }, + + { + SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, + { IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true } + }, + + { + SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, + { IMSSP, (SQLCHAR*)"An invalid size or precision for parameter %1!d! was specified.", -31, true } + }, + + { + SQLSRV_ERROR_INVALID_OPTION_KEY, + { IMSSP, (SQLCHAR*)"Option %1!s! is invalid.", -32, true } + }, + + // these three errors are returned for invalid options, so they are given the same number for compatibility with 1.1 + { + SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, + { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option SQLSRV_QUERY_TIMEOUT.", -33, true } + }, + + { + SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, + { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -33, true } + }, + + { + SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, + { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -33, true } + }, + + { + SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, + { IMSSP, (SQLCHAR*)"The type of output parameter %1!d! does not match the type specified by the SQLSRV_PHPTYPE_* constant." + " For output parameters, the type of the variable's current value must match the SQLSRV_PHPTYPE_* constant, or be NULL. " + "If the type is NULL, the PHP type of the output parameter is inferred from the SQLSRV_SQLTYPE_* constant.", -34, true } + }, + + { + SQLSRV_ERROR_INVALID_TYPE, + { IMSSP, (SQLCHAR*)"Invalid type", -35, false } + }, + + // 36-38 have no equivalent 2.0 errors + + { + SS_SQLSRV_ERROR_REGISTER_RESOURCE, + { IMSSP, (SQLCHAR*)"Registering the %1!s! resource failed.", -39, true } + }, + + { + SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -40, true } + }, + + { + SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating string for an output param to UTF-8: %1!s!", -41, true } + }, + + { + SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -42, true } + }, + + { + SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -43, true } + }, + + { + SQLSRV_ERROR_MARS_OFF, + { IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. " + "To make the connection available for other queries, either fetch all results or cancel or free the statement. " + "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -44, false } + }, + + { + SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, + { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of " + "key/value pairs.", -45, false } + }, + + { + SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -46, true } + }, + + { + SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -47, true } + }, + + { + SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, + { IMSSP, (SQLCHAR*)"The encoding '%1!s!' is not a supported encoding for the CharacterSet connection option.", -48, true } + }, + + { + SQLSRV_ERROR_DRIVER_NOT_INSTALLED, + { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 or 13 for SQL Server. " + "Access the following URL to download the ODBC Driver 11 or 13 for SQL Server for %1!s!: " + "http://go.microsoft.com/fwlink/?LinkId=163712", -49, true } + }, + + { + SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, + { IMSSP, (SQLCHAR*)"This function only works with statements that have static or keyset scrollable cursors.", -50, false } + }, + + { + SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE, + { IMSSP, (SQLCHAR*)"This function only works with statements that are not scrollable.", -51, false } + }, + + // new error for 2.0, used here since 1.1 didn't have a -52 + { + SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, + { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -52, true } + }, + + { + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE, + { IMSSP, (SQLCHAR*)"The scroll type passed to sqlsrv_fetch, sqlsrv_fetch_array, or sqlsrv_fetch_object was not valid. " + "Please use one of the SQLSRV_SCROLL constants.", -53, false } + }, + + { + SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, + { IMSSP, (SQLCHAR*)"The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', " + "'keyset', 'forward', or 'buffered'.", -54, false } + }, + + { + SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, + { IMSSP, (SQLCHAR*)"Failed to retrieve the server version. Unable to continue.", -55, false } + }, + + { + SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, + { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -56, true } + }, + + { + SS_SQLSRV_ERROR_PARAM_INVALID_INDEX, + { IMSSP, (SQLCHAR*)"String keys are not allowed in parameters arrays.", -57, false } + }, + + { + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, + { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -58, true } + }, + { + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, + { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -59, true } + }, + { + SQLSRV_ERROR_INVALID_BUFFER_LIMIT, + { IMSSP, (SQLCHAR*) "Setting for " INI_BUFFERED_QUERY_LIMIT " was non-int or non-positive.", -60, false } + }, + { + SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, + { IMSSP, (SQLCHAR*)"Variable parameter %1!d! not passed by reference (prefaced with an &). " + "Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value." + , -61, true } + }, + + // internal warning definitions + { + SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, + { SSPWARN, (SQLCHAR*)"An empty field name was skipped by sqlsrv_fetch_object.", -100, false } + }, + + // terminate the list of errors/warnings + { UINT_MAX, {} } +}; + +sqlsrv_error_const* get_error_message( unsigned int sqlsrv_error_code ) { + + sqlsrv_error_const *error_message = NULL; + + int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr( g_ss_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; + if( zr == FAILURE ) { + DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); + } + + SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null"); + + return error_message; +} + +// Formats an error message and finally writes it to the php log. +void ss_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ) +{ + if(( severity & SQLSRV_G( log_severity )) && ( SQLSRV_G( current_subsystem ) & SQLSRV_G( log_subsystems ))) { + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); + + // if an error occurs for FormatMessage, we just output an internal error occurred. + if( rc == 0 ) { + SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); + std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); + } + + php_log_err( log_msg TSRMLS_CC ); + } +} + +bool ss_error_handler(sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args ) +{ + logging_severity severity = SEV_ERROR; + if( warning && !SQLSRV_G( warnings_return_as_errors )) { + severity = SEV_WARNING; + } + + return handle_errors_and_warnings( ctx, &SQLSRV_G( errors ), &SQLSRV_G( warnings ), severity, sqlsrv_error_code, warning, + print_args TSRMLS_CC ); +} + +// sqlsrv_errors( [int $errorsAndOrWarnings] ) +// +// Returns extended error and/or warning information about the last sqlsrv +// operation performed. +// +// The sqlsrv_errors function can return error and/or warning information by +// calling it with one of the following parameter values below. +// +// Parameters +// +// $errorsAndOrWarnings[OPTIONAL]: A predefined constant. This parameter can +// take one of the values listed: +// +// SQLSRV_ERR_ALL +// Errors and warnings generated on the last sqlsrv function call are returned. +// SQLSRV_ERR_ERRORS +// Errors generated on the last sqlsrv function call are returned. +// SQLSRV_ERR_WARNINGS +// Warnings generated on the last sqlsrv function call are returned. +// +// If no parameter value is supplied, SQLSRV_ERR_ALL is the default +// +// Return Value +// An array of arrays, or null. An example of an error returned: +// Array +// ( +// [0] => Array +// ( +// [0] => HYT00 +// [SQLSTATE] => HYT00 +// [1] => 0 +// [code] => 0 +// [2] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired +// [message] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired +// ) +// ) + +PHP_FUNCTION( sqlsrv_errors ) +{ + SQLSRV_UNUSED( execute_data ); + + zend_long flags = SQLSRV_ERR_ALL; + + LOG_FUNCTION( "sqlsrv_errors" ); + + if(( zend_parse_parameters( ZEND_NUM_ARGS() 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( 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); + RETURN_FALSE; + } + } + if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_WARNINGS ) { + if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( warnings ) TSRMLS_CC )) { + zval_ptr_dtor(&err_z); + RETURN_FALSE; + } + } + if( zend_hash_num_elements( Z_ARRVAL_P( &err_z )) == 0 ) { + zval_ptr_dtor(&err_z); + RETURN_NULL(); + } + RETURN_ZVAL( &err_z, 1, 1 ); +} + +// sqlsrv_configure( string $setting, mixed $value ) +// +// Changes the settings for error handling and logging options. +// +// Parameters +// $setting: The name of the setting to be configured. The possible implemented values are +// "WarningsReturnAsErrors", "LogSubsystems", and "LogSeverity". +// +// $value: The value to be applied to the setting specified in the $setting +// parameter. See MSDN or the MINIT function for possible values. +// +// Return Value +// If sqlsrv_configure is called with an unsupported setting or value, the +// function returns false. Otherwise, the function returns true. + +PHP_FUNCTION( sqlsrv_configure ) +{ + SQLSRV_UNUSED( execute_data ); + + LOG_FUNCTION( "sqlsrv_configure" ); + + char* option; + size_t option_len; + zval* value_z; + sqlsrv_context_auto_ptr error_ctx; + + RETVAL_FALSE; + + reset_errors( TSRMLS_C ); + + try { + + // dummy context to pass onto the error handler + error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); + SET_FUNCTION_NAME( *error_ctx ); + + int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "sz", &option, &option_len, &value_z ); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + // WarningsReturnAsErrors + if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { + + SQLSRV_G( warnings_return_as_errors ) = zend_is_true( value_z ) ? true : false; + LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); + RETURN_TRUE; + } + + // LogSeverity + else if( !stricmp( option, INI_LOG_SEVERITY )) { + + CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + zend_long severity_mask = Z_LVAL_P( value_z ); + // make sure they can't use 0 to shut off the masking in the severity + if( severity_mask < SEV_ALL || severity_mask == 0 || severity_mask > (SEV_NOTICE + SEV_ERROR + SEV_WARNING) ) { + RETURN_FALSE; + } + + SQLSRV_G( log_severity ) = static_cast( severity_mask ); + LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); + RETURN_TRUE; + } + + // LogSubsystems + else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { + + CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + zend_long subsystem_mask = Z_LVAL_P( value_z ); + + if( subsystem_mask < LOG_ALL || subsystem_mask > (LOG_INIT + LOG_CONN + LOG_STMT + LOG_UTIL) ) { + RETURN_FALSE; + } + + SQLSRV_G( log_subsystems ) = static_cast( subsystem_mask ); + LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); + RETURN_TRUE; + } + + else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) { + + CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) { + + throw ss::SSException(); + } + + zend_long buffered_query_limit = Z_LVAL_P( value_z ); + + CHECK_CUSTOM_ERROR( buffered_query_limit <= 0, error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) { + + throw ss::SSException(); + } + + SQLSRV_G( buffered_query_limit ) = buffered_query_limit; + LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit )); + RETURN_TRUE; + } + + else { + + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_configure: Unknown exception caught." ); + } +} + + +// sqlsrv_get_config( string $setting ) +// +// Returns the current value of the specified configuration setting. +// +// Parameters +// $setting: The configuration setting for which the value is returned. For a +// list of configurable settings, see sqlsrv_configure. +// +// Return Value +// The value of the setting specified by the $setting parameter. If an invalid +// setting is specified, false is returned and an error is added to the error +// collection. Because false is a valid value for WarningsReturnAsErrors, to +// really determine if an error occurred, call sqlsrv_errors. + +PHP_FUNCTION( sqlsrv_get_config ) +{ + SQLSRV_UNUSED( execute_data ); + + char* option = NULL; + size_t option_len; + sqlsrv_context_auto_ptr error_ctx; + + LOG_FUNCTION( "sqlsrv_get_config" ); + + reset_errors( TSRMLS_C ); + + try { + + // dummy context to pass onto the error handler + error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); + SET_FUNCTION_NAME( *error_ctx ); + + int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &option, &option_len ); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { + + ZVAL_BOOL( return_value, SQLSRV_G( warnings_return_as_errors )); + return; + } + else if( !stricmp( option, INI_LOG_SEVERITY )) { + + ZVAL_LONG( return_value, SQLSRV_G( log_severity )); + return; + } + else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { + + ZVAL_LONG( return_value, SQLSRV_G( log_subsystems )); + return; + } + else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) { + + ZVAL_LONG( return_value, SQLSRV_G( buffered_query_limit )); + return; + } + else { + + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_get_config: Unknown exception caught." ); + } +} + + +namespace { + +void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reported_chain, zval* ignored_chain, + bool warning TSRMLS_DC ) +{ + + if( array_init( error_z ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + // sqlstate + zval temp; + ZVAL_UNDEF(&temp); + core::sqlsrv_zval_stringl( &temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE ); + //TODO: reference? + Z_TRY_ADDREF_P( &temp ); + if( add_next_index_zval( error_z, &temp ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + if( add_assoc_zval( error_z, "SQLSTATE", &temp ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + // native_code + if( add_next_index_long( error_z, error->native_code ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + if( add_assoc_long( error_z, "code", error->native_code ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + // native_message + ZVAL_UNDEF(&temp); + ZVAL_STRING( &temp, reinterpret_cast( error->native_message ) ); + //TODO: reference? + Z_TRY_ADDREF_P(&temp); + if( add_next_index_zval( error_z, &temp ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + if( add_assoc_zval( error_z, "message", &temp ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + // If it is an error or if warning_return_as_errors is true than + // add the error or warning to the reported_chain. + if( !warning || SQLSRV_G( warnings_return_as_errors ) ) + { + // if the warning is part of the ignored warning list than + // add to the ignored chain if the ignored chain is not null. + if( warning && ignore_warning( reinterpret_cast(error->sqlstate), error->native_code TSRMLS_CC ) && + ignored_chain != NULL ) { + + if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + } + else { + + // It is either an error or a warning which should not be ignored. + if( add_next_index_zval( reported_chain, error_z ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + } + } + else + { + // It is a warning with warning_return_as_errors as false, so simply add it to the ignored_chain list + if( ignored_chain != NULL ) { + + if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + } + } +} + +bool handle_errors_and_warnings( sqlsrv_context& ctx, zval* reported_chain, zval* ignored_chain, logging_severity log_severity, + unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC ) +{ + bool result = true; + bool errors_ignored = false; + size_t prev_reported_cnt = 0; + bool reported_chain_was_null = false; + bool ignored_chain_was_null = false; + int zr = SUCCESS; + zval error_z; + ZVAL_UNDEF(&error_z); + sqlsrv_error_auto_ptr error; + + // array of reported errors + if( Z_TYPE_P( reported_chain ) == IS_NULL ) { + + reported_chain_was_null = true; + zr = array_init( reported_chain ); + if( zr == FAILURE ) { + DIE( "Fatal error in handle_errors_and_warnings" ); + } + } + else { + prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_P( reported_chain )); + } + + // array of ignored errors + if( ignored_chain != NULL ) { + + if( Z_TYPE_P( ignored_chain ) == IS_NULL ) { + + ignored_chain_was_null = true; + zr = array_init( ignored_chain ); + if( zr == FAILURE ) { + DIE( "Fatal error in handle_errors_and_warnings" ); + } + } + } + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, log_severity TSRMLS_CC, print_args ); + copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC ); + } + + SQLSMALLINT record_number = 0; + do { + + result = core_sqlsrv_get_odbc_error( ctx, ++record_number, error, log_severity TSRMLS_CC ); + if( result ) { + copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC ); + } + } while( result ); + + // If it were a warning, we report that warnings where ignored except if warnings_return_as_errors + // was true and we added some warnings to the reported_chain. + if( warning ) { + + errors_ignored = true; + + if( SQLSRV_G( warnings_return_as_errors ) ) { + + if( zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) > prev_reported_cnt ) { + + // We actually added some errors + errors_ignored = false; + } + } + } + + // if the error array came in as NULL and didn't have anything added to it, return it as NULL + if( reported_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) == 0 ) { + zend_hash_destroy( Z_ARRVAL_P( reported_chain )); + FREE_HASHTABLE( Z_ARRVAL_P( reported_chain )); + ZVAL_NULL( reported_chain ); + } + if( ignored_chain != NULL && ignored_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( ignored_chain )) == 0 ) { + zend_hash_destroy( Z_ARRVAL_P( ignored_chain )); + FREE_HASHTABLE( Z_ARRVAL_P( ignored_chain )); + ZVAL_NULL( ignored_chain ); + } + + // If it was an error instead of a warning than we always return errors_ignored = false. + return errors_ignored; +} + +// return whether or not a warning should be ignored or returned as an error if WarningsReturnAsErrors is true +// see RINIT in init.cpp for information about which errors are ignored. +bool ignore_warning( char* sql_state, int native_code TSRMLS_DC ) +{ + zend_ulong index = -1; + zend_string* key = NULL; + void* error_temp = NULL; + + ZEND_HASH_FOREACH_KEY_PTR( g_ss_warnings_to_ignore_ht, index, key, error_temp ) { + sqlsrv_error* error = static_cast( error_temp ); + if (NULL == error) { + return false; + } + + if( !strncmp( reinterpret_cast( error->sqlstate ), sql_state, SQL_SQLSTATE_SIZE ) && + ( error->native_code == native_code || error->native_code == -1 )) { + return true; + } + } ZEND_HASH_FOREACH_END(); + + return false; +} + +int sqlsrv_merge_zend_hash_dtor( zval* dest TSRMLS_DC ) +{ + zval_ptr_dtor( dest ); + return ZEND_HASH_APPLY_REMOVE; +} + +// sqlsrv_merge_zend_hash +// merge a source hash into a dest hash table and return any errors. +bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ) +{ + if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" ); + if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" ); + + if( Z_TYPE_P( src_z ) == IS_NULL ) { + return true; + } + + HashTable* src_ht = Z_ARRVAL_P( src_z ); + zend_ulong index = -1; + zend_string* key = NULL; + zval* value_z = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( src_ht, index, key, value_z ) { + if ( !value_z ) { + zend_hash_apply( Z_ARRVAL_P(dest_z), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); + return false; + } + + int result = add_next_index_zval( dest_z, value_z ); + + if( result == FAILURE ) { + zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); + return false; + } + Z_TRY_ADDREF_P( value_z ); + } ZEND_HASH_FOREACH_END(); + + return true; +} + +} // namespace diff --git a/sqlsrv/CREDITS b/sqlsrv/CREDITS deleted file mode 100644 index dc85ce17..00000000 --- a/sqlsrv/CREDITS +++ /dev/null @@ -1 +0,0 @@ -Microsoft Drivers 4.1 for PHP for SQL Server (PDO driver) diff --git a/sqlsrv/core_conn.cpp b/sqlsrv/core_conn.cpp deleted file mode 100644 index 97566d3c..00000000 --- a/sqlsrv/core_conn.cpp +++ /dev/null @@ -1,774 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_conn.cpp -// -// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include -#include -#include -#include - -#include -#include - -// *** internal variables and constants *** - -namespace { - -// *** internal constants *** -// an arbitrary figure that should be large enough for most connection strings. -const int DEFAULT_CONN_STR_LEN = 2048; - -// length of buffer used to retrieve information for client and server info buffers -const int INFO_BUFFER_LEN = 256; - -// processor architectures -const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" }; - -// ODBC driver name. -const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"}; - -// default options if only the server is specified -const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}"; - -// connection option appended when no user name or password is given -const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};"; - -// connection option appended for MARS when MARS isn't explicitly mentioned -const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};"; - -// *** internal function prototypes *** - -void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, - HashTable* options_ht, const connection_option valid_conn_opts[], - void* driver,_Inout_ std::string& connection_string TSRMLS_DC ); -void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ); -const char* get_processor_arch( void ); -void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ); -connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, SQLULEN key_len TSRMLS_DC ); -void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ); - -} - -// core_sqlsrv_connect -// opens a connection and returns a sqlsrv_conn structure. -// Parameters: -// henv_cp - connection pooled env context -// henv_ncp - non connection pooled env context -// server - name of the server we're connecting to -// uid - username -// pwd - password -// options_ht - zend_hash list of options -// err - error callback to put into the connection's context -// valid_conn_opts[] - array of valid driver supported connection options. -// driver - reference to caller -// Return -// A sqlsrv_conn structure. An exception is thrown if an error occurs - -sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, - const char* server, const char* uid, const char* pwd, - HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[], - void* driver, const char* driver_func TSRMLS_DC ) - -{ - SQLRETURN r; - std::string conn_str; - conn_str.reserve( DEFAULT_CONN_STR_LEN ); - sqlsrv_malloc_auto_ptr conn; - sqlsrv_malloc_auto_ptr wconn_string; - unsigned int wconn_len = 0; - - try { - - sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv - - // check the connection pooling setting to determine which henv to use to allocate the connection handle - // we do this earlier because we have to allocate the connection handle prior to setting attributes on - // it in build_connection_string_and_set_conn_attr. - - if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - - zval* option_z = NULL; - int zr = SUCCESS; - - option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING); - if (option_z) { - - // if the option was found and it's not true, then use the non pooled environment handle - if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) { - - henv = &henv_ncp; - } - } - } - - SQLHANDLE temp_conn_h; - core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC ); - - conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); - conn->set_func( driver_func ); - - for ( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) { - conn_str = CONNECTION_STRING_DRIVER_NAME[i]; - build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, - conn_str TSRMLS_CC ); - - // We only support UTF-8 encoding for connection string. - // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW - wconn_len = static_cast( conn_str.length() + 1 ) * sizeof( wchar_t ); - - wconn_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast(conn_str.length()), &wconn_len); - CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) - { - throw core::CoreException(); - } - - SQLSMALLINT output_conn_size; - r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get()), - static_cast( wconn_len ), NULL, - 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); - // clear the connection string from memory to remove sensitive data (such as a password). - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn_str.clear(); - - if( !SQL_SUCCEEDED( r )) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; - SQLSMALLINT len; - SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); - bool missing_driver_error = ( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && - state[4] == '2' ); - // if it's a IM002, meaning that the correct ODBC driver is not installed - CHECK_CUSTOM_ERROR( missing_driver_error && ( i == DRIVER_VERSION::MAX ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { - throw core::CoreException(); - } - if ( !missing_driver_error ) { - break; - } - } else { - conn->driver_version = static_cast( i ); - break; - } - - } - CHECK_SQL_ERROR( r, conn ) { - throw core::CoreException(); - } - - CHECK_SQL_WARNING_AS_ERROR( r, conn ) { - throw core::CoreException(); - } - - // determine the version of the server we're connected to. The server version is left in the - // connection upon return. - determine_server_version( conn TSRMLS_CC ); - - } - catch( std::bad_alloc& ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn->invalidate(); - DIE( "C++ memory allocation failure building the connection string." ); - } - catch( std::out_of_range const& ex ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); - conn->invalidate(); - throw; - } - catch( std::length_error const& ex ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); - conn->invalidate(); - throw; - } - catch( core::CoreException& ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn->invalidate(); - throw; - } - - sqlsrv_conn* return_conn = conn; - conn.transferred(); - - return return_conn; -} - - - -// core_sqlsrv_begin_transaction -// Begins a transaction on a specified connection. The current transaction -// includes all statements on the specified connection that were executed after -// the call to core_sqlsrv_begin_transaction and before any calls to -// core_sqlsrv_rollback or core_sqlsrv_commit. -// The default transaction mode is auto-commit. This means that all queries -// are automatically committed upon success unless they have been designated -// as part of an explicit transaction by using core_sqlsrv_begin_transaction. -// Parameters: -// sqlsrv_conn*: The connection with which the transaction is associated. - -void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_OFF ), - SQL_IS_UINTEGER TSRMLS_CC ); - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_commit -// Commits the current transaction on the specified connection and returns the -// connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or -// core_sqlsrv_commit. -// Parameters: -// sqlsrv_conn*: The connection on which the transaction is active. - -void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." ); - - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), - SQL_IS_UINTEGER TSRMLS_CC ); - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_rollback -// Rolls back the current transaction on the specified connection and returns -// the connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or -// core_sqlsrv_commit. -// Parameters: -// sqlsrv_conn*: The connection on which the transaction is active. - -void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." ); - - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), - SQL_IS_UINTEGER TSRMLS_CC ); - - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_close -// Called when a connection resource is destroyed by the Zend engine. -// Parameters: -// conn - The current active connection. -void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ) -{ - // if the connection wasn't successful, just return. - if( conn == NULL ) - return; - - try { - - // rollback any transaction in progress (we don't care about the return result) - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); - } - catch( core::CoreException& ) { - LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." ); - } - - // disconnect from the server - SQLRETURN r = SQLDisconnect( conn->handle() ); - if( !SQL_SUCCEEDED( r )) { - LOG( SEV_ERROR, "Disconnect failed when closing the connection." ); - } - - // free the connection handle - conn->invalidate(); - - sqlsrv_free( conn ); -} - -// core_sqlsrv_prepare -// Create a statement object and prepare the SQL query passed in for execution at a later time. -// Parameters: -// stmt - statement to be prepared -// sql - T-SQL command to prepare -// sql_len - length of the T-SQL string - -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ) -{ - try { - - // convert the string from its encoding to UTf-16 - // if the string is empty, we initialize the fields and skip since an empty string is a - // failure case for utf16_string_from_mbcs_string - sqlsrv_malloc_auto_ptr wsql_string; - unsigned int wsql_len = 0; - if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { - wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t ))); - wsql_string[0] = L'\0'; - wsql_len = 0; - } - else { - - if (sql_len > INT_MAX) - { - LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); - throw core::CoreException(); - } - - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : - stmt->encoding() ); - wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - static_cast( sql_len ), &wsql_len ); - CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - get_last_error_message() ) { - throw core::CoreException(); - } - } - - // prepare our wide char query string - core::SQLPrepareW( stmt, reinterpret_cast( wsql_string.get() ), wsql_len TSRMLS_CC ); - } - catch( core::CoreException& ) { - - throw; - } -} - -// core_sqlsrv_get_server_version -// Determines the vesrion of the SQL Server we are connected to. Calls a helper function -// get_server_version to get the version of SQL Server. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// *server_version - zval for returning results. - -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); - if (NULL != buffer) { - sqlsrv_free( buffer ); - } - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_get_server_info -// Returns the Database name, the name of the SQL Server we are connected to -// and the version of the SQL Server. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// *server_info - zval for returning results. - -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval *server_info TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - // initialize the array - core::sqlsrv_array_init( *conn, server_info TSRMLS_CC ); - - // Get the database name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the server version - get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the server name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_get_client_info -// Returns the ODBC driver's dll name, version and the ODBC version. -// Parameters -// conn - The connection resource by which the client and server are connected. -// *client_info - zval for returning the results. - -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - // initialize the array - core::sqlsrv_array_init( *conn, client_info TSRMLS_CC ); - - // Get the ODBC driver's dll name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the ODBC driver's ODBC version - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the OBDC driver's version - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - } - - catch( core::CoreException& ) { - throw; - } -} - - -// core_is_conn_opt_value_escaped -// determine if connection string value is properly escaped. -// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that -// the value will be surrounded by { and } by the caller after it has been validated - -bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ) -{ - // 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 ] == '}' ) { - ++value; - value_len -= 2; - } - // check to make sure that all right braces are escaped - size_t i = 0; - while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) { - // skip both braces - if( value[i] == '}' ) - ++i; - ++i; - } - if( i < value_len && value[i] == '}' ) { - return false; - } - - return true; -} - - -// *** internal connection functions and classes *** - -namespace { - -connection_option const* get_connection_option( sqlsrv_conn* conn, SQLULEN key, - 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 ) { - - if( key == conn_opts[ opt_idx ].conn_option_key ) { - - return &conn_opts[ opt_idx ]; - } - } - - SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." ); - return NULL; // avoid a compiler warning -} - -// says what it does, and does what it says -// rather than have attributes and connection strings as ODBC does, we unify them into a hash table -// passed to the connection, and then break them out ourselves and either set attributes or put the -// option in the connection string. - -void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, - HashTable* options, const connection_option valid_conn_opts[], - void* driver,_Inout_ std::string& connection_string TSRMLS_DC ) -{ - bool credentials_mentioned = false; - bool mars_mentioned = false; - connection_option const* conn_opt; - int zr = SUCCESS; - - try { - - // Add the server name - common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC ); - - // if uid is not present then we use trusted connection. - if(uid == NULL || strlen( uid ) == 0 ) { - - connection_string += "Trusted_Connection={Yes};"; - } - else { - - bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { - throw core::CoreException(); - } - - common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( 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, strlen( pwd )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { - - throw core::CoreException(); - } - - common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC ); - } - } - - // if no options were given, then we set MARS the defaults and return immediately. - if( options == NULL || zend_hash_num_elements( options ) == 0 ) { - connection_string += CONNECTION_STRING_DEFAULT_OPTIONS; - return; - } - - // workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file - // if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn" - // flag is set to false. - if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) { - - zval* trace_value = NULL; - trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); - - if (trace_value == NULL || !zend_is_true(trace_value)) { - - zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE ); - } - } - - zend_string *key = NULL; - zend_ulong index = -1; - zval* data = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); - - conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); - - if( index == SQLSRV_CONN_OPTION_MARS ) { - mars_mentioned = true; - } - - conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - - // MARS on if not explicitly turned off - if( !mars_mentioned ) { - connection_string += CONNECTION_OPTION_MARS_ON; - } - - } - catch( core::CoreException& ) { - throw; - } -} - - -// get_server_version -// Helper function which returns the version of the SQL Server we are connected to. - -void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - *server_version = buffer; - len = buffer_len; - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - - -// get_processor_arch -// Calls GetSystemInfo to verify the what architecture of the processor is supported -// and return the string of the processor name. -const char* get_processor_arch( void ) -{ - SYSTEM_INFO sys_info; - GetSystemInfo( &sys_info); - switch( sys_info.wProcessorArchitecture ) { - - case PROCESSOR_ARCHITECTURE_INTEL: - return PROCESSOR_ARCH[0]; - - case PROCESSOR_ARCHITECTURE_AMD64: - return PROCESSOR_ARCH[1]; - - case PROCESSOR_ARCHITECTURE_IA64: - return PROCESSOR_ARCH[2]; - - default: - DIE( "Unknown Windows processor architecture." ); - return NULL; - } -} - - -// some features require a server of a certain version or later -// this function determines the version of the server we're connected to -// and stores it in the connection. Any errors are logged before return. -// Exception is thrown when the server version is either undetermined -// or is invalid (< 2000). - -void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) -{ - SQLSMALLINT info_len; - char p[ INFO_BUFFER_LEN ]; - core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); - - errno = 0; - char version_major_str[ 3 ]; - SERVER_VERSION version_major; - memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); - 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 ) - { - throw core::CoreException(); - } - - // SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least - // that high - conn->server_version = version_major; -} - -void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) -{ - // wrap a connection option in a quote. It is presumed that any character that need to be escaped will - // be escaped, such as a closing }. - TSRMLS_C; - - if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) { - ++val; - val_len -= 2; - } - conn_str += odbc_name; - conn_str += "={"; - conn_str.append( val, val_len ); - conn_str += "};"; -} - -} // namespace - -// simply add the parsed value to the connection string -void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str - TSRMLS_DC ) -{ - const char* val_str = Z_STRVAL_P( value ); - size_t val_len = Z_STRLEN_P( value ); - common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC ); -} - -// do nothing for connection pooling since we handled it earlier when -// deciding which environment handle to use. -void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ - TSRMLS_DC ) -{ - TSRMLS_C; -} - -// helper function to evaluate whether a string value is true or false. -// Values = ("true" or "1") are treated as true values. Everything else is treated as false. -// Returns 1 for true and 0 for false. - -size_t core_str_zval_is_true( zval* value_z ) -{ - SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); - - char* value_in = Z_STRVAL_P( value_z ); - size_t val_len = Z_STRLEN_P( 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( value_in[ last_char ] )) { - value_in[ last_char ] = '\0'; - val_len = last_char; - --last_char; - } - - // save adjustments to the value made by stripping whitespace at the end - Z_STRLEN_P( value_z ) = val_len; - - const char VALID_TRUE_VALUE_1[] = "true"; - const char VALID_TRUE_VALUE_2[] = "1"; - - if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) || - ( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len )) - ) { - - return 1; // true - } - - return 0; // false -} diff --git a/sqlsrv/core_init.cpp b/sqlsrv/core_init.cpp deleted file mode 100644 index 1b1e8e57..00000000 --- a/sqlsrv/core_init.cpp +++ /dev/null @@ -1,175 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_init.cpp -// -// Contents: common initialization routines shared by PDO and sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - - -// module global variables (initialized in minit and freed in mshutdown) -HMODULE g_sqlsrv_hmodule = NULL; -OSVERSIONINFO g_osversion; - - -// core_sqlsrv_minit -// Module initialization -// This function is called once per execution by the driver layer's MINIT function. -// The primary responsibility of this function is to allocate the two environment -// handles used by core_sqlsrv_connect to allocate either a pooled or non pooled ODBC -// connection handle. -// Parameters: -// henv_cp - Environment handle for pooled connection. -// henv_ncp - Environment handle for non-pooled connection. -// err - Driver specific error handler which handles any errors during initialization. -void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ) -{ - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); - - *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL - - try { - - // get the version of the OS we're running on. For now this governs certain flags used by - // WideCharToMultiByte. It might be relevant to other things in the future. - g_osversion.dwOSVersionInfoSize = sizeof( g_osversion ); - BOOL ver_return = GetVersionEx( &g_osversion ); - if( !ver_return ) { - LOG( SEV_ERROR, "Failed to retrieve Windows version information." ); - throw core::CoreException(); - } - - SQLHANDLE henv = SQL_NULL_HANDLE; - SQLRETURN r; - - // allocate the non pooled environment handle - // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so - // we use the direct ODBC function. - r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); - if( !SQL_SUCCEEDED( r )) { - throw core::CoreException(); - } - - *henv_ncp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); - (*henv_ncp)->set_func( driver_func ); - - // set to ODBC 3 - core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER - TSRMLS_CC ); - - // disable connection pooling - core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_OFF ), - SQL_IS_UINTEGER TSRMLS_CC ); - - // allocate the pooled envrionment handle - // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so - // we use the direct ODBC function. - r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); - if( !SQL_SUCCEEDED( r )) { - throw core::CoreException(); - } - - *henv_cp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); - (*henv_cp)->set_func( driver_func ); - - // set to ODBC 3 - core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC); - - // enable connection pooling - core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_ONE_PER_HENV ), - SQL_IS_UINTEGER TSRMLS_CC ); - - } - catch( core::CoreException& e ) { - - LOG( SEV_ERROR, "core_sqlsrv_minit: Failed to allocate environment handles." ); - - if( *henv_ncp != NULL ) { - // free the ODBC env handle allocated just above - SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); - delete *henv_ncp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) - *henv_ncp = NULL; - } - if( *henv_cp != NULL ) { - // free the ODBC env handle allocated just above - SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); - delete *henv_cp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) - *henv_cp = NULL; - } - - throw e; // rethrow for the driver to catch - } - catch( std::bad_alloc& e ) { - - LOG( SEV_ERROR, "core_sqlsrv_minit: Failed memory allocation for environment handles." ); - - if( *henv_ncp != NULL ) { - SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); - delete *henv_ncp; - *henv_ncp = NULL; - } - if( *henv_cp ) { - SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); - delete *henv_cp; - *henv_cp = NULL; - } - - throw e; // rethrow for the driver to catch - } -} - -// core_sqlsrv_mshutdown -// Module shutdown function -// Free the environment handles allocated in MINIT and unregister our stream wrapper. -// Resource types and constants are automatically released since we don't flag them as -// persistent when they are registered. -// Parameters: -// henv_cp - Pooled environment handle. -// henv_ncp - Non-pooled environment handle. -void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ) -{ - if( henv_ncp != SQL_NULL_HANDLE ) { - - henv_ncp.invalidate(); - } - delete &henv_ncp; - - if( henv_cp != SQL_NULL_HANDLE ) { - - henv_cp.invalidate(); - } - delete &henv_cp; - - return; -} - - -// DllMain for the extension. - -BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID ) -{ - switch( fdwReason ) { - case DLL_PROCESS_ATTACH: - // store the module handle for use by client_info and server_info - g_sqlsrv_hmodule = hinstDLL; - break; - default: - break; - } - - return TRUE; -} diff --git a/sqlsrv/core_results.cpp b/sqlsrv/core_results.cpp deleted file mode 100644 index 8b2657ed..00000000 --- a/sqlsrv/core_results.cpp +++ /dev/null @@ -1,1361 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_results.cpp -// -// Contents: Result sets -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include -#include -#include - -using namespace core; - -// conversion matrix -// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported -// this is initialized the first time the buffered result set is created. -sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix; - -namespace { - -// *** internal types *** - -#pragma warning(disable:4200) - -// *** internal constants *** - -const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field - -// *** internal functions *** - -// return an integral type rounded up to a certain number -template -T align_to( T number ) -{ - DEBUG_SQLSRV_ASSERT( (number + align) > number, "Number to align overflowed" ); - return ((number % align) == 0) ? number : (number + align - (number % align)); -} - -// return a pointer address aligned to a certain address boundary -template -T* align_to( T* ptr ) -{ - size_t p_value = (size_t) ptr; - return align_to( p_value ); -} - -// set the nth bit of the bitstream starting at ptr -void set_bit( void* ptr, unsigned int bit ) -{ - unsigned char* null_bits = reinterpret_cast( ptr ); - null_bits += bit >> 3; - *null_bits |= 1 << ( 7 - ( bit & 0x7 )); -} - -// retrieve the nth bit from the bitstream starting at ptr -bool get_bit( void* ptr, unsigned int bit ) -{ - unsigned char* null_bits = reinterpret_cast( ptr ); - null_bits += bit >> 3; - return ((*null_bits & (1 << ( 7 - ( bit & 0x07 )))) != 0); -} - -// read in LOB field during buffered result creation -SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - zend_long mem_used TSRMLS_DC ); - -// dtor for each row in the cache -void cache_row_dtor(zval* data); - -// convert a number to a string using locales -// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of -// subclassing a new streambuf just to avoid the copy, it's easier to do the copy -template -SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - sqlsrv_error_auto_ptr& last_error ) -{ - // get to display size by removing the null terminator from buffer length - size_t display_size = ( buffer_length - sizeof( Char )) / sizeof( Char ); - - std::basic_ostringstream os; - // use the display size to determine the sql type. And if it is a double, set the precision accordingly - // the display sizes are set by the ODBC driver based on the precision of the sql type - // otherwise we can just use the default precision as long will not be truncated - size_t real_display_size = 14; - size_t float_display_size = 24; - size_t real_precision = 7; - size_t float_precision = 15; - // this is the case of sql type float(24) or real - if ( display_size == real_display_size ) { - os.precision( real_precision ); - } - // this is the case of sql type float(53) - else if ( display_size == float_display_size ) { - os.precision( float_precision ); - } - std::locale loc; - os.imbue( loc ); - std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf() ), os, ' ', *number_data ); - std::basic_string& str_num = os.str(); - - if( os.fail() ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "IMSSP", (SQLCHAR*) "Failed to convert number to string", -1 ); - return SQL_ERROR; - } - - if( str_num.size() * sizeof(Char) > (size_t) buffer_length ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); - return SQL_ERROR; - } - - *out_buffer_length = str_num.size() * sizeof( Char ); // str_num.size() already include the NULL terminator - memcpy_s( buffer, buffer_length, str_num.c_str(), *out_buffer_length ); - - return SQL_SUCCESS; -} - -template -SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) -{ - Number* number_data = reinterpret_cast( buffer ); - std::locale loc; // default locale should match system - std::basic_istringstream is; - is.str( string_data ); - is.imbue( loc ); - std::ios_base::iostate st = 0; - - std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream::_Iter( is.rdbuf( ) ), - std::basic_istream::_Iter(0), is, st, *number_data ); - - if( st & std::ios_base::failbit ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103 ); - return SQL_ERROR; - } - - *out_buffer_length = sizeof( Number ); - - return SQL_SUCCESS; -} - -// "closure" for the hash table destructor -struct row_dtor_closure { - - sqlsrv_buffered_result_set* results; - BYTE* row_data; - - row_dtor_closure( sqlsrv_buffered_result_set* st, BYTE* row ) : - results( st ), row_data( row ) - { - } -}; - -sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number ) -{ - SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ]; - SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLINTEGER native_code; - SQLSMALLINT wnative_message_len = 0; - - SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message, - SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len ); - if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - return NULL; - } - - // convert the error into the encoding of the context - SQLSRV_ENCODING enc = odbc->encoding(); - if( enc == SQLSRV_ENCODING_DEFAULT ) { - enc = odbc->conn->encoding(); - } - - // convert the error into the encoding of the context - sqlsrv_malloc_auto_ptr sql_state; - SQLLEN sql_state_len = 0; - if (!convert_string_from_utf16( enc, wsql_state, sizeof(wsql_state), (char**)&sql_state, sql_state_len )) { - return NULL; - } - - sqlsrv_malloc_auto_ptr native_message; - SQLLEN native_message_len = 0; - if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) { - return NULL; - } - - return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) sql_state, (SQLCHAR*) native_message, - native_code ); -} - -} // namespace - -// base class result set - -sqlsrv_result_set::sqlsrv_result_set( sqlsrv_stmt* stmt ) : - odbc( stmt ) -{ -} - - -// ODBC result set -// This object simply wraps ODBC function calls - -sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( sqlsrv_stmt* stmt ) : - sqlsrv_result_set( stmt ) -{ -} - -sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void ) -{ -} - -SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC ); -} - -SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC ); -} - -SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length, - out_buffer_length TSRMLS_CC ); -} - -sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( SQLSMALLINT record_number ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return odbc_get_diag_rec( odbc, record_number ); -} - -SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLRowCount( odbc TSRMLS_CC ); -} - - -// Buffered result set -// This class holds a result set in memory - -sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS_DC ) : - sqlsrv_result_set( stmt ), - cache(NULL), - col_count(0), - meta(NULL), - current(0), - last_field_index(-1), - read_so_far(0) -{ - // 10 is an arbitrary number for now for the initial size of the cache - ALLOC_HASHTABLE( cache ); - core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); - col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); - // there is no result set to buffer - if( col_count == 0 ) { - return; - } - - SQLULEN null_bytes = ( col_count / 8 ) + 1; // number of bits to reserve at the beginning of each row for NULL flags - meta = static_cast( sqlsrv_malloc( col_count * - sizeof( sqlsrv_buffered_result_set::meta_data ))); - - // 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; - } - - // get the meta data and calculate the size of a row buffer - SQLULEN offset = null_bytes; - for( SQLSMALLINT i = 0; i < col_count; ++i ) { - - core::SQLDescribeCol( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC ); - - offset = align_to<4>( offset ); - meta[i].offset = offset; - - switch( meta[i].type ) { - - // these types are the display size - case SQL_BIGINT: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, - reinterpret_cast( &meta[i].length ) TSRMLS_CC ); - meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space - offset += meta[i].length; - break; - - // these types are the column size - case SQL_BINARY: - case SQL_CHAR: - case SQL_SS_UDT: - case SQL_VARBINARY: - case SQL_VARCHAR: - // var* field types are length prefixed - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - offset += sizeof( void* ); - } - else { - meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space - offset += meta[i].length; - } - break; - - case SQL_WCHAR: - case SQL_WVARCHAR: - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - offset += sizeof( void* ); - } - else { - meta[i].length *= sizeof( WCHAR ); - meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space - offset += meta[i].length; - } - break; - - // these types are LOBs - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - meta[i].length = sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN; - offset += sizeof( void* ); - break; - - // these types are the ISO date size - case SQL_DATETIME: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_TYPE_TIMESTAMP: - core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, - reinterpret_cast( &meta[i].length ) TSRMLS_CC ); - meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space - offset += meta[i].length; - break; - - // these types are the native size - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - meta[i].length = sizeof( long ); - offset += meta[i].length; - break; - - case SQL_REAL: - case SQL_FLOAT: - meta[i].length = sizeof( double ); - offset += meta[i].length; - break; - - default: - SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); - break; - } - - switch( meta[i].type ) { - - case SQL_BIGINT: - case SQL_CHAR: - case SQL_DATETIME: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - case SQL_LONGVARCHAR: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_XML: - case SQL_TYPE_TIMESTAMP: - case SQL_VARCHAR: - meta[i].c_type = SQL_C_CHAR; - break; - - case SQL_SS_UDT: - case SQL_LONGVARBINARY: - case SQL_BINARY: - case SQL_VARBINARY: - meta[i].c_type = SQL_C_BINARY; - break; - - case SQL_WLONGVARCHAR: - case SQL_WCHAR: - case SQL_WVARCHAR: - meta[i].c_type = SQL_C_WCHAR; - break; - - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - meta[i].c_type = SQL_C_LONG; - break; - - case SQL_REAL: - case SQL_FLOAT: - meta[i].c_type = SQL_C_DOUBLE; - break; - - default: - SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); - break; - } - - } - - // read the data into the cache - // (offset from the above loop has the size of the row buffer necessary) - zend_long mem_used = 0; - unsigned long row_count = 0; - - while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { - - // allocate the row buffer - unsigned char* row = static_cast( sqlsrv_malloc( offset )); - memset( row, 0, offset ); - - // read the fields into the row buffer - for( SQLSMALLINT i = 0; i < col_count; ++i ) { - - SQLLEN out_buffer_temp = SQL_NULL_DATA; - SQLPOINTER buffer; - SQLLEN* out_buffer_length = &out_buffer_temp; - - switch( meta[i].c_type ) { - - case SQL_C_CHAR: - case SQL_C_WCHAR: - case SQL_C_BINARY: - 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 ] ); - *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); - // a NULL pointer means NULL field - if( *lob_addr == NULL ) { - *out_buffer_length = SQL_NULL_DATA; - } - else { - *out_buffer_length = **reinterpret_cast( lob_addr ); - mem_used += *out_buffer_length; - } - } - else { - - mem_used += meta[i].length; - CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - - buffer = row + meta[i].offset + sizeof( SQLULEN ); - out_buffer_length = reinterpret_cast( row + meta[i].offset ); - core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); - } - break; - - case SQL_C_LONG: - case SQL_C_DOUBLE: - { - mem_used += meta[i].length; - CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - buffer = row + meta[i].offset; - out_buffer_length = &out_buffer_temp; - core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); - } - break; - - default: - SQLSRV_ASSERT( false, "Unknown C type" ); - break; - } - - if( *out_buffer_length == SQL_NULL_DATA ) { - unsigned char* null_bits = reinterpret_cast( row ); - set_bit( row, i ); - } - } - - SQLSRV_ASSERT( row_count < LONG_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); - - // add it to the cache - row_dtor_closure cl( this, row ); - sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); - } - -} - -sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void ) -{ - // free the rows - if( cache ) { - zend_hash_destroy( cache ); - FREE_HASHTABLE( cache ); - cache = NULL; - } - - // free the meta data - if( meta ) { - efree( meta ); - meta = NULL; - } -} - -SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) -{ - last_error = NULL; - last_field_index = -1; - read_so_far = 0; - - switch( orientation ) { - - case SQL_FETCH_NEXT: - offset = 1; - orientation = SQL_FETCH_RELATIVE; - break; - case SQL_FETCH_PRIOR: - offset = -1; - orientation = SQL_FETCH_RELATIVE; - break; - } - - switch( orientation ) { - - case SQL_FETCH_FIRST: - current = 1; - break; - case SQL_FETCH_LAST: - current = row_count( TSRMLS_C ); - break; - case SQL_FETCH_ABSOLUTE: - current = offset; - break; - case SQL_FETCH_RELATIVE: - current += offset; - break; - default: - SQLSRV_ASSERT( false, "Invalid fetch orientation. Should have been caught before here." ); - break; - } - - // check validity of current row - // the cursor can never get further away than just before the first row - if( current <= 0 && ( offset < 0 || orientation != SQL_FETCH_RELATIVE )) { - current = 0; - return SQL_NO_DATA; - } - - // the cursor can never get further away than just after the last row - if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) { - current = row_count( TSRMLS_C ) + 1; - return SQL_NO_DATA; - } - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) -{ - last_error = NULL; - field_index--; // convert from 1 based to 0 based - SQLSRV_ASSERT( field_index < column_count(), "Invalid field index requested" ); - - if( field_index != last_field_index ) { - last_field_index = field_index; - read_so_far = 0; - } - - unsigned char* row = get_row(); - - // if the field is null, then return SQL_NULL_DATA - if( get_bit( row, field_index )) { - *out_buffer_length = SQL_NULL_DATA; - return SQL_SUCCESS; - } - - // check to make sure the conversion type is valid - if( conv_matrix.find( meta[ field_index ].c_type ) == conv_matrix.end() || - conv_matrix.find( meta[ field_index ].c_type )->second.find( target_type ) == - conv_matrix.find( meta[ field_index ].c_type )->second.end() ) { - - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "07006", - (SQLCHAR*) "Restricted data type attribute violation", 0 ); - return SQL_ERROR; - } - - return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length, - out_buffer_length ); -} - -SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) -{ - SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); - SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE, - "Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); - SQLSRV_ASSERT( buffer_length >= SQL_SQLSTATE_BUFSIZE, - "Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field" ); - - if( last_error == NULL ) { - return SQL_NO_DATA; - } - - SQLSRV_ASSERT( last_error->sqlstate != NULL, - "Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" ); - - memcpy_s( diag_info_buffer, buffer_length, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE )); - - return SQL_SUCCESS; -} - -unsigned char* sqlsrv_buffered_result_set::get_row( void ) -{ - row_dtor_closure* cl_ptr; - cl_ptr = reinterpret_cast(zend_hash_index_find_ptr(cache, static_cast(current - 1))); - SQLSRV_ASSERT(cl_ptr != NULL, "Failed to find row %1!d! in the cache", current); - return cl_ptr->row_data; -} - -sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( SQLSMALLINT record_number ) -{ - // we only hold a single error if there is one, otherwise return the ODBC error(s) - if( last_error == NULL ) { - return odbc_get_diag_rec( odbc, record_number ); - } - if( record_number > 1 ) { - return NULL; - } - - return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code ); -} - -SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D ) -{ - last_error = NULL; - - return zend_hash_num_elements( cache ); -} - -// private functions -template -SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, _Out_ void* buffer, - SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - sqlsrv_error_auto_ptr& out_error ) -{ - // hex characters for the conversion loop below - static char hex_chars[] = "0123456789ABCDEF"; - - SQLSRV_ASSERT( out_error == NULL, "Pending error for sqlsrv_buffered_results_set::binary_to_string" ); - - SQLRETURN r = SQL_ERROR; - - // Set the amount of space necessary for null characters at the end of the data. - SQLSMALLINT extra = sizeof(Char); - - SQLSRV_ASSERT( ((buffer_length - extra) % (extra * 2)) == 0, "Must be multiple of 2 for binary to system string or " - "multiple of 4 for binary to wide string" ); - - // all fields will be treated as ODBC returns varchar(max) fields: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - // *2 is for each byte to hex conversion and * extra is for either system or wide string allocation - *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra; - - // copy as much as we can into the buffer - SQLLEN to_copy; - if( buffer_length < *out_buffer_length + extra ) { - to_copy = (buffer_length - extra); - out_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - r = SQL_SUCCESS; - to_copy = *out_buffer_length; - } - - // if there are bytes to copy as hex - if( to_copy > 0 ) { - // quick hex conversion routine - Char* h = reinterpret_cast( buffer ); - BYTE* b = reinterpret_cast( field_data ); - // to_copy contains the number of bytes to copy, so we divide the number in half (or quarter) - // to get the number of hex digits we can copy - SQLLEN to_copy_hex = to_copy / (2 * extra); - for( int i = 0; i < to_copy_hex; ++i ) { - *h = hex_chars[ (*b & 0xf0) >> 4 ]; - h++; - *h = hex_chars[ (*b++ & 0x0f) ]; - h++; - } - read_so_far += to_copy_hex; - *h = static_cast( 0 ); - } - else { - reinterpret_cast( buffer )[0] = '\0'; - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLCHAR* row = get_row(); - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - 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 ); -} - -SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLCHAR* row = get_row(); - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - 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 ); -} - - -SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - LONG* long_data = reinterpret_cast( buffer ); - - if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) { - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "22003", - (SQLCHAR*) "Numeric value out of range", 0 ); - return SQL_ERROR; - } - - if( *double_data != floor( *double_data )) { - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "01S07", - (SQLCHAR*) "Fractional truncation", 0 ); - return SQL_SUCCESS_WITH_INFO; - } - - *long_data = static_cast( *double_data ); - *out_buffer_length = sizeof( LONG ); - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - - return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - - return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - - *double_data = static_cast( *long_data ); - *out_buffer_length = sizeof( double ); - - return SQL_SUCCESS; -} -SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - - return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ] ); - - return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ); - - 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ); - - 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ); - - 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - 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 ); - - 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( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" ); - SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - SQLCHAR* field_data = NULL; - SQLULEN field_len = NULL; - - 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; - } - else { - - 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: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR); - - // to_copy is the number of characters to copy, not including the null terminator - // supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair. - SQLLEN to_copy; - - if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) { - - to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR); // to_copy is the number of characters - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - - r = SQL_SUCCESS; - to_copy = field_len - read_so_far; - } - - if( to_copy > 0 ) { - - bool tried_again = false; - do { - if (to_copy > INT_MAX ) { - LOG(SEV_ERROR, "MultiByteToWideChar: Buffer length exceeded."); - throw core::CoreException(); - } - - int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast(to_copy), - static_cast(buffer), static_cast(to_copy)); - if( ch_space == 0 ) { - - switch( GetLastError() ) { - - case ERROR_NO_UNICODE_TRANSLATION: - // the theory here is the conversion failed because the end of the buffer we provided contained only - // half a character at the end - if( !tried_again ) { - to_copy--; - tried_again = true; - continue; - } - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); - break; - default: - SQLSRV_ASSERT( false, "Severe error translating Unicode" ); - break; - } - - return SQL_ERROR; - } - - ((WCHAR*)buffer)[ to_copy ] = L'\0'; - read_so_far += to_copy; - break; - - } while( true ); - } - else { - reinterpret_cast( buffer )[0] = L'\0'; - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::to_same_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - // Set the amount of space necessary for null characters at the end of the data. - SQLSMALLINT extra = 0; - - switch( meta[ field_index ].c_type ) { - case SQL_C_WCHAR: - extra = sizeof( SQLWCHAR ); - break; - case SQL_C_BINARY: - extra = 0; - break; - case SQL_C_CHAR: - extra = sizeof( SQLCHAR ); - break; - default: - SQLSRV_ASSERT( false, "Invalid type in get_string_data" ); - break; - } - - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); - } - - // all fields will be treated as ODBC returns varchar(max) fields: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - *out_buffer_length = *reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far; - - // copy as much as we can into the buffer - SQLLEN to_copy; - if( buffer_length < *out_buffer_length + extra ) { - to_copy = buffer_length - extra; - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - r = SQL_SUCCESS; - to_copy = *out_buffer_length; - } - - SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" ); - - if( to_copy > 0 ) { - memcpy_s( buffer, buffer_length, field_data + read_so_far, to_copy ); - read_so_far += to_copy; - } - if( extra ) { - OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); - memcpy_s( reinterpret_cast( buffer ) + to_copy, buffer_length, L"\0", extra ); - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - SQLCHAR* field_data = NULL; - SQLLEN field_len = NULL; - - // if this is the first time called for this field, just convert the entire string to system first then - // use that to read from instead of converting chunk by chunk. This is because it's impossible to know - // the total length of the string for output_buffer_length without doing the conversion and returning - // SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and - // to_same_string). - - if( read_so_far == 0 ) { - - 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; - } - else { - - field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; - } - - BOOL default_char_used = FALSE; - char default_char = '?'; - - // allocate enough to handle WC -> DBCS conversion if it happens - temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof(char), sizeof(char))); - temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), - (LPSTR) temp_string.get(), static_cast(field_len), &default_char, &default_char_used ); - - if( temp_length == 0 ) { - - switch( GetLastError() ) { - - case ERROR_NO_UNICODE_TRANSLATION: - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); - break; - default: - SQLSRV_ASSERT( false, "Severe error translating Unicode" ); - break; - } - - return SQL_ERROR; - } - } - - *out_buffer_length = (temp_length - read_so_far); - - SQLLEN to_copy = 0; - - if( (size_t) buffer_length < (temp_length - read_so_far + sizeof(char))) { - - to_copy = buffer_length - sizeof(char); - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - - to_copy = (temp_length - read_so_far); - r = SQL_SUCCESS; - } - - if( to_copy > 0 ) { - - memcpy_s( buffer, buffer_length, temp_string.get() + read_so_far, to_copy ); - } - SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); - OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); - ((SQLCHAR*) buffer)[ to_copy ] = '\0'; - read_so_far += to_copy; - - return r; -} - - -SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - return to_same_string( field_index, buffer, buffer_length, out_buffer_length ); -} - -SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invlid 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 ] ); - - memcpy_s( buffer, buffer_length, long_data, sizeof( LONG )); - *out_buffer_length = sizeof( LONG ); - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invlid 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 ] ); - - memcpy_s( buffer, buffer_length, double_data, sizeof( double )); - *out_buffer_length = sizeof( double ); - - return SQL_SUCCESS; -} - -namespace { - -// called for each row in the cache when the cache is destroyed in the destructor -void cache_row_dtor( zval* data ) -{ - row_dtor_closure* cl = reinterpret_cast( Z_PTR_P( data ) ); - BYTE* row = cl->row_data; - // don't release this here, since this is called from the destructor of the result_set - sqlsrv_buffered_result_set* result_set = cl->results; - - for( SQLSMALLINT i = 0; i < result_set->column_count(); ++i ) { - - 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 ] ); - sqlsrv_free( out_of_row_data ); - } - } - - sqlsrv_free( row ); - sqlsrv_free( cl ); -} - -SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - zend_long mem_used TSRMLS_DC ) -{ - SQLSMALLINT extra = 0; - SQLULEN* output_buffer_len = NULL; - - // Set the amount of space necessary for null characters at the end of the data. - switch( meta.c_type ) { - case SQL_C_WCHAR: - extra = sizeof( SQLWCHAR ); - break; - case SQL_C_BINARY: - extra = 0; - break; - case SQL_C_CHAR: - extra = sizeof( SQLCHAR ); - break; - default: - SQLSRV_ASSERT( false, "Invalid type in read_lob_field" ); - break; - } - - SQLLEN already_read = 0; - SQLLEN to_read = INITIAL_FIELD_STRING_LEN; - sqlsrv_malloc_auto_ptr buffer; - buffer = static_cast( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN ))); - SQLRETURN r = SQL_SUCCESS; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLLEN last_field_len = 0; - bool full_length_returned = false; - - do { - - - output_buffer_len = reinterpret_cast( buffer.get() ); - r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ), - to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC ); - - // if the field is NULL, then return a NULL pointer - if( last_field_len == SQL_NULL_DATA ) { - return NULL; - } - - // if the last read was successful, we're done - if( r == SQL_SUCCESS ) { - // check to make sure we haven't overflown our memory limit - CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - break; - } - // else if it wasn't the truncated warning (01004) then we're done - else if( r == SQL_SUCCESS_WITH_INFO ) { - SQLSMALLINT len; - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - - if( !is_truncated_warning( state )) { - break; - } - } - - SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" ); - - // if the type of the field returns the total to be read, we use that and preallocate the buffer - if( last_field_len != SQL_NO_TOTAL ) { - - CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - - already_read += to_read - already_read; - to_read = last_field_len; - buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); - // record the size of the field since we have it available - *output_buffer_len = last_field_len; - full_length_returned = true; - } - // otherwise allocate another chunk of memory to read in - else { - already_read += to_read - already_read; - to_read *= 2; - CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); - } - - } while( true ); - - SQLSRV_ASSERT( output_buffer_len != NULL, "Output buffer not allocated properly" ); - - // most LOB field types return the total length in the last_field_len, but some field types such as XML - // only return the amount read on the last read - if( !full_length_returned ) { - *output_buffer_len = already_read + last_field_len; - } - - char* return_buffer = buffer; - buffer.transferred(); - return return_buffer; -} - -} diff --git a/sqlsrv/core_stream.cpp b/sqlsrv/core_stream.cpp deleted file mode 100644 index b85c63ce..00000000 --- a/sqlsrv/core_stream.cpp +++ /dev/null @@ -1,261 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_stream.cpp -// -// Contents: Implementation of PHP streams for reading SQL Server data -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" -#include - -namespace { - -// close a stream and free the PHP resources used by it - -int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) -{ - sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); - - // free the stream resources in the Zend engine - php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM ); - - // UNDEF the stream zval and delete our reference count to it. - ZVAL_UNDEF( &( ss->stmt->active_stream ) ); - - sqlsrv_free( ss ); - stream->abstract = NULL; - - return 0; -} - - -// read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are -// set when sqlsrv_get_field is called by the user specifying which field type they want. - -size_t sqlsrv_stream_read( php_stream* stream, _Out_writes_bytes_(count) char* buf, size_t count TSRMLS_DC ) -{ - SQLLEN read = 0; - SQLSMALLINT c_type = SQL_C_CHAR; - char* get_data_buffer = buf; - sqlsrv_malloc_auto_ptr temp_buf; - - sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); - - try { - - if( stream->eof ) { - return 0; - }; - - switch( ss->encoding ) { - case SQLSRV_ENCODING_CHAR: - c_type = SQL_C_CHAR; - break; - - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - break; - - case CP_UTF8: - { - c_type = SQL_C_WCHAR; - count /= 2; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes - if( count > PHP_STREAM_BUFFER_SIZE ) { - count = PHP_STREAM_BUFFER_SIZE; - } - - // use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16 - temp_buf = static_cast( sqlsrv_malloc( PHP_STREAM_BUFFER_SIZE )); - get_data_buffer = temp_buf; - break; - } - - default: - DIE( "Unknown encoding type when reading from a stream" ); - break; - } - - SQLRETURN r = SQLGetData( ss->stmt->handle(), ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read ); - - CHECK_SQL_ERROR( r, ss->stmt ) { - stream->eof = 1; - throw core::CoreException(); - } - - // if the stream returns either no data, NULL data, or returns data < than the count requested then - // we are at the "end of the stream" so we mark it - if( r == SQL_NO_DATA || read == SQL_NULL_DATA || ( static_cast( read ) <= count && read != SQL_NO_TOTAL )) { - stream->eof = 1; - } - - // if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator - // if it's not a binary encoded field - if( r == SQL_SUCCESS_WITH_INFO ) { - - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - - ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - - if( read == SQL_NO_TOTAL ) { - SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it " - "did not occur." ); - } - - if( is_truncated_warning( state ) ) { - switch( c_type ) { - - // As per SQLGetData documentation, if the length of character data exceeds the BufferLength, - // SQLGetData truncates the data to BufferLength less the length of null-termination character. - case SQL_C_BINARY: - read = count; - break; - case SQL_C_WCHAR: - read = ( count % 2 == 0 ? count - 2 : count - 3 ); - break; - case SQL_C_CHAR: - read = count - 1; - break; - default: - DIE( "sqlsrv_stream_read: should have never reached in this switch case."); - break; - } - } - else { - CHECK_SQL_WARNING( r, ss->stmt ); - } - } - - // if the encoding is UTF-8 - if (c_type == SQL_C_WCHAR) { - - count *= 2; // undo the shift to use the full buffer - - // flags set to 0 by default, which means that any invalid characters are dropped rather than causing - // an error. This happens only on XP. - DWORD flags = 0; - - // convert to UTF-8 - if (g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER) { - // Vista (and later) will detect invalid UTF-16 characters and raise an error. - flags = WC_ERR_INVALID_CHARS; - } - - if ( count > INT_MAX || (read >> 1) > INT_MAX ) - { - LOG(SEV_ERROR, "UTF-16 (wide character) string mapping: buffer length exceeded."); - throw core::CoreException(); - } - - int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast( temp_buf.get() ), - static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); - - if( enc_len == 0 ) { - - stream->eof = 1; - THROW_CORE_ERROR( ss->stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ); - } - - read = enc_len; - } - - return read; - } - - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." ); - return 0; - } -} - -// function table for stream operations. We only support reading and closing the stream -php_stream_ops sqlsrv_stream_ops = { - NULL, - sqlsrv_stream_read, - sqlsrv_stream_close, - NULL, - SQLSRV_STREAM, - NULL, - NULL, - NULL, - NULL -}; - -// open a stream and return the sqlsrv_stream_ops function table as part of the -// return value. There is only one valid way to open a stream, using sqlsrv_get_field on -// certain field types. A sqlsrv stream may only be opened in read mode. -static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, _In_ const char*, _In_ const char* mode, - int options, _In_ zend_string **, php_stream_context* STREAMS_DC TSRMLS_DC ) -{ - -#if ZEND_DEBUG - SQLSRV_UNUSED( __zend_orig_lineno ); - SQLSRV_UNUSED( __zend_orig_filename ); - SQLSRV_UNUSED( __zend_lineno ); - SQLSRV_UNUSED( __zend_filename ); - SQLSRV_UNUSED( __php_stream_call_depth ); -#endif - - sqlsrv_malloc_auto_ptr ss; - - ss = static_cast( sqlsrv_malloc( sizeof( sqlsrv_stream ))); - memset( ss, 0, sizeof( sqlsrv_stream )); - - // check for valid options - if( options != REPORT_ERRORS ) { - php_stream_wrapper_log_error( wrapper, options TSRMLS_CC, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" ); - return NULL; - } - - // allocate the stream from PHP - php_stream* php_str = php_stream_alloc( &sqlsrv_stream_ops, ss, 0, mode ); - if( php_str != NULL ) { - ss.transferred(); - } - - return php_str; -} - -// information structure that contains PHP stream wrapper info. We supply the minimal -// possible, including the open function and the name only. - -php_stream_wrapper_ops sqlsrv_stream_wrapper_ops = { - sqlsrv_stream_opener, - NULL, - NULL, - NULL, - NULL, - SQLSRV_STREAM_WRAPPER, - NULL, - NULL, - NULL, - NULL -}; - -} - -// structure used by PHP to get the function table for opening, closing, etc. of the stream -php_stream_wrapper g_sqlsrv_stream_wrapper = { - &sqlsrv_stream_wrapper_ops, - NULL, - 0 -}; diff --git a/sqlsrv/msodbcsql.h b/sqlsrv/msodbcsql.h deleted file mode 100644 index 8bcf83d0..00000000 --- a/sqlsrv/msodbcsql.h +++ /dev/null @@ -1,2343 +0,0 @@ -//----------------------------------------------------------------------------- -// File: msodbcsql.h -// -// Copyright: Copyright (c) Microsoft Corporation -// -// Contents: ODBC driver for SQL Server specific definitions. -// -//----------------------------------------------------------------------------- -#ifndef __msodbcsql_h__ -#define __msodbcsql_h__ - -#if !defined(SQLODBC_VER) -#define SQLODBC_VER 1100 -#endif - -#if SQLODBC_VER >= 1100 - -#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server" - -#define SQLODBC_FILE_NAME_ANSI "msodbcsql" -#define SQLODBC_FILE_NAME_VER_ANSI "msodbcsql11" -#define SQLODBC_FILE_NAME_FULL_ANSI "msodbcsql11.dll" - -#define SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_FULL_UNICODE L"Microsoft ODBC Driver for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE L"ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_UNICODE L"ODBC Driver for SQL Server" - -#define SQLODBC_FILE_NAME_UNICODE L"msodbcsql" -#define SQLODBC_FILE_NAME_VER_UNICODE L"msodbcsql11" -#define SQLODBC_FILE_NAME_FULL_UNICODE L"msodbcsql11.dll" - -// define the character type agnostic constants -#if defined(_UNICODE) || defined(UNICODE) - -#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE -#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_UNICODE -#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE -#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_UNICODE - -#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_UNICODE -#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_UNICODE -#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_UNICODE - -#else // _UNICODE || UNICODE - -#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI -#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI -#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI -#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI - -#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_ANSI -#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_ANSI -#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_ANSI - -#endif // _UNICODE || UNICODE - -#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER - -#endif // SQLODBC_VER - -#ifndef __sqlncli_h__ - -#if !defined(SQLNCLI_VER) -#define SQLNCLI_VER 1100 -#endif - -#if SQLNCLI_VER >= 1100 - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli11" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli11.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli11" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli11.dll" - -#elif SQLNCLI_VER >= 1000 - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli10" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli10.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli10" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli10.dll" - -#else - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli.dll" - -#endif // SQLNCLI_VER >= 1100 - -// define the character type agnostic constants -#if defined(_UNICODE) || defined(UNICODE) - -#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE -#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_UNICODE -#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE -#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_UNICODE - -#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_UNICODE -#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_UNICODE -#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_UNICODE - - -#else // _UNICODE || UNICODE - -#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI -#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_ANSI -#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI -#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_ANSI - -#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_ANSI -#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_ANSI -#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_ANSI - -#endif // _UNICODE || UNICODE - -#define SQLNCLI_DRIVER_NAME SQLNCLI_PRODUCT_NAME_SHORT_VER - - -#ifdef ODBCVER - -#ifdef __cplusplus -extern "C" { -#endif - -// max SQL Server identifier length -#define SQL_MAX_SQLSERVERNAME 128 - -// SQLSetConnectAttr driver specific defines. -// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage. -// Connection attributes -#define SQL_COPT_SS_BASE 1200 -#define SQL_COPT_SS_REMOTE_PWD (SQL_COPT_SS_BASE+1) // dbrpwset SQLSetConnectOption only -#define SQL_COPT_SS_USE_PROC_FOR_PREP (SQL_COPT_SS_BASE+2) // Use create proc for SQLPrepare -#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) // Force integrated security on login -#define SQL_COPT_SS_PRESERVE_CURSORS (SQL_COPT_SS_BASE+4) // Preserve server cursors after SQLTransact -#define SQL_COPT_SS_USER_DATA (SQL_COPT_SS_BASE+5) // dbgetuserdata/dbsetuserdata -#define SQL_COPT_SS_ENLIST_IN_DTC SQL_ATTR_ENLIST_IN_DTC // Enlist in a DTC transaction -#define SQL_COPT_SS_ENLIST_IN_XA SQL_ATTR_ENLIST_IN_XA // Enlist in a XA transaction -#define SQL_COPT_SS_FALLBACK_CONNECT (SQL_COPT_SS_BASE+10) // Enables FallBack connections -#define SQL_COPT_SS_PERF_DATA (SQL_COPT_SS_BASE+11) // Used to access SQL Server ODBC driver performance data -#define SQL_COPT_SS_PERF_DATA_LOG (SQL_COPT_SS_BASE+12) // Used to set the logfile name for the Performance data -#define SQL_COPT_SS_PERF_QUERY_INTERVAL (SQL_COPT_SS_BASE+13) // Used to set the query logging threshold in milliseconds. -#define SQL_COPT_SS_PERF_QUERY_LOG (SQL_COPT_SS_BASE+14) // Used to set the logfile name for saving queryies. -#define SQL_COPT_SS_PERF_QUERY (SQL_COPT_SS_BASE+15) // Used to start and stop query logging. -#define SQL_COPT_SS_PERF_DATA_LOG_NOW (SQL_COPT_SS_BASE+16) // Used to make a statistics log entry to disk. -#define SQL_COPT_SS_QUOTED_IDENT (SQL_COPT_SS_BASE+17) // Enable/Disable Quoted Identifiers -#define SQL_COPT_SS_ANSI_NPW (SQL_COPT_SS_BASE+18) // Enable/Disable ANSI NULL, Padding and Warnings -#define SQL_COPT_SS_BCP (SQL_COPT_SS_BASE+19) // Allow BCP usage on connection -#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) // Perform code page translation -#define SQL_COPT_SS_ATTACHDBFILENAME (SQL_COPT_SS_BASE+21) // File name to be attached as a database -#define SQL_COPT_SS_CONCAT_NULL (SQL_COPT_SS_BASE+22) // Enable/Disable CONCAT_NULL_YIELDS_NULL -#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) // Allow strong encryption for data -#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) // Multiple active result set per connection -#define SQL_COPT_SS_FAILOVER_PARTNER (SQL_COPT_SS_BASE+25) // Failover partner server -#define SQL_COPT_SS_OLDPWD (SQL_COPT_SS_BASE+26) // Old Password, used when changing password during login -#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) // Used to set/get any driver-specific or ODBC-defined TXN iso level -#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) // Trust server certificate -#define SQL_COPT_SS_SERVER_SPN (SQL_COPT_SS_BASE+29) // Server SPN -#define SQL_COPT_SS_FAILOVER_PARTNER_SPN (SQL_COPT_SS_BASE+30) // Failover partner server SPN -#define SQL_COPT_SS_INTEGRATED_AUTHENTICATION_METHOD (SQL_COPT_SS_BASE+31) // The integrated authentication method used for the connection -#define SQL_COPT_SS_MUTUALLY_AUTHENTICATED (SQL_COPT_SS_BASE+32) // Used to decide if the connection is mutually authenticated -#define SQL_COPT_SS_CLIENT_CONNECTION_ID (SQL_COPT_SS_BASE+33) // Post connection attribute used to get the ConnectionID -// Define old names -#define SQL_REMOTE_PWD SQL_COPT_SS_REMOTE_PWD -#define SQL_USE_PROCEDURE_FOR_PREPARE SQL_COPT_SS_USE_PROC_FOR_PREP -#define SQL_INTEGRATED_SECURITY SQL_COPT_SS_INTEGRATED_SECURITY -#define SQL_PRESERVE_CURSORS SQL_COPT_SS_PRESERVE_CURSORS - -// SQLSetStmtAttr SQL Server Native Client driver specific defines. -// Statement attributes -#define SQL_SOPT_SS_BASE 1225 -#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) // Text pointer logging -#define SQL_SOPT_SS_CURRENT_COMMAND (SQL_SOPT_SS_BASE+1) // dbcurcmd SQLGetStmtOption only -#define SQL_SOPT_SS_HIDDEN_COLUMNS (SQL_SOPT_SS_BASE+2) // Expose FOR BROWSE hidden columns -#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) // Set NOBROWSETABLE option -#define SQL_SOPT_SS_REGIONALIZE (SQL_SOPT_SS_BASE+4) // Regionalize output character conversions -#define SQL_SOPT_SS_CURSOR_OPTIONS (SQL_SOPT_SS_BASE+5) // Server cursor options -#define SQL_SOPT_SS_NOCOUNT_STATUS (SQL_SOPT_SS_BASE+6) // Real vs. Not Real row count indicator -#define SQL_SOPT_SS_DEFER_PREPARE (SQL_SOPT_SS_BASE+7) // Defer prepare until necessary -#define SQL_SOPT_SS_QUERYNOTIFICATION_TIMEOUT (SQL_SOPT_SS_BASE+8) // Notification timeout -#define SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT (SQL_SOPT_SS_BASE+9) // Notification message text -#define SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS (SQL_SOPT_SS_BASE+10)// SQL service broker name -#define SQL_SOPT_SS_PARAM_FOCUS (SQL_SOPT_SS_BASE+11)// Direct subsequent calls to parameter related methods to set properties on constituent columns/parameters of container types -#define SQL_SOPT_SS_NAME_SCOPE (SQL_SOPT_SS_BASE+12)// Sets name scope for subsequent catalog function calls -#define SQL_SOPT_SS_MAX_USED SQL_SOPT_SS_NAME_SCOPE -// Define old names -#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING -#define SQL_COPT_SS_BASE_EX 1240 -#define SQL_COPT_SS_BROWSE_CONNECT (SQL_COPT_SS_BASE_EX+1) // Browse connect mode of operation -#define SQL_COPT_SS_BROWSE_SERVER (SQL_COPT_SS_BASE_EX+2) // Single Server browse request. -#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) // Issues warning when data from the server had a loss during code page conversion. -#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) // dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check -#define SQL_COPT_SS_BROWSE_CACHE_DATA (SQL_COPT_SS_BASE_EX+5) // Determines if we should cache browse info. Used when returned buffer is greater then ODBC limit (32K) -#define SQL_COPT_SS_RESET_CONNECTION (SQL_COPT_SS_BASE_EX+6) // When this option is set, we will perform connection reset on next packet -#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) // Application Intent -#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) // Multi-subnet Failover -#define SQL_COPT_SS_EX_MAX_USED SQL_COPT_SS_MULTISUBNET_FAILOVER - -// SQLColAttributes driver specific defines. -// SQLSetDescField/SQLGetDescField driver specific defines. -// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_CA_SS_BASE 1200 -#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) // dbcoltype/dbalttype -#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) // dbcolutype/dbaltutype -#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) // dbnumorders -#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) // dbordercol -#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) // dbvarylen -#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) // dbnumcompute -#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) // dbnextrow status return -#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) // dbbylist -#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) // dbaltcolid -#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) // dbaltop -#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) // dbcollen -#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) // Column is hidden (FOR BROWSE) -#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) // Column is key column (FOR BROWSE) -//#define SQL_DESC_BASE_COLUMN_NAME_OLD (SQL_CA_SS_BASE+13) // This is defined at another location. -#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) // Column collation (only for chars) -#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15) -#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16) -#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17) - -// XML, CLR UDT, and table valued parameter related metadata -#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) // UDT catalog name -#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) // UDT schema name -#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) // UDT type name -#define SQL_CA_SS_UDT_ASSEMBLY_TYPE_NAME (SQL_CA_SS_BASE+21) // Qualified name of the assembly containing the UDT class -#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) // Name of the catalog that contains XML Schema collection -#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) // Name of the schema that contains XML Schema collection -#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) // Name of the XML Schema collection -#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) // Catalog name -#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) // Schema name -#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) // Type name - -// table valued parameter related metadata -#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) // column is computed -#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) // column is part of a unique key -#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) // column sort order -#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) // column sort ordinal -#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) // column has default value for all rows of the table valued parameter - -// sparse column related metadata -#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) // column is a column-set column for sparse columns - -// Legacy datetime related metadata -#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) // column type to send on the wire for datetime types - -#define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+36) - -// Defines returned by SQL_ATTR_CURSOR_TYPE/SQL_CURSOR_TYPE -#define SQL_CURSOR_FAST_FORWARD_ONLY 8 // Only returned by SQLGetStmtAttr/Option -// Defines for use with SQL_COPT_SS_USE_PROC_FOR_PREP -#define SQL_UP_OFF 0L // Procedures won't be used for prepare -#define SQL_UP_ON 1L // Procedures will be used for prepare -#define SQL_UP_ON_DROP 2L // Temp procedures will be explicitly dropped -#define SQL_UP_DEFAULT SQL_UP_ON -// Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only -#define SQL_IS_OFF 0L // Integrated security isn't used -#define SQL_IS_ON 1L // Integrated security is used -#define SQL_IS_DEFAULT SQL_IS_OFF -// Defines for use with SQL_COPT_SS_PRESERVE_CURSORS -#define SQL_PC_OFF 0L // Cursors are closed on SQLTransact -#define SQL_PC_ON 1L // Cursors remain open on SQLTransact -#define SQL_PC_DEFAULT SQL_PC_OFF -// Defines for use with SQL_COPT_SS_USER_DATA -#define SQL_UD_NOTSET NULL // No user data pointer set -// Defines for use with SQL_COPT_SS_TRANSLATE -#define SQL_XL_OFF 0L // Code page translation is not performed -#define SQL_XL_ON 1L // Code page translation is performed -#define SQL_XL_DEFAULT SQL_XL_ON -// Defines for use with SQL_COPT_SS_FALLBACK_CONNECT - Pre-Connect Option only -#define SQL_FB_OFF 0L // FallBack connections are disabled -#define SQL_FB_ON 1L // FallBack connections are enabled -#define SQL_FB_DEFAULT SQL_FB_OFF -// Defines for use with SQL_COPT_SS_BCP - Pre-Connect Option only -#define SQL_BCP_OFF 0L // BCP is not allowed on connection -#define SQL_BCP_ON 1L // BCP is allowed on connection -#define SQL_BCP_DEFAULT SQL_BCP_OFF -// Defines for use with SQL_COPT_SS_QUOTED_IDENT -#define SQL_QI_OFF 0L // Quoted identifiers are enable -#define SQL_QI_ON 1L // Quoted identifiers are disabled -#define SQL_QI_DEFAULT SQL_QI_ON -// Defines for use with SQL_COPT_SS_ANSI_NPW - Pre-Connect Option only -#define SQL_AD_OFF 0L // ANSI NULLs, Padding and Warnings are enabled -#define SQL_AD_ON 1L // ANSI NULLs, Padding and Warnings are disabled -#define SQL_AD_DEFAULT SQL_AD_ON -// Defines for use with SQL_COPT_SS_CONCAT_NULL - Pre-Connect Option only -#define SQL_CN_OFF 0L // CONCAT_NULL_YIELDS_NULL is off -#define SQL_CN_ON 1L // CONCAT_NULL_YIELDS_NULL is on -#define SQL_CN_DEFAULT SQL_CN_ON -// Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING -#define SQL_TL_OFF 0L // No logging on text pointer ops -#define SQL_TL_ON 1L // Logging occurs on text pointer ops -#define SQL_TL_DEFAULT SQL_TL_ON -// Defines for use with SQL_SOPT_SS_HIDDEN_COLUMNS -#define SQL_HC_OFF 0L // FOR BROWSE columns are hidden -#define SQL_HC_ON 1L // FOR BROWSE columns are exposed -#define SQL_HC_DEFAULT SQL_HC_OFF -// Defines for use with SQL_SOPT_SS_NOBROWSETABLE -#define SQL_NB_OFF 0L // NO_BROWSETABLE is off -#define SQL_NB_ON 1L // NO_BROWSETABLE is on -#define SQL_NB_DEFAULT SQL_NB_OFF -// Defines for use with SQL_SOPT_SS_REGIONALIZE -#define SQL_RE_OFF 0L // No regionalization occurs on output character conversions -#define SQL_RE_ON 1L // Regionalization occurs on output character conversions -#define SQL_RE_DEFAULT SQL_RE_OFF -// Defines for use with SQL_SOPT_SS_CURSOR_OPTIONS -#define SQL_CO_OFF 0L // Clear all cursor options -#define SQL_CO_FFO 1L // Fast-forward cursor will be used -#define SQL_CO_AF 2L // Autofetch on cursor open -#define SQL_CO_FFO_AF (SQL_CO_FFO|SQL_CO_AF) // Fast-forward cursor with autofetch -#define SQL_CO_FIREHOSE_AF 4L // Auto fetch on fire-hose cursors -#define SQL_CO_DEFAULT SQL_CO_OFF -//SQL_SOPT_SS_NOCOUNT_STATUS -#define SQL_NC_OFF 0L -#define SQL_NC_ON 1L -//SQL_SOPT_SS_DEFER_PREPARE -#define SQL_DP_OFF 0L -#define SQL_DP_ON 1L -//SQL_SOPT_SS_NAME_SCOPE -#define SQL_SS_NAME_SCOPE_TABLE 0L -#define SQL_SS_NAME_SCOPE_TABLE_TYPE 1L -#define SQL_SS_NAME_SCOPE_EXTENDED 2L -#define SQL_SS_NAME_SCOPE_SPARSE_COLUMN_SET 3L -#define SQL_SS_NAME_SCOPE_DEFAULT SQL_SS_NAME_SCOPE_TABLE -//SQL_COPT_SS_ENCRYPT -#define SQL_EN_OFF 0L -#define SQL_EN_ON 1L -//SQL_COPT_SS_TRUST_SERVER_CERTIFICATE -#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L -#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L -//SQL_COPT_SS_BROWSE_CONNECT -#define SQL_MORE_INFO_NO 0L -#define SQL_MORE_INFO_YES 1L -//SQL_COPT_SS_BROWSE_CACHE_DATA -#define SQL_CACHE_DATA_NO 0L -#define SQL_CACHE_DATA_YES 1L -//SQL_COPT_SS_RESET_CONNECTION -#define SQL_RESET_YES 1L -//SQL_COPT_SS_WARN_ON_CP_ERROR -#define SQL_WARN_NO 0L -#define SQL_WARN_YES 1L -//SQL_COPT_SS_MARS_ENABLED -#define SQL_MARS_ENABLED_NO 0L -#define SQL_MARS_ENABLED_YES 1L -/* SQL_TXN_ISOLATION_OPTION bitmasks */ -#define SQL_TXN_SS_SNAPSHOT 0x00000020L - -// The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER -#define SQL_SS_ORDER_UNSPECIFIED 0L -#define SQL_SS_DESCENDING_ORDER 1L -#define SQL_SS_ASCENDING_ORDER 2L -#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED - -// Driver specific SQL data type defines. -// Microsoft has -150 thru -199 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_SS_VARIANT (-150) -#define SQL_SS_UDT (-151) -#define SQL_SS_XML (-152) -#define SQL_SS_TABLE (-153) -#define SQL_SS_TIME2 (-154) -#define SQL_SS_TIMESTAMPOFFSET (-155) - -// Local types to be used with SQL_CA_SS_SERVER_TYPE -#define SQL_SS_TYPE_DEFAULT 0L -#define SQL_SS_TYPE_SMALLDATETIME 1L -#define SQL_SS_TYPE_DATETIME 2L - -// Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager. -#define SQL_C_TYPES_EXTENDED 0x04000L -#define SQL_C_SS_TIME2 (SQL_C_TYPES_EXTENDED+0) -#define SQL_C_SS_TIMESTAMPOFFSET (SQL_C_TYPES_EXTENDED+1) - -#ifndef SQLNCLI_NO_BCP -// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application -// and you want to exclude the BCP-related definitions in this header file. - -// SQL Server Data Type defines. -// New types for SQL 6.0 and later servers -#define SQLTEXT 0x23 -#define SQLVARBINARY 0x25 -#define SQLINTN 0x26 -#define SQLVARCHAR 0x27 -#define SQLBINARY 0x2d -#define SQLIMAGE 0x22 -#define SQLCHARACTER 0x2f -#define SQLINT1 0x30 -#define SQLBIT 0x32 -#define SQLINT2 0x34 -#define SQLINT4 0x38 -#define SQLMONEY 0x3c -#define SQLDATETIME 0x3d -#define SQLFLT8 0x3e -#define SQLFLTN 0x6d -#define SQLMONEYN 0x6e -#define SQLDATETIMN 0x6f -#define SQLFLT4 0x3b -#define SQLMONEY4 0x7a -#define SQLDATETIM4 0x3a -// New types for SQL 6.0 and later servers -#define SQLDECIMAL 0x6a -#define SQLNUMERIC 0x6c -// New types for SQL 7.0 and later servers -#define SQLUNIQUEID 0x24 -#define SQLBIGCHAR 0xaf -#define SQLBIGVARCHAR 0xa7 -#define SQLBIGBINARY 0xad -#define SQLBIGVARBINARY 0xa5 -#define SQLBITN 0x68 -#define SQLNCHAR 0xef -#define SQLNVARCHAR 0xe7 -#define SQLNTEXT 0x63 -// New types for SQL 2000 and later servers -#define SQLINT8 0x7f -#define SQLVARIANT 0x62 -// New types for SQL 2005 and later servers -#define SQLUDT 0xf0 -#define SQLXML 0xf1 -// New types for SQL 2008 and later servers -#define SQLTABLE 0xf3 -#define SQLDATEN 0x28 -#define SQLTIMEN 0x29 -#define SQLDATETIME2N 0x2a -#define SQLDATETIMEOFFSETN 0x2b -// Define old names -#define SQLDECIMALN 0x6a -#define SQLNUMERICN 0x6c -#endif // SQLNCLI_NO_BCP - -// SQL_SS_LENGTH_UNLIMITED is used to describe the max length of -// VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns -#define SQL_SS_LENGTH_UNLIMITED 0 - -// User Data Type definitions. -// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE. -#define SQLudtBINARY 3 -#define SQLudtBIT 16 -#define SQLudtBITN 0 -#define SQLudtCHAR 1 -#define SQLudtDATETIM4 22 -#define SQLudtDATETIME 12 -#define SQLudtDATETIMN 15 -#define SQLudtDECML 24 -#define SQLudtDECMLN 26 -#define SQLudtFLT4 23 -#define SQLudtFLT8 8 -#define SQLudtFLTN 14 -#define SQLudtIMAGE 20 -#define SQLudtINT1 5 -#define SQLudtINT2 6 -#define SQLudtINT4 7 -#define SQLudtINTN 13 -#define SQLudtMONEY 11 -#define SQLudtMONEY4 21 -#define SQLudtMONEYN 17 -#define SQLudtNUM 10 -#define SQLudtNUMN 25 -#define SQLudtSYSNAME 18 -#define SQLudtTEXT 19 -#define SQLudtTIMESTAMP 80 -#define SQLudtUNIQUEIDENTIFIER 0 -#define SQLudtVARBINARY 4 -#define SQLudtVARCHAR 2 -#define MIN_USER_DATATYPE 256 -// Aggregate operator types. -// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP. -#define SQLAOPSTDEV 0x30 // Standard deviation -#define SQLAOPSTDEVP 0x31 // Standard deviation population -#define SQLAOPVAR 0x32 // Variance -#define SQLAOPVARP 0x33 // Variance population -#define SQLAOPCNT 0x4b // Count -#define SQLAOPSUM 0x4d // Sum -#define SQLAOPAVG 0x4f // Average -#define SQLAOPMIN 0x51 // Min -#define SQLAOPMAX 0x52 // Max -#define SQLAOPANY 0x53 // Any -#define SQLAOPNOOP 0x56 // None -// SQLGetInfo driver specific defines. -// Microsoft has 1151 thru 1200 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_INFO_SS_FIRST 1199 -#define SQL_INFO_SS_NETLIB_NAMEW (SQL_INFO_SS_FIRST+0) // dbprocinfo -#define SQL_INFO_SS_NETLIB_NAMEA (SQL_INFO_SS_FIRST+1) // dbprocinfo -#define SQL_INFO_SS_MAX_USED SQL_INFO_SS_NETLIB_NAMEA -#ifdef UNICODE -#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEW -#else -#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEA -#endif - -// SQLGetDiagField driver specific defines. -// Microsoft has -1150 thru -1199 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_DIAG_SS_BASE (-1150) -#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE) -#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1) -#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2) -#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3) -#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4) -// SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines. -// Microsoft has -200 thru -299 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_DIAG_DFC_SS_BASE (-200) -#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0) -#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1) -#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2) -#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3) -#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4) -#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5) -#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6) -#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7) -#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8) -#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9) -#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10) -#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11) -#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12) -#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13) -#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14) -#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15) -#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16) -#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17) -#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18) -#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19) -#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) -#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) -#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21) -#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) -#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) -#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23) -#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24) -#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25) -#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26) -#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26) -#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) -#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) -#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28) -#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) -#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) -#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30) -#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31) -#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32) -#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33) -#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34) -#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35) -#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36) -#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37) -#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38) -#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39) -#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40) -#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41) -#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42) -#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43) -#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44) -#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45) -#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46) -#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47) -#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48) -#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49) -#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50) -#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51) -#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52) -#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53) -#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54) -#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55) -#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56) - -// Severity codes for SQL_DIAG_SS_SEVERITY -#define EX_ANY 0 -#define EX_INFO 10 -#define EX_MAXISEVERITY EX_INFO -#define EX_MISSING 11 -#define EX_TYPE 12 -#define EX_DEADLOCK 13 -#define EX_PERMIT 14 -#define EX_SYNTAX 15 -#define EX_USER 16 -#define EX_RESOURCE 17 -#define EX_INTOK 18 -#define MAXUSEVERITY EX_INTOK -#define EX_LIMIT 19 -#define EX_CMDFATAL 20 -#define MINFATALERR EX_CMDFATAL -#define EX_DBFATAL 21 -#define EX_TABCORRUPT 22 -#define EX_DBCORRUPT 23 -#define EX_HARDWARE 24 -#define EX_CONTROL 25 -// Internal server datatypes - used when binding to SQL_C_BINARY -#ifndef MAXNUMERICLEN // Resolve ODS/DBLib conflicts -// DB-Library datatypes -#define DBMAXCHAR (8000+1) // Max length of DBVARBINARY and DBVARCHAR, etc. +1 for zero byte -#define MAXNAME (SQL_MAX_SQLSERVERNAME+1) // Max server identifier length including zero byte -#ifdef UNICODE -typedef wchar_t DBCHAR; -#else -typedef char DBCHAR; - -#endif -typedef short SQLSMALLINT; - -typedef unsigned short SQLUSMALLINT; - -typedef unsigned char DBBINARY; - -typedef unsigned char DBTINYINT; - -typedef short DBSMALLINT; - -typedef unsigned short DBUSMALLINT; - -typedef double DBFLT8; - -typedef unsigned char DBBIT; - -typedef unsigned char DBBOOL; - -typedef float DBFLT4; - -typedef DBFLT4 DBREAL; - -typedef UINT DBUBOOL; - -typedef struct dbmoney - { - LONG mnyhigh; - ULONG mnylow; - } DBMONEY; - -typedef struct dbdatetime - { - LONG dtdays; - ULONG dttime; - } DBDATETIME; - -typedef struct dbdatetime4 - { - USHORT numdays; - USHORT nummins; - } DBDATETIM4; - -typedef LONG DBMONEY4; - -#include // 8-byte structure packing - -// New Date Time Structures -// New Structure for TIME2 -typedef struct tagSS_TIME2_STRUCT -{ - SQLUSMALLINT hour; - SQLUSMALLINT minute; - SQLUSMALLINT second; - SQLUINTEGER fraction; -} SQL_SS_TIME2_STRUCT; -// New Structure for TIMESTAMPOFFSET -typedef struct tagSS_TIMESTAMPOFFSET_STRUCT -{ - SQLSMALLINT year; - SQLUSMALLINT month; - SQLUSMALLINT day; - SQLUSMALLINT hour; - SQLUSMALLINT minute; - SQLUSMALLINT second; - SQLUINTEGER fraction; - SQLSMALLINT timezone_hour; - SQLSMALLINT timezone_minute; -} SQL_SS_TIMESTAMPOFFSET_STRUCT; - -typedef struct tagDBTIME2 -{ - USHORT hour; - USHORT minute; - USHORT second; - ULONG fraction; -} DBTIME2; - -typedef struct tagDBTIMESTAMPOFFSET -{ - SHORT year; - USHORT month; - USHORT day; - USHORT hour; - USHORT minute; - USHORT second; - ULONG fraction; - SHORT timezone_hour; - SHORT timezone_minute; -} DBTIMESTAMPOFFSET; - -#include // restore original structure packing - -// Money value *10,000 -#define DBNUM_PREC_TYPE BYTE -#define DBNUM_SCALE_TYPE BYTE -#define DBNUM_VAL_TYPE BYTE - -#if (ODBCVER < 0x0300) -#define MAXNUMERICLEN 16 -typedef struct dbnumeric // Internal representation of NUMERIC data type -{ - DBNUM_PREC_TYPE precision; // Precision - DBNUM_SCALE_TYPE scale; // Scale - BYTE sign; // Sign (1 if positive, 0 if negative) - DBNUM_VAL_TYPE val[MAXNUMERICLEN];// Value -} DBNUMERIC; -typedef DBNUMERIC DBDECIMAL;// Internal representation of DECIMAL data type -#else // Use ODBC 3.0 definitions since same as DBLib -#define MAXNUMERICLEN SQL_MAX_NUMERIC_LEN -typedef SQL_NUMERIC_STRUCT DBNUMERIC; -typedef SQL_NUMERIC_STRUCT DBDECIMAL; -#endif // ODCBVER -#endif // MAXNUMERICLEN - -#ifndef INT -typedef int INT; -typedef LONG DBINT; -typedef DBINT * LPDBINT; -#ifndef _LPCBYTE_DEFINED -#define _LPCBYTE_DEFINED -typedef BYTE const* LPCBYTE; -#endif //_LPCBYTE_DEFINED -#endif // INT -/************************************************************************** -This struct is a global used for gathering statistical data on the driver. -Access to this structure is controlled via the pStatCrit; -***************************************************************************/ -typedef struct sqlperf -{ - // Application Profile Statistics - DWORD TimerResolution; - DWORD SQLidu; - DWORD SQLiduRows; - DWORD SQLSelects; - DWORD SQLSelectRows; - DWORD Transactions; - DWORD SQLPrepares; - DWORD ExecDirects; - DWORD SQLExecutes; - DWORD CursorOpens; - DWORD CursorSize; - DWORD CursorUsed; - LDOUBLE PercentCursorUsed; - LDOUBLE AvgFetchTime; - LDOUBLE AvgCursorSize; - LDOUBLE AvgCursorUsed; - DWORD SQLFetchTime; - DWORD SQLFetchCount; - DWORD CurrentStmtCount; - DWORD MaxOpenStmt; - DWORD SumOpenStmt; - // Connection Statistics - DWORD CurrentConnectionCount; - DWORD MaxConnectionsOpened; - DWORD SumConnectionsOpened; - DWORD SumConnectiontime; - LDOUBLE AvgTimeOpened; - // Network Statistics - DWORD ServerRndTrips; - DWORD BuffersSent; - DWORD BuffersRec; - DWORD BytesSent; - DWORD BytesRec; - // Time Statistics; - DWORD msExecutionTime; - DWORD msNetWorkServerTime; -} SQLPERF; -// The following are options for SQL_COPT_SS_PERF_DATA and SQL_COPT_SS_PERF_QUERY -#define SQL_PERF_START 1 // Starts the driver sampling performance data. -#define SQL_PERF_STOP 2 // Stops the counters from sampling performance data. -// The following are defines for SQL_COPT_SS_PERF_DATA_LOG -#define SQL_SS_DL_DEFAULT TEXT("STATS.LOG") -// The following are defines for SQL_COPT_SS_PERF_QUERY_LOG -#define SQL_SS_QL_DEFAULT TEXT("QUERY.LOG") -// The following are defines for SQL_COPT_SS_PERF_QUERY_INTERVAL -#define SQL_SS_QI_DEFAULT 30000 // 30,000 milliseconds - -#ifndef SQLNCLI_NO_BCP -// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application -// and you want to exclude the BCP-related definitions in this header file. - -// ODBC BCP prototypes and defines -// Return codes -#define SUCCEED 1 -#define FAIL 0 -#define SUCCEED_ABORT 2 -#define SUCCEED_ASYNC 3 -// Transfer directions -#define DB_IN 1 // Transfer from client to server -#define DB_OUT 2 // Transfer from server to client -// bcp_control option -#define BCPMAXERRS 1 // Sets max errors allowed -#define BCPFIRST 2 // Sets first row to be copied out -#define BCPLAST 3 // Sets number of rows to be copied out -#define BCPBATCH 4 // Sets input batch size -#define BCPKEEPNULLS 5 // Sets to insert NULLs for empty input values -#define BCPABORT 6 // Sets to have bcpexec return SUCCEED_ABORT -#define BCPODBC 7 // Sets ODBC canonical character output -#define BCPKEEPIDENTITY 8 // Sets IDENTITY_INSERT on -#if SQLNCLI_VER < 1000 -#define BCP6xFILEFMT 9 // DEPRECATED: Sets 6x file format on -#endif -#define BCPHINTSA 10 // Sets server BCP hints (ANSI string) -#define BCPHINTSW 11 // Sets server BCP hints (UNICODE string) -#define BCPFILECP 12 // Sets clients code page for the file -#define BCPUNICODEFILE 13 // Sets that the file contains unicode header -#define BCPTEXTFILE 14 // Sets BCP mode to expect a text file and to detect Unicode or ANSI automatically -#define BCPFILEFMT 15 // Sets file format version -#define BCPFMTXML 16 // Sets the format file type to xml -#define BCPFIRSTEX 17 // Starting Row for BCP operation (64 bit) -#define BCPLASTEX 18 // Ending Row for BCP operation (64 bit) -#define BCPROWCOUNT 19 // Total Number of Rows Copied (64 bit) -#define BCPDELAYREADFMT 20 // Delay reading format file unil bcp_exec -// BCPFILECP values -// Any valid code page that is installed on the client can be passed plus: -#define BCPFILECP_ACP 0 // Data in file is in Windows code page -#define BCPFILECP_OEMCP 1 // Data in file is in OEM code page (default) -#define BCPFILECP_RAW (-1)// Data in file is in Server code page (no conversion) -// bcp_collen definition -#define SQL_VARLEN_DATA (-10) // Use default length for column -// BCP column format properties -#define BCP_FMT_TYPE 0x01 -#define BCP_FMT_INDICATOR_LEN 0x02 -#define BCP_FMT_DATA_LEN 0x03 -#define BCP_FMT_TERMINATOR 0x04 -#define BCP_FMT_SERVER_COL 0x05 -#define BCP_FMT_COLLATION 0x06 -#define BCP_FMT_COLLATION_ID 0x07 -// bcp_setbulkmode properties -#define BCP_OUT_CHARACTER_MODE 0x01 -#define BCP_OUT_WIDE_CHARACTER_MODE 0x02 -#define BCP_OUT_NATIVE_TEXT_MODE 0x03 -#define BCP_OUT_NATIVE_MODE 0x04 - - - -// BCP functions -DBINT SQL_API bcp_batch (HDBC); -RETCODE SQL_API bcp_bind (HDBC, LPCBYTE, INT, DBINT, LPCBYTE, INT, INT, INT); -RETCODE SQL_API bcp_colfmt (HDBC, INT, BYTE, INT, DBINT, LPCBYTE, INT, INT); -RETCODE SQL_API bcp_collen (HDBC, DBINT, INT); -RETCODE SQL_API bcp_colptr (HDBC, LPCBYTE, INT); -RETCODE SQL_API bcp_columns (HDBC, INT); -RETCODE SQL_API bcp_control (HDBC, INT, void *); -DBINT SQL_API bcp_done (HDBC); -RETCODE SQL_API bcp_exec (HDBC, LPDBINT); -RETCODE SQL_API bcp_getcolfmt (HDBC, INT, INT, void *, INT, INT *); -RETCODE SQL_API bcp_initA (HDBC, LPCSTR, LPCSTR, LPCSTR, INT); -RETCODE SQL_API bcp_initW (HDBC, LPCWSTR, LPCWSTR, LPCWSTR, INT); -RETCODE SQL_API bcp_moretext (HDBC, DBINT, LPCBYTE); -RETCODE SQL_API bcp_readfmtA (HDBC, LPCSTR); -RETCODE SQL_API bcp_readfmtW (HDBC, LPCWSTR); -RETCODE SQL_API bcp_sendrow (HDBC); -RETCODE SQL_API bcp_setbulkmode (HDBC, INT, _In_reads_bytes_(cbField) void*, INT cbField, _In_reads_bytes_(cbRow) void *, INT cbRow); -RETCODE SQL_API bcp_setcolfmt (HDBC, INT, INT, void *, INT); -RETCODE SQL_API bcp_writefmtA (HDBC, LPCSTR); -RETCODE SQL_API bcp_writefmtW (HDBC, LPCWSTR); -CHAR* SQL_API dbprtypeA (INT); -WCHAR* SQL_API dbprtypeW (INT); -CHAR* SQL_API bcp_gettypenameA (INT, DBBOOL); -WCHAR* SQL_API bcp_gettypenameW (INT, DBBOOL); - -#ifdef UNICODE -#define bcp_init bcp_initW -#define bcp_readfmt bcp_readfmtW -#define bcp_writefmt bcp_writefmtW -#define dbprtype dbprtypeW -#define bcp_gettypename bcp_gettypenameW -#define BCPHINTS BCPHINTSW -#else -#define bcp_init bcp_initA -#define bcp_readfmt bcp_readfmtA -#define bcp_writefmt bcp_writefmtA -#define dbprtype dbprtypeA -#define bcp_gettypename bcp_gettypenameA -#define BCPHINTS BCPHINTSA -#endif // UNICODE - -#endif // SQLNCLI_NO_BCP - -// The following options have been deprecated -#define SQL_FAST_CONNECT (SQL_COPT_SS_BASE+0) -// Defines for use with SQL_FAST_CONNECT - only useable before connecting -#define SQL_FC_OFF 0L // Fast connect is off -#define SQL_FC_ON 1L // Fast connect is on -#define SQL_FC_DEFAULT SQL_FC_OFF -#define SQL_COPT_SS_ANSI_OEM (SQL_COPT_SS_BASE+6) -#define SQL_AO_OFF 0L -#define SQL_AO_ON 1L -#define SQL_AO_DEFAULT SQL_AO_OFF -#define SQL_CA_SS_BASE_COLUMN_NAME SQL_DESC_BASE_COLUMN_NAME - - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // ODBCVER - - - -#ifdef __cplusplus -extern "C" { -#endif -#include - -//The following facilitates opening a handle to a SQL filestream -typedef enum _SQL_FILESTREAM_DESIRED_ACCESS { - SQL_FILESTREAM_READ = 0, - SQL_FILESTREAM_WRITE = 1, - SQL_FILESTREAM_READWRITE = 2 -} SQL_FILESTREAM_DESIRED_ACCESS; -#define SQL_FILESTREAM_OPEN_FLAG_ASYNC 0x00000001L -#define SQL_FILESTREAM_OPEN_FLAG_NO_BUFFERING 0x00000002L -#define SQL_FILESTREAM_OPEN_FLAG_NO_WRITE_THROUGH 0x00000004L -#define SQL_FILESTREAM_OPEN_FLAG_SEQUENTIAL_SCAN 0x00000008L -#define SQL_FILESTREAM_OPEN_FLAG_RANDOM_ACCESS 0x00000010L - - -HANDLE __stdcall OpenSqlFilestream ( - LPCWSTR FilestreamPath, - SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess, - ULONG OpenOptions, - _In_reads_bytes_(FilestreamTransactionContextLength) - LPBYTE FilestreamTransactionContext, - SSIZE_T FilestreamTransactionContextLength, - PLARGE_INTEGER AllocationSize); -#define FSCTL_SQL_FILESTREAM_FETCH_OLD_CONTENT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 2392, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#ifdef __cplusplus -} // extern "C" -#endif - - -#endif //__sqlncli_h__ - -#define SQL_COPT_SS_CONNECT_RETRY_COUNT (SQL_COPT_SS_BASE+34) // Post connection attribute used to get ConnectRetryCount -#define SQL_COPT_SS_CONNECT_RETRY_INTERVAL (SQL_COPT_SS_BASE+35) // Post connection attribute used to get ConnectRetryInterval -#ifdef SQL_COPT_SS_MAX_USED -#undef SQL_COPT_SS_MAX_USED -#endif // SQL_COPT_SS_MAX_USED -#define SQL_COPT_SS_MAX_USED SQL_COPT_SS_CONNECT_RETRY_INTERVAL - - -#ifndef _SQLUSERINSTANCE_H_ -#define _SQLUSERINSTANCE_H_ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Recommended buffer size to store a LocalDB connection string -#define LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE 260 - -// type definition for LocalDBCreateInstance function -typedef HRESULT __cdecl FnLocalDBCreateInstance ( - // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - _In_z_ PCWSTR wszVersion, - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -); - -// type definition for pointer to LocalDBCreateInstance function -typedef FnLocalDBCreateInstance* PFnLocalDBCreateInstance; - -// type definition for LocalDBStartInstance function -typedef HRESULT __cdecl FnLocalDBStartInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags, - // O the buffer to store the connection string to the LocalDB instance - _Out_writes_opt_z_(*lpcchSqlConnection) LPWSTR wszSqlConnection, - // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. - _Inout_opt_ LPDWORD lpcchSqlConnection -); - -// type definition for pointer to LocalDBStartInstance function -typedef FnLocalDBStartInstance* PFnLocalDBStartInstance; - -// Flags for the LocalDBFormatMessage function -#define LOCALDB_TRUNCATE_ERR_MESSAGE 0x0001L - -// type definition for LocalDBFormatMessage function -typedef HRESULT __cdecl FnLocalDBFormatMessage( - // I the LocalDB error code - _In_ HRESULT hrLocalDB, - // I Available flags: - // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, - // the error message will be truncated to fit into the buffer - _In_ DWORD dwFlags, - // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - _In_ DWORD dwLanguageId, - // O the buffer to store the LocalDB error message - _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, - // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. If the function succeeds - // contains the number of characters in the message, excluding the trailing null - _Inout_ LPDWORD lpcchMessage -); - -// type definition for function pointer to LocalDBFormatMessage function -typedef FnLocalDBFormatMessage* PFnLocalDBFormatMessage; - - -// MessageId: LOCALDB_ERROR_NOT_INSTALLED -// -// MessageText: -// -// LocalDB is not installed. -// -#define LOCALDB_ERROR_NOT_INSTALLED ((HRESULT)0x89C50116L) - -//--------------------------------------------------------------------- -// Function: LocalDBCreateInstance -// -// Description: This function will create the new LocalDB instance. -// -// Available Flags: -// No flags available. Reserved for future use. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_VERSION, if the version parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INVALID_OPERATION, if the user tries to create a default instance -// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH -// LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED, if the specified service level is not installed -// LOCALDB_ERROR_INSTANCE_FOLDER_ALREADY_EXISTS, if the instance folder already exists and is not empty -// LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION, if the specified instance already exists but with lower version -// LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER, if a folder cannot be created under %userprofile% -// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified -// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created -// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed. -// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted -// -FnLocalDBCreateInstance LocalDBCreateInstance; - -//--------------------------------------------------------------------- -// Function: LocalDBStartInstance -// -// Description: This function will start the given LocalDB instance. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_CONNECTION, if the wszSqlConnection parameter is NULL -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the buffer wszSqlConnection is too small -// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH - -// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified -// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created -// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed. -// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted -// -FnLocalDBStartInstance LocalDBStartInstance; - -// type definition for LocalDBStopInstance function -typedef HRESULT __cdecl FnLocalDBStopInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I Available flags: - // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately - // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - _In_ DWORD dwFlags, - // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately - // without waiting for LocalDB instance to stop - _In_ ULONG ulTimeout -); - -// type definition for pointer to LocalDBStopInstance function -typedef FnLocalDBStopInstance* PFnLocalDBStopInstance; - -// Flags for the StopLocalDBInstance function -#define LOCALDB_SHUTDOWN_KILL_PROCESS 0x0001L -#define LOCALDB_SHUTDOWN_WITH_NOWAIT 0x0002L - -//--------------------------------------------------------------------- -// Function: LocalDBStopInstance -// -// Description: This function will shutdown the given LocalDB instance. -// If the flag LOCALDB_SHUTDOWN_KILL_PROCESS is set, the LocalDB instance will be killed immediately. -// IF the flag LOCALDB_SHUTDOWN_WITH_NOWAIT is set, the LocalDB instance will shutdown with NOWAIT option. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_WAIT_TIMEOUT - if this function has not finished in given time -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBStopInstance LocalDBStopInstance; - -// type definition for LocalDBDeleteInstance function -typedef HRESULT __cdecl FnLocalDBDeleteInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -); - -// type definition for pointer to LocalDBDeleteInstance function -typedef FnLocalDBDeleteInstance* PFnLocalDBDeleteInstance; - -//--------------------------------------------------------------------- -// Function: LocalDBDeleteInstance -// -// Description: This function will remove the given LocalDB instance. If the given instance is running this function will -// fail with the error code LOCALDB_ERROR_INSTANCE_BUSY. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INSTANCE_BUSY, if the given instance is running -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBDeleteInstance LocalDBDeleteInstance; - -// Function: LocalDBFormatMessage -// -// Description: This function will return the localized textual description for the given LocalDB error -// -// Available Flags: -// LOCALDB_TRUNCATE_ERR_MESSAGE - the error message should be truncated to fit into the provided buffer -// -// Return Value: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_UNKNOWN_HRESULT, if the given HRESULT is unknown -// LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID, if the given language id is unknown (0 is recommended for the // default language) -// LOCALDB_ERROR_UNKNOWN_ERROR_CODE, if the LocalDB error code is unknown -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the input buffer is too short and LOCALDB_TRUNCATE_ERR_MESSAGE flag -// is not set -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBFormatMessage LocalDBFormatMessage; - -#define MAX_LOCALDB_INSTANCE_NAME_LENGTH 128 -#define MAX_LOCALDB_PARENT_INSTANCE_LENGTH MAX_INSTANCE_NAME - -typedef WCHAR TLocalDBInstanceName[MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1]; -typedef TLocalDBInstanceName* PTLocalDBInstanceName; - -// type definition for LocalDBGetInstances function -typedef HRESULT __cdecl FnLocalDBGetInstances( - // O buffer for a LocalDB instance names - _Out_ PTLocalDBInstanceName pInstanceNames, - // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, - // has the number of existing LocalDB instances - _Inout_ LPDWORD lpdwNumberOfInstances -); - -// type definition for pointer to LocalDBGetInstances function -typedef FnLocalDBGetInstances* PFnLocalDBGetInstances; - -// Function: LocalDBGetInstances -// -// Description: This function returns names for all existing Local DB instances -// -// Usage Example: -// DWORD dwN = 0; -// LocalDBGetInstances(NULL, &dwN); - -// PTLocalDBInstanceName insts = (PTLocalDBInstanceName) malloc(dwN * sizeof(TLocalDBInstanceName)); -// LocalDBGetInstances(insts, &dwN); - -// for (int i = 0; i < dwN; i++) -// wprintf(L"%s\n", insts[i]); -// -// Return values: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBGetInstances LocalDBGetInstances; - -// SID string format: S - Revision(1B) - Authority ID (6B) {- Sub authority ID (4B)} * max 15 sub-authorities = 1 + 1 + 3 + 1 + 15 + (1 + 10) * 15 -#define MAX_STRING_SID_LENGTH 186 - -#pragma pack(push) -#pragma pack(8) - -// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetInstanceInfo in sqluserinstance.cpp file. -// -typedef struct _LocalDBInstanceInfo -{ - DWORD cbLocalDBInstanceInfoSize; - TLocalDBInstanceName wszInstanceName; - BOOL bExists; - BOOL bConfigurationCorrupted; - BOOL bIsRunning; - DWORD dwMajor; - DWORD dwMinor; - DWORD dwBuild; - DWORD dwRevision; - FILETIME ftLastStartDateUTC; - WCHAR wszConnection[LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE]; - BOOL bIsShared; - TLocalDBInstanceName wszSharedInstanceName; - WCHAR wszOwnerSID[MAX_STRING_SID_LENGTH + 1]; - BOOL bIsAutomatic; -} LocalDBInstanceInfo; - -#pragma pack(pop) - -typedef LocalDBInstanceInfo* PLocalDBInstanceInfo; - -// type definition for LocalDBGetInstanceInfo function -typedef HRESULT __cdecl FnLocalDBGetInstanceInfo( - // I the LocalDB instance name - _In_z_ PCWSTR wszInstanceName, - // O instance information - _Out_ PLocalDBInstanceInfo pInfo, - // I Size of LocalDBInstanceInfo structure in bytes - _In_ DWORD cbInfo); - -// type definition for pointer to LocalDBGetInstances function -typedef FnLocalDBGetInstanceInfo* PFnLocalDBGetInstanceInfo; - -// Function: LocalDBGetInstanceInfo -// -// Description: This function returns information about the given instance. -// -// Return values: -// S_OK, if the function succeeds -// -// ERROR_INVALID_PARAMETER, if some of the parameters is invalid -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBGetInstanceInfo LocalDBGetInstanceInfo; - -// Version has format: Major.Minor[.Build[.Revision]]. Each of components is 32bit integer which is at most 40 digits and 3 dots -// -#define MAX_LOCALDB_VERSION_LENGTH 43 - -typedef WCHAR TLocalDBVersion[MAX_LOCALDB_VERSION_LENGTH + 1]; -typedef TLocalDBVersion* PTLocalDBVersion; - -// type definition for LocalDBGetVersions function -typedef HRESULT __cdecl FnLocalDBGetVersions( - // O buffer for installed LocalDB versions - _Out_ PTLocalDBVersion pVersions, - // I/O on input has the number slots for versions in the pVersions buffer. On output, - // has the number of existing LocalDB versions - _Inout_ LPDWORD lpdwNumberOfVersions -); - -// type definition for pointer to LocalDBGetVersions function -typedef FnLocalDBGetVersions* PFnLocalDBGetVersions; - -// Function: LocalDBGetVersions -// -// Description: This function returns all installed LocalDB versions. Returned versions will be in format Major.Minor -// -// Usage Example: -// DWORD dwN = 0; -// LocalDBGetVersions(NULL, &dwN); - -// PTLocalDBVersion versions = (PTLocalDBVersion) malloc(dwN * sizeof(TLocalDBVersion)); -// LocalDBGetVersions(insts, &dwN); - -// for (int i = 0; i < dwN; i++) -// wprintf(L"%s\n", insts[i]); -// -// Return values: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurs. -// -FnLocalDBGetVersions LocalDBGetVersions; - -#pragma pack(push) -#pragma pack(8) - -// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetVersionInfo in sqluserinstance.cpp file. -// -typedef struct _LocalDBVersionInfo -{ - DWORD cbLocalDBVersionInfoSize; - TLocalDBVersion wszVersion; - BOOL bExists; - DWORD dwMajor; - DWORD dwMinor; - DWORD dwBuild; - DWORD dwRevision; -} LocalDBVersionInfo; - -#pragma pack(pop) - -typedef LocalDBVersionInfo* PLocalDBVersionInfo; - -// type definition for LocalDBGetVersionInfo function -typedef HRESULT __cdecl FnLocalDBGetVersionInfo( - // I LocalDB version string - _In_z_ PCWSTR wszVersion, - // O version information - _Out_ PLocalDBVersionInfo pVersionInfo, - // I Size of LocalDBVersionInfo structure in bytes - _In_ DWORD cbVersionInfo -); - -// type definition for pointer to LocalDBGetVersionInfo function -typedef FnLocalDBGetVersionInfo* PFnLocalDBGetVersionInfo; - -// Function: LocalDBGetVersionInfo -// -// Description: This function returns information about the given LocalDB version -// -// Return values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INTERNAL_ERROR, if some internal error occurred -// LOCALDB_ERROR_INVALID_PARAMETER, if a input parameter is invalid -// -FnLocalDBGetVersionInfo LocalDBGetVersionInfo; - -typedef HRESULT __cdecl FnLocalDBStartTracing(); -typedef FnLocalDBStartTracing* PFnLocalDBStartTracing; - -// Function: LocalDBStartTracing -// -// Description: This function will write in registry that Tracing sessions should be started for the current user. -// -// Return values: -// S_OK - on success -// Propper HRESULT in case of failure -// -FnLocalDBStartTracing LocalDBStartTracing; - -typedef HRESULT __cdecl FnLocalDBStopTracing(); -typedef FnLocalDBStopTracing* PFnFnLocalDBStopTracing; - -// Function: LocalDBStopTracing -// -// Description: This function will write in registry that Tracing sessions should be stopped for the current user. -// -// Return values: -// S_OK - on success -// Propper HRESULT in case of failure -// -FnLocalDBStopTracing LocalDBStopTracing; - -// type definition for LocalDBShareInstance function -typedef HRESULT __cdecl FnLocalDBShareInstance( - // I the SID of the LocalDB instance owner - _In_opt_ PSID pOwnerSID, - // I the private name of LocalDB instance which should be shared - _In_z_ PCWSTR wszPrivateLocalDBInstanceName, - // I the public shared name - _In_z_ PCWSTR wszSharedName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags); - -// type definition for pointer to LocalDBShareInstance function -typedef FnLocalDBShareInstance* PFnLocalDBShareInstance; - -// Function: LocalDBShareInstance -// -// Description: This function will share the given private instance of the given user with the given shared name. -// This function has to be executed elevated. -// -// Return values: -// HRESULT -// -FnLocalDBShareInstance LocalDBShareInstance; - -// type definition for LocalDBUnshareInstance function -typedef HRESULT __cdecl FnLocalDBUnshareInstance( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags); - -// type definition for pointer to LocalDBUnshareInstance function -typedef FnLocalDBUnshareInstance* PFnLocalDBUnshareInstance; - -// Function: LocalDBUnshareInstance -// -// Description: This function unshares the given LocalDB instance. -// If a shared name is given then that shared instance will be unshared. -// If a private name is given then we will check if the caller -// shares a private instance with the given private name and unshare it. -// -// Return values: -// HRESULT -// -FnLocalDBUnshareInstance LocalDBUnshareInstance; - -#ifdef __cplusplus -} // extern "C" -#endif - -#if defined(LOCALDB_DEFINE_PROXY_FUNCTIONS) -//--------------------------------------------------------------------- -// The following section is enabled only if the constant LOCALDB_DEFINE_PROXY_FUNCTIONS -// is defined. It provides an implementation of proxies for each of the LocalDB APIs. -// The proxy implementations use a common function to bind to entry points in the -// latest installed SqlUserInstance DLL, and then forward the requests. -// -// The current implementation loads the SqlUserInstance DLL on the first call into -// a proxy function. There is no provision for unloading the DLL. Note that if the -// process includes multiple binaries (EXE and one or more DLLs), each of them could -// load a separate instance of the SqlUserInstance DLL. -// -// For future consideration: allow the SqlUserInstance DLL to be unloaded dynamically. -// -// WARNING: these functions must not be called in DLL initialization, since a deadlock -// could result loading dependent DLLs. -//--------------------------------------------------------------------- - -// This macro provides the body for each proxy function. -// -#define LOCALDB_PROXY(LocalDBFn) static Fn##LocalDBFn* pfn##LocalDBFn = NULL; if (!pfn##LocalDBFn) {HRESULT hr = LocalDBGetPFn(#LocalDBFn, (FARPROC *)&pfn##LocalDBFn); if (FAILED(hr)) return hr;} return (*pfn##LocalDBFn) - -// Structure and function to parse the "Installed Versions" registry subkeys -// -typedef struct { - DWORD dwComponent[2]; - WCHAR wszKeyName[256]; -} Version; - -// The following algorithm is intended to match, in part, the .NET Version class. -// A maximum of two components are allowed, which must be separated with a period. -// Valid: "11", "11.0" -// Invalid: "", ".0", "11.", "11.0." -// -static BOOL ParseVersion(Version * pVersion) -{ - pVersion->dwComponent[0] = 0; - pVersion->dwComponent[1] = 0; - WCHAR * pwch = pVersion->wszKeyName; - - for (int i = 0; i<2; i++) - { - LONGLONG llVal = 0; - BOOL fHaveDigit = FALSE; - - while (*pwch >= L'0' && *pwch <= L'9') - { - llVal = llVal * 10 + (*pwch++ - L'0'); - fHaveDigit = TRUE; - - if (llVal > 0x7fffffff) - { - return FALSE; - } - } - - if (!fHaveDigit) - return FALSE; - - pVersion->dwComponent[i] = (DWORD) llVal; - - if (*pwch == L'\0') - return TRUE; - - if (*pwch != L'.') - return FALSE; - - pwch++; - } - // If we get here, the version string was terminated with L'.', which is not valid - // - return FALSE; -} - -#include - -// This function loads the correct LocalDB API DLL (if required) and returns a pointer to a procedure. -// Note that the first-loaded API DLL for the process will be used until process termination: installation of -// a new version of the API will not be recognized after first load. -// -static HRESULT LocalDBGetPFn(LPCSTR szLocalDBFn, FARPROC *pfnLocalDBFn) -{ - static volatile HMODULE hLocalDBDll = NULL; - - if (!hLocalDBDll) - { - LONG ec; - HKEY hkeyVersions = NULL; - HKEY hkeyVersion = NULL; - Version verHigh = {0}; - Version verCurrent; - DWORD cchKeyName; - DWORD dwValueType; - WCHAR wszLocalDBDll[MAX_PATH+1]; - DWORD cbLocalDBDll = sizeof(wszLocalDBDll) - sizeof(WCHAR); // to deal with RegQueryValueEx null-termination quirk - HMODULE hLocalDBDllTemp = NULL; - - if (ERROR_SUCCESS != (ec = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions", 0, KEY_READ, &hkeyVersions))) - { - goto Cleanup; - } - - for (int i = 0; ; i++) - { - cchKeyName = 256; - if (ERROR_SUCCESS != (ec = RegEnumKeyExW(hkeyVersions, i, verCurrent.wszKeyName, &cchKeyName, 0, NULL, NULL, NULL))) - { - if (ERROR_NO_MORE_ITEMS == ec) - { - break; - } - goto Cleanup; - } - - if (!ParseVersion(&verCurrent)) - { - continue; // invalid version syntax - } - - if (verCurrent.dwComponent[0] > verHigh.dwComponent[0] || - (verCurrent.dwComponent[0] == verHigh.dwComponent[0] && verCurrent.dwComponent[1] > verHigh.dwComponent[1])) - { - verHigh = verCurrent; - } - } - if (!verHigh.wszKeyName[0]) - { - // ec must be ERROR_NO_MORE_ITEMS here - // - assert(ec == ERROR_NO_MORE_ITEMS); - - // We will change the error code to ERROR_FILE_NOT_FOUND in order to indicate that - // LocalDB instalation is not found. Registry key "SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions" exists - // but it is empty. - // - ec = ERROR_FILE_NOT_FOUND; - goto Cleanup; - } - - if (ERROR_SUCCESS != (ec = RegOpenKeyExW(hkeyVersions, verHigh.wszKeyName, 0, KEY_READ, &hkeyVersion))) - { - goto Cleanup; - } - if (ERROR_SUCCESS != (ec = RegQueryValueExW(hkeyVersion, L"InstanceAPIPath", NULL, &dwValueType, (PBYTE) wszLocalDBDll, &cbLocalDBDll))) - { - goto Cleanup; - } - if (dwValueType != REG_SZ) - { - ec = ERROR_INVALID_DATA; - goto Cleanup; - } - // Ensure string value null-terminated - // Note that we left a spare character in the output buffer for RegQueryValueEx for this purpose - // - wszLocalDBDll[cbLocalDBDll/sizeof(WCHAR)] = L'\0'; - - hLocalDBDllTemp = LoadLibraryW(wszLocalDBDll); - if (NULL == hLocalDBDllTemp) - { - ec = GetLastError(); - goto Cleanup; - } - if (NULL == InterlockedCompareExchangePointer((volatile PVOID *)&hLocalDBDll, hLocalDBDllTemp, NULL)) - { - // We were the winner: we gave away our DLL handle - // - hLocalDBDllTemp = NULL; - } - ec = ERROR_SUCCESS; -Cleanup: - if (hLocalDBDllTemp) - FreeLibrary(hLocalDBDllTemp); - if (hkeyVersion) - RegCloseKey(hkeyVersion); - if (hkeyVersions) - RegCloseKey(hkeyVersions); - - // Error code ERROR_FILE_NOT_FOUND can occure if registry hive with installed LocalDB versions is missing. - // In that case we should return the LocalDB specific error code - // - if (ec == ERROR_FILE_NOT_FOUND) - return LOCALDB_ERROR_NOT_INSTALLED; - - if (ec != ERROR_SUCCESS) - return HRESULT_FROM_WIN32(ec); - } - - FARPROC pfn = GetProcAddress(hLocalDBDll, szLocalDBFn); - - if (!pfn) - { - return HRESULT_FROM_WIN32(GetLastError()); - } - *pfnLocalDBFn = pfn; - return S_OK; -} - -// The following proxy functions forward calls to the latest LocalDB API DLL. -// - -HRESULT __cdecl -LocalDBCreateInstance ( - // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - _In_z_ PCWSTR wszVersion, - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -) -{ - LOCALDB_PROXY(LocalDBCreateInstance)(wszVersion, pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBStartInstance( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags, - // O the buffer to store the connection string to the LocalDB instance - _Out_writes_z__opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, - // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. - _Inout_opt_ LPDWORD lpcchSqlConnection -) -{ - LOCALDB_PROXY(LocalDBStartInstance)(pInstanceName, dwFlags, wszSqlConnection, lpcchSqlConnection); -} - -HRESULT __cdecl -LocalDBStopInstance ( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I Available flags: - // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately - // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - _In_ DWORD dwFlags, - // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately - // without waiting for LocalDB instance to stop - _In_ ULONG ulTimeout -) -{ - LOCALDB_PROXY(LocalDBStopInstance)(pInstanceName, dwFlags, ulTimeout); -} - -HRESULT __cdecl -LocalDBDeleteInstance ( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -) -{ - LOCALDB_PROXY(LocalDBDeleteInstance)(pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBFormatMessage( - // I the LocalDB error code - _In_ HRESULT hrLocalDB, - // I Available flags: - // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, - // the error message will be truncated to fit into the buffer - _In_ DWORD dwFlags, - // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - _In_ DWORD dwLanguageId, - // O the buffer to store the LocalDB error message - _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, - // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. If the function succeeds - // contains the number of characters in the message, excluding the trailing null - _Inout_ LPDWORD lpcchMessage -) -{ - LOCALDB_PROXY(LocalDBFormatMessage)(hrLocalDB, dwFlags, dwLanguageId, wszMessage, lpcchMessage); -} - -HRESULT __cdecl -LocalDBGetInstances( - // O buffer with instance names - _Out_ PTLocalDBInstanceName pInstanceNames, - // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, - // has the number of existing LocalDB instances - _Inout_ LPDWORD lpdwNumberOfInstances -) -{ - LOCALDB_PROXY(LocalDBGetInstances)(pInstanceNames, lpdwNumberOfInstances); -} - -HRESULT __cdecl -LocalDBGetInstanceInfo( - // I the instance name - _In_z_ PCWSTR wszInstanceName, - // O instance information - _Out_ PLocalDBInstanceInfo pInfo, - // I Size of LocalDBInstanceInfo structure in bytes - _In_ DWORD cbInfo -) -{ - LOCALDB_PROXY(LocalDBGetInstanceInfo)(wszInstanceName, pInfo, cbInfo); -} - -HRESULT __cdecl -LocalDBStartTracing() -{ - LOCALDB_PROXY(LocalDBStartTracing)(); -} - -HRESULT __cdecl -LocalDBStopTracing() -{ - LOCALDB_PROXY(LocalDBStopTracing)(); -} - -HRESULT __cdecl -LocalDBShareInstance( - // I the SID of the LocalDB instance owner - _In_opt_ PSID pOwnerSID, - // I the private name of LocalDB instance which should be shared - _In_z_ PCWSTR wszLocalDBInstancePrivateName, - // I the public shared name - _In_z_ PCWSTR wszSharedName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags) -{ - LOCALDB_PROXY(LocalDBShareInstance)(pOwnerSID, wszLocalDBInstancePrivateName, wszSharedName, dwFlags); -} - -HRESULT __cdecl -LocalDBGetVersions( - // O buffer for installed LocalDB versions - _Out_ PTLocalDBVersion pVersions, - // I/O on input has the number slots for versions in the pVersions buffer. On output, - // has the number of existing LocalDB versions - _Inout_ LPDWORD lpdwNumberOfVersions -) -{ - LOCALDB_PROXY(LocalDBGetVersions)(pVersions, lpdwNumberOfVersions); -} - -HRESULT __cdecl -LocalDBUnshareInstance( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags) -{ - LOCALDB_PROXY(LocalDBUnshareInstance)(pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBGetVersionInfo( - // I LocalDB version string - _In_z_ PCWSTR wszVersion, - // O version information - _Out_ PLocalDBVersionInfo pVersionInfo, - // I Size of LocalDBVersionInfo structure in bytes - _In_ DWORD cbVersionInfo) -{ - LOCALDB_PROXY(LocalDBGetVersionInfo)(wszVersion, pVersionInfo, cbVersionInfo); -} - -#endif - -#endif // _SQLUSERINSTANCE_H_ - -//----------------------------------------------------------------------------- -// File: sqluserinstancemsgs.mc -// -// Copyright: Copyright (c) Microsoft Corporation -//----------------------------------------------------------------------------- -#ifndef _LOCALDB_MESSAGES_H_ -#define _LOCALDB_MESSAGES_H_ -// Header section -// -// Section with the LocalDB messages -// -// -// Values are 32 bit values laid out as follows: -// -// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 -// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 -// +-+-+-+-+-+---------------------+-------------------------------+ -// |S|R|C|N|r| Facility | Code | -// +-+-+-+-+-+---------------------+-------------------------------+ -// -// where -// -// S - Severity - indicates success/fail -// -// 0 - Success -// 1 - Fail (COERROR) -// -// R - reserved portion of the facility code, corresponds to NT's -// second severity bit. -// -// C - reserved portion of the facility code, corresponds to NT's -// C field. -// -// N - reserved portion of the facility code. Used to indicate a -// mapped NT status value. -// -// r - reserved portion of the facility code. Reserved for internal -// use. Used to indicate HRESULT values that are not status -// values, but are instead message ids for display strings. -// -// Facility - is the facility code -// -// Code - is the facility's status code -// -// -// Define the facility codes -// -#define FACILITY_LOCALDB 0x9C5 - - -// -// Define the severity codes -// -#define LOCALDB_SEVERITY_SUCCESS 0x0 -#define LOCALDB_SEVERITY_ERROR 0x2 - - -// -// MessageId: LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot create folder for the LocalDB instance at: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER ((HRESULT)0x89C50100L) - -// -// MessageId: LOCALDB_ERROR_INVALID_PARAMETER -// -// MessageText: -// -// The parameter for the LocalDB Instance API method is incorrect. Consult the API documentation. -// -#define LOCALDB_ERROR_INVALID_PARAMETER ((HRESULT)0x89C50101L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION -// -// MessageText: -// -// Unable to create the LocalDB instance with specified version. An instance with the same name already exists, but it has lower version than the specified version. -// -#define LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION ((HRESULT)0x89C50102L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER -// -// MessageText: -// -// Cannot access the user profile folder for local application data (%%LOCALAPPDATA%%). -// -#define LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER ((HRESULT)0x89C50103L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG -// -// MessageText: -// -// The full path length of the LocalDB instance folder is longer than MAX_PATH. The instance must be stored in folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG ((HRESULT)0x89C50104L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot access LocalDB instance folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50105L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY -// -// MessageText: -// -// Unexpected error occurred while trying to access the LocalDB instance registry configuration. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY ((HRESULT)0x89C50106L) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_INSTANCE -// -// MessageText: -// -// The specified LocalDB instance does not exist. -// -#define LOCALDB_ERROR_UNKNOWN_INSTANCE ((HRESULT)0x89C50107L) - -// -// MessageId: LOCALDB_ERROR_INTERNAL_ERROR -// -// MessageText: -// -// Unexpected error occurred inside a LocalDB instance API method call. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_INTERNAL_ERROR ((HRESULT)0x89C50108L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY -// -// MessageText: -// -// Unexpected error occurred while trying to modify the registry configuration for the LocalDB instance. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY ((HRESULT)0x89C50109L) - -// -// MessageId: LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED -// -// MessageText: -// -// Error occurred during LocalDB instance startup: SQL Server process failed to start. -// -#define LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED ((HRESULT)0x89C5010AL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT -// -// MessageText: -// -// LocalDB instance is corrupted. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT ((HRESULT)0x89C5010BL) - -// -// MessageId: LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS -// -// MessageText: -// -// Error occurred during LocalDB instance startup: unable to create the SQL Server process. -// -#define LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS ((HRESULT)0x89C5010CL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_VERSION -// -// MessageText: -// -// The specified LocalDB version is not available on this computer. -// -#define LOCALDB_ERROR_UNKNOWN_VERSION ((HRESULT)0x89C5010DL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID -// -// MessageText: -// -// Error getting the localized error message. The language specified by 'Language ID' parameter is unknown. -// -#define LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID ((HRESULT)0x89C5010EL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_STOP_FAILED -// -// MessageText: -// -// Stop operation for LocalDB instance failed to complete within the specified time. -// -#define LOCALDB_ERROR_INSTANCE_STOP_FAILED ((HRESULT)0x89C5010FL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_ERROR_CODE -// -// MessageText: -// -// Error getting the localized error message. The specified error code is unknown. -// -#define LOCALDB_ERROR_UNKNOWN_ERROR_CODE ((HRESULT)0x89C50110L) - -// -// MessageId: LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED -// -// MessageText: -// -// The LocalDB version available on this workstation is lower than the requested LocalDB version. -// -#define LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED ((HRESULT)0x89C50111L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_BUSY -// -// MessageText: -// -// Requested operation on LocalDB instance cannot be performed because specified instance is currently in use. Stop the instance and try again. -// -#define LOCALDB_ERROR_INSTANCE_BUSY ((HRESULT)0x89C50112L) - -// -// MessageId: LOCALDB_ERROR_INVALID_OPERATION -// -// MessageText: -// -// Default LocalDB instances cannot be created, stopped or deleted manually. -// -#define LOCALDB_ERROR_INVALID_OPERATION ((HRESULT)0x89C50113L) - -// -// MessageId: LOCALDB_ERROR_INSUFFICIENT_BUFFER -// -// MessageText: -// -// The buffer passed to the LocalDB instance API method has insufficient size. -// -#define LOCALDB_ERROR_INSUFFICIENT_BUFFER ((HRESULT)0x89C50114L) - -// -// MessageId: LOCALDB_ERROR_WAIT_TIMEOUT -// -// MessageText: -// -// Timeout occurred inside the LocalDB instance API method. -// -#define LOCALDB_ERROR_WAIT_TIMEOUT ((HRESULT)0x89C50115L) - -// MessageId=0x0116 message id is reserved. This message ID will be used for error LOCALDB_ERROR_NOT_INSTALLED. -// This message is specific since it has to be present in SqlUserIntsnace.h because it can be returned by discovery API. -// -// -// MessageId: LOCALDB_ERROR_XEVENT_FAILED -// -// MessageText: -// -// Failed to start XEvent engine within the LocalDB Instance API. -// -#define LOCALDB_ERROR_XEVENT_FAILED ((HRESULT)0x89C50117L) - -// -// MessageId: LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED -// -// MessageText: -// -// Cannot create an automatic instance. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED ((HRESULT)0x89C50118L) - -// -// MessageId: LOCALDB_ERROR_SHARED_NAME_TAKEN -// -// MessageText: -// -// Cannot create a shared instance. The specified shared instance name is already in use. -// -#define LOCALDB_ERROR_SHARED_NAME_TAKEN ((HRESULT)0x89C50119L) - -// -// MessageId: LOCALDB_ERROR_CALLER_IS_NOT_OWNER -// -// MessageText: -// -// API caller is not LocalDB instance owner. -// -#define LOCALDB_ERROR_CALLER_IS_NOT_OWNER ((HRESULT)0x89C5011AL) - -// -// MessageId: LOCALDB_ERROR_INVALID_INSTANCE_NAME -// -// MessageText: -// -// Specified LocalDB instance name is invalid. -// -#define LOCALDB_ERROR_INVALID_INSTANCE_NAME ((HRESULT)0x89C5011BL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_ALREADY_SHARED -// -// MessageText: -// -// The specified LocalDB instance is already shared with different shared name. -// -#define LOCALDB_ERROR_INSTANCE_ALREADY_SHARED ((HRESULT)0x89C5011CL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_NOT_SHARED -// -// MessageText: -// -// The specified LocalDB instance is not shared. -// -#define LOCALDB_ERROR_INSTANCE_NOT_SHARED ((HRESULT)0x89C5011DL) - -// -// MessageId: LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED -// -// MessageText: -// -// Administrator privileges are required in order to execute this operation. -// -#define LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED ((HRESULT)0x89C5011EL) - -// -// MessageId: LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES -// -// MessageText: -// -// There are too many shared instance and we cannot generate unique User Instance Name. Unshare some of the existing shared instances. -// -#define LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES ((HRESULT)0x89C5011FL) - -// -// MessageId: LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH -// -// MessageText: -// -// Cannot get a local application data path. Most probably a user profile is not loaded. If LocalDB is executed under IIS, make sure that profile loading is enabled for the current user. -// -#define LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH ((HRESULT)0x89C50120L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_LOAD_RESOURCES -// -// MessageText: -// -// Cannot load resources for this DLL. Resources for this DLL should be stored in a subfolder Resources, with the same file name as this DLL and the extension ".RLL". -// -#define LOCALDB_ERROR_CANNOT_LOAD_RESOURCES ((HRESULT)0x89C50121L) - - // Detailed error descriptions -// -// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING -// -// MessageText: -// -// The "DataDirectory" registry value is missing in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING ((HRESULT)0x89C50200L) - -// -// MessageId: LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot access LocalDB instance folder: %1 -// -#define LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50201L) - -// -// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG -// -// MessageText: -// -// The "DataDirectory" registry value is too long in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG ((HRESULT)0x89C50202L) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING -// -// MessageText: -// -// The "Parent Instance" registry value is missing in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING ((HRESULT)0x89C50203L) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG -// -// MessageText: -// -// The "Parent Instance" registry value is too long in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG ((HRESULT)0x89C50204L) - -// -// MessageId: LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID -// -// MessageText: -// -// Data directory for LocalDB instance is invalid: %1 -// -#define LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID ((HRESULT)0x89C50205L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_ASSERT -// -// MessageText: -// -// LocalDB instance API: XEvent engine assert: %1 in %2:%3 (%4) -// -#define LOCALDB_EDETAIL_XEVENT_ASSERT ((HRESULT)0x89C50206L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_ERROR -// -// MessageText: -// -// LocalDB instance API: XEvent error: %1 -// -#define LOCALDB_EDETAIL_XEVENT_ERROR ((HRESULT)0x89C50207L) - -// -// MessageId: LOCALDB_EDETAIL_INSTALLATION_CORRUPTED -// -// MessageText: -// -// LocalDB installation is corrupted. Reinstall the LocalDB. -// -#define LOCALDB_EDETAIL_INSTALLATION_CORRUPTED ((HRESULT)0x89C50208L) - -// -// MessageId: LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION -// -// MessageText: -// -// LocalDB XEvent error: cannot determine %ProgramFiles% folder location. -// -#define LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION ((HRESULT)0x89C50209L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE -// -// MessageText: -// -// LocalDB XEvent error: Cannot initialize XEvent engine. -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE ((HRESULT)0x89C5020AL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE -// -// MessageText: -// -// LocalDB XEvent error: Cannot find XEvents configuration file: %1 -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE ((HRESULT)0x89C5020BL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE -// -// MessageText: -// -// LocalDB XEvent error: Cannot configure XEvents engine with the configuration file: %1 -// HRESULT returned: %2 -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE ((HRESULT)0x89C5020CL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG -// -// MessageText: -// -// LocalDB XEvent error: XEvents engine configuration file too long -// -#define LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG ((HRESULT)0x89C5020DL) - -// -// MessageId: LOCALDB_EDETAIL_COINITIALIZEEX_FAILED -// -// MessageText: -// -// CoInitializeEx API failed. HRESULT returned: %1 -// -#define LOCALDB_EDETAIL_COINITIALIZEEX_FAILED ((HRESULT)0x89C5020EL) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID -// -// MessageText: -// -// LocalDB parent instance version is invalid: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID ((HRESULT)0x89C5020FL) - -// -// MessageId: LOCALDB_EDETAIL_WINAPI_ERROR -// -// MessageText: -// -// Windows API call %1 returned error code: %2. Windows system error message is: %3Reported at line: %4. %5 -// -#define LOCALDB_EDETAIL_WINAPI_ERROR ((HRESULT)0xC9C50210L) - -// -// MessageId: LOCALDB_EDETAIL_UNEXPECTED_RESULT -// -// MessageText: -// -// Function %1 returned %2 at line %3. -// -#define LOCALDB_EDETAIL_UNEXPECTED_RESULT ((HRESULT)0x89C50211L) - -// -#endif // _LOCALDB_MESSAGES_H_ - -#endif //__msodbcsql_h__ diff --git a/test/output.py b/test/output.py new file mode 100644 index 00000000..bb6fa0d7 --- /dev/null +++ b/test/output.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +######################################################################################### +# +# Description: +# Requirement of python 3.4 to execute this script and required result log file are in the same location +# Run with command line without options required. Example: py output.py +# This script parse output of PHP Native Test +# +# +############################################################################################# + +import os +import stat +import re + +# This module returns either the number of test or the number of failed test +# depending on the argument you requested. +# Input: var - a single variable containing either "FAIL" or "TOTAL" +# Output: Returns a number of test/s or failed test/s +def returnCount(var): + with open(os.path.dirname(os.path.realpath(__file__)) + os.sep + logfile) as f: + num = 0 + failnum = 0 + for line in f: + if "FAIL" in line or "PASS" in line: + if ".phpt" in line: + if "FAIL" in line: + failnum += 1 + num += 1 + if var == 'total': + return str(num) + else: + return str(failnum) + +# This module prints the line that matches the expression. +# Input: inputStr - String that matches +# file - file name +# path - path of the file. +# Output: null +def readAndPrint(inputStr, file, path): + filn = open(path + os.sep + file).readlines() + for lines in filn: + if inputStr in lines: + print(lines) + +# This module returns the test file name. +# Input: line - current line of the log file +# Output: Returns the filename. +def TestFilename(line): + terminateChar = os.sep + currentPos = 0 + while True: + currentPos = currentPos - 1 + line[currentPos] + if line[currentPos] == terminateChar: + break + file = line[currentPos+1:-1] + return file + +def genXML(logfile,number): + # Generating the nativeresult.xml file. + file = open('nativeresult' + str(number) + '.xml','w') + file.write('' + os.linesep) + file.write('' + os.linesep) + file.close() + + # Extract individual test results from the log file and + # enter it in the nativeresult.xml file. + + with open(os.path.dirname(os.path.realpath(__file__)) + os.sep + logfile) as f: + num = 1 + failnum = 0 + for line in f: + file = open('nativeresult' + str(number) + '.xml','a') + if "FAIL" in line or "PASS" in line: + if ".phpt" in line: + + file.write('\t' + os.linesep) + stop_pos = result.group(1).find('[') + file.write('\t\t' + os.linesep) + file.write('\t' + os.linesep) + else: + result = re.search('PASS(.*).', line) + file.write(TestFilename(str(result.group(1))) + '-' + str(num) + '"/>' + os.linesep) + num += 1 + file.close() + + file = open('nativeresult' + str(number) + '.xml','a') + file.write('' + os.linesep) + file.close() + +def run(): + num = 1 + for f in os.listdir(os.path.dirname(os.path.realpath(__file__))): + if f.endswith("log"): + print('================================================') + print(os.path.splitext(f)[0]) + readAndPrint('Number of tests :', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests skipped ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests warned ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests failed ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Expected fail ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests passed ', f, os.path.dirname(os.path.realpath(__file__))) + print('================================================') + logfile = f + genXML(logfile,num) + num = num + 1 + + +# ------------------------------------------------------- Main Function --------------------------------------------------- + +# Display results on screen from result log file. +if __name__ == '__main__': + num = 1 + for f in os.listdir(os.path.dirname(os.path.realpath(__file__))): + if f.endswith("log"): + print('================================================') + print("\n" + os.path.splitext(f)[0] + "\n") + readAndPrint('Number of tests :', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests skipped ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests warned ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests failed ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Expected fail ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests passed ', f, os.path.dirname(os.path.realpath(__file__))) + print('================================================') + logfile = f + genXML(logfile,num) + num = num + 1 + diff --git a/test/pdo_sqlsrv/autonomous_setup.php b/test/pdo_sqlsrv/autonomous_setup.php new file mode 100644 index 00000000..2e80f6a2 --- /dev/null +++ b/test/pdo_sqlsrv/autonomous_setup.php @@ -0,0 +1,16 @@ +"; + + + // Generate unique DB name, example: php_20160817_1471475608267 + $dbName = "php_" . date("Ymd") . "_" . round(microtime(true)*1000); + + // Generic table name example: php_20160817_1471475608267.dbo.php_firefly + $tableName = $dbName.".dbo.php_firefly"; + + // Connection options + $connectionInfo = array("UID"=>"$username", "PWD"=>"$password"); +?> diff --git a/test/pdo_sqlsrv/pdo_002_connect_app.phpt b/test/pdo_sqlsrv/pdo_002_connect_app.phpt new file mode 100644 index 00000000..fa94dcae --- /dev/null +++ b/test/pdo_sqlsrv/pdo_002_connect_app.phpt @@ -0,0 +1,34 @@ +--TEST-- +Connection option APP name unicode +--SKIPIF-- +--FILE-- +query($query); +while ( $row = $stmt->fetch(PDO::FETCH_NUM) ){ + echo $row[0]."\n"; +} + +$stmt = $conn->query($query); +while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ){ + echo $row['']."\n"; +} + +// Free the connection +$conn=null; +echo "Done" +?> + +--EXPECTREGEX-- +APP_PoP_银河 +APP_PoP_银河 +Done diff --git a/test/pdo_sqlsrv/pdo_011_quote.phpt b/test/pdo_sqlsrv/pdo_011_quote.phpt new file mode 100644 index 00000000..46cd9b28 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_011_quote.phpt @@ -0,0 +1,55 @@ +--TEST-- +Insert with quoted parameters +--SKIPIF-- + +--FILE-- +quote( $param ); + +// CREATE database +$conn->query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$query = "CREATE TABLE ".$tableName." (col1 VARCHAR(10), col2 VARCHAR(20))"; +$stmt = $conn->query($query); +if( $stmt === false ) { die(); } + +// Inserd data +$query = "INSERT INTO $tableName VALUES( ?, '1' )"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param)); + +// Inserd data +$query = "INSERT INTO $tableName VALUES( ?, ? )"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param, $param2)); + +// Query +$query = "SELECT * FROM $tableName"; +$stmt = $conn->query($query); +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print_r( $row['col1'] ." was inserted\n" ); +} + +// Revert the inserts +$query = "delete from $tableName where col1 = ?"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param)); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +//free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +a ' g was inserted +a ' g was inserted + diff --git a/test/pdo_sqlsrv/pdo_012_bind_param.phpt b/test/pdo_sqlsrv/pdo_012_bind_param.phpt new file mode 100644 index 00000000..bdd786df --- /dev/null +++ b/test/pdo_sqlsrv/pdo_012_bind_param.phpt @@ -0,0 +1,30 @@ +--TEST-- +uses an input/output parameter +--SKIPIF-- + +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +$dbh->query("IF OBJECT_ID('dbo.sp_ReverseString', 'P') IS NOT NULL DROP PROCEDURE dbo.sp_ReverseString"); +$dbh->query("CREATE PROCEDURE dbo.sp_ReverseString @String as VARCHAR(2048) OUTPUT as SELECT @String = REVERSE(@String)"); +$stmt = $dbh->prepare("EXEC dbo.sp_ReverseString ?"); +$string = "123456789"; +$stmt->bindParam(1, $string, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 2048); +$stmt->execute(); +print "Result: ".$string."\n"; // Expect 987654321 + +// DROP database +$dbh->query("DROP DATABASE ". $dbName) ?: die(); + +//free the statement and connection +$stmt = null; +$dbh = null; +?> +--EXPECT-- +Result: 987654321 diff --git a/test/pdo_sqlsrv/pdo_013_row_count.phpt b/test/pdo_sqlsrv/pdo_013_row_count.phpt new file mode 100644 index 00000000..fb27996c --- /dev/null +++ b/test/pdo_sqlsrv/pdo_013_row_count.phpt @@ -0,0 +1,60 @@ +--TEST-- +Number of rows in a result set +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$stmt = $conn->query("CREATE TABLE ".$tableName." (c1 VARCHAR(32))"); +$stmt=null; + +// Insert data +$query = "INSERT INTO ".$tableName." VALUES ('Salmon'),('Butterfish'),('Cod'),('NULL'),('Crab')"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='Cod'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='NULL'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='NO_NAME'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='N/A'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +print_r($res); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +$stmt=null; +$conn=null; +print "Done" +?> +--EXPECT-- +Array +( + [0] => 5 + [1] => 1 + [2] => 1 + [3] => 0 + [4] => 5 +) +Done diff --git a/test/pdo_sqlsrv/pdo_014_integer_custom_formats.phpt b/test/pdo_sqlsrv/pdo_014_integer_custom_formats.phpt new file mode 100644 index 00000000..9503f397 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_014_integer_custom_formats.phpt @@ -0,0 +1,56 @@ +--TEST-- +Number MAX_INT to string with custom formats +--SKIPIF-- +--FILE-- +query("CREATE DATABASE $dbName") ?: die(); + +// Create table +$query = "CREATE TABLE $tableName (col1 INT)"; +$stmt = $conn->query($query); + +// Query number with custom format +$query ="SELECT CAST($sample as varchar) + '.00'"; +$stmt = $conn->query($query); +$data = $stmt->fetchColumn(); +var_dump ($data); + +// Insert data using bind parameters +$query = "INSERT INTO $tableName VALUES(:p0)"; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +// Fetching. Prepare with client buffered cursor +$query = "SELECT TOP 1 cast(col1 as varchar) + '.00 EUR' FROM $tableName"; +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, + PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +//Free the statement and connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +int(2147483647) +string(13) "2147483647.00" +string(17) "2147483647.00 EUR" +Done diff --git a/test/pdo_sqlsrv/pdo_015_integer_custom_formats_pooling.phpt b/test/pdo_sqlsrv/pdo_015_integer_custom_formats_pooling.phpt new file mode 100644 index 00000000..2a2172e7 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_015_integer_custom_formats_pooling.phpt @@ -0,0 +1,61 @@ +--TEST-- +Number MAX_INT to string with custom formats, see pdo_014. Pooling enabled. +--SKIPIF-- +--FILE-- +query("select 1"); +$conn0 = null; + +/* Connect */ +$conn = new PDO("sqlsrv:server=$serverName;ConnectionPooling=$pooling", $username, $password); + +// Create database +$conn->query("CREATE DATABASE $dbName") ?: die(); + +// Create table +$query = "CREATE TABLE $tableName (col1 INT)"; +$stmt = $conn->query($query); + +// Query number with custom format +$query ="SELECT CAST($sample as varchar) + '.00'"; +$stmt = $conn->query($query); +$data = $stmt->fetchColumn(); +var_dump ($data); + +// Insert data using bind parameters +$query = "INSERT INTO $tableName VALUES(:p0)"; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +// Fetching. Prepare with client buffered cursor +$query = "SELECT TOP 1 cast(col1 as varchar) + '.00 EUR' FROM $tableName"; +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, + PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +//Free the statement and connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +string(13) "2147483647.00" +string(17) "2147483647.00 EUR" +Done diff --git a/test/pdo_sqlsrv/pdo_016.phpt b/test/pdo_sqlsrv/pdo_016.phpt new file mode 100644 index 00000000..33441cdb --- /dev/null +++ b/test/pdo_sqlsrv/pdo_016.phpt @@ -0,0 +1,52 @@ +--TEST-- +Bind integer parameters; allow fetch numeric types. +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (c1 INT, c2 INT)"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName VALUES (:num1, :num2)"; +$stmt = $conn->prepare($sql); +$stmt->bindParam(':num1', $sample[0], PDO::PARAM_INT); +$stmt->bindParam(':num2', $sample[1], PDO::PARAM_INT); +$stmt->execute(); + +// Fetch, get data +$sql = "SELECT * FROM $tableName"; +$stmt = $conn->query($sql); +$row = $stmt->fetch(PDO::FETCH_NUM); +var_dump ($row); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +array(2) { + [0]=> + int(-2147483648) + [1]=> + int(2147483647) +} +Done diff --git a/test/pdo_sqlsrv/pdo_017.phpt b/test/pdo_sqlsrv/pdo_017.phpt new file mode 100644 index 00000000..082f31d8 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_017.phpt @@ -0,0 +1,60 @@ +--TEST-- +Fetch string with new line and tab characters +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE ".$tableName. + " (c1 VARCHAR(32), c2 CHAR(32), c3 NVARCHAR(32), c4 NCHAR(32))"; +$stmt = $conn->query($sql); + +// Bind parameters and insert data +$sql = "INSERT INTO $tableName VALUES (:val1, :val2, :val3, :val4)"; +$value = "I USE\nMSPHPSQL\tDRIVERS WITH PHP7"; +$stmt = $conn->prepare($sql); +$stmt->bindParam(':val1', $value); +$stmt->bindParam(':val2', $value); +$stmt->bindParam(':val3', $value); +$stmt->bindParam(':val4', $value); +$stmt->execute(); + +// Get data +$sql = "SELECT UPPER(c1) AS VARCHAR, UPPER(c2) AS CHAR, + UPPER(c3) AS NVARCHAR, UPPER(c4) AS NCHAR FROM $tableName"; +$stmt = $conn->query($sql); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt=null; +$conn=null; +print "Done" +?> + +--EXPECT-- +array(4) { + ["VARCHAR"]=> + string(32) "I USE +MSPHPSQL DRIVERS WITH PHP7" + ["CHAR"]=> + string(32) "I USE +MSPHPSQL DRIVERS WITH PHP7" + ["NVARCHAR"]=> + string(32) "I USE +MSPHPSQL DRIVERS WITH PHP7" + ["NCHAR"]=> + string(32) "I USE +MSPHPSQL DRIVERS WITH PHP7" +} +Done diff --git a/test/pdo_sqlsrv/pdo_018_next_result_set.phpt b/test/pdo_sqlsrv/pdo_018_next_result_set.phpt new file mode 100644 index 00000000..647681c3 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_018_next_result_set.phpt @@ -0,0 +1,60 @@ +--TEST-- +Moves the cursor to the next result set +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (c1 INT, c2 VARCHAR(40))"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName VALUES (?,?)"; +for($t=200; $t<220; $t++) { + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $t); + $ts = sha1($t); + $stmt->bindParam(2, $ts); + $stmt->execute(); +} + +// Fetch, get data and move the cursor to the next result set +$sql = "SELECT * from $tableName WHERE c1 = '204' OR c1 = '210'; + SELECT Top 3 * FROM $tableName ORDER BY c1 DESC"; +$stmt = $conn->query($sql); +$data1 = $stmt->fetchAll(PDO::FETCH_ASSOC); +$stmt->nextRowset(); +$data2 = $stmt->fetchAll(PDO::FETCH_NUM); + +// Array: FETCH_ASSOC +foreach ($data1 as $a) +echo $a['c1'] . "|" . $a['c2'] . "\n"; + +// Array: FETCH_NUM +foreach ($data2 as $a) +echo $a[0] . "|" . $a[1] . "\n"; + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +204|1cc641954099c249e0e4ef0402da3fd0364d95f0 +210|135debd4837026bf06c7bfc5d1e0c6a31611af1d +219|c0ba17c23a26ff8c314478bc69f30963a6e4a754 +218|3d5bdf107de596ce77e8ce48a61b585f52bbb61d +217|49e3d046636e06b2d82ee046db8e6eb9a2e11e16 +Done diff --git a/test/pdo_sqlsrv/pdo_019_next_result_set_pooling.phpt b/test/pdo_sqlsrv/pdo_019_next_result_set_pooling.phpt new file mode 100644 index 00000000..9e570159 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_019_next_result_set_pooling.phpt @@ -0,0 +1,65 @@ +--TEST-- +Moves the cursor to the next result set with pooling enabled +--SKIPIF-- +--FILE-- +query("SELECT 1"); +$conn0 = null; + +// Connect +$conn = new PDO("sqlsrv:server=$serverName;ConnectionPooling=1", $username, $password); + +// CREATE database +$conn->query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (c1 INT, c2 XML)"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName VALUES (?,?)"; +for($t=200; $t<220; $t++) { + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $t); + $ts = substr(sha1($t),0,5); + $stmt->bindParam(2, $ts); + $stmt->execute(); +} + +// Fetch, get data and move the cursor to the next result set +$sql = "SELECT * from $tableName WHERE c1 = '204' OR c1 = '210'; + SELECT Top 3 * FROM $tableName ORDER BY c1 DESC"; +$stmt = $conn->query($sql); +$data1 = $stmt->fetchAll(PDO::FETCH_ASSOC); +$stmt->nextRowset(); +$data2 = $stmt->fetchAll(PDO::FETCH_NUM); + +// Array: FETCH_ASSOC +foreach ($data1 as $a) +echo $a['c1']."|".$a['c2']."\n"; + +// Array: FETCH_NUM +foreach ($data2 as $a) +echo $a[0] . "|".$a[1]."\n"; + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +204|1cc64 +210|135de +219|c0ba1 +218|3d5bd +217|49e3d +Done diff --git a/test/pdo_sqlsrv/pdo_020_bind_params_array.phpt b/test/pdo_sqlsrv/pdo_020_bind_params_array.phpt new file mode 100644 index 00000000..05ea05a0 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_020_bind_params_array.phpt @@ -0,0 +1,58 @@ +--TEST-- +Bind parameters using an array +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (ID TINYINT, SID CHAR(5))"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName VALUES (?,?)"; +for($t=100; $t<103; $t++) { + $stmt = $conn->prepare($sql); + $ts = substr(sha1($t),0,5); + $params = array($t,$ts); + $stmt->execute($params); +} + +// Query, but do not fetch +$sql = "SELECT * from $tableName"; +$stmt = $conn->query($sql); + +// Insert duplicate row, ID = 100 +$t = 100; +$sql = "INSERT INTO $tableName VALUES (?,?)"; +$stmt1 = $conn->prepare($sql); +$ts = substr(sha1($t),0,5); +$params = array($t,$ts); +$stmt1->execute($params); + +// Fetch. The result set should not contain duplicates +$data = $stmt->fetchAll(); +foreach ($data as $a) +echo $a['ID'] . "|" . $a['SID'] . "\n"; + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +100|310b8 +101|dbc0f +102|c8306 +Done diff --git a/test/pdo_sqlsrv/pdo_021_extended_ascii.phpt b/test/pdo_sqlsrv/pdo_021_extended_ascii.phpt new file mode 100644 index 00000000..6158c1b6 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_021_extended_ascii.phpt @@ -0,0 +1,52 @@ +--TEST-- +Bind parameters VARCHAR(n) extended ASCII +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (code CHAR(2), city VARCHAR(32))"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName VALUES (?,?)"; + +// First row +$stmt = $conn->prepare($sql); +$params = array("FI","Järvenpää"); +$stmt->execute($params); + +// Second row +$params = array("DE","München"); +$stmt->execute($params); + +// Query, fetch +$sql = "SELECT * from $tableName"; +$stmt = $conn->query($sql); +$data = $stmt->fetchAll(); + +// Print out +foreach ($data as $a) +echo $a[0] . "|" . $a[1] . "\n"; + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +FI|Järvenpää +DE|München +Done diff --git a/test/pdo_sqlsrv/pdo_022_xml_bind_value.phpt b/test/pdo_sqlsrv/pdo_022_xml_bind_value.phpt new file mode 100644 index 00000000..6b1164a6 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_022_xml_bind_value.phpt @@ -0,0 +1,79 @@ +--TEST-- +Unicode XML message using bindValue() +--SKIPIF-- +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +// Create database +$conn->query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (ID INT PRIMARY KEY NOT NULL IDENTITY, XMLMessage XML)"; +$stmt = $conn->query($sql); + +// XML samples +$xml1 = ' + + APP_PoP_银河 + Το Παρίσι (γαλλικά: Paris, ΔΦΑ [paˈʁi]), γνωστό και ως η Πόλη του φωτός (Ville lumière), από τότε που εφοδιάστηκαν οι κύριες λεωφόροι του με φανούς γκαζιού το 1828, είναι η πρωτεύουσα της Γαλλίας και της περιφέρειας Ιλ ντε Φρανς (Île-de-France) και μία από τις ιστορικότερες πόλεις της Ευρώπης. +'; + +$xml2 = ' + + NULL + +'; + +// Insert data +try +{ + $stmt = $conn->prepare("INSERT INTO $tableName (XMLMessage) VALUES (:msg)"); + $stmt->bindValue(':msg', $xml1); + $stmt->execute(); + + $stmt = $conn->prepare("INSERT INTO $tableName (XMLMessage) VALUES (?)"); + $stmt->bindValue(1, $xml2); + $stmt->execute(); +} +catch (PDOException $ex) { + echo "Error: " . $ex->getMessage(); +} + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetchAll(PDO::FETCH_ASSOC); +var_dump($row); + +// Drop database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt=null; +$conn=null; + +print "Done" +?> + +--EXPECT-- +array(2) { + [0]=> + array(2) { + ["ID"]=> + string(1) "1" + ["XMLMessage"]=> + string(553) "APP_PoP_银河Το Παρίσι (γαλλικά: Paris, ΔΦΑ [paˈʁi]), γνωστό και ως η Πόλη του φωτός (Ville lumière), από τότε που εφοδιάστηκαν οι κύριες λεωφόροι του με φανούς γκαζιού το 1828, είναι η πρωτεύουσα της Γαλλίας και της περιφέρειας Ιλ ντε Φρανς (Île-de-France) και μία από τις ιστορικότερες πόλεις της Ευρώπης." + } + [1]=> + array(2) { + ["ID"]=> + string(1) "2" + ["XMLMessage"]=> + string(43) "NULL" + } +} +Done diff --git a/test/pdo_sqlsrv/pdo_023.phpt b/test/pdo_sqlsrv/pdo_023.phpt new file mode 100644 index 00000000..c9d634db --- /dev/null +++ b/test/pdo_sqlsrv/pdo_023.phpt @@ -0,0 +1,90 @@ +--TEST-- +Bind values with PDO::PARAM_BOOL, enable/disable fetch numeric type attribute +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Run test +Test(); + +// Set PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE = false (default) +$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, FALSE); +Test(); + +// Set PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE = true +$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, TRUE); +Test(); + +// Drop database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; + +// Generic test starts here +function Test() +{ + global $conn, $tableName, $sample; + + // Drop table if exists + $sql = "IF OBJECT_ID('$tableName') IS NOT NULL DROP TABLE $tableName"; + $stmt = $conn->query($sql); + + // Create table + $sql = "CREATE TABLE $tableName (c1 INT, c2 BIT)"; + $stmt = $conn->query($sql) ?: die(); + + // Insert data using bind values + $sql = "INSERT INTO $tableName VALUES (:v1, :v2)"; + foreach ($sample as $s) { + $stmt = $conn->prepare($sql); + $stmt->bindValue(':v1', $s[0], PDO::PARAM_BOOL); + $stmt->bindValue(':v2', $s[1], PDO::PARAM_BOOL); + $stmt->execute(); + } + + // Get data + $sql = "SELECT * FROM $tableName"; + $stmt = $conn->query($sql); + $row = $stmt->fetchAll(PDO::FETCH_NUM); + + // Print out + for($i=0; $i<$stmt->rowCount(); $i++) + { var_dump($row[$i][0]); var_dump($row[$i][1]); } +} +?> + +--EXPECT-- +string(1) "1" +string(1) "0" +string(1) "1" +string(1) "1" +string(1) "0" +NULL +string(1) "1" +string(1) "0" +string(1) "1" +string(1) "1" +string(1) "0" +NULL +int(1) +int(0) +int(1) +int(1) +int(0) +NULL +Done diff --git a/test/pdo_sqlsrv/pdo_040_error_information.phpt b/test/pdo_sqlsrv/pdo_040_error_information.phpt new file mode 100644 index 00000000..a9c83410 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_040_error_information.phpt @@ -0,0 +1,45 @@ +--TEST-- +Retrieve error information; supplied values does not match table definition +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (code INT)"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +// Number of supplied values does not match table definition +$sql = "INSERT INTO $tableName VALUES (?,?)"; +$stmt = $conn->prepare($sql); +$params = array(2010,"London"); + +// SQL statement has an error, which is then reported +$stmt->execute($params); +print_r($stmt->errorInfo()); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +Array +( + [0] => 21S01 + [1] => 213 + [2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Column name or number of supplied values does not match table definition. +) +Done diff --git a/test/pdo_sqlsrv/pdo_060_prepare_execute_fetch_pooling_default.phpt b/test/pdo_sqlsrv/pdo_060_prepare_execute_fetch_pooling_default.phpt new file mode 100644 index 00000000..ee2e66cb --- /dev/null +++ b/test/pdo_sqlsrv/pdo_060_prepare_execute_fetch_pooling_default.phpt @@ -0,0 +1,73 @@ +--TEST-- +Prepare, execute statement and fetch with pooling unset (default) +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (Столица NVARCHAR(32), year INT)"; +$stmt = $conn->query($sql); + +// Insert data +$sql = "INSERT INTO ".$tableName." VALUES (?,?)"; +$stmt = $conn->prepare($sql); +$stmt->execute(array("Лондон",2012)); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); +$conn = null; + +// Create a new pool +$conn0 = new PDO( "sqlsrv:server=$serverName;", + $username, $password); +$conn0 = null; + +// Connection can use an existing pool +$conn = new PDO( "sqlsrv:server=$serverName;", + $username, $password); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt=null; +$conn=null; +print "Done" +?> +--EXPECT-- +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + int(2012) +} +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + string(4) "2012" +} +Done diff --git a/test/pdo_sqlsrv/pdo_061_prepare_execute_fetch_pooling_enabled.phpt b/test/pdo_sqlsrv/pdo_061_prepare_execute_fetch_pooling_enabled.phpt new file mode 100644 index 00000000..0c46ff87 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_061_prepare_execute_fetch_pooling_enabled.phpt @@ -0,0 +1,73 @@ +--TEST-- +Prepare, execute statement and fetch with pooling enabled +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (Столица NVARCHAR(32), year INT)"; +$stmt = $conn->query($sql); + +// Insert data +$sql = "INSERT INTO ".$tableName." VALUES (?,?)"; +$stmt = $conn->prepare($sql); +$stmt->execute(array("Лондон",2012)); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); +$conn = null; + +// Create a new pool +$conn0 = new PDO( "sqlsrv:server=$serverName;ConnectionPooling=1;", + $username, $password); +$conn0 = null; + +// Connection can use an existing pool +$conn = new PDO( "sqlsrv:server=$serverName;ConnectionPooling=1;", + $username, $password); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt=null; +$conn=null; +print "Done" +?> +--EXPECT-- +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + int(2012) +} +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + string(4) "2012" +} +Done diff --git a/test/pdo_sqlsrv/pdo_062_prepare_execute_fetch_pooling_disabled.phpt b/test/pdo_sqlsrv/pdo_062_prepare_execute_fetch_pooling_disabled.phpt new file mode 100644 index 00000000..dc30a772 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_062_prepare_execute_fetch_pooling_disabled.phpt @@ -0,0 +1,73 @@ +--TEST-- +Prepare, execute statement and fetch with pooling disabled +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (Столица NVARCHAR(32), year INT)"; +$stmt = $conn->query($sql); + +// Insert data +$sql = "INSERT INTO ".$tableName." VALUES (?,?)"; +$stmt = $conn->prepare($sql); +$stmt->execute(array("Лондон",2012)); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); +$conn = null; + +// Create a new pool +$conn0 = new PDO( "sqlsrv:server=$serverName;ConnectionPooling=0;", + $username, $password); +$conn0 = null; + +// Connection can use an existing pool +$conn = new PDO( "sqlsrv:server=$serverName;ConnectionPooling=0;", + $username, $password); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt=null; +$conn=null; +print "Done" +?> +--EXPECT-- +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + int(2012) +} +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + string(4) "2012" +} +Done diff --git a/test/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt b/test/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt new file mode 100644 index 00000000..4d240b96 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt @@ -0,0 +1,58 @@ +--TEST-- +sqlsrv_has_rows() using a forward and scrollable cursor +--SKIPIF-- +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + +// Create database +$conn->query("CREATE DATABASE ". $dbName); +// Create table +$stmt = $conn->query("CREATE TABLE ".$dbName.".[dbo].[php_test_table_1] ([c1_int] int, [c2_varchar] varchar(1000))"); +$query = "INSERT INTO ".$dbName.".[dbo].[php_test_table_1] (c1_int, c2_varchar) VALUES ((990021574), ('>vh~Ö.bÐ*äß/ÄAabýZâOüzr£ðAß+|~|OU¢a|U<ßrv.uCB.ÐÜh_î+ãå@üðöã,U+ßvuU:/ý_Öãî/ð|bB|_Zbua©r++BA¢z£.üî¢öåäözÜ¢ßb:aöCrÄ~ýZ¢uªÐö.hhßð*zÜÜß*ãüåýãÄ+åýüüaߢÃÐBî@~AZöÃOßC@äoÃuCÜ,ÐÄa:îäÄÖý:h*ouªuåvUz_ArßAªãaãvÐåAUüAB:¢Äz|öub<üZvößüå:ãÄ@r/ZAÄðÄÄvzîv~C/£|ýýbüÖ~£|Öå<Üa~/v@åAz©¢£U_ßhbaÃß,zz<ã¢|<ä©>öAuövÖ>abu,zå,+ß/ü/ª_bbB:ÃC~£ü/O©O©ªAª_,|a¢~ýý/b>ßC@/böîöh>~£ð+Bßr©ÄÐÖßã:bA@:>B:UAbããîÜ~uÜ£îCöÖ£©_ÜßzÐ+ÖýZb,A:<z.ãîÄzC@©*ä|ã._ßZOäb¢Cßovå+uv.£B~~b£ª|ÖÄîßö>©Ãbb|©©ðA£åO~âãüîuvÄÜýUzîOÖ/oOßO*>ªßzêÖÐböÄåbîðîÐa~©ßîÄßУ<î>åBã_ý*ah¢rOĪ,ßo¢¢a|BÖäzU£.B£@Ü,ßAÃ>,ðßß+ßÜ©|Ðr©bCðТüãz>AßðåÃ>bÄåÄ|Z~äÃ/Cb*£bð_/Ða@~AÜãO+ý*CîîÃzÄöÃa©+@vuz>î>©.Cv>hÃý>©Bä,ö~@~@r,AðCu@Ü,@U*ÐvöÃêuã.Öa*uZªoZ/ðÖ©ßv_<ÖvåÜÐÜOÐoðßðÃUýZÐB:+ÄÃã£'))"; +$stmt = $conn->query($query); + +$stmt = $conn->query("CREATE TABLE ".$dbName.".[dbo].[php_test_table_2] ([c1_int] int, [c2_varchar] varchar(max))"); +$query = "INSERT INTO ".$dbName.".[dbo].[php_test_table_2] (c1_int, c2_varchar) VALUES ((990021574), ('>vh~Ö.bÐ*äß/ÄAabýZâOüzr£ðAß+|~|OU¢a|U<ßrv.uCB.ÐÜh_î+ãå@üðöã,U+ßvuU:/ý_Öãî/ð|bB|_Zbua©r++BA¢z£.üî¢öåäözÜ¢ßb:aöCrÄ~ýZ¢uªÐö.hhßð*zÜÜß*ãüåýãÄ+åýüüaߢÃÐBî@~AZöÃOßC@äoÃuCÜ,ÐÄa:îäÄÖý:h*ouªuåvUz_ArßAªãaãvÐåAUüAB:¢Äz|öub<üZvößüå:ãÄ@r/ZAÄðÄÄvzîv~C/£|ýýbüÖ~£|Öå<Üa~/v@åAz©¢£U_ßhbaÃß,zz<ã¢|<ä©>öAuövÖ>abu,zå,+ß/ü/ª_bbB:ÃC~£ü/O©O©ªAª_,|a¢~ýý/b>ßC@/böîöh>~£ð+Bßr©ÄÐÖßã:bA@:>B:UAbããîÜ~uÜ£îCöÖ£©_ÜßzÐ+ÖýZb,A:<z.ãîÄzC@©*ä|ã._ßZOäb¢Cßovå+uv.£B~~b£ª|ÖÄîßö>©Ãbb|©©ðA£åO~âãüîuvÄÜýUzîOÖ/oOßO*>ªßzêÖÐböÄåbîðîÐa~©ßîÄßУ<î>åBã_ý*ah¢rOĪ,ßo¢¢a|BÖäzU£.B£@Ü,ßAÃ>,ðßß+ßÜ©|Ðr©bCðТüãz>AßðåÃ>bÄåÄ|Z~äÃ/Cb*£bð_/Ða@~AÜãO+ý*CîîÃzÄöÃa©+@vuz>î>©.Cv>hÃý>©Bä,ö~@~@r,AðCu@Ü,@U*ÐvöÃêuã.Öa*uZªoZ/ðÖ©ßv_<ÖvåÜÐÜOÐoðßðÃUýZÐB:+ÄÃã£'))"; +$stmt = $conn->query($query); + +$size = 2; +$stmt = $conn->prepare("SELECT * FROM ".$dbName.".[dbo].[php_test_table_1]", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); +$attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); +echo("Client Buffer Size in KB: $attr\n"); +$stmt->execute(); +$numRows = 0; +while ($result = $stmt->fetch()) + $numRows++; + +echo ("Number of rows: $numRows\n"); + +$size = 3; +$stmt = $conn->prepare("SELECT * FROM ".$dbName.".[dbo].[php_test_table_2]", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); +$attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); +echo("Client Buffer Size in KB: $attr\n"); +$stmt->execute(); +$numRows = 0; +while ($result = $stmt->fetch()) + $numRows++; + +echo ("Number of rows: $numRows\n"); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); +$stmt=null; +$conn=null; +print "Done" +?> + +--EXPECT-- +Client Buffer Size in KB: 2 +Number of rows: 1 +Client Buffer Size in KB: 3 +Number of rows: 1 +Done diff --git a/test/sqlsrv/autonomous_setup.php b/test/sqlsrv/autonomous_setup.php new file mode 100644 index 00000000..2e80f6a2 --- /dev/null +++ b/test/sqlsrv/autonomous_setup.php @@ -0,0 +1,16 @@ +"; + + + // Generate unique DB name, example: php_20160817_1471475608267 + $dbName = "php_" . date("Ymd") . "_" . round(microtime(true)*1000); + + // Generic table name example: php_20160817_1471475608267.dbo.php_firefly + $tableName = $dbName.".dbo.php_firefly"; + + // Connection options + $connectionInfo = array("UID"=>"$username", "PWD"=>"$password"); +?> diff --git a/test/sqlsrv/srv_001.phpt b/test/sqlsrv/srv_001.phpt new file mode 100644 index 00000000..91c17f66 --- /dev/null +++ b/test/sqlsrv/srv_001.phpt @@ -0,0 +1,21 @@ +--TEST-- +Connect to the default database with credentials +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password" ); +$conn = sqlsrv_connect( $serverName, $connectionInfo ); + +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +Done diff --git a/test/sqlsrv/srv_002.phpt b/test/sqlsrv/srv_002.phpt new file mode 100644 index 00000000..7b4226ca --- /dev/null +++ b/test/sqlsrv/srv_002.phpt @@ -0,0 +1,21 @@ +--TEST-- +Connect with options +--SKIPIF-- +--FILE-- +"master", "UID"=>"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +Done diff --git a/test/sqlsrv/srv_011_temporary_table.phpt b/test/sqlsrv/srv_011_temporary_table.phpt new file mode 100644 index 00000000..fb89e7bf --- /dev/null +++ b/test/sqlsrv/srv_011_temporary_table.phpt @@ -0,0 +1,32 @@ +--TEST-- +Temporary table +--SKIPIF-- +--FILE-- + + +--EXPECT-- +string(11) "PHP7 SQLSRV" +Done diff --git a/test/sqlsrv/srv_012_sqlsrv_fetch_array.phpt b/test/sqlsrv/srv_012_sqlsrv_fetch_array.phpt new file mode 100644 index 00000000..82786d5a --- /dev/null +++ b/test/sqlsrv/srv_012_sqlsrv_fetch_array.phpt @@ -0,0 +1,52 @@ +--TEST-- +sqlsrv_fetch_array() using a scrollable cursor +--SKIPIF-- +--FILE-- +"buffered")) + ?: die( print_r(sqlsrv_errors(), true)); + +// Fetch first row +$row = sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC,SQLSRV_SCROLL_NEXT); +echo $row['ID']."\n"; + +// Fetch third row +$row = sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC,SQLSRV_SCROLL_ABSOLUTE,2); +echo $row['ID']."\n"; + +// Fetch last row +$row = sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC,SQLSRV_SCROLL_LAST); +echo $row['ID']."\n"; + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +1998.1 +2016 +4.2EUR +Done diff --git a/test/sqlsrv/srv_013_sqlsrv_get_field.phpt b/test/sqlsrv/srv_013_sqlsrv_get_field.phpt new file mode 100644 index 00000000..935add7f --- /dev/null +++ b/test/sqlsrv/srv_013_sqlsrv_get_field.phpt @@ -0,0 +1,48 @@ +--TEST-- +sqlsrv_get_field() using SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR) +--SKIPIF-- +--FILE-- + + +--EXPECT-- +resource(9) of type (stream) +1998.1 +resource(10) of type (stream) +-2004.2436 +resource(11) of type (stream) +4.2 EUR +Done diff --git a/test/sqlsrv/srv_014_sqlsrv_get_field.phpt b/test/sqlsrv/srv_014_sqlsrv_get_field.phpt new file mode 100644 index 00000000..270edc1b --- /dev/null +++ b/test/sqlsrv/srv_014_sqlsrv_get_field.phpt @@ -0,0 +1,53 @@ +--TEST-- +sqlsrv_get_field() using SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY) +--SKIPIF-- +--FILE-- + + +--EXPECT-- +resource(10) of type (stream) +1998.1 +resource(11) of type (stream) +-2004.2436 +resource(12) of type (stream) +4.2EUR +Done diff --git a/test/sqlsrv/srv_028_data_conversion_nvarchar.phpt b/test/sqlsrv/srv_028_data_conversion_nvarchar.phpt new file mode 100644 index 00000000..f12a8e54 --- /dev/null +++ b/test/sqlsrv/srv_028_data_conversion_nvarchar.phpt @@ -0,0 +1,55 @@ +--TEST-- +Data type precedence: conversion NVARCHAR(n) +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"UTF-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo) ?: die(); + +// Create database +sqlsrv_query($conn,"CREATE DATABASE ". $dbName) ?: die(); + +// Create table. Column names: passport +$sql = "CREATE TABLE $tableName (c1 NVARCHAR(8))"; +$stmt = sqlsrv_query($conn, $sql); + +// Insert data. The data type with the lower precedence +// is converted to the data type with the higher precedence +$sql = "INSERT INTO $tableName VALUES (3.1415),(-32),(null)"; +$stmt = sqlsrv_query($conn, $sql); + +// Insert more data +$sql = "INSERT INTO $tableName VALUES (''),('Galaxy'),('-- GO'),(N'银河系')"; +$stmt = sqlsrv_query($conn, $sql); + +// Read data from the table +$sql = "SELECT * FROM $tableName"; +$stmt = sqlsrv_query($conn, $sql); + +while($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC)) { + var_dump($row[0]); +} + +// DROP database +sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +// Free statement and connection resources +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +print "Done" +?> + +--EXPECT-- +string(6) "3.1415" +string(8) "-32.0000" +NULL +string(0) "" +string(6) "Galaxy" +string(5) "-- GO" +string(9) "银河系" +Done diff --git a/test/sqlsrv/srv_029_data_conversion_varchar_datetime.phpt b/test/sqlsrv/srv_029_data_conversion_varchar_datetime.phpt new file mode 100644 index 00000000..a519f3f9 --- /dev/null +++ b/test/sqlsrv/srv_029_data_conversion_varchar_datetime.phpt @@ -0,0 +1,47 @@ +--TEST-- +Data type precedence: conversion VARCHAR(n) +--SKIPIF-- +--FILE-- + + +--EXPECT-- +string(19) "Jan 1 1900 12:00AM" +string(19) "Dec 18 1898 2:24PM" +string(19) "Sep 24 2017 9:36AM" +Done diff --git a/test/sqlsrv/srv_031_sqlsrv_field_metadata.phpt b/test/sqlsrv/srv_031_sqlsrv_field_metadata.phpt new file mode 100644 index 00000000..7bf25190 --- /dev/null +++ b/test/sqlsrv/srv_031_sqlsrv_field_metadata.phpt @@ -0,0 +1,97 @@ +--TEST-- +sqlsrv_field_metadata() VARCHAR(n), NVARCHAR(n), INT +--SKIPIF-- +--FILE-- + + +--EXPECT-- +array(3) { + [0]=> + array(6) { + ["Name"]=> + string(9) "FirstName" + ["Type"]=> + int(12) + ["Size"]=> + int(10) + ["Precision"]=> + NULL + ["Scale"]=> + NULL + ["Nullable"]=> + int(1) + } + [1]=> + array(6) { + ["Name"]=> + string(8) "LastName" + ["Type"]=> + int(-9) + ["Size"]=> + int(20) + ["Precision"]=> + NULL + ["Scale"]=> + NULL + ["Nullable"]=> + int(1) + } + [2]=> + array(6) { + ["Name"]=> + string(3) "Age" + ["Type"]=> + int(4) + ["Size"]=> + NULL + ["Precision"]=> + int(10) + ["Scale"]=> + NULL + ["Nullable"]=> + int(1) + } +} +Done diff --git a/test/sqlsrv/srv_034_field_metadata_unicode.phpt b/test/sqlsrv/srv_034_field_metadata_unicode.phpt new file mode 100644 index 00000000..800c7f85 --- /dev/null +++ b/test/sqlsrv/srv_034_field_metadata_unicode.phpt @@ -0,0 +1,68 @@ +--TEST-- +Field metadata unicode +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password", "CharacterSet"=>"UTF-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo) ?: die(); + +// Create database +sqlsrv_query($conn,"CREATE DATABASE ". $dbName) ?: die(); + +// Create table. Column names: passport +$sql = "CREATE TABLE $tableName (पासपोर्ट CHAR(2), پاسپورٹ VARCHAR(2), Διαβατήριο VARCHAR(MAX))"; +$stmt = sqlsrv_query($conn, $sql); + +// Prepare the statement +$sql = "SELECT * FROM $tableName"; +$stmt = sqlsrv_prepare($conn, $sql); + +// Get and display field metadata +foreach(sqlsrv_field_metadata($stmt) as $meta) +{ + print_r($meta); +} + +// DROP database +sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +// Free statement and connection resources +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +print "Done" +?> + +--EXPECT-- +Array +( + [Name] => पासपोर्ट + [Type] => 1 + [Size] => 2 + [Precision] => + [Scale] => + [Nullable] => 1 +) +Array +( + [Name] => پاسپورٹ + [Type] => 12 + [Size] => 2 + [Precision] => + [Scale] => + [Nullable] => 1 +) +Array +( + [Name] => Διαβατήριο + [Type] => 12 + [Size] => 0 + [Precision] => + [Scale] => + [Nullable] => 1 +) +Done diff --git a/test/sqlsrv/srv_036_transaction_commit.phpt b/test/sqlsrv/srv_036_transaction_commit.phpt new file mode 100644 index 00000000..55b6de22 --- /dev/null +++ b/test/sqlsrv/srv_036_transaction_commit.phpt @@ -0,0 +1,83 @@ +--TEST-- +Transaction operations: commit successful transactions +--SKIPIF-- +--FILE-- += 0))"; +$stmt = sqlsrv_query($conn, $sql); + + +// Set initial data +$sql = "INSERT INTO $tableName VALUES ('ID1','12','5'),('ID102','20','1')"; +$stmt = sqlsrv_query($conn, $sql) ?: die(print_r(sqlsrv_errors(), true)); + +//Initiate transaction +sqlsrv_begin_transaction($conn) ?: die(print_r( sqlsrv_errors(), true)); + +// Update parameters +$count = 4; +$groupId = "ID1"; +$params = array($count, $groupId); + +// Update Accepted column +$sql = "UPDATE $tableName SET Accepted = (Accepted + ?) WHERE GroupId = ?"; +$stmt1 = sqlsrv_query( $conn, $sql, $params) ?: die(print_r(sqlsrv_errors(), true)); + +// Update Tentative column +$sql = "UPDATE $tableName SET Tentative = (Tentative - ?) WHERE GroupId = ?"; +$stmt2 = sqlsrv_query($conn, $sql, $params); + +// Commit the transactions +if ($stmt1 && $stmt2) +{ + sqlsrv_commit($conn); +} +else +{ + echo "\nERROR: $stmt1 and $stmt2 should be valid\n"; + sqlsrv_rollback($conn); + echo "\nTransactions were rolled back.\n"; +} + +PrintContent($conn); + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +Array +( + [GroupId] => ID1 + [Accepted] => 16 + [Tentative] => 1 +) +Done + diff --git a/test/sqlsrv/srv_037_sqlsrv_has_rows.phpt b/test/sqlsrv/srv_037_sqlsrv_has_rows.phpt new file mode 100644 index 00000000..c93bef77 --- /dev/null +++ b/test/sqlsrv/srv_037_sqlsrv_has_rows.phpt @@ -0,0 +1,107 @@ +--TEST-- +sqlsrv_has_rows() using a forward and scrollable cursor +--DESCRIPTION-- +This test calls sqlsrv_has_rows multiple times. Previously, multiple calls +with a forward cursor would advance the cursor. Subsequent fetch calls +would then fail. +--SKIPIF-- +--FILE-- +"buffered")) + ?: die( print_r(sqlsrv_errors(), true)); + +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; + +if (sqlsrv_has_rows($stmt)) { + while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC)) + { + echo $row[0]."\n"; + } +} + +$query = "SELECT ID FROM $tableName where ID='nomatch'"; +$stmt = sqlsrv_query($conn, $query) + ?: die( print_r(sqlsrv_errors(), true)); + +// repeated calls should return false if there are no rows. +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; + +// Fetch data using a scrollable cursor +$stmt = sqlsrv_query($conn, $query, [], array("Scrollable"=>"buffered")) + ?: die( print_r(sqlsrv_errors(), true)); + +// repeated calls should return false if there are no rows. +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; + + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +Has Rows? Yes! +Has Rows? Yes! +Has Rows? Yes! +Has Rows? Yes! +1998.1 +-2004 +2016 +4.2EUR +Has Rows? Yes! +Has Rows? Yes! +Has Rows? Yes! +Has Rows? Yes! +1998.1 +-2004 +2016 +4.2EUR +Has Rows? NO! +Has Rows? NO! +Has Rows? NO! +Has Rows? NO! +Done diff --git a/test/sqlsrv/srv_037_transaction_rollback.phpt b/test/sqlsrv/srv_037_transaction_rollback.phpt new file mode 100644 index 00000000..b7065a66 --- /dev/null +++ b/test/sqlsrv/srv_037_transaction_rollback.phpt @@ -0,0 +1,82 @@ +--TEST-- +Transaction operations: rolled-back transactions +--SKIPIF-- +--FILE-- += 0))"; +$stmt = sqlsrv_query($conn, $sql); + + +// Set initial data +$sql = "INSERT INTO $tableName VALUES ('ID1','12','5'),('ID102','20','1')"; +$stmt = sqlsrv_query($conn, $sql) ?: die(print_r(sqlsrv_errors(), true)); + +//Initiate transaction +sqlsrv_begin_transaction($conn) ?: die(print_r( sqlsrv_errors(), true)); + +// Update parameters +$count = 8; +$groupId = "ID1"; +$params = array($count, $groupId); + +// Update Accepted column +$sql = "UPDATE $tableName SET Accepted = (Accepted + ?) WHERE GroupId = ?"; +$stmt1 = sqlsrv_query( $conn, $sql, $params) ?: die(print_r(sqlsrv_errors(), true)); + +// Update Tentative column +// This statement returns FALSE because Tentative column should be non-negative +$sql = "UPDATE $tableName SET Tentative = (Tentative - ?) WHERE GroupId = ?"; +$stmt2 = sqlsrv_query($conn, $sql, $params); + +// Commit the transactions +if ($stmt1 && $stmt2) +{ + echo "\nERROR: $stmt2 should be bool(false)\n"; +} +else +{ + sqlsrv_rollback($conn); +} + +PrintContent($conn); + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +Array +( + [GroupId] => ID1 + [Accepted] => 12 + [Tentative] => 5 +) +Done + diff --git a/test/sqlsrv/srv_047_stream_nvarchar.phpt b/test/sqlsrv/srv_047_stream_nvarchar.phpt new file mode 100644 index 00000000..13d14f04 --- /dev/null +++ b/test/sqlsrv/srv_047_stream_nvarchar.phpt @@ -0,0 +1,55 @@ +--TEST-- +Streaming nvarchar(max) unicode (Russian) with CharacterSet=utf-8 +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"utf-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo); +if( !$conn ) { die( print_r( sqlsrv_errors(), true)); } + +// Create table +$sql = "CREATE TABLE #Table (c1 NVARCHAR(max))"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Insert data, 4538 characters +$data = "Первые публикации об объектно-ориентированных базах данных появились в середине 80-х годов. Поддержка сложных объектов. В системе должна быть предусмотрена возможность создания составных объектов за счет применения конструкторов составных объектов. Необходимо, чтобы конструкторы объектов были ортогональны, то есть любой конструктор можно было применять к любому объекту. Поддержка индивидуальности объектов. Все объекты должны иметь уникальный идентификатор, который не зависит от значений их атрибутов. Поддержка инкапсуляции. Корректная инкапсуляция достигается за счет того, что программисты обладают правом доступа только к спецификации интерфейса методов, а данные и реализация методов скрыты внутри объектов. Поддержка типов и классов. Требуется, чтобы в ООБД поддерживалась хотя бы одна концепция различия между типами и классами. (Термин «тип» более соответствует понятию абстрактного типа данных. В языках программирования переменная объявляется с указанием её типа. Компилятор может использовать эту информацию для проверки выполняемых с переменной операций на совместимость с её типом, что позволяет гарантировать корректность программного обеспечения. С другой стороны класс является неким шаблоном для создания объектов и предоставляет методы, которые могут применяться к этим объектам. Таким образом, понятие «класс» в большей степени относится ко времени исполнения, чем ко времени компиляции.) Поддержка наследования типов и классов от их предков. Подтип, или подкласс, должен наследовать атрибуты и методы от его супертипа, или суперкласса, соответственно. Перегрузка в сочетании с полным связыванием. Методы должны применяться к объектам разных типов. Реализация метода должна зависеть от типа объектов, к которым данный метод применяется. Для обеспечения этой функциональности связывание имен методов в системе не должно выполняться до времени выполнения программы. Вычислительная полнота. Язык манипулирования данными должен быть языком программирования общего назначения. Набор типов данных должен быть расширяемым. Пользователь должен иметь средства создания новых типов данных на основе набора предопределенных системных типов. Более того, между способами использования системных и пользовательских типов данных не должно быть никаких различий.Первые публикации об объектно-ориентированных базах данных появились в середине 80-х годов. Поддержка сложных объектов. В системе должна быть предусмотрена возможность создания составных объектов за счет применения конструкторов составных объектов. Необходимо, чтобы конструкторы объектов были ортогональны, то есть любой конструктор можно было применять к любому объекту. Поддержка индивидуальности объектов. Все объекты должны иметь уникальный идентификатор, который не зависит от значений их атрибутов. Поддержка инкапсуляции. Корректная инкапсуляция достигается за счет того, что программисты обладают правом доступа только к спецификации интерфейса методов, а данные и реализация методов скрыты внутри объектов. Поддержка типов и классов. Требуется, чтобы в ООБД поддерживалась хотя бы одна концепция различия между типами и классами. (Термин «тип» более соответствует понятию абстрактного типа данных. В языках программирования переменная объявляется с указанием её типа. Компилятор может использовать эту информацию для проверки выполняемых с переменной операций на совместимость с её типом, что позволяет гарантировать корректность программного обеспечения. С другой стороны класс является неким шаблоном для создания объектов и предоставляет методы, которые могут применяться к этим объектам. Таким образом, понятие «класс» в большей степени относится ко времени исполнения, чем ко времени компиляции.) Поддержка наследования типов и классов от их предков. Подтип, или подкласс, должен наследовать атрибуты и методы от его супертипа, или суперкласса, соответственно. Перегрузка в сочетании с полным связыванием. Методы должны применяться к объектам разных типов. Реализация метода должна зависеть от типа объектов, к которым данный метод применяется. Для обеспечения этой функциональности связывание имен методов в системе не должно выполняться до времени выполнения программы. Вычислительная полнота. Язык манипулирования данными должен быть языком программирования общего назначения. Набор типов данных должен быть расширяемым. Пользователь должен иметь средства создания новых типов данных на основе набора предопределенных системных типов. Более того, между способами использования системных и пользовательских типов данных не должно быть никаких различий."; +$sql = "INSERT INTO #Table VALUES (N'$data')"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Query and fetch +$sql = "SELECT * FROM #Table"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } +sqlsrv_fetch($stmt); + +// Get the data +$field = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STREAM('utf-8')); +$out = ""; +while(!feof($field)) +{ + $out .= fread($field, 512); +} + +// Output string length +var_dump(strlen($out)); + +// Compare output +echo ($out === $data) ? "True\n" : "False\n"; + +// Close connection +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +print "Done"; +?> + +--EXPECT-- +int(8408) +True +Done diff --git a/test/sqlsrv/srv_048_stream_nvarchar.phpt b/test/sqlsrv/srv_048_stream_nvarchar.phpt new file mode 100644 index 00000000..93e8972f --- /dev/null +++ b/test/sqlsrv/srv_048_stream_nvarchar.phpt @@ -0,0 +1,42 @@ +--TEST-- +Streaming nvarchar(max) with sqlsrv_fetch_array() +--SKIPIF-- +--FILE-- + + +--EXPECT-- +True +Done diff --git a/test/sqlsrv/srv_049_stream_nvarchar_utf8.phpt b/test/sqlsrv/srv_049_stream_nvarchar_utf8.phpt new file mode 100644 index 00000000..d245873a --- /dev/null +++ b/test/sqlsrv/srv_049_stream_nvarchar_utf8.phpt @@ -0,0 +1,43 @@ +--TEST-- +Streaming nvarchar(max) with sqlsrv_fetch_array() and CharacterSet=utf-8 +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"utf-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo); +if( !$conn ) { die( print_r( sqlsrv_errors(), true)); } + +// Create table +$sql = "CREATE TABLE #Table (c1 NVARCHAR(max))"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Insert data +$data = "Первые публикации об объектно-ориентированных базах данных появились в середине 80-х годов. Поддержка сложных объектов. В системе должна быть предусмотрена возможность создания составных объектов за счет применения конструкторов составных объектов. Необходимо, чтобы конструкторы объектов были ортогональны, то есть любой конструктор можно было применять к любому объекту. Поддержка индивидуальности объектов. Все объекты должны иметь уникальный идентификатор, который не зависит от значений их атрибутов. Поддержка инкапсуляции. Корректная инкапсуляция достигается за счет того, что программисты обладают правом доступа только к спецификации интерфейса методов, а данные и реализация методов скрыты внутри объектов. Поддержка типов и классов. Требуется, чтобы в ООБД поддерживалась хотя бы одна концепция различия между типами и классами. (Термин «тип» более соответствует понятию абстрактного типа данных. В языках программирования переменная объявляется с указанием её типа. Компилятор может использовать эту информацию для проверки выполняемых с переменной операций на совместимость с её типом, что позволяет гарантировать корректность программного обеспечения. С другой стороны класс является неким шаблоном для создания объектов и предоставляет методы, которые могут применяться к этим объектам. Таким образом, понятие «класс» в большей степени относится ко времени исполнения, чем ко времени компиляции.) Поддержка наследования типов и классов от их предков. Подтип, или подкласс, должен наследовать атрибуты и методы от его супертипа, или суперкласса, соответственно. Перегрузка в сочетании с полным связыванием. Методы должны применяться к объектам разных типов. Реализация метода должна зависеть от типа объектов, к которым данный метод применяется. Для обеспечения этой функциональности связывание имен методов в системе не должно выполняться до времени выполнения программы. Вычислительная полнота. Язык манипулирования данными должен быть языком программирования общего назначения. Набор типов данных должен быть расширяемым. Пользователь должен иметь средства создания новых типов данных на основе набора предопределенных системных типов. Более того, между способами использования системных и пользовательских типов данных не должно быть никаких различий.Первые публикации об объектно-ориентированных базах данных появились в середине 80-х годов. Поддержка сложных объектов. В системе должна быть предусмотрена возможность создания составных объектов за счет применения конструкторов составных объектов. Необходимо, чтобы конструкторы объектов были ортогональны, то есть любой конструктор можно было применять к любому объекту. Поддержка индивидуальности объектов. Все объекты должны иметь уникальный идентификатор, который не зависит от значений их атрибутов. Поддержка инкапсуляции. Корректная инкапсуляция достигается за счет того, что программисты обладают правом доступа только к спецификации интерфейса методов, а данные и реализация методов скрыты внутри объектов. Поддержка типов и классов. Требуется, чтобы в ООБД поддерживалась хотя бы одна концепция различия между типами и классами. (Термин «тип» более соответствует понятию абстрактного типа данных. В языках программирования переменная объявляется с указанием её типа. Компилятор может использовать эту информацию для проверки выполняемых с переменной операций на совместимость с её типом, что позволяет гарантировать корректность программного обеспечения. С другой стороны класс является неким шаблоном для создания объектов и предоставляет методы, которые могут применяться к этим объектам. Таким образом, понятие «класс» в большей степени относится ко времени исполнения, чем ко времени компиляции.) Поддержка наследования типов и классов от их предков. Подтип, или подкласс, должен наследовать атрибуты и методы от его супертипа, или суперкласса, соответственно. Перегрузка в сочетании с полным связыванием. Методы должны применяться к объектам разных типов. Реализация метода должна зависеть от типа объектов, к которым данный метод применяется. Для обеспечения этой функциональности связывание имен методов в системе не должно выполняться до времени выполнения программы. Вычислительная полнота. Язык манипулирования данными должен быть языком программирования общего назначения. Набор типов данных должен быть расширяемым. Пользователь должен иметь средства создания новых типов данных на основе набора предопределенных системных типов. Более того, между способами использования системных и пользовательских типов данных не должно быть никаких различий."; +$sql = "INSERT INTO #Table VALUES (N'$data')"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Fetch +$sql = "SELECT * FROM #Table"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Compare output +$row = sqlsrv_fetch_array($stmt); +echo ($row['c1'] == $data) ? "True\n" : "False\n"; + +// Close connection +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +print "Done"; +?> + +--EXPECT-- +True +Done diff --git a/test/sqlsrv/srv_050_error_conversion_varchar_int.phpt b/test/sqlsrv/srv_050_error_conversion_varchar_int.phpt new file mode 100644 index 00000000..6ffd189e --- /dev/null +++ b/test/sqlsrv/srv_050_error_conversion_varchar_int.phpt @@ -0,0 +1,46 @@ +--TEST-- +Error checking for failed conversion: VARCHAR value 'null' to data type INT +--SKIPIF-- +--FILE-- + + +--EXPECT-- +Array +( + [0] => 22018 + [SQLSTATE] => 22018 + [1] => 245 + [code] => 245 + [2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the varchar value 'null' to data type int. + [message] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the varchar value 'null' to data type int. +) +Done \ No newline at end of file diff --git a/test/sqlsrv/srv_051_error_conversion_nchar.phpt b/test/sqlsrv/srv_051_error_conversion_nchar.phpt new file mode 100644 index 00000000..46870645 --- /dev/null +++ b/test/sqlsrv/srv_051_error_conversion_nchar.phpt @@ -0,0 +1,47 @@ +--TEST-- +Error checking for failed explicit data type conversions NCHAR(2) +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"UTF-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo) ?: die(); + +// Create database +sqlsrv_query($conn,"CREATE DATABASE ". $dbName) ?: die(); + +// Create table. Column names: passport +$sql = "CREATE TABLE $tableName (c1 NCHAR(2))"; +$stmt = sqlsrv_query($conn, $sql); + +// Insert data. Invalid statement +$sql = "INSERT INTO $tableName VALUES (10),(N'银河')"; +$stmt = sqlsrv_query($conn, $sql); + +// Get extended error +$err = sqlsrv_errors(); +print_r($err[0]); + +// DROP database +sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +// Free statement and connection resources +sqlsrv_close($conn); + +print "Done" +?> + +--EXPECT-- +Array +( + [0] => 22018 + [SQLSTATE] => 22018 + [1] => 245 + [code] => 245 + [2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the nvarchar value '银河' to data type int. + [message] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the nvarchar value '银河' to data type int. +) +Done diff --git a/test/sqlsrv/srv_052_mars.phpt b/test/sqlsrv/srv_052_mars.phpt new file mode 100644 index 00000000..ee61f5c4 --- /dev/null +++ b/test/sqlsrv/srv_052_mars.phpt @@ -0,0 +1,40 @@ +--TEST-- +Enable multiple active result sets (MARS) +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, 'MultipleActiveResultSets' => true); +$conn = sqlsrv_connect($serverName, $connectionInfo) ?: die(); + +// Query +$stmt1 = sqlsrv_query( $conn, "SELECT 'ONE'" ) ?: die(print_r( sqlsrv_errors(), true )); +sqlsrv_fetch( $stmt1 ); + +// Query. Returns if multiple result sets are disabled +$stmt2 = sqlsrv_query( $conn, "SELECT 'TWO'" ) ?: die(print_r( sqlsrv_errors(), true )); +sqlsrv_fetch( $stmt2 ); + +// Print the data +$res = [ sqlsrv_get_field($stmt1, 0), sqlsrv_get_field($stmt2, 0) ]; +var_dump($res); + +// Free statement and connection resources +sqlsrv_free_stmt($stmt1); +sqlsrv_free_stmt($stmt2); +sqlsrv_close($conn); + +print "Done" +?> + +--EXPECT-- +array(2) { + [0]=> + string(3) "ONE" + [1]=> + string(3) "TWO" +} +Done diff --git a/test/sqlsrv/srv_053_mars_disabled_error_checks.phpt b/test/sqlsrv/srv_053_mars_disabled_error_checks.phpt new file mode 100644 index 00000000..0f03db7c --- /dev/null +++ b/test/sqlsrv/srv_053_mars_disabled_error_checks.phpt @@ -0,0 +1,56 @@ +--TEST-- +Error checking for multiple active result sets (MARS) disabled +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, 'MultipleActiveResultSets' => false); +$conn = sqlsrv_connect($serverName, $connectionInfo) ?: die(); + +// Query +$stmt1 = sqlsrv_query( $conn, "SELECT 'ONE'" ) ?: die(print_r( sqlsrv_errors(), true )); +sqlsrv_fetch( $stmt1 ); + +// Query. Returns if multiple result sets are disabled +$stmt2 = sqlsrv_query( $conn, "SELECT 'TWO'" ) ?: die(print_r( sqlsrv_errors(), true )); +sqlsrv_fetch( $stmt2 ); + +// Print the data +$res = [ sqlsrv_get_field($stmt1, 0), sqlsrv_get_field($stmt2, 0) ]; +var_dump($res); + +// Free statement and connection resources +sqlsrv_free_stmt($stmt1); +sqlsrv_free_stmt($stmt2); +sqlsrv_close($conn); + +print "Done" +?> + +--EXPECT-- +Array +( + [0] => Array + ( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -44 + [code] => -44 + [2] => The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. + [message] => The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. + ) + + [1] => Array + ( + [0] => HY000 + [SQLSTATE] => HY000 + [1] => 0 + [code] => 0 + [2] => [Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command + [message] => [Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command + ) + +) diff --git a/test/sqlsrv/srv_073_database.phpt b/test/sqlsrv/srv_073_database.phpt new file mode 100644 index 00000000..639941c8 --- /dev/null +++ b/test/sqlsrv/srv_073_database.phpt @@ -0,0 +1,41 @@ +--TEST-- +Create/drop database +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { die( print_r( sqlsrv_errors(), true)); } + +// Set database name +$dbUniqueName = "php_uniqueDB01"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +if($stmt === false){ die( print_r( sqlsrv_errors(), true )); } +sqlsrv_free_stmt($stmt); + +// CREATE database +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); +if($stmt === false){ die( print_r( sqlsrv_errors(), true )); } +echo "DATABASE CREATED\n"; + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbUniqueName); +if($stmt === false){ die( print_r( sqlsrv_errors(), true )); } +echo "DATABASE DROPPED\n"; + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +DATABASE CREATED +DATABASE DROPPED +Done diff --git a/test/sqlsrv/srv_074_create_existing_database.phpt b/test/sqlsrv/srv_074_create_existing_database.phpt new file mode 100644 index 00000000..598a8c72 --- /dev/null +++ b/test/sqlsrv/srv_074_create_existing_database.phpt @@ -0,0 +1,62 @@ +--TEST-- +Create database that already exists +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// Set database name +$dbUniqueName = "uniqueDB01"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +sqlsrv_free_stmt($stmt); + + +// CREATE database +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); + +if( $stmt === false) +{ + printf("%-20s%10s\n","CREATE DATABASE","FAILED"); + die( print_r( sqlsrv_errors(), true )); +} + + +// CREATE database that already exists +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); +var_dump($stmt); +if( $stmt === false) +{ + $res = array_values(sqlsrv_errors()); + var_dump($res[0]['SQLSTATE']); + var_dump($res[0][1]); + // var_dump($res[0][2]); +} +else { + printf("%-20s\n","ERROR: CREATE database MUST return bool(false)"); +} + +// DROP database +sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); + +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +bool(false) +string(5) "42000" +int(1801) +Done diff --git a/test/sqlsrv/srv_074_database.phpt b/test/sqlsrv/srv_074_database.phpt new file mode 100644 index 00000000..598a8c72 --- /dev/null +++ b/test/sqlsrv/srv_074_database.phpt @@ -0,0 +1,62 @@ +--TEST-- +Create database that already exists +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// Set database name +$dbUniqueName = "uniqueDB01"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +sqlsrv_free_stmt($stmt); + + +// CREATE database +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); + +if( $stmt === false) +{ + printf("%-20s%10s\n","CREATE DATABASE","FAILED"); + die( print_r( sqlsrv_errors(), true )); +} + + +// CREATE database that already exists +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); +var_dump($stmt); +if( $stmt === false) +{ + $res = array_values(sqlsrv_errors()); + var_dump($res[0]['SQLSTATE']); + var_dump($res[0][1]); + // var_dump($res[0][2]); +} +else { + printf("%-20s\n","ERROR: CREATE database MUST return bool(false)"); +} + +// DROP database +sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); + +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +bool(false) +string(5) "42000" +int(1801) +Done diff --git a/test/sqlsrv/srv_074_database_wide_string.phpt b/test/sqlsrv/srv_074_database_wide_string.phpt new file mode 100644 index 00000000..0f70a648 --- /dev/null +++ b/test/sqlsrv/srv_074_database_wide_string.phpt @@ -0,0 +1,61 @@ +--TEST-- +Create database that already exists +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// Set database name +$dbUniqueName = "uniqueDB01"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +sqlsrv_free_stmt($stmt); + + +// CREATE database +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); + +if( $stmt === false) +{ + printf("%-20s%10s\n","CREATE DATABASE","FAILED"); + die( print_r( sqlsrv_errors(), true )); +} + + +// CREATE database that already exists +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); +var_dump($stmt); +if( $stmt === false) +{ + $res = array_values(sqlsrv_errors()); + var_dump($res[0]['SQLSTATE']); + var_dump($res[0][1]); +} +else { + printf("%-20s\n","ERROR: CREATE database MUST return bool(false)"); +} + +// DROP database +sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); + +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +bool(false) +string(5) "42000" +int(1801) +Done diff --git a/test/sqlsrv/srv_075_database.phpt b/test/sqlsrv/srv_075_database.phpt new file mode 100644 index 00000000..6f0fa74e --- /dev/null +++ b/test/sqlsrv/srv_075_database.phpt @@ -0,0 +1,44 @@ +--TEST-- +Drop missing database +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// Set database name +$dbUniqueName = "uniqueDB01"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +sqlsrv_free_stmt($stmt); + +// DROP missing database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbUniqueName); +var_dump($stmt); +if( $stmt === false ) +{ + $res = array_values(sqlsrv_errors()); + var_dump($res[0]['code']); +} +else { + printf("%-20s\n","ERROR: DROP missing database MUST return bool(false)"); +} + +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +bool(false) +int(3701) +Done diff --git a/test/sqlsrv/srv_075_database_wide_string.phpt b/test/sqlsrv/srv_075_database_wide_string.phpt new file mode 100644 index 00000000..8a448bd3 --- /dev/null +++ b/test/sqlsrv/srv_075_database_wide_string.phpt @@ -0,0 +1,49 @@ +--TEST-- +Drop missing database unicode +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password", "CharacterSet" => 'UTF-8'); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// Set database name +$dbUniqueName = "uniqueDB01_银河系"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +sqlsrv_free_stmt($stmt); + +// DROP missing database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbUniqueName); +var_dump($stmt); +if( $stmt === false ) +{ + $res = array_values(sqlsrv_errors()); + var_dump($res[0]['SQLSTATE']); + var_dump($res[0][1]); + var_dump($res[0][2]); + +} +else { + printf("%-20s\n","ERROR: DROP missing database MUST return bool(false)"); +} + +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +bool(false) +string(5) "42S02" +int(3701) +string(159) "[Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Cannot drop the database 'uniqueDB01_银河系', because it does not exist or you do not have permission." +Done diff --git a/test/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt b/test/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt new file mode 100644 index 00000000..d047ed03 --- /dev/null +++ b/test/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt @@ -0,0 +1,81 @@ +--TEST-- +sqlsrv_has_rows() using a forward and scrollable cursor +--SKIPIF-- +--FILE-- +"buffered")); + $attr = sqlsrv_get_config('ClientBufferMaxKBSize'); + echo("ClientBufferMaxKBSize is $attr\n"); + + sqlsrv_execute($stmt); + $rows = sqlsrv_has_rows($stmt); + var_dump($rows); + + sqlsrv_execute($stmt); + $numRowsFetched = 0; + while ($row = sqlsrv_fetch_array($stmt)) + { + $numRowsFetched++; + } + echo("Number of rows fetched: $numRowsFetched\n"); +} + +// Connect +$conn = sqlsrv_connect( $serverName, $connectionInfo); +if( !$conn ) { die( print_r( sqlsrv_errors(), true)); } + +// Create database +sqlsrv_query($conn,"CREATE DATABASE ". $dbName) ?: die(); +// Create table +$stmt = sqlsrv_query($conn, "CREATE TABLE ".$dbName.".[dbo].[php_test_table_1] ([c1_int] int, [c2_varchar_max] varchar(max))"); +$stmt = sqlsrv_query($conn, "CREATE TABLE ".$dbName.".[dbo].[php_test_table_2] ([c1_int] int, [c2_varchar_1036] varchar(1036))"); +// insert > 1KB into c2_varchar_max & c2_varchar_1036 (1036 characters). +$stmt = sqlsrv_query($conn, "INSERT INTO ".$dbName.".[dbo].[php_test_table_1] (c1_int, c2_varchar_max) VALUES (1, 'This is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a test')"); +$stmt = sqlsrv_query($conn, "INSERT INTO ".$dbName.".[dbo].[php_test_table_2] (c1_int, c2_varchar_1036) VALUES (1, 'This is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a test')"); + +// set client buffer size to 0KB returns false +$ret = sqlsrv_configure('ClientBufferMaxKBSize', 0); +var_dump($ret); +// set client buffer size to 1KB +$size = 1; +fetchData($conn, $dbName.".[dbo].[php_test_table_1]", $size); // this should return 0 rows. +fetchData($conn, $dbName.".[dbo].[php_test_table_2]", $size); // this should return 0 rows. +// set client buffer size to 2KB +$size = 2; +fetchData($conn, $dbName.".[dbo].[php_test_table_1]", $size); // this should return 1 row. +fetchData($conn, $dbName.".[dbo].[php_test_table_2]", $size); // this should return 1 row. + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbName); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +bool(false) +bool(true) +ClientBufferMaxKBSize is 1 +bool(false) +Number of rows fetched: 0 +bool(true) +ClientBufferMaxKBSize is 1 +bool(false) +Number of rows fetched: 0 +bool(true) +ClientBufferMaxKBSize is 2 +bool(true) +Number of rows fetched: 1 +bool(true) +ClientBufferMaxKBSize is 2 +bool(true) +Number of rows fetched: 1 +Done From cbb0f2ee3a54f9338dcc56350f4a2cb9afbe1a75 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Tue, 7 Feb 2017 12:28:01 -0800 Subject: [PATCH 11/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b87ad74c..dd8469dd 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ SQL Server Team * [**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) * [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php-windows) - +* [**Docker**](https://hub.docker.com/r/lbosqmsft/php-mssql/) ##Announcements From 766060b56f020c7c750ae68a6c315f49906faef2 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Tue, 14 Feb 2017 01:57:18 -0800 Subject: [PATCH 12/43] Update README.md --- README.md | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index dd8469dd..dfb39737 100644 --- a/README.md +++ b/README.md @@ -127,21 +127,9 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac yum-config-manager --enable remi-php71 yum update yum install php php-pdo php-xml php-pear php-devel - - - ### Step 2: Install pre-requisites -**Ubuntu 15.04** - - sudo su - sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/mssql-ubuntu-vivid-release/ vivid main" > /etc/apt/sources.list.d/mssqlpreview.list' - sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893 - apt-get update - apt-get install msodbcsql - #for silent install use ACCEPT_EULA=Y apt-get install msodbcsql - sudo apt-get install unixodbc-dev-utf16 **Ubuntu 15.10** @@ -151,7 +139,10 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac exit sudo apt-get update sudo ACCEPT_EULA=Y apt-get install msodbcsql mssql-tools - sudo apt-get install unixodbc-dev-utf16 + sudo apt-get install unixodbc-dev + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc + source ~/.bashrc **Ubuntu 16.04** @@ -162,7 +153,10 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac exit sudo apt-get update sudo ACCEPT_EULA=Y apt-get install msodbcsql mssql-tools - sudo apt-get install unixodbc-dev-utf16 + sudo apt-get install unixodbc-dev + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc + source ~/.bashrc **RedHat 7** @@ -170,9 +164,12 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac curl https://packages.microsoft.com/config/rhel/7/prod.repo > /etc/yum.repos.d/mssql-release.repo exit sudo yum update - sudo yum remove unixODBC #to avoid conflicts + sudo yum remove unixODBC-devel unixODBC-utf16-devel #to avoid conflicts sudo ACCEPT_EULA=Y yum install msodbcsql mssql-tools - sudo yum install unixODBC-utf16-devel + sudo yum install unixODBC-utf16 + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc + source ~/.bashrc From 378ff3f8be3ac2437e64c85afe4b9de52d9e8008 Mon Sep 17 00:00:00 2001 From: v-dareck Date: Tue, 14 Feb 2017 14:35:06 -0800 Subject: [PATCH 13/43] Update README.md Add redhat pre-req for unixODBC-devel --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dfb39737..cee39fb7 100644 --- a/README.md +++ b/README.md @@ -164,9 +164,9 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac curl https://packages.microsoft.com/config/rhel/7/prod.repo > /etc/yum.repos.d/mssql-release.repo exit sudo yum update - sudo yum remove unixODBC-devel unixODBC-utf16-devel #to avoid conflicts + sudo yum remove unixODBC-utf16-devel #to avoid conflicts sudo ACCEPT_EULA=Y yum install msodbcsql mssql-tools - sudo yum install unixODBC-utf16 + sudo yum install unixODBC-devel echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc source ~/.bashrc From aab01ce1452182c744c564ce151cda029cc68218 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Fri, 17 Feb 2017 14:57:11 -0800 Subject: [PATCH 14/43] Added survey button --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cee39fb7..2c8e1478 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,12 @@ The Microsoft Drivers for PHP for SQL Server are PHP extensions that allow for t 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 release(s) will contain more functionality, bug fixes, and more (see Plans below for more details). SQL Server Team + +###Take our survey + + + + ###Status of Most Recent Builds | AppVeyor (Windows) |Travis CI (Linux) | Coverage Status |-------------------------|--------------------------| ------------------ From 03a24154cf1a2c361044420204177da6f060a494 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Mon, 20 Feb 2017 22:58:55 -0800 Subject: [PATCH 15/43] Updated README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2c8e1478..823d165e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ SQL Server Team ###Take our survey +Let us know how we are doing by taking our monthly sentiment survey: + From 0135dd5302639fbb21d3bbbb65f2844cdfe4b8b6 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Mon, 20 Feb 2017 23:36:15 -0800 Subject: [PATCH 16/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 823d165e..015130ad 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7 with improveme SQL Server Team -###Take our survey +##Take our survey Let us know how we are doing by taking our monthly sentiment survey: From 6ed71972b483ea683aecc34d2c07c1dd995b0382 Mon Sep 17 00:00:00 2001 From: Michael Jensch Date: Fri, 24 Feb 2017 12:59:46 +0100 Subject: [PATCH 17/43] removed unnecessary conditional at sqlsrv/conn.cpp --- source/sqlsrv/conn.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 59a2479f..645dfd3a 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -37,12 +37,7 @@ struct date_as_string_func { ss_sqlsrv_conn* ss_conn = static_cast( conn ); - if( zend_is_true( value )) { - ss_conn->date_as_string = true; - } - else { - ss_conn->date_as_string = false; - } + ss_conn->date_as_string = zend_is_true( value ); } }; @@ -1296,4 +1291,4 @@ void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ cha } } -} // namespace +} // namespace \ No newline at end of file From c8efeedae02ca29343a317d336fb3689198a3e6b Mon Sep 17 00:00:00 2001 From: Hadis Kakanejadi Fard Date: Fri, 24 Feb 2017 10:59:01 -0800 Subject: [PATCH 18/43] Revert "removed unnecessary conditional at sqlsrv/conn.cpp" --- source/sqlsrv/conn.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 645dfd3a..59a2479f 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -37,7 +37,12 @@ struct date_as_string_func { ss_sqlsrv_conn* ss_conn = static_cast( conn ); - ss_conn->date_as_string = zend_is_true( value ); + if( zend_is_true( value )) { + ss_conn->date_as_string = true; + } + else { + ss_conn->date_as_string = false; + } } }; @@ -1291,4 +1296,4 @@ void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ cha } } -} // namespace \ No newline at end of file +} // namespace From 5a992be52a5585729d8ad7f826aed109a02c8eb3 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Wed, 1 Mar 2017 17:25:05 -0800 Subject: [PATCH 19/43] Updated survey link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 015130ad..0e42f244 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ SQL Server Team Let us know how we are doing by taking our monthly sentiment survey: - + ###Status of Most Recent Builds From 75f78bf24805733ebea00181ece03c593861a16f Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Wed, 1 Mar 2017 17:28:03 -0800 Subject: [PATCH 20/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e42f244..814a2631 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ SQL Server Team ##Take our survey -Let us know how we are doing by taking our monthly sentiment survey: +Thank you for taking time to take our Febraury survey. Let us know how we are doing and how you use PHP by taking our March pulse survey: From 2d9a4442a0e3f0f16370052ef7d6d0c2a659f315 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Fri, 3 Mar 2017 17:53:11 -0800 Subject: [PATCH 21/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 814a2631..48529b13 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Thank you for taking time to take our Febraury survey. Let us know how we are do * [**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) * [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php-windows) -* [**Docker**](https://hub.docker.com/r/lbosqmsft/php-mssql/) +* [**Docker**](https://hub.docker.com/r/lbosqmsft/mssql-php-msphpsql/) ##Announcements From 531b574f3527799ee0caf26517ee5dd576b7e669 Mon Sep 17 00:00:00 2001 From: Hadis Kakanejadi Fard Date: Tue, 14 Mar 2017 13:04:23 -0700 Subject: [PATCH 22/43] Updated master branch with 4.1.7-preview release (#325) * fixed Github #182, empty bound output parameters * Fixing the issue with buffered result sets, when connection option is set to utf8. See issue #192 * removing new lines * Replcaing SQLSRV_ENCODING ( CP_UTF8) with SQLSRV_ENCODING_UTF8 * Replcaing SQLSRV_ENCODING ( CP_UTF8) with SQLSRV_ENCODING_UTF8 * fixed tab and spacing * updated the code structure * deleted pdo_sqlsrv obsolete folder * Updated pdo driver source code path * added pdo shared folder * updated sqlsrv and pdo_sqlsrv config.w32 to get the dir name dynamically * deleted shared folder inside pdo * Initial AppVeyor CI test build. * Remove AppVeyor whitelist for testing. * Fix source file path. * Update pdo_util.cpp * changed tab to spaces * fixed indentations * fixed indentations * moved templates to driver folder * Enable build matrix for different SQL Server versions. * fixed the limit for row count * fix #173, #138 on Windows code * fixed indentations * revert core_results to 365220a * Readme and Change updates for Windows release. * Fix table rendering. * Readme and Change updates for Windows release. * Update change log. * Readme and Change updates for Windows release. * Remove Linux section. * merged Linux to CPP files * merged Linux to php_sqlsrv.h * Merged Linux and Windows PDO_SQLSRV code * merged core_conn from linux to Windows * modified based on reviews * updated to version 4.1.5 * fixed indentations * updated the date to Jan 19 * updated core_init * Small ifdef fix * Spacing fixes * Commented code removed * fixed preprocessor directives * merged core_stmt from Linux * merged conversion functions in core_sqlsrv and core_util * config.m4 * Capitalised win32 * Commented code removed * merged core_sqlsrv.h * merged core_stream * merged core_results * added Linux specific files * added Linux specific files * removed windows header * removed header files included in core_sqlv from xplat * Issue 228. Don't allow 0 for sqlsrv ClientBufferMaxKBSize. Add new tests for sqlsrv and pdo. * Issue 37. sqlsrv_has_rows does not fetch if already true. Change sqlsrv_has_rows to not scroll if already true when using forward only cursor. * gaurded strsafe header * Update pdo_dbh.cpp * Update pdo_stmt.cpp * Update pdo_init.cpp * Update pdo_parser.cpp * Remove comment line from test. * Update test comments. * Add Description section to phpt test. * fixed included headers order * fixed indentations and spacing * fixed indentations on core_results * fixed indentations on rowCount * fixed indentations in updated code * fixed indentations * fixed indentations * bumped version to windows version * made the ifdef consitent * fixed L166 indentations * bumped version to 4.1 * bumped up the version to 4.1 * bumped up the version to 4.1 * updated to version 4.1 * updated version to 4.1 * Added config.m4 * Added build directory * fixed closing } dropped indentations * fixed core_conn * renamed header files with the same name as windows headers * renamed in xplat.h to xplat_intsafe * safeguard get_col_meta * edited included headers * fixed invalid user pass issue in core_conn * added TravisCI to merged-code branch * updated renamed header * removed comments * updated travis docker with latest from linux branch * applied peer review comments * added packagize.sh * fixed get_processor_arch comparisons * removed unused sqlsrv_zend_hash_add * Update srv_075_database_wide_string.phpt * Update srv_075_database.phpt * Update srv_074_database.phpt * Update srv_074_database_wide_string.phpt * Update srv_073_database.phpt * Merge remaining PHP-7.0-Linux changes into merged codebase. * Change $sample calculation to avoid conversion to float. * Change FORMAT SQL to CAST as SQL 2008 does not support FORMAT. * modified ifdef __linux__ * Update srv_037_transaction_rollback.phpt * Tab/indentation fixes. * replace __linux__ with _WIN32 define. * Update Readme and Changelog for merged code. * Add re2c and gcc to readme steps. * Update version.h * removed inconsistent code calling convert_string_from_utf16 * corrected comment * Change branch links to dev in README * Test: Bind values with PDO::PARAM_BOOL * Update pdo_023.phpt * Update README.md Add unixODBC 2.3.1 limitation for connection pooling with PDO. * define SQL_GUID for PECL * check if constant defined first * In closeCursor check if statement has been executed before moving to next result. * Change tab to spaces. * Fix for issue 223 * added a test * Modified the test to make it more robust * Fix error message in close cursor. Change prepare to query in test. * Fix and tests for issue #69 * fix formatting * Fix issue #35 * add a simple test case with an input file * simplified the test without image input * Fix clang compile errors. * Update README.md * Add check for SQL_NO_TOTAL for SQLBindParameter out parameter. * Update PHPT comment. * Update PHPT comment and script. * calculate field size instead of using column size and fixed the parameter encoding * Remove uchar.h usage from buffered results. * fix PHP7.1 debug abort error * Add buffered sqlsrv test. * use static cast for SQLSRV_ENCODING * Update phpt comment. * fixed issue #270 * test fetch binary * added autonomous setup to test * caps description * Modify string_to_number utf8 conversion. * Added survey * fixed indentation and typo * fixed indentations * Changed db to tempdb and random table name * fixed string quotes and free stmt * fixed comment * Change errno check to ERROR_SUCCESS * Fix baseline for #297 changes. * batch 1 * batch 2 * Fix memory leaks in buffered resultsets. * batch 3 * modified tests based on review * Added SELinux note for RedHat * Issue #308 * Fix spacing * Use a simpler way * String release first * String release first * Modified based on review * Modified note * added a sqlsrv test, a variant * Update srv_308_empty_output_param.phpt * Update README.md * appveyor.yml now outputs details of failing tests * updated versioning to semantic versioning #282 * Update README.md * changed locale for mac * added client info tests * added versioning notes * updated precedence order example * added 4.1.7 to changedlog * updated PECL package version * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * part 1 with utf8 data * MAC OS X announcement * part 2 testing diff API * part 3 testing rowCount * part 4 fetching various types * part 5 new sqlsrv tests * removed redundant tests * fixed some tests * modified a failing test * one more test * Mac instructions (#319) * Initial Mac instructions * Add pecl install for mac * Changing instructions for loading the drivers * Updating apache install intructions for MAC * Adding Mac related notes * Minor cleanup * appveyor.yml now outputs details of failing tests * updated versioning to semantic versioning #282 * Update README.md * changed locale for mac * added client info tests * added versioning notes * updated precedence order example * added 4.1.7 to changedlog * updated PECL package version * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * MAC OS X announcement * Updating Mac Apache Instructions. Also removing the step of manually adding drivers to the ini file. * Adding php7.1-xml installation * Apache config for Mac * Fixing Apache config * Update PECL instructions * Fixed headers formatting --- CHANGELOG.md | 33 +- README.md | 158 +-- appveyor.yml | 6 +- source/pdo_sqlsrv/config.m4 | 136 +-- source/pdo_sqlsrv/pdo_stmt.cpp | 30 +- source/pdo_sqlsrv/template.rc | 8 +- source/shared/core_results.cpp | 238 +++-- source/shared/core_sqlsrv.h | 7 +- source/shared/core_stmt.cpp | 32 +- source/shared/localization.hpp | 7 +- source/shared/localizationimpl.cpp | 2 +- source/shared/msodbcsql.h | 852 ++++++++-------- source/shared/typedefs_for_linux.h | 3 - source/shared/version.h | 26 +- source/sqlsrv/conn.cpp | 2 +- source/sqlsrv/init.cpp | 14 +- source/sqlsrv/php_sqlsrv.h | 6 +- source/sqlsrv/stmt.cpp | 9 +- source/sqlsrv/template.rc | 8 +- test/pdo_sqlsrv/pdo_002_connect_app.phpt | 68 +- test/pdo_sqlsrv/pdo_011_quote.phpt | 110 +-- test/pdo_sqlsrv/pdo_013_row_count.phpt | 120 +-- test/pdo_sqlsrv/pdo_023.phpt | 1 - ...5_binary_encoding_error_bound_by_name.phpt | 50 + ...pdo_069_fetch_empty_nvarchar_buffered.phpt | 38 + test/pdo_sqlsrv/pdo_267_closeCursor.phpt | 70 ++ test/pdo_sqlsrv/pdo_270_fetch_binary.phpt | 92 ++ .../pdo_308_empty_output_param.phpt | 48 + test/pdo_sqlsrv/pdo_connection_quote.phpt | 74 ++ .../pdo_fetch_bindcolumn_fetchmode.phpt | 128 +++ .../pdo_fetch_columns_fetchmode.phpt | 646 ++++++++++++ .../pdo_fetch_cursorBuffered_binary.phpt | 105 ++ .../pdo_fetch_cursorBuffered_char.phpt | 105 ++ .../pdo_fetch_cursorBuffered_datetime.phpt | 105 ++ .../pdo_fetch_cursorBuffered_decimal.phpt | 105 ++ ...cursorBuffered_decimal_bindColumn_int.phpt | 113 +++ .../pdo_fetch_cursorBuffered_float.phpt | 141 +++ ...h_cursorBuffered_float_bindColumn_lob.phpt | 117 +++ .../pdo_fetch_cursorBuffered_int.phpt | 105 ++ ...tch_cursorBuffered_int_bindColumn_int.phpt | 114 +++ .../pdo_fetch_cursorBuffered_money.phpt | 105 ++ ...h_cursorBuffered_money_bindColumn_int.phpt | 115 +++ ...pdo_fetch_cursorBuffered_multicolumns.phpt | 271 +++++ ...ch_cursorBuffered_strings_to_integers.phpt | 232 +++++ .../pdo_fetch_cursorBuffered_unicode.phpt | 105 ++ .../pdo_fetch_fetchinto_query_args.phpt | 134 +++ .../pdo_getAttribute_clientInfo.phpt | 22 + test/pdo_sqlsrv/pdo_nested_query_mars.phpt | 108 ++ test/pdo_sqlsrv/pdo_query_timeout.phpt | 92 ++ .../pdo_statement_rowcount_query.phpt | 181 ++++ .../pdo_stored_proc_fetch_datatypes.phpt | 320 ++++++ test/pdo_sqlsrv/pdo_tools.inc | 446 +++++++++ .../pdo_utf8_stored_proc_unicode_chars.phpt | 105 ++ test/sqlsrv/sqlsrv_bind_param_out_string.phpt | 459 +++++++++ test/sqlsrv/sqlsrv_client_info.phpt | 25 + test/sqlsrv/sqlsrv_close_twice.phpt | 61 ++ test/sqlsrv/sqlsrv_complex_query.phpt | 103 ++ .../sqlsrv_data_types_explict_fetch.phpt | 100 ++ ...sqlsrv_data_types_fetch_binary_stream.phpt | 111 +++ test/sqlsrv/sqlsrv_execute_twice.phpt | 72 ++ .../sqlsrv_fetch_datetime_as_strings.phpt | 120 +++ .../sqlsrv_fetch_field_twice_data_types.phpt | 141 +++ test/sqlsrv/sqlsrv_fetch_missing_row.phpt | 68 ++ test/sqlsrv/sqlsrv_fetch_object_class.phpt | 140 +++ test/sqlsrv/sqlsrv_param_floats.phpt | 112 +++ .../sqlsrv_param_ints_with_deletes.phpt | 162 +++ .../sqlsrv_param_query_array_inputs.phpt | 158 +++ .../sqlsrv/sqlsrv_param_query_data_types.phpt | 65 ++ test/sqlsrv/sqlsrv_statement_cancel.phpt | 105 ++ .../sqlsrv_statement_query_timeout.phpt | 77 ++ ...v_statement_query_timeout_transaction.phpt | 90 ++ test/sqlsrv/sqlsrv_stored_proc_varchar.phpt | 78 ++ ...srv_069_fetch_empty_nvarchar_buffered.phpt | 44 + .../sqlsrv/srv_223_sqlsrv_fetch_absolute.phpt | 59 ++ ...srv_230_sqlsrv_buffered_numeric_types.phpt | 208 ++++ ...srv_231_string_truncation_varchar_max.phpt | 189 ++++ test/sqlsrv/srv_308_empty_output_param.phpt | 47 + test/sqlsrv/tools.inc | 928 ++++++++++++++++++ 78 files changed, 9113 insertions(+), 877 deletions(-) create mode 100644 test/pdo_sqlsrv/pdo_035_binary_encoding_error_bound_by_name.phpt create mode 100644 test/pdo_sqlsrv/pdo_069_fetch_empty_nvarchar_buffered.phpt create mode 100644 test/pdo_sqlsrv/pdo_267_closeCursor.phpt create mode 100644 test/pdo_sqlsrv/pdo_270_fetch_binary.phpt create mode 100644 test/pdo_sqlsrv/pdo_308_empty_output_param.phpt create mode 100644 test/pdo_sqlsrv/pdo_connection_quote.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_bindcolumn_fetchmode.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_columns_fetchmode.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_binary.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_char.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_datetime.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_decimal.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_decimal_bindColumn_int.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_float.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_float_bindColumn_lob.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_int.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_int_bindColumn_int.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_money.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_money_bindColumn_int.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_multicolumns.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_strings_to_integers.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_cursorBuffered_unicode.phpt create mode 100644 test/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt create mode 100644 test/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt create mode 100644 test/pdo_sqlsrv/pdo_nested_query_mars.phpt create mode 100644 test/pdo_sqlsrv/pdo_query_timeout.phpt create mode 100644 test/pdo_sqlsrv/pdo_statement_rowcount_query.phpt create mode 100644 test/pdo_sqlsrv/pdo_stored_proc_fetch_datatypes.phpt create mode 100644 test/pdo_sqlsrv/pdo_tools.inc create mode 100644 test/pdo_sqlsrv/pdo_utf8_stored_proc_unicode_chars.phpt create mode 100644 test/sqlsrv/sqlsrv_bind_param_out_string.phpt create mode 100644 test/sqlsrv/sqlsrv_client_info.phpt create mode 100644 test/sqlsrv/sqlsrv_close_twice.phpt create mode 100644 test/sqlsrv/sqlsrv_complex_query.phpt create mode 100644 test/sqlsrv/sqlsrv_data_types_explict_fetch.phpt create mode 100644 test/sqlsrv/sqlsrv_data_types_fetch_binary_stream.phpt create mode 100644 test/sqlsrv/sqlsrv_execute_twice.phpt create mode 100644 test/sqlsrv/sqlsrv_fetch_datetime_as_strings.phpt create mode 100644 test/sqlsrv/sqlsrv_fetch_field_twice_data_types.phpt create mode 100644 test/sqlsrv/sqlsrv_fetch_missing_row.phpt create mode 100644 test/sqlsrv/sqlsrv_fetch_object_class.phpt create mode 100644 test/sqlsrv/sqlsrv_param_floats.phpt create mode 100644 test/sqlsrv/sqlsrv_param_ints_with_deletes.phpt create mode 100644 test/sqlsrv/sqlsrv_param_query_array_inputs.phpt create mode 100644 test/sqlsrv/sqlsrv_param_query_data_types.phpt create mode 100644 test/sqlsrv/sqlsrv_statement_cancel.phpt create mode 100644 test/sqlsrv/sqlsrv_statement_query_timeout.phpt create mode 100644 test/sqlsrv/sqlsrv_statement_query_timeout_transaction.phpt create mode 100644 test/sqlsrv/sqlsrv_stored_proc_varchar.phpt create mode 100644 test/sqlsrv/srv_069_fetch_empty_nvarchar_buffered.phpt create mode 100644 test/sqlsrv/srv_223_sqlsrv_fetch_absolute.phpt create mode 100644 test/sqlsrv/srv_230_sqlsrv_buffered_numeric_types.phpt create mode 100644 test/sqlsrv/srv_231_string_truncation_varchar_max.phpt create mode 100644 test/sqlsrv/srv_308_empty_output_param.phpt create mode 100644 test/sqlsrv/tools.inc diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b78e599..0db09801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## Windows/Linux/MAC 4.1.7-preview - 2017-03-07 +Updated PECL release packages. Here is the list of updates: +### Added +- The early technical preview (ETP) for SQLSRV and PDO_SQLSRV drivers for MAC with basic functionalities is now available. Both drivers has been built and tested on MAC OS version El Capitan (OS X 10.11). + +### Fixed +#### SQLSRV and PDO_SQLSRV +- Fixed null returned when an empty string is set to an output parameter ([issue #308](https://github.com/Microsoft/msphpsql/issues/308)). +- Fixed memory leaks in buffered result sets. +- Fixed clang compile errors. + +#### SQLSRV only +- Fixed debug abort error when building the driver in debug mode with PHP 7.1. +- Fixed string truncation when binding varchar(max), nvarchar(max), varbinary(max), and xml types ([issue #231](https://github.com/Microsoft/msphpsql/issues/231)). +- Fixed fatal error when fetching empty nvarchar ([issue #69](https://github.com/Microsoft/msphpsql/issues/69)). +- Fixed fatal error when calling sqlsrv_fetch() with an out of bound offset for SQLSRV_SCROLL_ABSOLUTE ([issue #223](https://github.com/Microsoft/msphpsql/issues/223)). + +#### PDO_SQLSRV only +- Fixed wrong value returned when fetching varbinary value on Linux ([issue #270](https://github.com/Microsoft/msphpsql/issues/270)). +- Fixed binary data not returned when the column is bound by name ([issue #35](https://github.com/Microsoft/msphpsql/issues/35)). +- Fixed exception thrown on closeCursor() when the statement has not been executed ([issue #267](https://github.com/Microsoft/msphpsql/issues/267)). + +### Known Issues +- User defined data types and SQL_VARIANT ([issue #127](https://github.com/Microsoft/msphpsql/issues/127)). +- Binary column binding with emulate prepare ([issue #140](https://github.com/Microsoft/msphpsql/issues/140)). +- Segmentation fault may result when an unsupported attribute is used for connection. + +#### MAC only +- If loading both sqlsrv and pdo_sqlsrv, the order matters (even when dynamically). For PDO_SQLSRV scripts, load pdo_sqlsrv.so first. For SQLSRV scripts, load sqlsrv.so first. +- Connection pooling not working. + ## Windows/Linux 4.1.6 - 2017-02-03 Updated PECL release packages. Here is the list of updates: ### Added @@ -61,7 +92,7 @@ Linux drivers compiled with PHP 7.0.13 are available for Ubuntu 15.04, Ubuntu 16 ### Changed - Code structure is updated to facilitate the development; shared codes between both drivers are moved to "shared" folder to avoid code duplication issues in development. To build the driver from source, use "packagize" script as follows: - - if you are using the phpize, clone or download the source, run the script within the source directory and then run phpize. + - if you are using the phpize, clone or download the “source”, run the script within the “source” directory and then run phpize. - if you are building the driver from source using PHP source, give the path to the PHP source to the script. ### Fixed diff --git a/README.md b/README.md index 48529b13..f9d15705 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,14 @@ This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7 with improveme SQL Server Team -##Take our survey + +## Take our survey Thank you for taking time to take our Febraury survey. Let us know how we are doing and how you use PHP by taking our March pulse survey: - -###Status of Most Recent Builds +### Status of Most Recent Builds | AppVeyor (Windows) |Travis CI (Linux) | Coverage Status |-------------------------|--------------------------| ------------------ | [![av-image][]][av-site]| [![tv-image][]][tv-site] |[![Coverage Status][]][coveralls-site] @@ -27,16 +27,17 @@ Thank you for taking time to take our Febraury survey. Let us know how we are do [Coverage Status]: https://coveralls.io/repos/github/Microsoft/msphpsql/badge.svg?branch=dev [coveralls-site]: https://coveralls.io/github/Microsoft/msphpsql?branch=dev -##Get Started +## Get Started * [**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) * [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php-windows) * [**Docker**](https://hub.docker.com/r/lbosqmsft/mssql-php-msphpsql/) -##Announcements -**December 19, 2016**: We are delighted announce that production release for PHP Linux Driver for SQL Server is available. PECL packages (4.0.8) are updated with the latest changes, and Linux binaries (4.0.8) compiled with PHP 7.0.14 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. For complete list of changes please visit [CHANGELOG](https://github.com/Microsoft/msphpsql/blob/dev/CHANGELOG.md) file. +## Announcements + +**March 7, 2017**: We are trilled to announce that the early technical preview for SQLSRV and PDO_SQLSRV drivers is now available, both drivers have been built and tested on El Capitan (OS X 10.11). For complete list of changes please visit [4.1.7-preview release notes](https://github.com/Microsoft/msphpsql/releases/tag/v4.1.7-preview). Please visit the [blog][blog] for more announcements. @@ -45,11 +46,11 @@ Thank you for taking time to take our Febraury survey. Let us know how we are do Note: if you prefer, you can use the pre-compiled binary found [HERE](https://github.com/Azure/msphpsql/releases) -####Prerequisites +#### Prerequisites You must first be able to build PHP 7 without including these extensions. For help with doing this, see the [official PHP website][phpbuild] for building your own PHP on Windows. -####Compile the drivers +#### Compile the drivers 1. Copy the sqlsrv and/or pdo_sqlsrv source code directory from this repository into the ext subdirectory. @@ -67,12 +68,12 @@ This software has been compiled and tested under PHP 7.0.8 using the Visual C++ ## Install (Windows) -####Prerequisites +#### Prerequisites - A Web server such as Internet Information Services (IIS) is required. Your Web server must be configured to run PHP - [Microsoft ODBC Driver 11][odbc11] or [Microsoft ODBC Driver 13][odbc13] -####Enable the drivers +#### Enable the drivers 1. Make sure that the driver is in your PHP extension directory (you can simply copy it there if you did not use nmake install). @@ -80,10 +81,10 @@ This software has been compiled and tested under PHP 7.0.8 using the Visual C++ 3. Restart the Web server. -## Install (Linux) -Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apache, and Microsoft PHP drivers on Ubuntu 15, 16 and RedHat 7. To see how to get PHP SQLSRV drivers running on Debian, please visit [Wiki](https://github.com/Microsoft/msphpsql/wiki/Dockerfile-for-getting-pdo_sqlsrv-for-PHP-7.0-on-Debian-in-3-ways). Note that Debian is not officially supported and this instruction hasn't been tested in our test lab. +## Install (UNIX) +Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apache, and Microsoft PHP drivers on Ubuntu 15, 16, RedHat 7 and Mac OS X. To see how to get PHP SQLSRV drivers running on Debian, please visit [Wiki](https://github.com/Microsoft/msphpsql/wiki/Dockerfile-for-getting-pdo_sqlsrv-for-PHP-7.0-on-Debian-in-3-ways). Note that Debian is not officially supported and this instruction hasn't been tested in our test lab. -### Step 1: Install PHP (unless already installed) +### Step 1: Install PHP7 (unless already installed) #### PHP 7.0 @@ -113,6 +114,17 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac yum update yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc +**Mac OS X** + + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + brew tap + brew tap homebrew/dupes + brew tap homebrew/versions + brew tap homebrew/homebrew-php + brew install php70 --with-pear --with-httpd24 --with-cgi + echo 'export PATH="/usr/local/sbin:$PATH"' >> ~/.bash_profile + echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bash_profile +*Note: Restart the terminal if PHP(`php -v`) is not updated. #### PHP 7.1 @@ -123,7 +135,7 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac sudo su add-apt-repository ppa:ondrej/php apt-get update - apt-get -y install php7.1 mcrypt php7.1-mcrypt php-mbstring php-pear php7.1-dev + apt-get -y install php7.1 mcrypt php7.1-mcrypt php-mbstring php-pear php7.1-dev php7.1-xml **RedHat 7** @@ -135,6 +147,19 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac yum-config-manager --enable remi-php71 yum update yum install php php-pdo php-xml php-pear php-devel + +**Mac OS X** + + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + brew tap + brew tap homebrew/dupes + brew tap homebrew/versions + brew tap homebrew/homebrew-php + brew install php71 --with-pear --with-httpd24 --with-cgi + echo 'export PATH="/usr/local/sbin:$PATH"' >> ~/.bash_profile + echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bash_profile +*Note: Restart the terminal if PHP(`php -v`) is not updated. + ### Step 2: Install pre-requisites @@ -178,15 +203,21 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc source ~/.bashrc + +**Mac OS X** + brew tap microsoft/msodbcsql https://github.com/Microsoft/homebrew-msodbcsql + brew update + brew install unixodbc + brew install msodbcsql + brew install llvm --with-clang --with-clang-extra-tools + brew install autoconf - - -*Note: On Ubuntu, you need to make sure you install PHP 7 before you proceed to step 2. The Microsoft PHP Drivers for SQL Server will only work for PHP 7+. +*Note: You need to make sure you install PHP 7 before you proceed to step 3. The Microsoft PHP Drivers for SQL Server will only work for PHP 7+. ### Step 3: Install Apache -####PHP 7.0 +#### PHP 7.0 **Ubuntu** @@ -197,65 +228,36 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac sudo yum install httpd -####PHP 7.1 +**Mac OS X** + + (echo ""; echo "SetHandler application/x-httpd-php"; echo "";) >> /usr/local/etc/apache2/2.4/httpd.conf + +#### PHP 7.1 **Ubuntu** - sudo apt-get install libapache2-mod-php7.1 - sudo apt-get install apache2 + sudo apt-get install libapache2-mod-php7.1 + sudo apt-get install apache2 **RedHat** sudo yum install httpd + +**Mac OS X** + + (echo ""; echo "SetHandler application/x-httpd-php"; echo "";) >> /usr/local/etc/apache2/2.4/httpd.conf ### Step 4: Install the Microsoft PHP Drivers for SQL Server + sudo pear config-set php_ini `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` system sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv -*Note: it installs the stable version, for specific version you should set the version. For example, `sudo pecl install sqlsrv-4.0.8` - - -### Step 5: Add the Microsoft PHP Drivers for SQL Server to php.ini - - -####PHP 7.0 - -**Ubuntu** +*Note: it installs the stable version, for specific version you should set the version. For example, do `sudo pecl search sqlsrv` to search for the latest releases and `sudo pecl install sqlsrv-[version]` to install a specific version. +Drivers are Mac-compatible starting from 4.1.7preview release. . - echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/apache2/php.ini - echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/apache2/php.ini - echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/cli/php.ini - echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/cli/php.ini - - -**RedHat** - - echo "extension= /usr/lib64/php/modules/sqlsrv.so" > /etc/php.d/sqlsrv.ini - echo "extension= /usr/lib64/php/modules/pdo_sqlsrv.so" > /etc/php.d/pdo_sqlsrv.ini - - -####PHP 7.1 - - -**Ubuntu 16.04** - - echo "extension=/usr/lib/php/20160303/sqlsrv.so" >> /etc/php/7.1/apache2/php.ini - echo "extension=/usr/lib/php/20160303/pdo_sqlsrv.so" >> /etc/php/7.1/apache2/php.ini - echo "extension=/usr/lib/php/20160303/sqlsrv.so" >> /etc/php/7.1/cli/php.ini - echo "extension=/usr/lib/php/20160303/pdo_sqlsrv.so" >> /etc/php/7.1/cli/php.ini - - - -**RedHat** - - echo "extension= /usr/lib64/php/modules/sqlsrv.so" > /etc/php.d/sqlsrv.ini - echo "extension= /usr/lib64/php/modules/pdo_sqlsrv.so" > /etc/php.d/pdo_sqlsrv.ini - - - -### Step 6: Restart Apache to load the new php.ini file +### Step 5: Restart Apache to load the new php.ini file **Ubuntu** @@ -264,9 +266,15 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac **RedHat** sudo apachectl restart + +**Mac OS X** -### Step 7: Create your sample app -Navigate to `/var/www/html` and create a new file called testsql.php. Copy and paste the following code in tetsql.php and change the servername, username, password and databasename. + sudo apachectl restart + +*Note to RedHat users: SELinux is installed by default and runs in Enforcing mode. To allow Apache to connect to database through SELinux, do this `sudo setsebool -P httpd_can_network_connect_db 1` + +### Step 6: Create your sample app +Navigate to `/var/www/html` (`/usr/local/var/www/htdocs` on Mac) and create a new file called testsql.php. Copy and paste the following code in tetsql.php and change the servername, username, password and databasename. -### Step 8: Run your sample app +### Step 7: Run your sample app -Go to your browser and type in http://localhost/testsql.php +Go to your browser and type in http://localhost/testsql.php (http://localhost:8080/testsql.php on Mac) You should be able to connect to your SQL Server/Azure SQL Database. The drivers are distributed as shared binary extensions for PHP. They are available in thread safe (*_ts.so) and-non thread safe (*_nts.so) versions. The source code for the drivers is also available, and you can choose whether to compile them as thread safe or non-thread safe versions. The thread safety configuration of your web server will determine which version you need. @@ -331,11 +339,29 @@ For samples, please see the sample folder. For setup instructions, see [here] [ - Binary column binding with emulate prepare ([issue#140](https://github.com/Microsoft/msphpsql/issues/140) ) - Linux - The following features are not supported with connection pooling: + - PDO is only supported with unixODBC 2.3.1. - Unicode connection strings - sqlsrv_server_info and sqlsrv_client_info return false - In certain scenarios a generic error message maybe returned instead of a specific error when pooling is disabled - When retrieving data from columns with a data type of XML, varchar(max), nvarchar(max), or varbinary(max) no data maybe returned or the data maybe truncated depending on the length of the data in the source table. +## Version number +Version number of PHP drivers follow the [semantic versioning](http://semver.org/): + +Given a version number MAJOR.MINOR.PATCH, + + - MAJOR version is incremented when an incompatible API changes is made, + - MINOR version is incremented when a functionality in a backwards-compatible manner is added, and + - PATCH version is incremented when backwards-compatible bug fixes are made. + +version number MAY have trailing pre-release version to indicate the stability, and/or build meta data. + +- Pre-release version is denoted by hyphen followed by `preview` or `rc` keyword and may be followed by a series of dot separated identifiers. Production quality releases do not contain the pre-release version. `preview` has lower precedence than `rc`. Example of precedence: *preview < preview.1 < rc < rc.1*. +*Note that PECL package version does not have the hyphen before pre-release version, due to restrictions in PECL. Example of PECL package version: 1.2.3preview* +- Build metadata MAY be denoted by a plus sign followed by 4 digits, such as `1.2.3-preview+5678` or `1.2.3+5678`. Build meta data does NOT figure into the precedence order. + + + ## Future Plans - Expand SQL 16 Feature Support (example: Always Encrypted). - Build Verification/Fundamental Tests. diff --git a/appveyor.yml b/appveyor.yml index f45dc84d..98edf750 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -165,7 +165,11 @@ test_script: after_test: - cd %APPVEYOR_BUILD_FOLDER%\test\ - - python output.py + - python output.py + - ps: $files = Get-ChildItem sqlsrv\*.diff + - ps: foreach($file in $files){ls $file; more $file; more "$file.out"} + - ps: $files = Get-ChildItem pdo_sqlsrv\*.diff + - ps: foreach($file in $files){ls $file; more $file; more "$file.out"} - ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\nativeresult1.xml)) - ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\nativeresult2.xml)) - ps: >- diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index 411d405a..2b310f92 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -1,68 +1,68 @@ -PHP_ARG_WITH(pdo_sqlsrv, for pdo_sqlsrv support, -[ --with-pdo_sqlsrv Include pdo_sqlsrv support]) - -if test "$PHP_PDO_SQLSRV" != "no"; then - if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then - AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) - fi - - ifdef([PHP_CHECK_PDO_INCLUDES], - [ - PHP_CHECK_PDO_INCLUDES - ],[ - AC_MSG_CHECKING([for PDO includes]) - if test -f $abs_srcdir/include/php/ext/pdo/php_pdo_driver.h; then - pdo_cv_inc_path=$abs_srcdir/ext - elif test -f $abs_srcdir/ext/pdo/php_pdo_driver.h; then - pdo_cv_inc_path=$abs_srcdir/ext - elif test -f $phpincludedir/ext/pdo/php_pdo_driver.h; then - pdo_cv_inc_path=$phpincludedir/ext - else - AC_MSG_ERROR([Cannot find php_pdo_driver.h.]) - fi - AC_MSG_RESULT($pdo_cv_inc_path) - ]) - - pdo_sqlsrv_src_class="\ - pdo_dbh.cpp \ - pdo_parser.cpp \ - pdo_util.cpp \ - pdo_init.cpp \ - pdo_stmt.cpp \ - " - - shared_src_class="\ - shared/core_conn.cpp \ - shared/core_results.cpp \ - shared/core_stream.cpp \ - shared/core_init.cpp \ - shared/core_stmt.cpp \ - shared/core_util.cpp \ - shared/FormattedPrint.cpp \ - shared/localizationimpl.cpp \ - shared/StringFunctions.cpp \ - " - AC_MSG_CHECKING([for PDO_SQLSRV headers]) - if test -f $srcdir/ext/pdo_sqlsrv/shared/core_sqlsrv.h; then - pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/ - elif test -f $srcdir/shared/core_sqlsrv.h; then - pdo_sqlsrv_inc_path=$srcdir/shared/ - else - AC_MSG_ERROR([Cannot find PDO_SQLSRV headers]) - fi - AC_MSG_RESULT($pdo_sqlsrv_inc_path) - - - CXXFLAGS="$CXXFLAGS -std=c++11" - PHP_REQUIRE_CXX() - PHP_ADD_LIBRARY(stdc++, 1, PDO_SQLSRV_SHARED_LIBADD) - PHP_ADD_LIBRARY(odbc, 1, PDO_SQLSRV_SHARED_LIBADD) - PHP_ADD_LIBRARY(odbcinst, 1, PDO_SQLSRV_SHARED_LIBADD) - AC_DEFINE(HAVE_PDO_SQLSRV, 1, [ ]) - PHP_ADD_INCLUDE([$pdo_sqlsrv_inc_path]) - PHP_NEW_EXTENSION(pdo_sqlsrv, $pdo_sqlsrv_src_class $shared_src_class, $ext_shared,,-I$pdo_cv_inc_path -std=c++11) - PHP_SUBST(PDO_SQLSRV_SHARED_LIBADD) - PHP_ADD_EXTENSION_DEP(pdo_sqlsrv, pdo) - PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1) -fi - +PHP_ARG_WITH(pdo_sqlsrv, for pdo_sqlsrv support, +[ --with-pdo_sqlsrv Include pdo_sqlsrv support]) + +if test "$PHP_PDO_SQLSRV" != "no"; then + if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then + AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) + fi + + ifdef([PHP_CHECK_PDO_INCLUDES], + [ + PHP_CHECK_PDO_INCLUDES + ],[ + AC_MSG_CHECKING([for PDO includes]) + if test -f $abs_srcdir/include/php/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$abs_srcdir/ext + elif test -f $abs_srcdir/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$abs_srcdir/ext + elif test -f $phpincludedir/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$phpincludedir/ext + else + AC_MSG_ERROR([Cannot find php_pdo_driver.h.]) + fi + AC_MSG_RESULT($pdo_cv_inc_path) + ]) + + pdo_sqlsrv_src_class="\ + pdo_dbh.cpp \ + pdo_parser.cpp \ + pdo_util.cpp \ + pdo_init.cpp \ + pdo_stmt.cpp \ + " + + shared_src_class="\ + shared/core_conn.cpp \ + shared/core_results.cpp \ + shared/core_stream.cpp \ + shared/core_init.cpp \ + shared/core_stmt.cpp \ + shared/core_util.cpp \ + shared/FormattedPrint.cpp \ + shared/localizationimpl.cpp \ + shared/StringFunctions.cpp \ + " + AC_MSG_CHECKING([for PDO_SQLSRV headers]) + if test -f $srcdir/ext/pdo_sqlsrv/shared/core_sqlsrv.h; then + pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/ + elif test -f $srcdir/shared/core_sqlsrv.h; then + pdo_sqlsrv_inc_path=$srcdir/shared/ + else + AC_MSG_ERROR([Cannot find PDO_SQLSRV headers]) + fi + AC_MSG_RESULT($pdo_sqlsrv_inc_path) + + + CXXFLAGS="$CXXFLAGS -std=c++11" + PHP_REQUIRE_CXX() + PHP_ADD_LIBRARY(stdc++, 1, PDO_SQLSRV_SHARED_LIBADD) + PHP_ADD_LIBRARY(odbc, 1, PDO_SQLSRV_SHARED_LIBADD) + PHP_ADD_LIBRARY(odbcinst, 1, PDO_SQLSRV_SHARED_LIBADD) + AC_DEFINE(HAVE_PDO_SQLSRV, 1, [ ]) + PHP_ADD_INCLUDE([$pdo_sqlsrv_inc_path]) + PHP_NEW_EXTENSION(pdo_sqlsrv, $pdo_sqlsrv_src_class $shared_src_class, $ext_shared,,-I$pdo_cv_inc_path -std=c++11) + PHP_SUBST(PDO_SQLSRV_SHARED_LIBADD) + PHP_ADD_EXTENSION_DEP(pdo_sqlsrv, pdo) + PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1) +fi + diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index ff92c6d4..2506960b 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -393,17 +393,20 @@ int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC) try { - SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" ); + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_close_cursor: pdo_stmt object was null" ); sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_close_cursor: driver_data object was null" ); // to "close the cursor" means we make the statement ready for execution again. To do this, we // skip all the result sets on the current statement. - while( driver_stmt->past_next_result_end == false ) { - - core_sqlsrv_next_result( driver_stmt TSRMLS_CC ); + // If the statement has not been executed there are no next results to iterate over. + if ( driver_stmt->executed == true ) + { + while( driver_stmt->past_next_result_end == false ) { + core_sqlsrv_next_result( driver_stmt TSRMLS_CC ); + } } } catch( core::CoreException& ) { @@ -412,7 +415,7 @@ int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC) } catch( ... ) { - DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advanding to the next result set." ); + DIE( "pdo_sqlsrv_stmt_close_cursor: Unknown exception occurred while advancing to the next result set." ); } return 1; @@ -718,12 +721,10 @@ int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, sqlsrv_phptype sqlsrv_php_type; SQLSRV_ASSERT( colno >= 0 && colno < static_cast( driver_stmt->current_meta_data.size()), "Invalid column number in pdo_sqlsrv_stmt_get_col_data" ); - sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast( driver_stmt->current_meta_data[ colno ]->field_type ), - static_cast( driver_stmt->current_meta_data[ colno ]->field_size ), - true ); // set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding - sqlsrv_php_type.typeinfo.encoding = driver_stmt->encoding(); + sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast( driver_stmt->current_meta_data[colno]->field_type ), + static_cast( driver_stmt->current_meta_data[colno]->field_size ), true ); // 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 @@ -735,6 +736,10 @@ int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, pdo_bound_param_data* bind_data = NULL; bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, colno)); + if (bind_data == NULL) { + // can't find by index then try searching by name + bind_data = reinterpret_cast(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[colno].name)); + } if( bind_data != NULL && !Z_ISUNDEF(bind_data->driver_params) ) { @@ -1299,6 +1304,8 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUI "Invalid encoding on the connection. Must not be invalid or default." ); } + sqlsrv_phptype.typeinfo.encoding = local_encoding; + switch( sql_type ) { case SQL_BIT: case SQL_INTEGER: @@ -1309,7 +1316,6 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUI } else { sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = local_encoding; } break; case SQL_FLOAT: @@ -1319,7 +1325,6 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUI } else { sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = local_encoding; } break; case SQL_BIGINT: @@ -1338,7 +1343,6 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUI case SQL_WLONGVARCHAR: case SQL_SS_XML: sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = local_encoding; break; case SQL_BINARY: case SQL_LONGVARBINARY: diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index d36cd82f..285762db 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -43,8 +43,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US //Version VS_VERSION_INFO VERSIONINFO - FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD - PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_RELEASE,0 + FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH, SQLVERSION_BUILD + PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_PATCH,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG @@ -62,12 +62,12 @@ BEGIN VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright © 2001-2016 The PHP Group. All rights reserved.\0" VALUE "CompanyName", "Microsoft Corp.\0" VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0" - VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD) + VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH, SQLVERSION_BUILD) VALUE "InternalName", FILE_NAME "\0" VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" VALUE "OriginalFilename", FILE_NAME "\0" VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" - VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE) + VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH) VALUE "URL", "http://www.microsoft.com\0" END END diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 1f1593a2..fd720961 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -25,7 +25,6 @@ #ifndef _WIN32 #include -#include #endif // !_WIN32 @@ -210,21 +209,20 @@ SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buff if ( std::is_same::value ) { + std::basic_string str; - char *str_num_ptr = &str_num[0], *str_num_end = &str_num[0] + str_num.size(); - - for ( const auto &mb : str_num ) + + for (const auto &mb : str_num ) { - char16_t ch16; - std::mbstate_t mbs = std::mbstate_t(); - - int len = mbrtoc16( &ch16, &mb, str_num_end - str_num_ptr, &mbs ); - if ( len > 0 || len == -3 ) + size_t cch = SystemLocale::NextChar( CP_ACP, &mb ) - &mb; + if ( cch > 0 ) { - str.push_back( ch16 ); - if ( len > 0 ) + WCHAR ch16; + DWORD rc; + size_t cchActual = SystemLocale::ToUtf16( CP_ACP, &mb, cch, &ch16, 1, &rc); + if (cchActual > 0) { - str_num_ptr += len; + str.push_back ( ch16 ); } } } @@ -238,6 +236,39 @@ SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buff } +#ifndef _WIN32 + + +std::string getUTF8StringFromString( const SQLWCHAR* source ) +{ + // convert to regular character string first + char c_str[4] = ""; + mbstate_t mbs; + + SQLLEN i = 0; + std::string str; + while ( source[i] ) + { + memset( c_str, 0, sizeof( c_str ) ); + DWORD rc; + int cch = 0; + errno_t err = mplat_wctomb_s( &cch, c_str, sizeof( c_str ), source[i++] ); + if ( cch > 0 && err == ERROR_SUCCESS ) + { + str.append( std::string( c_str, cch ) ); + } + } + return str; +} + + +std::string getUTF8StringFromString( const char* source ) +{ + return std::string( source ); +} + +#endif // !_WIN32 + template SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) @@ -259,32 +290,13 @@ SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, _Out_ void* buffe *out_buffer_length = sizeof( Number ); #else - std::string str; - if ( std::is_same::value ) - { - // convert to regular character string first - char c_str[3] = ""; - mbstate_t mbs; - - SQLLEN i = 0; - while ( string_data[i] ) - { - memset( &mbs, 0, sizeof( mbs )); //set shift state to the initial state - memset( c_str, 0, sizeof( c_str )); - int len = c16rtomb( c_str, string_data[i++], &mbs ); // treat string_data as a char16_t string - str.append(std::string( c_str, len )); - } - } - else - { - str.append( std::string(( char * )string_data )); - } + std::string str = getUTF8StringFromString( string_data ); std::istringstream is( str ); std::locale loc; // default locale should match system is.imbue( loc ); - auto& facet = std::use_facet>( is.getloc()); + auto& facet = std::use_facet>( is.getloc() ); std::istreambuf_iterator beg( is ), end; std::ios_base::iostate err = std::ios_base::goodbit; @@ -428,15 +440,11 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS sqlsrv_result_set( stmt ), cache(NULL), col_count(0), - meta(NULL), current(0), last_field_index(-1), read_so_far(0), temp_length(0) { - // 10 is an arbitrary number for now for the initial size of the cache - ALLOC_HASHTABLE( cache ); - core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); // there is no result set to buffer if( col_count == 0 ) { @@ -650,87 +658,104 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS // (offset from the above loop has the size of the row buffer necessary) zend_long mem_used = 0; size_t row_count = 0; + // 10 is an arbitrary number for now for the initial size of the cache + ALLOC_HASHTABLE( cache ); + core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); - while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { - - // allocate the row buffer - unsigned char* row = static_cast( sqlsrv_malloc( offset )); - memset( row, 0, offset ); + try { + while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { + + // allocate the row buffer + sqlsrv_malloc_auto_ptr rowAuto; + rowAuto = static_cast( sqlsrv_malloc( offset )); + unsigned char* row = rowAuto.get(); + memset( row, 0, offset ); - // read the fields into the row buffer - for( SQLSMALLINT i = 0; i < col_count; ++i ) { + // read the fields into the row buffer + for( SQLSMALLINT i = 0; i < col_count; ++i ) { - SQLLEN out_buffer_temp = SQL_NULL_DATA; - SQLPOINTER buffer; - SQLLEN* out_buffer_length = &out_buffer_temp; + SQLLEN out_buffer_temp = SQL_NULL_DATA; + SQLPOINTER buffer; + SQLLEN* out_buffer_length = &out_buffer_temp; - switch( meta[i].c_type ) { + switch( meta[i].c_type ) { - case SQL_C_CHAR: - case SQL_C_WCHAR: - case SQL_C_BINARY: - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_BINARY: + 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 ] ); - *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); - // a NULL pointer means NULL field - if( *lob_addr == NULL ) { - *out_buffer_length = SQL_NULL_DATA; + out_buffer_length = &out_buffer_temp; + 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 ) { + *out_buffer_length = SQL_NULL_DATA; + } + else { + *out_buffer_length = **reinterpret_cast( lob_addr ); + mem_used += *out_buffer_length; + } } else { - *out_buffer_length = **reinterpret_cast( lob_addr ); - mem_used += *out_buffer_length; + + mem_used += meta[i].length; + CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + + buffer = row + meta[i].offset + sizeof( SQLULEN ); + out_buffer_length = reinterpret_cast( row + meta[i].offset ); + core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, + false TSRMLS_CC ); } - } - else { + break; - mem_used += meta[i].length; - CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + case SQL_C_LONG: + case SQL_C_DOUBLE: + { + mem_used += meta[i].length; + CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - throw core::CoreException(); + throw core::CoreException(); + } + buffer = row + meta[i].offset; + out_buffer_length = &out_buffer_temp; + core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, + false TSRMLS_CC ); } + break; - buffer = row + meta[i].offset + sizeof( SQLULEN ); - out_buffer_length = reinterpret_cast( row + meta[i].offset ); - core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); - } - break; + default: + SQLSRV_ASSERT( false, "Unknown C type" ); + break; + } - case SQL_C_LONG: - case SQL_C_DOUBLE: - { - mem_used += meta[i].length; - CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - buffer = row + meta[i].offset; - out_buffer_length = &out_buffer_temp; - core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); - } - break; - - default: - SQLSRV_ASSERT( false, "Unknown C type" ); - break; + row_count++; + if( *out_buffer_length == SQL_NULL_DATA ) { + set_bit( row, i ); + } } - row_count++; - if( *out_buffer_length == SQL_NULL_DATA ) { - set_bit( row, i ); - } + SQLSRV_ASSERT( row_count < INT_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); + + // add it to the cache + row_dtor_closure cl( this, row ); + sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); + rowAuto.transferred(); + } + } + catch( core::CoreException& ) { + // free the rows + if( cache ) { + zend_hash_destroy( cache ); + FREE_HASHTABLE( cache ); + cache = NULL; } - - SQLSRV_ASSERT( row_count < INT_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); - - // add it to the cache - row_dtor_closure cl( this, row ); - sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); + throw; } } @@ -743,12 +768,6 @@ sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void ) FREE_HASHTABLE( cache ); cache = NULL; } - - // free the meta data - if( meta ) { - efree( meta ); - meta = NULL; - } } SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) @@ -1351,6 +1370,11 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_i field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; } + if ( field_len == 0 ) { // empty string, no need for conversion + *out_buffer_length = 0; + return SQL_SUCCESS; + } + // allocate enough to handle WC -> DBCS conversion if it happens temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof(char), sizeof(char))); diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 9b6c2d2d..55c0678b 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -131,6 +131,11 @@ OACR_WARNING_POP #include #endif // _WIN32 +#if !defined(SQL_GUID) +// imported from sqlext.h +#define SQL_GUID (-11) +#endif + #if !defined(WC_ERR_INVALID_CHARS) // imported from winnls.h as it isn't included by 5.3.0 #define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars @@ -1479,7 +1484,7 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { HashTable* cache; // rows of data kept in index based hash table SQLSMALLINT col_count; // number of columns in the current result set - meta_data* meta; // metadata for fields in the cache + sqlsrv_malloc_auto_ptr meta; // metadata for fields in the cache SQLLEN current; // 1 based, 0 means before first row sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here SQLUSMALLINT last_field_index; // the last field data retrieved from diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index faa0c523..9dd505ec 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -208,7 +208,11 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) // create a new result set if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { - current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC ); + sqlsrv_malloc_auto_ptr result; + result = reinterpret_cast ( sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ) ) ); + new ( result.get() ) sqlsrv_buffered_result_set( this TSRMLS_CC ); + current_results = result.get(); + result.transferred(); } else { current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this ); @@ -776,6 +780,7 @@ bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULE if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { stmt->past_fetch_end = true; } + stmt->fetch_called = false; // reset this flag return false; } @@ -2002,7 +2007,11 @@ void finalize_output_parameters( 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 ]; - if( str_len == SQL_NULL_DATA || str_len == 0 ) { + if( str_len == 0 ) { + core::sqlsrv_zval_stringl( value_z, "", 0 ); + continue; + } + if( str_len == SQL_NULL_DATA ) { zend_string_release( Z_STR_P( value_z )); ZVAL_NULL( value_z ); continue; @@ -2029,6 +2038,15 @@ void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) throw core::CoreException(); } + // For ODBC 11+ see https://msdn.microsoft.com/en-us/library/jj219209.aspx + // A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains up to + // output_param->original_buffer_len data and is NULL terminated. + // The IF statement can be true when using connection pooling with unixODBC 2.3.4. + if ( str_len == SQL_NO_TOTAL ) + { + str_len = output_param->original_buffer_len - null_size; + } + // if it's not in the 8 bit encodings, then it's in UTF-16 if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); @@ -2410,16 +2428,20 @@ void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULE // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, // as is a n(var)char/ntext field being returned as a binary field. - elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1; + elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR || sql_type == SQL_WLONGVARCHAR ))) ? 2 : 1; // account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning - expected_len = column_size * elem_size + elem_size; + SQLULEN field_size = column_size; + if (column_size == SQL_SS_LENGTH_UNLIMITED) { + field_size = SQL_SERVER_MAX_FIELD_SIZE / elem_size; + } + expected_len = field_size * elem_size + elem_size; // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter - without_null_len = column_size * elem_size; + without_null_len = field_size * elem_size; // increment to include the null terminator since the Zend length doesn't include the null terminator buffer_len += elem_size; diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index a7307889..18e30b27 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -25,13 +25,10 @@ #include "typedefs_for_linux.h" #ifdef MPLAT_UNIX -namespace std -{ - // Forward reference - class locale; -} +#include #endif + #define CP_UTF8 65001 #define CP_UTF16 1200 #define CP_UTF32 12000 diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index eb66d34a..bff42721 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -311,7 +311,7 @@ const SystemLocale & SystemLocale::Singleton() #if !defined(__GNUC__) || defined(NO_THREADSAFE_STATICS) #error "Relying on GCC's threadsafe initialization of local statics." #endif - static const SystemLocale s_Default( "en_US.utf8" ); + static const SystemLocale s_Default( "en_US.utf-8" ); return s_Default; } diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index 278a8617..98b23f19 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -1,426 +1,426 @@ -#ifndef __msodbcsql_h__ -#define __msodbcsql_h__ - -//--------------------------------------------------------------------------------------------------------------------------------- -// File: msodbcsql.h -// -// Contents: Routines that use statement handles -// -// Contents: This SDK is not supported under any Microsoft standard support -// program or service. The information is provided AS IS without -// warranty of any kind. Microsoft disclaims all implied -// warranties including, without limitation, any implied -// warranties of merchantability or of fitness for a particular -// purpose. The entire risk arising out of the use of this SDK -// remains with you. In no event shall Microsoft, its authors, or -// anyone else involved in the creation, production, or delivery -// of this SDK be liable for any damages whatsoever (including, -// without limitation, damages for loss of business profits, -// business interruption, loss of business information, or other -// 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 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - - -#if !defined(SQLODBC_VER) -#define SQLODBC_VER 1100 -#endif - -#if SQLODBC_VER >= 1300 - -#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 13 for SQL Server" -#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 13 for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server" - -#endif /* SQLODBC_VER >= 1300 */ - -#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI -#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI -#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI -#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI - -#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER - -/* max SQL Server identifier length */ -#define SQL_MAX_SQLSERVERNAME 128 - -/* - * SQLSetConnectAttr driver specific defines. - * Microsoft has 1200 thru 1249 reserved for Microsoft ODBC Driver for SQL Server usage. - * Connection attributes - */ -#define SQL_COPT_SS_BASE 1200 -#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) /* Force integrated security on login */ -#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) /* Perform code page translation */ -#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) /* Allow strong encryption for data */ -#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) /* Multiple active result set per connection */ -#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) /* Used to set/get any driver-specific or ODBC-defined TXN iso level */ -#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) /* Trust server certificate */ -/* - * SQLSetStmtAttr Microsoft ODBC Driver for SQL Server specific defines. - * Statement attributes - */ -#define SQL_SOPT_SS_BASE 1225 -#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) /* Text pointer logging */ -#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) /* Set NOBROWSETABLE option */ -#define SQL_SOPT_SS_COLUMN_ENCRYPTION (SQL_SOPT_SS_BASE+13)/* Sets the column encryption mode */ -/* Define old names */ -#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING -#define SQL_COPT_SS_BASE_EX 1240 -#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) /* Issues warning when data from the server had a loss during code page conversion. */ -#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) /* dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check */ -#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) /* Application Intent */ -#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) /* Multi-subnet Failover */ -#define SQL_COPT_SS_TNIR (SQL_COPT_SS_BASE_EX+9) /* Transparent Network IP Resolution */ -#define SQL_COPT_SS_COLUMN_ENCRYPTION (SQL_COPT_SS_BASE_EX+10) /* Always Encrypted Enabled or Disabled */ -#define SQL_COPT_SS_AEKEYSTOREPROVIDER (SQL_COPT_SS_BASE_EX+11) /* Load a keystore provider or read the list of loaded keystore providers */ -#define SQL_COPT_SS_AEKEYSTOREDATA (SQL_COPT_SS_BASE_EX+12) /* Communicate with a loaded keystore provider */ -#define SQL_COPT_SS_AETRUSTEDCMKPATHS (SQL_COPT_SS_BASE_EX+13) /* List of trusted CMK paths */ -#define SQL_COPT_SS_AECEKCACHETTL (SQL_COPT_SS_BASE_EX+14)// Symmetric Key Cache TTL -/* - * SQLColAttributes driver specific defines. - * SQLSetDescField/SQLGetDescField driver specific defines. - * Microsoft has 1200 thru 1249 reserved for Microsoft ODBC Driver for SQL Server usage. - */ -#define SQL_CA_SS_BASE 1200 -#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) /* dbcoltype/dbalttype */ -#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) /* dbcolutype/dbaltutype */ -#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) /* dbnumorders */ -#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) /* dbordercol */ -#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) /* dbvarylen */ -#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) /* dbnumcompute */ -#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) /* dbnextrow status return */ -#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) /* dbbylist */ -#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) /* dbaltcolid */ -#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) /* dbaltop */ -#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) /* dbcollen */ -#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) /* Column is hidden (FOR BROWSE) */ -#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) /* Column is key column (FOR BROWSE) */ -#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) /* Column collation (only for chars) */ -#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15) -#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16) -#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17) - -/* XML, CLR UDT, and table valued parameter related metadata */ -#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) /* UDT catalog name */ -#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) /* UDT schema name */ -#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) /* UDT type name */ -#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) /* Name of the catalog that contains XML Schema collection */ -#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) /* Name of the schema that contains XML Schema collection */ -#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) /* Name of the XML Schema collection */ -#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) /* Catalog name */ -#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) /* Schema name */ -#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) /* Type name */ - -/* table valued parameter related metadata */ -#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) /* column is computed */ -#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) /* column is part of a unique key */ -#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) /* column sort order */ -#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) /* column sort ordinal */ -#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) /* column has default value for all rows of the table valued parameter */ - -/* sparse column related metadata */ -#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) /* column is a column-set column for sparse columns */ - -/* Legacy datetime related metadata */ -#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) /* column type to send on the wire for datetime types */ - -/* force column encryption */ -#define SQL_CA_SS_FORCE_ENCRYPT (SQL_CA_SS_BASE+36) /* indicate mandatory encryption for this parameter */ - -/* Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only */ -#define SQL_IS_OFF 0L /* Integrated security isn't used */ -#define SQL_IS_ON 1L /* Integrated security is used */ -#define SQL_IS_DEFAULT SQL_IS_OFF -/* Defines for use with SQL_COPT_SS_TRANSLATE */ -#define SQL_XL_OFF 0L /* Code page translation is not performed */ -#define SQL_XL_ON 1L /* Code page translation is performed */ -#define SQL_XL_DEFAULT SQL_XL_ON -/* Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING */ -#define SQL_TL_OFF 0L /* No logging on text pointer ops */ -#define SQL_TL_ON 1L /* Logging occurs on text pointer ops */ -#define SQL_TL_DEFAULT SQL_TL_ON -/* Defines for use with SQL_SOPT_SS_NOBROWSETABLE */ -#define SQL_NB_OFF 0L /* NO_BROWSETABLE is off */ -#define SQL_NB_ON 1L /* NO_BROWSETABLE is on */ -#define SQL_NB_DEFAULT SQL_NB_OFF -/* Defines for use with SQL_SOPT_SS_COLUMN_ENCRYPTION */ -#define SQL_CE_DISABLED 0L /* Disabled */ -#define SQL_CE_RESULTSETONLY 1L /* Decryption Only (resultsets and return values) */ -#define SQL_CE_ENABLED 3L /* Enabled (both encryption and decryption) */ - -/* SQL_COPT_SS_ENCRYPT */ -#define SQL_EN_OFF 0L -#define SQL_EN_ON 1L -/* SQL_COPT_SS_TRUST_SERVER_CERTIFICATE */ -#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L -#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L -/* SQL_COPT_SS_WARN_ON_CP_ERROR */ -#define SQL_WARN_NO 0L -#define SQL_WARN_YES 1L -/* SQL_COPT_SS_MARS_ENABLED */ -#define SQL_MARS_ENABLED_NO 0L -#define SQL_MARS_ENABLED_YES 1L -/* SQL_TXN_ISOLATION_OPTION bitmasks */ -#define SQL_TXN_SS_SNAPSHOT 0x00000020L -/* SQL_COPT_SS_COLUMN_ENCRYPTION */ -#define SQL_COLUMN_ENCRYPTION_DISABLE 0L -#define SQL_COLUMN_ENCRYPTION_ENABLE 1L -#define SQL_COLUMN_ENCRYPTION_DEFAULT SQL_COLUMN_ENCRYPTION_DISABLE -// Defines for use with SQL_COPT_SS_AECEKCACHETTL -#define SQL_AECEKCACHETTL_DEFAULT 7200L // TTL value in seconds (2 hours) - -/* The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER */ -#define SQL_SS_ORDER_UNSPECIFIED 0L -#define SQL_SS_DESCENDING_ORDER 1L -#define SQL_SS_ASCENDING_ORDER 2L -#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED - -/* - * Driver specific SQL data type defines. - * Microsoft has -150 thru -199 reserved for Microsoft ODBC Driver for SQL Server usage. - */ -#define SQL_SS_VARIANT (-150) -#define SQL_SS_UDT (-151) -#define SQL_SS_XML (-152) -#define SQL_SS_TABLE (-153) -#define SQL_SS_TIME2 (-154) -#define SQL_SS_TIMESTAMPOFFSET (-155) - -/* Local types to be used with SQL_CA_SS_SERVER_TYPE */ -#define SQL_SS_TYPE_DEFAULT 0L -#define SQL_SS_TYPE_SMALLDATETIME 1L -#define SQL_SS_TYPE_DATETIME 2L - -/* Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager. */ -#define SQL_C_TYPES_EXTENDED 0x04000L - -/* - * SQL_SS_LENGTH_UNLIMITED is used to describe the max length of - * VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns - */ -#define SQL_SS_LENGTH_UNLIMITED 0 - -/* - * User Data Type definitions. - * Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE. - */ -#define SQLudtBINARY 3 -#define SQLudtBIT 16 -#define SQLudtBITN 0 -#define SQLudtCHAR 1 -#define SQLudtDATETIM4 22 -#define SQLudtDATETIME 12 -#define SQLudtDATETIMN 15 -#define SQLudtDECML 24 -#define SQLudtDECMLN 26 -#define SQLudtFLT4 23 -#define SQLudtFLT8 8 -#define SQLudtFLTN 14 -#define SQLudtIMAGE 20 -#define SQLudtINT1 5 -#define SQLudtINT2 6 -#define SQLudtINT4 7 -#define SQLudtINTN 13 -#define SQLudtMONEY 11 -#define SQLudtMONEY4 21 -#define SQLudtMONEYN 17 -#define SQLudtNUM 10 -#define SQLudtNUMN 25 -#define SQLudtSYSNAME 18 -#define SQLudtTEXT 19 -#define SQLudtTIMESTAMP 80 -#define SQLudtUNIQUEIDENTIFIER 0 -#define SQLudtVARBINARY 4 -#define SQLudtVARCHAR 2 -#define MIN_USER_DATATYPE 256 -/* - * Aggregate operator types. - * Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP. - */ -#define SQLAOPSTDEV 0x30 /* Standard deviation */ -#define SQLAOPSTDEVP 0x31 /* Standard deviation population */ -#define SQLAOPVAR 0x32 /* Variance */ -#define SQLAOPVARP 0x33 /* Variance population */ -#define SQLAOPCNT 0x4b /* Count */ -#define SQLAOPSUM 0x4d /* Sum */ -#define SQLAOPAVG 0x4f /* Average */ -#define SQLAOPMIN 0x51 /* Min */ -#define SQLAOPMAX 0x52 /* Max */ -#define SQLAOPANY 0x53 /* Any */ -#define SQLAOPNOOP 0x56 /* None */ -/* - * SQLGetDiagField driver specific defines. - * Microsoft has -1150 thru -1199 reserved for Microsoft ODBC Driver for SQL Server usage. - */ -#define SQL_DIAG_SS_BASE (-1150) -#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE) -#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1) -#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2) -#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3) -#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4) -/* - * SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines. - * Microsoft has -200 thru -299 reserved for Microsoft ODBC Driver for SQL Server usage. - */ -#define SQL_DIAG_DFC_SS_BASE (-200) -#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0) -#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1) -#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2) -#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3) -#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4) -#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5) -#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6) -#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7) -#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8) -#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9) -#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10) -#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11) -#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12) -#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13) -#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14) -#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15) -#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16) -#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17) -#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18) -#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19) -#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) -#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) -#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21) -#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) -#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) -#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23) -#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24) -#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25) -#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26) -#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26) -#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) -#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) -#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28) -#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) -#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) -#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30) -#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31) -#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32) -#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33) -#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34) -#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35) -#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36) -#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37) -#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38) -#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39) -#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40) -#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41) -#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42) -#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43) -#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44) -#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45) -#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46) -#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47) -#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48) -#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49) -#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50) -#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51) -#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52) -#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53) -#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54) -#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55) -#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56) - -/* Severity codes for SQL_DIAG_SS_SEVERITY */ -#define EX_ANY 0 -#define EX_INFO 10 -#define EX_MAXISEVERITY EX_INFO -#define EX_MISSING 11 -#define EX_TYPE 12 -#define EX_DEADLOCK 13 -#define EX_PERMIT 14 -#define EX_SYNTAX 15 -#define EX_USER 16 -#define EX_RESOURCE 17 -#define EX_INTOK 18 -#define MAXUSEVERITY EX_INTOK -#define EX_LIMIT 19 -#define EX_CMDFATAL 20 -#define MINFATALERR EX_CMDFATAL -#define EX_DBFATAL 21 -#define EX_TABCORRUPT 22 -#define EX_DBCORRUPT 23 -#define EX_HARDWARE 24 -#define EX_CONTROL 25 - -/* Keystore Provider interface definition */ - -typedef void errFunc(void *ctx, const wchar_t *msg, ...); - -#define IDS_MSG(x) ((const wchar_t*)(x)) - -typedef struct AEKeystoreProvider -{ - wchar_t *Name; - int (*Init)(void *ctx, errFunc *onError); - int (*Read)(void *ctx, errFunc *onError, void *data, unsigned int *len); - int (*Write)(void *ctx, errFunc *onError, void *data, unsigned int len); - int (*DecryptCEK)( - void *ctx, - errFunc *onError, - const wchar_t *keyPath, - const wchar_t *alg, - unsigned char *ecek, - unsigned short ecek_len, - unsigned char **cek_out, - unsigned short *cek_len); - void (*Free)(); -} AEKEYSTOREPROVIDER; - -/* Data is defined to be past the end of the structure header. - This is accepted by MSVC, GCC, and C99 standard but former emits - unnecessary warning, hence it has to be disabled. -*/ -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4200) -#endif - -typedef struct AEKeystoreData -{ - wchar_t *Name; - unsigned int dataSize; - char Data[]; -} AEKEYSTOREPROVIDERDATA; - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - -/* The following constants are for the Azure Key Vault configuration interface */ -#define AKV_CONFIG_FLAGS 0 - #define AKVCFG_USECLIENTID 0x00000001 - #define AKVCFG_AUTORENEW 0x00000002 - -#define AKV_CONFIG_CLIENTID 1 -#define AKV_CONFIG_CLIENTKEY 2 - -#define AKV_CONFIG_ACCESSTOKEN 3 -#define AKV_CONFIG_TOKENEXPIRY 4 - -#define AKV_CONFIG_MAXRETRIES 5 -#define AKV_CONFIG_RETRYTIMEOUT 6 -#define AKV_CONFIG_RETRYWAIT 7 - -#endif /* __msodbcsql_h__ */ - +#ifndef __msodbcsql_h__ +#define __msodbcsql_h__ + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: msodbcsql.h +// +// Contents: Routines that use statement handles +// +// Contents: This SDK is not supported under any Microsoft standard support +// program or service. The information is provided AS IS without +// warranty of any kind. Microsoft disclaims all implied +// warranties including, without limitation, any implied +// warranties of merchantability or of fitness for a particular +// purpose. The entire risk arising out of the use of this SDK +// remains with you. In no event shall Microsoft, its authors, or +// anyone else involved in the creation, production, or delivery +// of this SDK be liable for any damages whatsoever (including, +// without limitation, damages for loss of business profits, +// business interruption, loss of business information, or other +// 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 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + + +#if !defined(SQLODBC_VER) +#define SQLODBC_VER 1100 +#endif + +#if SQLODBC_VER >= 1300 + +#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 13 for SQL Server" +#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server" +#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 13 for SQL Server" +#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server" + +#endif /* SQLODBC_VER >= 1300 */ + +#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI +#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI +#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI +#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI + +#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER + +/* max SQL Server identifier length */ +#define SQL_MAX_SQLSERVERNAME 128 + +/* + * SQLSetConnectAttr driver specific defines. + * Microsoft has 1200 thru 1249 reserved for Microsoft ODBC Driver for SQL Server usage. + * Connection attributes + */ +#define SQL_COPT_SS_BASE 1200 +#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) /* Force integrated security on login */ +#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) /* Perform code page translation */ +#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) /* Allow strong encryption for data */ +#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) /* Multiple active result set per connection */ +#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) /* Used to set/get any driver-specific or ODBC-defined TXN iso level */ +#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) /* Trust server certificate */ +/* + * SQLSetStmtAttr Microsoft ODBC Driver for SQL Server specific defines. + * Statement attributes + */ +#define SQL_SOPT_SS_BASE 1225 +#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) /* Text pointer logging */ +#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) /* Set NOBROWSETABLE option */ +#define SQL_SOPT_SS_COLUMN_ENCRYPTION (SQL_SOPT_SS_BASE+13)/* Sets the column encryption mode */ +/* Define old names */ +#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING +#define SQL_COPT_SS_BASE_EX 1240 +#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) /* Issues warning when data from the server had a loss during code page conversion. */ +#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) /* dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check */ +#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) /* Application Intent */ +#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) /* Multi-subnet Failover */ +#define SQL_COPT_SS_TNIR (SQL_COPT_SS_BASE_EX+9) /* Transparent Network IP Resolution */ +#define SQL_COPT_SS_COLUMN_ENCRYPTION (SQL_COPT_SS_BASE_EX+10) /* Always Encrypted Enabled or Disabled */ +#define SQL_COPT_SS_AEKEYSTOREPROVIDER (SQL_COPT_SS_BASE_EX+11) /* Load a keystore provider or read the list of loaded keystore providers */ +#define SQL_COPT_SS_AEKEYSTOREDATA (SQL_COPT_SS_BASE_EX+12) /* Communicate with a loaded keystore provider */ +#define SQL_COPT_SS_AETRUSTEDCMKPATHS (SQL_COPT_SS_BASE_EX+13) /* List of trusted CMK paths */ +#define SQL_COPT_SS_AECEKCACHETTL (SQL_COPT_SS_BASE_EX+14)// Symmetric Key Cache TTL +/* + * SQLColAttributes driver specific defines. + * SQLSetDescField/SQLGetDescField driver specific defines. + * Microsoft has 1200 thru 1249 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_CA_SS_BASE 1200 +#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) /* dbcoltype/dbalttype */ +#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) /* dbcolutype/dbaltutype */ +#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) /* dbnumorders */ +#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) /* dbordercol */ +#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) /* dbvarylen */ +#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) /* dbnumcompute */ +#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) /* dbnextrow status return */ +#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) /* dbbylist */ +#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) /* dbaltcolid */ +#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) /* dbaltop */ +#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) /* dbcollen */ +#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) /* Column is hidden (FOR BROWSE) */ +#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) /* Column is key column (FOR BROWSE) */ +#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) /* Column collation (only for chars) */ +#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15) +#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16) +#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17) + +/* XML, CLR UDT, and table valued parameter related metadata */ +#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) /* UDT catalog name */ +#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) /* UDT schema name */ +#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) /* UDT type name */ +#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) /* Name of the catalog that contains XML Schema collection */ +#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) /* Name of the schema that contains XML Schema collection */ +#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) /* Name of the XML Schema collection */ +#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) /* Catalog name */ +#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) /* Schema name */ +#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) /* Type name */ + +/* table valued parameter related metadata */ +#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) /* column is computed */ +#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) /* column is part of a unique key */ +#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) /* column sort order */ +#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) /* column sort ordinal */ +#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) /* column has default value for all rows of the table valued parameter */ + +/* sparse column related metadata */ +#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) /* column is a column-set column for sparse columns */ + +/* Legacy datetime related metadata */ +#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) /* column type to send on the wire for datetime types */ + +/* force column encryption */ +#define SQL_CA_SS_FORCE_ENCRYPT (SQL_CA_SS_BASE+36) /* indicate mandatory encryption for this parameter */ + +/* Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only */ +#define SQL_IS_OFF 0L /* Integrated security isn't used */ +#define SQL_IS_ON 1L /* Integrated security is used */ +#define SQL_IS_DEFAULT SQL_IS_OFF +/* Defines for use with SQL_COPT_SS_TRANSLATE */ +#define SQL_XL_OFF 0L /* Code page translation is not performed */ +#define SQL_XL_ON 1L /* Code page translation is performed */ +#define SQL_XL_DEFAULT SQL_XL_ON +/* Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING */ +#define SQL_TL_OFF 0L /* No logging on text pointer ops */ +#define SQL_TL_ON 1L /* Logging occurs on text pointer ops */ +#define SQL_TL_DEFAULT SQL_TL_ON +/* Defines for use with SQL_SOPT_SS_NOBROWSETABLE */ +#define SQL_NB_OFF 0L /* NO_BROWSETABLE is off */ +#define SQL_NB_ON 1L /* NO_BROWSETABLE is on */ +#define SQL_NB_DEFAULT SQL_NB_OFF +/* Defines for use with SQL_SOPT_SS_COLUMN_ENCRYPTION */ +#define SQL_CE_DISABLED 0L /* Disabled */ +#define SQL_CE_RESULTSETONLY 1L /* Decryption Only (resultsets and return values) */ +#define SQL_CE_ENABLED 3L /* Enabled (both encryption and decryption) */ + +/* SQL_COPT_SS_ENCRYPT */ +#define SQL_EN_OFF 0L +#define SQL_EN_ON 1L +/* SQL_COPT_SS_TRUST_SERVER_CERTIFICATE */ +#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L +#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L +/* SQL_COPT_SS_WARN_ON_CP_ERROR */ +#define SQL_WARN_NO 0L +#define SQL_WARN_YES 1L +/* SQL_COPT_SS_MARS_ENABLED */ +#define SQL_MARS_ENABLED_NO 0L +#define SQL_MARS_ENABLED_YES 1L +/* SQL_TXN_ISOLATION_OPTION bitmasks */ +#define SQL_TXN_SS_SNAPSHOT 0x00000020L +/* SQL_COPT_SS_COLUMN_ENCRYPTION */ +#define SQL_COLUMN_ENCRYPTION_DISABLE 0L +#define SQL_COLUMN_ENCRYPTION_ENABLE 1L +#define SQL_COLUMN_ENCRYPTION_DEFAULT SQL_COLUMN_ENCRYPTION_DISABLE +// Defines for use with SQL_COPT_SS_AECEKCACHETTL +#define SQL_AECEKCACHETTL_DEFAULT 7200L // TTL value in seconds (2 hours) + +/* The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER */ +#define SQL_SS_ORDER_UNSPECIFIED 0L +#define SQL_SS_DESCENDING_ORDER 1L +#define SQL_SS_ASCENDING_ORDER 2L +#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED + +/* + * Driver specific SQL data type defines. + * Microsoft has -150 thru -199 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_SS_VARIANT (-150) +#define SQL_SS_UDT (-151) +#define SQL_SS_XML (-152) +#define SQL_SS_TABLE (-153) +#define SQL_SS_TIME2 (-154) +#define SQL_SS_TIMESTAMPOFFSET (-155) + +/* Local types to be used with SQL_CA_SS_SERVER_TYPE */ +#define SQL_SS_TYPE_DEFAULT 0L +#define SQL_SS_TYPE_SMALLDATETIME 1L +#define SQL_SS_TYPE_DATETIME 2L + +/* Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager. */ +#define SQL_C_TYPES_EXTENDED 0x04000L + +/* + * SQL_SS_LENGTH_UNLIMITED is used to describe the max length of + * VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns + */ +#define SQL_SS_LENGTH_UNLIMITED 0 + +/* + * User Data Type definitions. + * Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE. + */ +#define SQLudtBINARY 3 +#define SQLudtBIT 16 +#define SQLudtBITN 0 +#define SQLudtCHAR 1 +#define SQLudtDATETIM4 22 +#define SQLudtDATETIME 12 +#define SQLudtDATETIMN 15 +#define SQLudtDECML 24 +#define SQLudtDECMLN 26 +#define SQLudtFLT4 23 +#define SQLudtFLT8 8 +#define SQLudtFLTN 14 +#define SQLudtIMAGE 20 +#define SQLudtINT1 5 +#define SQLudtINT2 6 +#define SQLudtINT4 7 +#define SQLudtINTN 13 +#define SQLudtMONEY 11 +#define SQLudtMONEY4 21 +#define SQLudtMONEYN 17 +#define SQLudtNUM 10 +#define SQLudtNUMN 25 +#define SQLudtSYSNAME 18 +#define SQLudtTEXT 19 +#define SQLudtTIMESTAMP 80 +#define SQLudtUNIQUEIDENTIFIER 0 +#define SQLudtVARBINARY 4 +#define SQLudtVARCHAR 2 +#define MIN_USER_DATATYPE 256 +/* + * Aggregate operator types. + * Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP. + */ +#define SQLAOPSTDEV 0x30 /* Standard deviation */ +#define SQLAOPSTDEVP 0x31 /* Standard deviation population */ +#define SQLAOPVAR 0x32 /* Variance */ +#define SQLAOPVARP 0x33 /* Variance population */ +#define SQLAOPCNT 0x4b /* Count */ +#define SQLAOPSUM 0x4d /* Sum */ +#define SQLAOPAVG 0x4f /* Average */ +#define SQLAOPMIN 0x51 /* Min */ +#define SQLAOPMAX 0x52 /* Max */ +#define SQLAOPANY 0x53 /* Any */ +#define SQLAOPNOOP 0x56 /* None */ +/* + * SQLGetDiagField driver specific defines. + * Microsoft has -1150 thru -1199 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_DIAG_SS_BASE (-1150) +#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE) +#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1) +#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2) +#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3) +#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4) +/* + * SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines. + * Microsoft has -200 thru -299 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_DIAG_DFC_SS_BASE (-200) +#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0) +#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1) +#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2) +#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3) +#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4) +#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5) +#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6) +#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7) +#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8) +#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9) +#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10) +#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11) +#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12) +#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13) +#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14) +#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15) +#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16) +#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17) +#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18) +#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19) +#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) +#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) +#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21) +#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) +#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) +#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23) +#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24) +#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25) +#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26) +#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26) +#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) +#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) +#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28) +#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) +#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) +#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30) +#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31) +#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32) +#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33) +#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34) +#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35) +#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36) +#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37) +#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38) +#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39) +#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40) +#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41) +#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42) +#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43) +#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44) +#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45) +#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46) +#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47) +#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48) +#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49) +#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50) +#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51) +#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52) +#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53) +#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54) +#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55) +#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56) + +/* Severity codes for SQL_DIAG_SS_SEVERITY */ +#define EX_ANY 0 +#define EX_INFO 10 +#define EX_MAXISEVERITY EX_INFO +#define EX_MISSING 11 +#define EX_TYPE 12 +#define EX_DEADLOCK 13 +#define EX_PERMIT 14 +#define EX_SYNTAX 15 +#define EX_USER 16 +#define EX_RESOURCE 17 +#define EX_INTOK 18 +#define MAXUSEVERITY EX_INTOK +#define EX_LIMIT 19 +#define EX_CMDFATAL 20 +#define MINFATALERR EX_CMDFATAL +#define EX_DBFATAL 21 +#define EX_TABCORRUPT 22 +#define EX_DBCORRUPT 23 +#define EX_HARDWARE 24 +#define EX_CONTROL 25 + +/* Keystore Provider interface definition */ + +typedef void errFunc(void *ctx, const wchar_t *msg, ...); + +#define IDS_MSG(x) ((const wchar_t*)(x)) + +typedef struct AEKeystoreProvider +{ + wchar_t *Name; + int (*Init)(void *ctx, errFunc *onError); + int (*Read)(void *ctx, errFunc *onError, void *data, unsigned int *len); + int (*Write)(void *ctx, errFunc *onError, void *data, unsigned int len); + int (*DecryptCEK)( + void *ctx, + errFunc *onError, + const wchar_t *keyPath, + const wchar_t *alg, + unsigned char *ecek, + unsigned short ecek_len, + unsigned char **cek_out, + unsigned short *cek_len); + void (*Free)(); +} AEKEYSTOREPROVIDER; + +/* Data is defined to be past the end of the structure header. + This is accepted by MSVC, GCC, and C99 standard but former emits + unnecessary warning, hence it has to be disabled. +*/ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4200) +#endif + +typedef struct AEKeystoreData +{ + wchar_t *Name; + unsigned int dataSize; + char Data[]; +} AEKEYSTOREPROVIDERDATA; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +/* The following constants are for the Azure Key Vault configuration interface */ +#define AKV_CONFIG_FLAGS 0 + #define AKVCFG_USECLIENTID 0x00000001 + #define AKVCFG_AUTORENEW 0x00000002 + +#define AKV_CONFIG_CLIENTID 1 +#define AKV_CONFIG_CLIENTKEY 2 + +#define AKV_CONFIG_ACCESSTOKEN 3 +#define AKV_CONFIG_TOKENEXPIRY 4 + +#define AKV_CONFIG_MAXRETRIES 5 +#define AKV_CONFIG_RETRYTIMEOUT 6 +#define AKV_CONFIG_RETRYWAIT 7 + +#endif /* __msodbcsql_h__ */ + diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h index 45798b86..15113d85 100644 --- a/source/shared/typedefs_for_linux.h +++ b/source/shared/typedefs_for_linux.h @@ -95,9 +95,6 @@ typedef struct _SYSTEMTIME { typedef HINSTANCE HMODULE; /* HMODULEs can be used in place of HINSTANCEs */ -//From sqlext.h -#define SQL_GUID (-11) - size_t mplat_wcslen( const WCHAR * ); #endif // __linux_typedefs__ diff --git a/source/shared/version.h b/source/shared/version.h index c6c5a0c9..88c0c15a 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -24,14 +24,28 @@ #define SQLVERSION_MAJOR 4 #define SQLVERSION_MINOR 1 -#define SQLVERSION_RELEASE 6 +#define SQLVERSION_PATCH 7 #define SQLVERSION_BUILD 0 -#define VER_FILEVERSION_STR STRINGIFY( SQLVERSION_MAJOR ) "." STRINGIFY( SQLVERSION_MINOR ) "." STRINGIFY( SQLVERSION_RELEASE ) "." STRINGIFY( SQLVERSION_BUILD ) -#define _FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_RELEASE,SQLVERSION_BUILD -#define PHP_SQLSRV_VERSION STRINGIFY( SQLVERSION_MAJOR ) "." STRINGIFY( SQLVERSION_MINOR ) "." STRINGIFY( SQLVERSION_RELEASE ) +// Semantic versioning pre-release, for stable releases should be empty +#define SEMVER_PRERELEASE "preview" +// Semantic versioning build metadata +#define SEMVER_BUILDMETA + +#if SQLVERSION_BUILD > 0 +#undef SEMVER_BUILDMETA +#define SEMVER_BUILDMETA "+" STRINGIFY( SQLVERSION_BUILD ) +#endif + +// Main version +#define VER_APIVERSION_STR STRINGIFY( SQLVERSION_MAJOR ) "." STRINGIFY( SQLVERSION_MINOR ) "." STRINGIFY( SQLVERSION_PATCH ) + +// Remove "-" if SEMVER_PRERELEASE is empty (for stable releases) +#define VER_FILEVERSION_STR VER_APIVERSION_STR "-" SEMVER_PRERELEASE SEMVER_BUILDMETA +#define _FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_PATCH,SQLVERSION_BUILD + +// PECL package version macros (can't have '-' or '+') +#define PHP_SQLSRV_VERSION VER_APIVERSION_STR SEMVER_PRERELEASE #define PHP_PDO_SQLSRV_VERSION PHP_SQLSRV_VERSION #endif // VERSION_H - - diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 59a2479f..51096cfb 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -143,7 +143,7 @@ int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ); // constants for parameters used by process_params function(s) int ss_sqlsrv_conn::descriptor; -char* ss_sqlsrv_conn::resource_name = static_cast("ss_sqlsrv_conn"); +const char* ss_sqlsrv_conn::resource_name = "ss_sqlsrv_conn"; // connection specific parameter proccessing. Use the generic function specialised to return a connection // resource. diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 5f047f4b..65f91860 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -82,7 +82,7 @@ ZEND_BEGIN_ARG_INFO_EX( sqlsrv_connect_arginfo, 0, 0, 1 ) ZEND_ARG_ARRAY_INFO( 0, connection_info, 0 ) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_errors_arginfo, 0, 1, 0 ) +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_errors_arginfo, 0, 0, 0 ) ZEND_ARG_INFO( 0, errors_and_or_warnings ) ZEND_END_ARG_INFO() @@ -94,14 +94,14 @@ ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_arginfo, 0, 0, 1 ) ZEND_ARG_INFO( 0, stmt ) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 1, 1 ) +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 0, 1 ) ZEND_ARG_INFO( 0, stmt ) ZEND_ARG_INFO( 0, fetch_type ) ZEND_ARG_INFO( 0, row ) ZEND_ARG_INFO( 0, offset ) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 1, 1 ) +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 0, 1 ) ZEND_ARG_INFO( 0, stmt ) ZEND_ARG_INFO( 0, class_name ) ZEND_ARG_INFO( 0, ctor_params ) @@ -109,7 +109,7 @@ ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 1, 1 ) ZEND_ARG_INFO( 0, offset ) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_field_metadata_arginfo, 0, 1, 1 ) +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_field_metadata_arginfo, 0, 0, 1 ) ZEND_ARG_INFO( 0, stmt ) ZEND_END_ARG_INFO() @@ -121,7 +121,7 @@ ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_config_arginfo, 0, 0, 1 ) ZEND_ARG_INFO( 0, setting ) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 1, 2 ) +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 0, 2 ) ZEND_ARG_INFO( 0, stmt ) ZEND_ARG_INFO( 0, field_index ) ZEND_ARG_INFO( 0, get_as_type ) @@ -143,14 +143,14 @@ ZEND_BEGIN_ARG_INFO( sqlsrv_num_rows_arginfo, 0 ) ZEND_ARG_INFO( 0, stmt ) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 1, 2 ) +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 0, 2 ) ZEND_ARG_INFO( 0, conn ) ZEND_ARG_INFO( 0, tsql ) ZEND_ARG_INFO( 0, params ) ZEND_ARG_INFO( 0, options ) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_query_arginfo, 0, 1, 2 ) +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_query_arginfo, 0, 0, 2 ) ZEND_ARG_INFO( 0, conn ) ZEND_ARG_INFO( 0, tsql ) ZEND_ARG_INFO( 0, params ) diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 043a108b..71328a19 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -134,7 +134,7 @@ struct ss_sqlsrv_conn : sqlsrv_conn bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls // static variables used in process_params - static char* resource_name; // char because const char forces casting all over the place. Just easier to leave it char here. + static const char* resource_name; static int descriptor; // initialize with default values @@ -184,7 +184,7 @@ struct ss_sqlsrv_stmt : public sqlsrv_stmt { int fetch_fields_count; // static variables used in process_params - static char* resource_name; // char because const char forces casting all over the place in ODBC functions + static const char* resource_name; static int descriptor; }; @@ -490,7 +490,7 @@ namespace ss { } }; - inline void zend_register_resource(_Out_ zval& rsrc_result, void* rsrc_pointer, int rsrc_type, char* rsrc_name TSRMLS_DC) + inline void zend_register_resource(_Out_ zval& rsrc_result, void* rsrc_pointer, int rsrc_type, const char* rsrc_name TSRMLS_DC) { int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE, diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 7834aa83..d962d6d1 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -28,7 +28,7 @@ // // our resource descriptor assigned in minit int ss_sqlsrv_stmt::descriptor = 0; -char* ss_sqlsrv_stmt::resource_name = static_cast("ss_sqlsrv_stmt"); // not const for a reason. see sqlsrv_stmt in php_sqlsrv.h +const char* ss_sqlsrv_stmt::resource_name = "ss_sqlsrv_stmt"; namespace { @@ -2028,11 +2028,10 @@ void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ul decimal_digits = 0; } - // if the user for some reason provides an output parameter with a null phptype and a specified + // if the user for some reason provides an inout / output parameter with a null phptype and a specified // sql server type, infer the php type from the sql server type. - if( direction == SQL_PARAM_OUTPUT && php_type_param_was_null && !sql_type_param_was_null ) { + if( direction != SQL_PARAM_INPUT && php_type_param_was_null && !sql_type_param_was_null ) { - int encoding; sqlsrv_phptype sqlsrv_phptype; sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, (SQLUINTEGER)column_size, true ); @@ -2043,7 +2042,7 @@ void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ul "validated sql type and column_size" ); php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); - encoding = sqlsrv_phptype.typeinfo.encoding; + encoding = static_cast( sqlsrv_phptype.typeinfo.encoding ); } // verify that the parameter is a valid output param type diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index 5d148c59..0e20602d 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -43,8 +43,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US //Version VS_VERSION_INFO VERSIONINFO - FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD - PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_RELEASE,0 + FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH, SQLVERSION_BUILD + PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG @@ -62,12 +62,12 @@ BEGIN VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright © 2001-2016 The PHP Group. All rights reserved.\0" VALUE "CompanyName", "Microsoft Corp.\0" VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server\0" - VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD) + VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH, SQLVERSION_BUILD) VALUE "InternalName", FILE_NAME "\0" VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" VALUE "OriginalFilename", FILE_NAME "\0" VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" - VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE) + VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH) VALUE "URL", "http://www.microsoft.com\0" END END diff --git a/test/pdo_sqlsrv/pdo_002_connect_app.phpt b/test/pdo_sqlsrv/pdo_002_connect_app.phpt index fa94dcae..717a5020 100644 --- a/test/pdo_sqlsrv/pdo_002_connect_app.phpt +++ b/test/pdo_sqlsrv/pdo_002_connect_app.phpt @@ -1,34 +1,34 @@ ---TEST-- -Connection option APP name unicode ---SKIPIF-- ---FILE-- -query($query); -while ( $row = $stmt->fetch(PDO::FETCH_NUM) ){ - echo $row[0]."\n"; -} - -$stmt = $conn->query($query); -while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ){ - echo $row['']."\n"; -} - -// Free the connection -$conn=null; -echo "Done" -?> - ---EXPECTREGEX-- -APP_PoP_银河 -APP_PoP_银河 -Done +--TEST-- +Connection option APP name unicode +--SKIPIF-- +--FILE-- +query($query); +while ( $row = $stmt->fetch(PDO::FETCH_NUM) ){ + echo $row[0]."\n"; +} + +$stmt = $conn->query($query); +while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ){ + echo $row['']."\n"; +} + +// Free the connection +$conn=null; +echo "Done" +?> + +--EXPECTREGEX-- +APP_PoP_银河 +APP_PoP_银河 +Done diff --git a/test/pdo_sqlsrv/pdo_011_quote.phpt b/test/pdo_sqlsrv/pdo_011_quote.phpt index 46cd9b28..04d791fd 100644 --- a/test/pdo_sqlsrv/pdo_011_quote.phpt +++ b/test/pdo_sqlsrv/pdo_011_quote.phpt @@ -1,55 +1,55 @@ ---TEST-- -Insert with quoted parameters ---SKIPIF-- - ---FILE-- -quote( $param ); - -// CREATE database -$conn->query("CREATE DATABASE ". $dbName) ?: die(); - -// Create table -$query = "CREATE TABLE ".$tableName." (col1 VARCHAR(10), col2 VARCHAR(20))"; -$stmt = $conn->query($query); -if( $stmt === false ) { die(); } - -// Inserd data -$query = "INSERT INTO $tableName VALUES( ?, '1' )"; -$stmt = $conn->prepare( $query ); -$stmt->execute(array($param)); - -// Inserd data -$query = "INSERT INTO $tableName VALUES( ?, ? )"; -$stmt = $conn->prepare( $query ); -$stmt->execute(array($param, $param2)); - -// Query -$query = "SELECT * FROM $tableName"; -$stmt = $conn->query($query); -while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ - print_r( $row['col1'] ." was inserted\n" ); -} - -// Revert the inserts -$query = "delete from $tableName where col1 = ?"; -$stmt = $conn->prepare( $query ); -$stmt->execute(array($param)); - -// DROP database -$conn->query("DROP DATABASE ". $dbName) ?: die(); - -//free the statement and connection -$stmt=null; -$conn=null; -?> ---EXPECT-- -a ' g was inserted -a ' g was inserted - +--TEST-- +Insert with quoted parameters +--SKIPIF-- + +--FILE-- +quote( $param ); + +// CREATE database +$conn->query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$query = "CREATE TABLE ".$tableName." (col1 VARCHAR(10), col2 VARCHAR(20))"; +$stmt = $conn->query($query); +if( $stmt === false ) { die(); } + +// Inserd data +$query = "INSERT INTO $tableName VALUES( ?, '1' )"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param)); + +// Inserd data +$query = "INSERT INTO $tableName VALUES( ?, ? )"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param, $param2)); + +// Query +$query = "SELECT * FROM $tableName"; +$stmt = $conn->query($query); +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print_r( $row['col1'] ." was inserted\n" ); +} + +// Revert the inserts +$query = "delete from $tableName where col1 = ?"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param)); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +//free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +a ' g was inserted +a ' g was inserted + diff --git a/test/pdo_sqlsrv/pdo_013_row_count.phpt b/test/pdo_sqlsrv/pdo_013_row_count.phpt index fb27996c..1aad0a2c 100644 --- a/test/pdo_sqlsrv/pdo_013_row_count.phpt +++ b/test/pdo_sqlsrv/pdo_013_row_count.phpt @@ -1,60 +1,60 @@ ---TEST-- -Number of rows in a result set ---SKIPIF-- ---FILE-- -query("CREATE DATABASE ". $dbName) ?: die(); - -// Create table -$stmt = $conn->query("CREATE TABLE ".$tableName." (c1 VARCHAR(32))"); -$stmt=null; - -// Insert data -$query = "INSERT INTO ".$tableName." VALUES ('Salmon'),('Butterfish'),('Cod'),('NULL'),('Crab')"; -$stmt = $conn->query($query); -$res[] = $stmt->rowCount(); - -// Update data -$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='Cod'"; -$stmt = $conn->query($query); -$res[] = $stmt->rowCount(); - -// Update data -$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='NULL'"; -$stmt = $conn->query($query); -$res[] = $stmt->rowCount(); - -// Update data -$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='NO_NAME'"; -$stmt = $conn->query($query); -$res[] = $stmt->rowCount(); - -// Update data -$query = "UPDATE ".$tableName." SET c1='N/A'"; -$stmt = $conn->query($query); -$res[] = $stmt->rowCount(); - -print_r($res); - -// DROP database -$conn->query("DROP DATABASE ". $dbName) ?: die(); - -$stmt=null; -$conn=null; -print "Done" -?> ---EXPECT-- -Array -( - [0] => 5 - [1] => 1 - [2] => 1 - [3] => 0 - [4] => 5 -) -Done +--TEST-- +Number of rows in a result set +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$stmt = $conn->query("CREATE TABLE ".$tableName." (c1 VARCHAR(32))"); +$stmt=null; + +// Insert data +$query = "INSERT INTO ".$tableName." VALUES ('Salmon'),('Butterfish'),('Cod'),('NULL'),('Crab')"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='Cod'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='NULL'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='NO_NAME'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='N/A'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +print_r($res); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +$stmt=null; +$conn=null; +print "Done" +?> +--EXPECT-- +Array +( + [0] => 5 + [1] => 1 + [2] => 1 + [3] => 0 + [4] => 5 +) +Done diff --git a/test/pdo_sqlsrv/pdo_023.phpt b/test/pdo_sqlsrv/pdo_023.phpt index c9d634db..5c213705 100644 --- a/test/pdo_sqlsrv/pdo_023.phpt +++ b/test/pdo_sqlsrv/pdo_023.phpt @@ -3,7 +3,6 @@ Bind values with PDO::PARAM_BOOL, enable/disable fetch numeric type attribute --SKIPIF-- --FILE-- query($sql); + + // Insert data using bind parameters + $sql = "INSERT INTO $tableName VALUES (?)"; + $stmt = $conn->prepare($sql); + $message = "This is to test github issue 35."; + $value = base64_encode($message); + + $stmt->setAttribute(constant('PDO::SQLSRV_ATTR_ENCODING'), PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(1, $value, PDO::PARAM_LOB); + $result = $stmt->execute(); + + // fetch it back + $stmt = $conn->prepare("SELECT Value FROM $tableName"); + $stmt->bindColumn('Value', $val1, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->execute(); + $stmt->fetch(PDO::FETCH_BOUND); + var_dump($val1 === $value); + + $stmt = $conn->query("DROP TABLE $tableName"); + + // Close connection + $stmt = null; + $conn = null; +} + +test(); +print "Done"; +?> +--EXPECT-- +bool(true) +Done + diff --git a/test/pdo_sqlsrv/pdo_069_fetch_empty_nvarchar_buffered.phpt b/test/pdo_sqlsrv/pdo_069_fetch_empty_nvarchar_buffered.phpt new file mode 100644 index 00000000..b650dc54 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_069_fetch_empty_nvarchar_buffered.phpt @@ -0,0 +1,38 @@ +--TEST-- +GitHub issue #69 - fetching an empty nvarchar using client buffer +--SKIPIF-- +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + +$sql = "EXEC dbo.sp_executesql +N'DECLARE @x nvarchar(max) +SET @x = '''' -- empty string +SELECT @x AS [Empty_Nvarchar_Max]'"; + +$stmt = $conn->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); + +$return = $stmt->fetchAll( PDO::FETCH_ASSOC ); +print_r($return); + +// Free the statement and connection resources. +$stmt = null; +$conn = null; + +print "Done"; +?> +--EXPECT-- +Array +( + [0] => Array + ( + [Empty_Nvarchar_Max] => + ) + +) +Done \ No newline at end of file diff --git a/test/pdo_sqlsrv/pdo_267_closeCursor.phpt b/test/pdo_sqlsrv/pdo_267_closeCursor.phpt new file mode 100644 index 00000000..b49bdf20 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_267_closeCursor.phpt @@ -0,0 +1,70 @@ +--TEST-- +Test closeCursor with a stmt before/after execute and fetch. +--SKIPIF-- +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + + // prepare a stmt but don't execute, then closeCursor. + $stmt = $conn->prepare("select 123 as 'IntCol'"); + $ret = $stmt->closeCursor(); + var_dump($ret); + $ret = $stmt->closeCursor(); + var_dump($ret); + + // prepare a stmt and execute, then closeCursor. + $stmt = $conn->prepare("select 123 as 'IntCol'"); + $stmt->execute(); + $ret = $stmt->closeCursor(); + var_dump($ret); + $ret = $stmt->closeCursor(); + var_dump($ret); + + + // use two stmt, execute, and fetch, then closeCursor. + // use one with client side buffering. + $stmt1 = $conn->query("select 123 as 'IntCol'"); + $stmt2 = $conn->prepare("select 'abc' as 'Charcol'", array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $result = $stmt1->fetch(PDO::FETCH_NUM); + print_r($result[0]); + echo "\n"; + $ret = $stmt1->closeCursor(); + var_dump($ret); + $stmt2->execute(); + $result = $stmt2->fetch(PDO::FETCH_NUM); + print_r($result[0]); + echo "\n"; + $ret = $stmt2->closeCursor(); + var_dump($ret); + + $stmt1 = null; + $stmt2 = null; + $stmt = null; + $conn = null; + +} + +catch( PDOException $e ) { + var_dump($e); + exit; +} + +print "Done"; +?> + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +123 +bool(true) +abc +bool(true) +Done \ No newline at end of file diff --git a/test/pdo_sqlsrv/pdo_270_fetch_binary.phpt b/test/pdo_sqlsrv/pdo_270_fetch_binary.phpt new file mode 100644 index 00000000..5edebcce --- /dev/null +++ b/test/pdo_sqlsrv/pdo_270_fetch_binary.phpt @@ -0,0 +1,92 @@ +--TEST-- +Test fetch from binary, varbinary, varbinary(max), image columns, without setting binary encoding. +--DESCRIPTION-- +Verifies GitHub issue 270 is fixed, users could not retrieve the data as inserted in binary columns without setting the binary encoding either on stmt or using bindCoulmn encoding. +This test verifies that the data inserted in binary columns can be retrieved using fetch, fetchColumn, fetchObject, and fetchAll functions. + +--FILE-- +exec($sql); + +$icon = base64_decode("This is some text to test retrieving from binary type columns"); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName($columns[0], $columns[1], $columns[2], $columns[3]) VALUES(?, ?, ?, ?)"; +$stmt = $conn->prepare($sql); +$stmt->bindParam(1, $icon, PDO::PARAM_LOB, null, PDO::SQLSRV_ENCODING_BINARY); +$stmt->bindParam(2, $icon, PDO::PARAM_LOB, null, PDO::SQLSRV_ENCODING_BINARY); +$stmt->bindParam(3, $icon, PDO::PARAM_LOB, null, PDO::SQLSRV_ENCODING_BINARY); +$stmt->bindParam(4, $icon, PDO::PARAM_LOB, null, PDO::SQLSRV_ENCODING_BINARY); +$stmt->execute(); + +// loop through each column in the table +foreach ($columns as $col){ + test_fetch($conn, $tableName, $col, $icon); +} +// DROP table +$conn->query("DROP TABLE $tableName") ?: die(); + +//free statement and connection +$stmt = null; +$conn = null; + +print_r("Test finished successfully"); + +//calls various fetch methods +function test_fetch($conn, $tableName, $columnName, $input){ + + $len = strlen($input); + $result = ""; + $sql = "SELECT $columnName from $tableName"; + + $stmt = $conn->query($sql); + $stmt->bindColumn(1, $result, PDO::PARAM_LOB); + $stmt->fetch(PDO::FETCH_BOUND); + //binary is fixed size, to evaluate output, compare it using strncmp + if( strncmp($result, $input, $len) !== 0){ + print_r("\nRetrieving using bindColumn failed"); + } + + $result = ""; + $stmt = $conn->query($sql); + $stmt->bindColumn(1, $result, PDO::PARAM_LOB, 0 , PDO::SQLSRV_ENCODING_BINARY); + $stmt->fetch(PDO::FETCH_BOUND); + if( strncmp($result, $input, $len) !== 0){ + print_r("\nRetrieving using bindColumn with encoding set failed"); + } + + $result = ""; + $stmt = $conn->query($sql); + $result = $stmt->fetchColumn(); + if( strncmp($result, $input, $len) !== 0){ + print_r("\nRetrieving using fetchColumn failed"); + } + + $result = ""; + $stmt = $conn->query($sql); + $result = $stmt->fetchObject(); + if( strncmp($result->$columnName, $input, $len) !== 0){ + print_r("\nRetrieving using fetchObject failed"); + } + + $result = ""; + $stmt = $conn->query($sql); + $result = $stmt->fetchAll( PDO::FETCH_COLUMN ); + if( strncmp($result[0], $input, $len) !== 0){ + print_r("\nRetrieving using fetchAll failed"); + } +} + +?> +--EXPECT-- +Test finished successfully diff --git a/test/pdo_sqlsrv/pdo_308_empty_output_param.phpt b/test/pdo_sqlsrv/pdo_308_empty_output_param.phpt new file mode 100644 index 00000000..80984d18 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_308_empty_output_param.phpt @@ -0,0 +1,48 @@ +--TEST-- +GitHub issue #308 - empty string set to output parameter on stored procedure +--DESCRIPTION-- +Verifies GitHub issue 308 is fixed, empty string returned as output parameter will remain an empty string. +--SKIPIF-- +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + +$procName = GetTempProcName(); + +$sql = "CREATE PROCEDURE $procName @TEST VARCHAR(200)='' OUTPUT +AS BEGIN +SET NOCOUNT ON; +SET @TEST=''; +SELECT HELLO_WORLD_COLUMN='THIS IS A COLUMN IN A SINGLE DATASET'; +END"; +$stmt = $conn->exec($sql); + +$sql = "EXEC $procName @Test = :Test"; +$stmt = $conn->prepare($sql); +$out = ''; +$stmt->bindParam(':Test', $out, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 200); +$stmt->execute(); + +$result = $stmt->fetchAll(); +$stmt->closeCursor(); + +echo "OUT value: "; +var_dump($out); + +// Free the statement and connection resources. +$stmt = null; +$conn = null; + +print "Done"; +?> +--EXPECT-- +OUT value: string(0) "" +Done \ No newline at end of file diff --git a/test/pdo_sqlsrv/pdo_connection_quote.phpt b/test/pdo_sqlsrv/pdo_connection_quote.phpt new file mode 100644 index 00000000..b05d6a32 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_connection_quote.phpt @@ -0,0 +1,74 @@ +--TEST-- +testing the quote method with different inputs and then test with a empty query +--SKIPIF-- + +--FILE-- +quote("1'2'3'4'5'6'7'8", PDO::PARAM_INT); + var_dump($output1); + + $output2 = $conn->quote("{ABCD}'{EFGH}", PDO::PARAM_STR); + var_dump($output2); + + $output3 = $conn->quote("The quick brown fox jumps over the lazy dog0123456789"); + var_dump($output3); + + $stmt = $conn->query(""); + if ($stmt != false) + { + echo("Empty query was expected to fail!\n"); + } + + $stmt1 = $conn->prepare($output2); + $result = $stmt1->execute(); + if ($result != false) + { + echo("This query was expected to fail!\n"); + } + $stmt1 = null; + + $stmt2 = $conn->query($output3); + if ($stmt2 != false) + { + echo("This query was expected to fail!\n"); + } + + $conn = null; +} + +function Repro() +{ + StartTest("pdo_connection_quote"); + try + { + Quote(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("pdo_connection_quote"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'pdo_connection_quote' test... +string(24) "'1''2''3''4''5''6''7''8'" +string(16) "'{ABCD}''{EFGH}'" +string(118) "'The quick brown fox jumps over the lazy dog0123456789'" + +Done +...Test 'pdo_connection_quote' completed successfully. diff --git a/test/pdo_sqlsrv/pdo_fetch_bindcolumn_fetchmode.phpt b/test/pdo_sqlsrv/pdo_fetch_bindcolumn_fetchmode.phpt new file mode 100644 index 00000000..e1d07324 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_bindcolumn_fetchmode.phpt @@ -0,0 +1,128 @@ +--TEST-- +fetch columns using fetch mode and different ways of binding columns +--SKIPIF-- + +--FILE-- +exec("CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_nchar] nchar(512), [c16_nvarchar] nvarchar(512), [c17_nvarchar_max] nvarchar(max), [c18_text] text, [c19_ntext] ntext, [c20_binary] binary(512), [c21_varbinary] varbinary(512), [c22_varbinary_max] varbinary(max), [c23_image] image, [c24_uniqueidentifier] uniqueidentifier, [c25_datetime] datetime, [c26_smalldatetime] smalldatetime, [c27_timestamp] timestamp, [c28_xml] xml, [c29_time] time, [c30_date] date, [c31_datetime2] datetime2, [c32_datetimeoffset] datetimeoffset)"); + + $numRows = 0; + $query = GetQuery($tableName, ++$numRows); + $stmt = $conn->query($query); + + $query = GetQuery($tableName, ++$numRows); + $stmt = $conn->query($query); + + $cols = array_fill(0, 32, null); + $stmt = $conn->prepare("SELECT * FROM $tableName ORDER BY c27_timestamp"); + $result = $stmt->execute(); + $stmt->bindColumn('c1_int', $cols[0]); + $stmt->bindColumn('c2_tinyint', $cols[1]); + $stmt->bindColumn(3, $cols[2]); + $stmt->bindColumn(4, $cols[3]); + $stmt->bindColumn(5, $cols[4]); + $stmt->bindColumn(6, $cols[5]); + $stmt->bindColumn(7, $cols[6]); + $stmt->bindColumn(8, $cols[7]); + $stmt->bindColumn('c9_numeric', $cols[8]); + $stmt->bindColumn('c10_money', $cols[9]); + $stmt->bindColumn('c11_smallmoney', $cols[10]); + $stmt->bindColumn('c12_char', $cols[11]); + $stmt->bindColumn(13, $cols[12]); + $stmt->bindColumn('c14_varchar_max', $cols[13]); + $stmt->bindColumn(15, $cols[14]); + $stmt->bindColumn('c16_nvarchar', $cols[15]); + $stmt->bindColumn(17, $cols[16]); + $stmt->bindColumn(18, $cols[17]); + $stmt->bindColumn(19, $cols[18]); + $stmt->bindColumn('c20_binary', $cols[19]); + $stmt->bindColumn(21, $cols[20]); + $stmt->bindColumn('c22_varbinary_max', $cols[21]); + $stmt->bindColumn(23, $cols[22]); + $stmt->bindColumn(24, $cols[23]); + $stmt->bindColumn(25, $cols[24]); + $stmt->bindColumn(26, $cols[25]); + $stmt->bindColumn('c27_timestamp', $cols[26]); + $stmt->bindColumn('c28_xml', $cols[27]); + $stmt->bindColumn(29, $cols[28]); + $stmt->bindColumn(30, $cols[29]); + $stmt->bindColumn(31, $cols[30]); + $stmt->bindColumn('c32_datetimeoffset', $cols[31]); + + $numFields = $stmt->columnCount(); + + $i = 0; + while ($row = $stmt->fetch(PDO::FETCH_BOUND)) + { + echo "Comparing data in row " . ++$i . "\n"; + $query = GetQuery($tableName, $i); + $dataArray = InsertDataToArray($stmt, $query, $numFields, $i); + $j = 0; + foreach ($cols as $value) + { + CompareData($stmt, $i, $j, $value, $dataArray[$j]); + $j++; + } + } + $stmt = null; + $conn = null; +} + +function GetQuery($tableName, $index) +{ + $query = ""; + switch ($index) + { + case 1: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((1448461366), (110), (-28270), (804279686), (0), (0), (null), (-100000000000000000000000), (0.6685), (0.2997), (0.5352), ('äðubý/ö*bUah¢AoÖrZÃ_oßoüöÐ>ßÄßUAüîÖh_u*uh.uå:,öî@UCO,<¢<:Ö@>+ß,ªåÜbrª¢öãäo,ü£/b,|ýãý~öߣîUö_¢ªðu.+ýÃhAaäzvzrb£ßAÃhö,ö.aöü/Z+Ã.uvUo~v:+u_ýý©z¢ª|U/îã<©|vý+bÐÄЩoðbbüðb_~*î..üÐÃz,äAðß~Ö¢Äå~ð.£_ßzãÖv~¢£Oå*@|UozU©Ð+ãÄÐ,*Z/vA>ªOÄ,¢bhý/ÖÖuäAoü.a@@ßaðvbaߣ@v,ub+Oä@oBBÖöAüßö|Ö~hhvbuäo/<Ã+£¢Ã¢ß>'), ('Z:Uî/ÜãýüÄzãüvä/Ühý£||ãoå,ªÜ©uÖ_.>ßýbåää|üð/ý.BO:ZCu©ß<£ªãÄ@ýß©vöß:>:ä+åvCBª£.o>Z/*,B_å~AO,rO+åÖZ£>rö¢Ð~ðuö_Ðä'), ('ur|hÄðu*Zýðü_ÃÄÖ:ä~*äorO|üh*vaªOðäv¢<|OouÃbä.ßoßU.zZ>,ýra¢boýv|_Ää+ÐaOöC@h_::zBýßOCÐüß+Üu£/üö+.oßã.~|AbOÐåî@Z.buu£,CªãîvUbv,ÖüOoÖ,£ªî@/ÜBðaBåã<ß~OÖZüZ|ýöhz<ÜÄ:ÖÃÐÜðÜ<*Äu¢a©ßß..¢:ß|rîr:ߣ©Üröa.>U.hb|bCäbb+ZîoCÐðäzðå_ß_*CCß~>vªªo~/v*b.©Äoßãhî>_AZbÃ>¢ÐªªÐ/ðzv|ßÖÄЪÄ<~ßðUO+ýÄOr£v_rrðC|äß©¢£<+/¢Ab:Oîß~ÄZÖU/+¢å_uZAÖ*ªã~OÐîßüo<åÜÖü£ÖAÐ:ü>ðOåh@rÜb.oz©v/UäýoðÜÖCZ+©ª©Ußr|~ÜãéÄAuÃAîÖZ~B>_,åA~_OÖßî@<ð|@vr¢Ðå/ÖªhöðUÖä,ýß|BCåðoýb~BîÜO~zh@£|ÃãrbßßÐb£Z:î/îÐðß:£+A¢ÃZzãßü,¢Buußzö+azbOB*Ühr<ä<©>ÄäZ@boîÖöüÜÃaOr©ªªß|ßC~oðvîoC+ü@ÐC.,özUß:v+B//*ªð_åUÃBÃ~ZC~ÜUÖ£~avöåÜãÐZ+ýC_Ö>Ö@özU~>Äã£:ßBßÐ<_Ä¢ÜBh<:ãbߪîÃühãß,bßö>bBZ:Z:öÖð|å:/ðü,ªA@>oÜîßhÃZýZ/££ªr|ÃßÄOýåzåAAu|uä/ßAzZ¢oö<>îuär+Zªåä@ªãåäÜîCbC䣢*CåUBãªãZåÐzCßZÖü_ßO|bÖ:|U.ãC~abÐBU|ãð£aÖÜ_/äCO>öªÜäÄðv|Bã*>ðZÃÐ*ÐåÖB|öãöåhÄã,_ÃvÃ/ßß/ß/£o~hÃÄAÜ©aZ.AÃåÃzýå/aoîÐhäb_îýÃðð䣪¢Cb¢Ar:Ðh<å,|b©©CßZªz@,@УBbäbaöåÜÜ*ruü£OÜÄO/üð@îv~Ã|bvCÐ|î,B.åB<ö@Üvz<îã©.|ßrBÖЪh~>*.ã@©¢£*ª_ßî¢Ð>ªvª,~Öß@@Oß*BOOöA_¢AªðßäªåaB~ÖABhbääbCÃ_Ü¢A>>vª¢,zBBahåÃ>ÐÜÃÖÐðÜhÄrb*zåðãbUýåZ,*v,ÄU£öbýoO,**ýßbÃv+Üb|Zb:OUöîåßO*:/,'), (null), (null), (N'råhð_~ãZOZ¢öªUÄb£ß:ÄCBv.raî~.bahohåÃhhð¢.©ÐªouÖOÄZ,äÃ>@BªÖÄ_©UåhZ>ß:,Að>åßZ<,ÃuýUÄåC©h:©£Üb.:.öoÜ~ZÃ/ZßÃ,îýärbÐð@Ðvö,ÖåhÃß.||üîrÖÜo~:~boåÄCýÜaBh¢zýb+ð_üoZbOaÄý<©u|ߣ*+å+©ÄUðU@Ä<îð©~,uC¢ð|ãö~a.oo£u©ðr@CBÄÃuZAðß*ÄCßzîZa/oÐÜ_öß©zbܪvA|Ä.|<©_î*å¢AªäåªäU*uUÖ<ÖªoðaðrUz@Zo/,Ü/£<@ßão>*ðÜÄu@vAßßu©özÐ@ýh¢z_ZÄ@*OÐßCðö@ÃräüArz>h©~vbåªbã<ÄvßoåvAß+v>vÐÜßrU/ÐhßßðÐãbÐBhr~üîÐýhÃÖªã:zvývz©r@ßBåö+o~ãrz+îbr>bü<**ÃCbÖbu_aBZßß@ª*CÃa_~.Z£Ü©B|h.öä||ðÃ_îu/@,ð~ö/A+¢ÐZO*_ö|bÖÄrUãß_ãbäBÖߢörßÄ~AîÃ*£Öð/CÖ_a¢+¢ßOAoê.Öå~ðªßB*ý_ã+_ob,~@î+/b_ã>,OãÖo¢Aý£rüåÖCzü>_,Ä+Ü/~ªÃB|hu|ðz:Z+,*~£zÖöOãý.AðBCî@_h©>U@hÄOz.©A~zßü~oöÐ.¢ßUÖ¢vb£AC£.ÜZ£ähß/öh/|_ß.buö_öößh@ãU>åä£|ZvA£ßaOC:+@ßUOvÄöurãÃ@oâ¢ßߢ£ð~Z~hv@o~bbãbª:ýîz~_|£©ßåßC*aru,@ß/ö@©barÜ©hÜã/~U£Ð>uv|zÜý@AA+_B+<Üo/ÖoaAöÃäb¢ZzB>O.Öo@O©ÜZZ./Ãa¢bßîb@ð@a<:v~ÄUoÃOð_oC*rü~@£:墪vÃuýã.Uª~Ã.vÄuªö@hªu>hã>Ð~ðvzZö*ü>Ðb>Ðý|hZZ.BhãÐvU<ߪz¢v/äUÄ:hÜã<ÖãbÖýbãhÖ©ä:££>vBîü¢ovZhOã:öC.©ÄUÖ:h/@>za:Är+uzã+ßå|ß,ð~ãª>AaÄ@B¢,£Ð|ü@:ý£ªÄ|Bßö_ª~Z@BðäãäZãA/Cr~_<ßîÃÃÃ/_*zÜö@ß+üzrýB_ÜB¢ã|a>ýåOÐAb>ßêĢabß,B<ü¢OÜ'), (0xF502D70F2F74A32894021775707AEE3D8601A0E601FF565636A220DBFE213F3B143FA70B33712EC31501D0202A6125E5EA13FCD7F33991F6AC80D88D53C82A73C3DB6130D3E20914D2DDD1002E352BD57D3AF1EA246748DBADB05FB398A16F4DD75D5D4F00F4120709E704166891C77755030F18D63F4F5C9386822283567B316D8328D0D8DCD58828E9E13C6232731CE9E85D95915676980E01BB7A), (0xB36CD3A8E468F69E792D86F0ED5E12F9611266399BF8E6A0160D90C2D6205B1638642DD08F898EB3F249E4670A66883AFB075A670CB6E9BA853292D7D834C758D270B889304269D884B24751147E95B08456C6CFC6F40A817B734A5CF7B6DBBD818C959AADFF09B99D82E2596F97A6079CE153816DF892DE65370DBDF80DE0CDD689D087E9FB03844C0D314311B012E3CC43BF15635A4F88FAB63475F14CC090A11583E5C61E1DA1DECE3460C64ECDB4252AF0B54DCB697C39488D33C68D93004CA1A2FC2D2C1DAD251E379525EFC1ACE98050C75B0B42D6AB06AB7E91EADA503B331325ABD186F80C42902F94D4564986E14A463DCBA5415ECC5026809E1C3A43E65AF1DC9C0017F957BA187B1341D6AF61F8AFA09412), (0xC6DF805F786E2655EBAD7A656DE9E1324CE6118CC54A8A79771EA66D99AFD4EA630AD621979ADE4CF22528945C4AF40FCE6E482B5E010C7B5406E85AC8B9BC3CD98F016313BF9AB208C49A9F22F61D2C2A979E2A67A88C61FD5ACC91B184843969ECBBCC4890FB0ED3952399666D585EE8BC2F301A91D43003A066B9F3090083406136363A50E3999B9056D184F2FB90016EA8F554531ED7178AC9A82A421BCA95A0590536222398D896958A3A03AC10A5D5B784610B2EEC435E1FAF863B6E58758C3BE399F559DF4A0F1CBEBD949C13DEB903548A96688A716CC1D70273119D283C15AEC7C877AF7BD83A52EE702EAEC20BD1CD3719CEDE24B1722C3382DCD6FD7F95817FEE4B154254D8B25C169B003D1315F7A244650E6E368A16564A7BF9FC63546EDAAD2FD4C0BB8BE3401B820D1571EE206DE534B9198713496335BFF7D9DFB6F8C12C2590E44036E6B882CD52B37C00E45B5A8C172E9A55539AE6708AFC93096165894BE94B5E3C2CAE4E0196BAC095E735DA9B7E529FC927D7D2F6FDD0ABEA24786BED29301699FD1E6068E099BE98D950885521F88B0EC6C0241278673E5B0263DC1D4ED26BB71E409E60F236E88E), (0x94ADDFCEF48E90FEED2E4587B4401554C9C0B58BC1F60BDC5FAC801266895C178DCFCFF21C6CC699418336F7775A9C43B83D7D1E3BF6AC9EA2036E480D7E9864D12EF9E57123385DF91C4A3DBF1464050F3D5A280B3EDB9722315018FD7E8BC2EBD2254960B7D2940420241EAB6EDE8E9F6938D0236F59935262C23FD320607B918752EDEE1BA5586AC524EA1ABCEBB452C1799346BA9F7B7992F6FDF592C1E4C0A98D231CF19F84C4DF665E4B6E7995770DED11A7CC34D6E8D6745C24A863D7033F29702FC7976022F4CF665440300A196085E7A4B03DF475B73B457D425F1C7C8E5A0B440866B0768CEEB2FCCD82A7A66A43C4227B32530235FB778C51E3DF95B3EAC5E624C510F9D57117C18AF7A735939F12FF9BF9005B06080E0DEE2324A90F1D49E3BCC181A432CA4C0A1A92773E5342F8B2D0DFF9B9E96ADF1BB6030BB14C6EB26DA0CE5D50F7DCCD55A66C580B04ABE0CDD82B7FE6BF2297ADBA64DF606C88180D8069E505620B6FB4D1D345BF1DFEA213A4E6993B8C091C9E37B09592FD6CE91E534B8B9BC6DB6BCCBCCA15ECDCD78FBCC1CCBC85EFDD80764C47CEB176DCB764EA276BD272064DC97BCC7EC2F330C25F21D8D030B0632E76CA154F7E0CBA56E012B101B46BBC4ABB7FF20A225EBD821B846E909D0B), ('00000000-0000-0000-0000-000000000000'), ('2819-01-08 00:12:52.445'), ('2079-06-06 23:59:00'), ('10/31/2016 11:46:25 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/0110/31/2016 11:46:25 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/01'), ('03:46:33.6181920'), ('2148-04-25'), ('0269-03-15 01:59:43.6050438'), ('0001-01-01 12:00:00.0000000+00:00'))"; + break; + case 2: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((1824541628), (28), (32767), (-5982062), (0), (2.3), (-3.4E+38), (0.4893), (0.9114), (0.7207), (0.4408), ('£åîýÖö£büªü*ýhÐî@.+uðU,ª,ÐÐößö©ÜÃr@üvbo>ä㪪ðÃ*.üÄöäöB<ð©oã>@ãb.ßЩߣܢ:Uå+B©ß©Ã.*üBaßߪÐ~zCu©üAÜrÜrA_Ürb>¢bÐ,vä>hbOäü,aîbbb:@u~î**:a:£ä|ÜA@oÜä+Z:¢b~ßoßßÜzü>ÄÖ~vbh,bãäb@r¢BðåährÃÖaåhýCO_¢uh©,äa:UC¢¢Ö@Ö,v/z~¢öB©©züî~åUÖCb~UßvöÃÖ_.ý,zO©a/Ã*,|:BCä_zððvåãÐ@Ãð>~a<¢ãB_Cv*hzîhrð,|ª>hðÖ£ÖßU_o©ß>Ð_ã©äÖÐ*|ýªOo¢AC©î+üB/+£vßåaãaö¢_©~+z|b¢ßUöh*|>hßhäUzã.ZOO.båÐ@_Aý£@A©ßCäÐhã>rzÄ*ÖrÃhÄzÃvZOChaÐBÖ/B+ðýB'), ('.~Ã~ßüåÄÐh@ß,*ÃöuÖ>ÄüäabbbBüß*£b+.zÐýÄÖäðð,>/<ýAöü_vv<_~/>oA*vߪz:Ä¢ÜO+rÐ+z_îBhü.å@:aUzãߣÖðBv,öðrý:Ð,:_B>~_,oývC~.@Ä¢_C>O.uvî~oÖüU@ÄAuA/ý+@ÃÜ:£©<ß_äåuZ©äö|üuvOð.ß/ð>|b*,bÐUA:ÐÐÜ~ßBbZäÐöãäbÃbrÄoªvýAÄOîÐO<ß@A£/ußr£>BåBðÃü£Äa£*åAB/oÐÄb.å,äßbuîr/äåã~ubÐOb+Ð_rÄ|hð>örv>BhUªÄaZä:b:ZoÖ>zßAÄýÃ*zäézÃhÖöýh+ÜÄz£+Ä'), (''), (N'Bî|ß©¢Ãð>ZßÜã.îbÄ¢ÐÃC:hßýßýo©aB>/©ÜÜB@a@bA>ä_aäðÐZývr**O£höOªu¢bövvüðb:,aßAOBCa+Ähä|Üa©r©ÃZª+ßÃu¢<Ã>>ãö~bå<.zob@Cª<:+bzö¢bzuÜäArCß|£/@äåOZ<î_vå@¢ß<Ö*uä~oÖå/@Äßuävv@ª:b¢Ðªvbª/.*Oߢý.vååðý>â:,<>UAUa+îOÄãAÐüßüÖ*|uÄBßãª.,~¢ü,ývuß~+,h*ßð/v|UhðhaÐ+bu©,Ã.:ä¢ÜvuzäÖ@Ou¢+Or.ÜÃ_Z+v<:ªuAîb|/îöðZÄA£rüÃ>¢Öî,+OäãîÄhCB~o*ÃaZöÄüÜ*Ã:å>+h<~ªä©Ä|räãAu©ÐÖßãÐ|Äür©Öߣü~ÄðZ,<Öu|@uð:Üzb~äªoö£ovðäbaÖü@ð|©Öî>rz¢ßBð@brãz*ðaä*h/ã~ö££oîÄßüuîuÐ/|,|ý+ãÄ©uÄAÜÃßü©ª.uðz||©:î,C|ßzª~ð,£z+ß~CðÐð¢.uîäz£>aßBaßå_::ªC..:OÜ:z*u¢£ß.'), (N'bbÃ>vUOߪÖß+ß/ã*h_ßz@Or:<_ü.å+aÄOOã<Üüzårªã:öb>ð.v@v¢@CäãuAÐZßðuÐCO£ª+|orîBð*Ü>.AAaªãÖbÃbü¢|ðªª/©ªÄãåäzbÄ*bÄ.O_bУÃUß*.ýA@|¢ÐauªzÃUÐb©@oÐöå>Ã,vå:|ãZî+£*rÐß,zÃzÄC<,ÄößabC_@ã:îz£u©OoOöÄüAð@*ähäA¢O,ra|ö£|Üo,ãßåz/oh£>o@oÖ/©aZ©rý©>rv_B£©Ä|¢/Ü*CuArðÃar_<©r<~îð+å|OÄ*ª¢Üz_<öö_B./Z:ýbÐ@ý:üЪz£bÜÜrÐä~¢Ü£//¢o_v~ö|ßAZ:öZoArU,åa<Ã>ÃoÖßußß_ß|£C+:O,ßb@ªÜzßð~ã,,,Ö.üðÃãCãhzýUÜ£.£A©ÜbaBüüBÐ,*ãu.:/hboÃOêb_£Ð@+ýÃ/v_oªZ,©:ýãü<ßýîî_ߢªuüãýoa<:U:ÐÐÄî~ÄUãCÜ,ÐÃ+Ähv_Ößü_,brZÃo:Zîur|BUÜå/O©ÃÃär@Z>vaÐðÃ/å~b/oî+,*|B.UBîu/CåÐÃrz*å*.äCobÖuUOÖ_ßbbAr£:/::Öbä,Bv©ß©vÄ_ã>>UO@*OýðoâbßÄ>~äð:î©ßUßö>|:@>ßUu||ü<öbU:ãåoÖA:üzB.ßBðßÃý.|ä£|.Ãå,ävo_CðãO<ãðBAðA~C/bahZÐåüb.u¢å/rZ,oOßßü>öð<ýÖî+¢ßb/üªäu~_Z|b_¢|~.Ðß©ÖäÜz<ªbußÖCbÄÜ.|<<ª/hª£_Uðö*+î~ABÃäöZB@aßýÖÜo>+ÜraBðC+_orub|£ÐåðoBüaîýoÃbob£ZCý>£u£u*ü/ÐvÖ<~,:bzbäoª>é,+Bvã©@ªª~rªª©@@orª:Ö@~Z£bÜ~:ü.ýü|UåuêbÖoåaoä>£|*ý.îUðäÜߣÖåðh¢ou£A/£Z<ª@Ã~£~hÜ~hACüaBÄ¢,B/bärãaUãörßAÃ|+ääðä£Cä@bÜv©:ãö/ýãbZOÐ/Ab¢zOCîî.üß|O/ru©råäÄr_h¢zbßOªvOC:åÜaß©,OååãZÖaߢ/bZäÐã_>ÄäbBÄßĪ/Ö_/rå|@Ããzä>z~bA~OOîvÄbU_bßzboBüAåý<+.|_ßOå_Ãý,ZabC¢_ÄoA>O©ßÖrª©uå::ªª©o££Bbã_|_Ã@zAA>:zÐöüOÐÖߪªrZß|öÃ.,üüåªbrü¢hCuå|¢Ö/CÄ©¢>/åä@<¢uÖýÐãî£b@|УÐa~ª£|Äb~|,/UAßî_BAB¢ªÐåh,.ýî|Uå.bð¢ðßUzß/AÃ+~>ßCÖ|._£Ahvâ¢Zzüð©Zo,Bý>ÐZröA_@äߣå~ärÃãüCÄÐäUð媪ÖýÐ,@ã.Ã|rrUO~_¢ßü.ÐCÄý+ã+_öb_ã@zZb,A:ä<öÐ@äUå¢bCª¢ÖßrzrÖbBB*_ý¢CöÖ<ã<ðBÃCýä¢ö*ÜC/Öb:~ü<ý/|uÐ,.UÐ_u_rb©oý*Ð+Övª~ªroÃî~îü@+üA©+öaý+/Or+_baC,.Oao/z*Ä|Zzý¢£UaäB£@¢,Ä,.ä.,UÃbÃÜO>Ü@O_Ð*©.Щ+ß|ö*Ãý,¢ÜA>¢Ö~îö<år£r:h>@©|îOüýüÐbAä:îýBzo.<Äð©:ýbü©ãЪ:ÜaÐ.Ü壪.ÄB+ÐUußß,£A_brßå¢@>ð:>z/u/ª@OzðCåbýãzÄÐãOä:B~îÐßö_h+hÃürÜð@ã£Z¢bÐî©ýrü|ðÜ©Öªbrä*Äß~©O|ýrBÜb,rîOîu,,©UЪüüuª>ö+o~boCäÄüîOð:î/îoÐå<ä,©ãrBýOªÃöü>vТ>zAß*OUÐåbC©CZöu/Ooß/äÃbC©Cr£OÖCÖ/ö©©bü_üZª£,¢öߢî/åZB/ßö©ª.@ýöa/ÄÃ:Ö/:¢h©©oÄ_¢îö£¢~Ü'), (0x86C692589A736BD5D7741E6D8EDC33FC5A4F6C421A5C4C55BD6451787A0876B28E0BBB3043DA32E3D11102C09DAF140B4A7C978D0906B22A793D9B3521F35ACFFD6CDE822103B87F1C897108598BB70E4F452DFE70E9A8885990B6063FFCC1DEB733C230D092EA47C417708094A9D0EE858DE6DCC55B5E14C45629914CB14020C8925126C8873DBC5BE63A597927F3B0C0C881B215E5195BD4AE6C7A6958FA12D74FB80257A925FD4F2980DE21059B9C6278D084308D0AE279F5521A1AC35302EB2781296FF15816A28362EB643A39CE12D017F08876E14A44D589554060CC000EC08828B7), (0x8EBA1C29159FDB52AF42A3AC3A50B9435455115E29EC9B7BD911), (0xC1C8537B74D6971F9B7E417596A6FEBC20C5BE0645D461D401A5A11EA15004526209ED795031C6792223B9D7D0E992469CBFB94C8E7203ED667F7ACE34787E4B7AEE128DB9856BEFFF44E3211A8A0317125B81E197B7C3F006FF12D0B6F745A9A56D5DE1D7058C1753CA4C4A8EE1D2DC7B62BCD8F69CB3C22A2186BA729B3807FF3A7CAD2D223F0DCF6A8ECC6B7962252C3655543B6CB208B99AB7B67ED2A5B892454B546425AF5922AF098C0EE757B0BE618D3B45E3CC938773F3926A898EFCC4D53A262E0EC00687FB318C6225F9D24306F71D446972AE78B8CCF1321CEE99200DC739AC3182B0A187B449B240306179204E649271B75B628CAD7641CFD36A0D986B545DC0261284477BBDB428FB37876B7712CE8A06AF556CD61F830B6ED2B34A3C19D5DB03B9F93BDF4AF5F9A63A9075EFD658C19B8291BBFEF1EA078F78D9A90A6AECE262AB67BD1892AABCB95CC69C8E0D1C977AF1AEB98819DC12CEF2AC67714CEF3C4DAE13436F9AA809D8D501A22B4E78825C412F18BB325A48CE55EE26E5434B6D6C894EE0F9D827DF48A5E1B2183AE7807A2A0A2F1DD6811B54CB751AAB8223958A9436A1FF9888841A4FCF1651A72F946D6EABBB381B509608338B2E620D33C6814C2884044580E2A28615CD066BC9AD9BE1AC3DD6685C6E439F98CC0C5A210C1B0994057EAB9027EDC919B206838F9D69DE928160F6EF18EFBA4064ACB4D5476508B1FCD6C32FEF5084500D4FA967C00FFB622EC79FAC251A7B15C36B2B7B38F4D14DFED193C7A6AEA230DCA5E2C946528054B151A780C55C8BE25AAD6B84250917187E5C69EE7C34BC7F246FD07F38B2CF71EAD7C9E64C5088A246B16CF8431E0BD28EC94AEAE891D87C95275C568B0BA9A8DEF9994D3F30CB2ED4C7A8CBC8C33C0663FDF16E67E58959FE4D5125F432163A07C41A4984BD925DFD1C1621E80DB4D2EB55F28E1A3B40F870404FF2DFA639A2B16EC403A9F281A56A94714ACF6770F1987E3A9E3F871FCC8421D9436EA07D57E374707A48B128F9D34D2385C9F52908D05D5A606C9A62294EF87F87183E37CA171A675BF7F6A8E75641CAF2649A028EF7D07311461CEA4811462B5031A0E09516596EAF8DCE11DAB4730A2BD070C4B4F5F09048CA2584FA2F1F4AB0CA57B5B56134DC0B2B520129BF995CD211BF78A082775A401097CBF0D473806E84600F5DBD55C99B94B988A3EF668930C9B1E96FA6A92E53FC74A4C969E36B8D292A55E8AFF15BBF5DD0CC8EEB751AA0E9A26238A10C7DA32BC6049B10031A882D0D05CC02AA6CA9C2D79C99C65654523F9BBCEC5132EC1CBCB2CCE9ECC0AF5AB64451EE7F4EAE3F45B865C96FD3DCC561B25B84563E26FA10E91F5FDD82BD023841E381F465D2E0FD7DA6A8CD1240CE0A), (0xDB77416314031168D028CBD1E9CE16232C86DBF947B3FE044AE2B215E69B415F8A0FE62298D220172A1BACB3C4C6DBF478C614482F8DA728793D183251FB8135E66B47DAE8F48A5966C3CF2B064A3F8406BE9C1F87F92FFC68BDA95ABAEDC62E9A77CC587F010C843BBBE26CBE7B212FFD942B935E62AEED3B4A712A4A309F78FCA0918F7DE60405B118E3CB17C23EEC9C7A5C25C133CF38C9DF1D348FE23A0B1B69701DE991B1179026E1467EE2D6790C848DD0D379B7C8D059C59DC9D076CFDDD8AFAD5221030AC845959B5289B9E708A3280A546938A1B2A74C90CC144DC0B01168A6322926EB8182078EE41C76479FDE9425A76B7B74A00DFAAAD4723EB4F7D5EA402013B4DF7D3D88D161A9BD963104F1ED7F28184496F406242E7B160F4353960F3136268B29B5A86AE3A912F6506479D0815135CFBDA6A92C0D90B10126798E0E12AE64FADD65C4CA4561208EBF933D7D472DF203F1509C7CC902970A9BAA57F47A0DCC23043399B4BD1017305A094F4627C97AF6376439A9CCB595ED544209ACBB0C13B2C61A7913933D45E1396B21BFB351E2D13060BC16D322564E9EAC607B076B744EF69D7F0D506662F91E58706094D53A025F377C5A4FFCDEA3C32B75E7DCC9D9CDF6D3C915D1D581C9FEC3F77BB2F5BAC9725D1CCFEF19FEF9BEDC4A24F17A1C547EA26AC00B243189B5B25C8C0563F1CD71AC757FAD0ADEDCD5D056F58B5DD883A6A2FD0F8BF9BD4D2C93193CBD71F45BEA7CB344059DA773C48DEA3BCD3CFE58D9), ('99999999-9999-9999-9999-999999999999'), ('3033-06-11 22:03:27.832'), ('2034-03-25 03:58:00'), ('10/31/2016 11:46:25 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/0110/31/2016 11:46:25 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/01'), (' '), ('0338-01-15'), ('7589-10-29 06:22:50.6660670'), ('0001-01-01 12:00:00.0000000+00:00'))"; + break; + default: + break; + } + return $query; +} + +function Repro() +{ + StartTest("pdo_fetch_bindcolumn_fetchmode"); + try + { + FetchMode_BoundMixed(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("pdo_fetch_bindcolumn_fetchmode"); +} + +Repro(); + +?> +--EXPECT-- + + +...Starting 'pdo_fetch_bindcolumn_fetchmode' test... +Comparing data in row 1 +Comparing data in row 2 + +Done +...Test 'pdo_fetch_bindcolumn_fetchmode' completed successfully. + diff --git a/test/pdo_sqlsrv/pdo_fetch_columns_fetchmode.phpt b/test/pdo_sqlsrv/pdo_fetch_columns_fetchmode.phpt new file mode 100644 index 00000000..dff29982 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_columns_fetchmode.phpt @@ -0,0 +1,646 @@ +--TEST-- +fetch columns using fetch mode +--SKIPIF-- + +--FILE-- +exec("CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_nchar] nchar(512), [c16_nvarchar] nvarchar(512), [c17_nvarchar_max] nvarchar(max), [c18_text] text, [c19_ntext] ntext, [c20_binary] binary(512), [c21_varbinary] varbinary(512), [c22_varbinary_max] varbinary(max), [c23_image] image, [c24_uniqueidentifier] uniqueidentifier, [c25_datetime] datetime, [c26_smalldatetime] smalldatetime, [c27_timestamp] timestamp, [c28_xml] xml, [c29_time] time, [c30_date] date, [c31_datetime2] datetime2, [c32_datetimeoffset] datetimeoffset)"); + + $numRows = 0; + $query = GetQuery($tableName, ++$numRows); + $stmt = $conn->query($query); + + $query = GetQuery($tableName, ++$numRows); + $stmt = $conn->query($query); + + $stmt = $conn->prepare("SELECT * FROM $tableName ORDER BY c27_timestamp"); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 0); + $result = $stmt->execute(); + $meta = $stmt->getColumnMeta(0); + $colName = $meta['name']; + + // Fetching with fetch mode PDO::FETCH_ASSOC + echo "\nComparing data in column 0 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 0); + $value = $row[$colName]; + CompareData($stmt, $i, 0, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 1); + $result = $stmt->execute(); + $meta = $stmt->getColumnMeta(1); + $colName = $meta['name']; + + // Fetching with fetch mode PDO::FETCH_ASSOC + echo "\nComparing data in column 1 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 1); + $value = $row[$colName]; + CompareData($stmt, $i, 1, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 2); + $result = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_NUM + echo "\nComparing data in column 2 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_NUM); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 2); + $value = $row[2]; + CompareData($stmt, $i, 2, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 3); + $result = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_COLUMN + echo "\nComparing data in column 3 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_COLUMN); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 3); + CompareData($stmt, $i, 3, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 4); + $result = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_COLUMN + echo "\nComparing data in column 4 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_COLUMN); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 4); + CompareData($stmt, $i, 4, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 5); + $result = $stmt->execute(); + + // Fetching with the default fetch mode + echo "\nComparing data in column 5 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 5); + CompareData($stmt, $i, 5, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 6); + $result = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_BOTH + echo "\nComparing data in column 6 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_BOTH); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 6); + $value = $row[6]; + CompareData($stmt, $i, 6, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 7); + $result = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_COLUMN + echo "\nComparing data in column 7 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_COLUMN); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 7); + CompareData($stmt, $i, 7, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 8); + $result = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_COLUMN + echo "\nComparing data in column 8 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_COLUMN); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 8); + CompareData($stmt, $i, 8, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 9); + $result0 = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_NUM + echo "\nComparing data in column 9 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_NUM); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 9); + $value = $row[9]; + CompareData($stmt, $i, 9, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 10); + $result1 = $stmt->execute(); + $meta = $stmt->getColumnMeta(10); + $colName = $meta['name']; + + // Fetching with fetch mode PDO::FETCH_ASSOC + echo "\nComparing data in column 10 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 10); + $value = $row[$colName]; + CompareData($stmt, $i, 10, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 11); + $result2 = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_NUM + echo "\nComparing data in column 11 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_NUM); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 11); + $value = $row[11]; + CompareData($stmt, $i, 11, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 12); + $result3 = $stmt->execute(); + $meta = $stmt->getColumnMeta(12); + $colName = $meta['name']; + + // Fetching with fetch mode PDO::FETCH_ASSOC + echo "\nComparing data in column 12 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 12); + $value = $row[$colName]; + CompareData($stmt, $i, 12, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 13); + $result4 = $stmt->execute(); + + // Fetching with the default fetch mode + echo "\nComparing data in column 13 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 13); + CompareData($stmt, $i, 13, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 14); + $result5 = $stmt->execute(); + + // Fetching with the default fetch mode + echo "\nComparing data in column 14 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 14); + CompareData($stmt, $i, 14, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 15); + $result6 = $stmt->execute(); + $meta = $stmt->getColumnMeta(15); + $colName = $meta['name']; + + // Fetching with fetch mode PDO::FETCH_ASSOC + echo "\nComparing data in column 15 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 15); + $value = $row[$colName]; + CompareData($stmt, $i, 15, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 16); + $result7 = $stmt->execute(); + $meta = $stmt->getColumnMeta(16); + $colName = $meta['name']; + + // Fetching with fetch mode PDO::FETCH_ASSOC + echo "\nComparing data in column 16 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 16); + $value = $row[$colName]; + CompareData($stmt, $i, 16, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 17); + $result8 = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_COLUMN + echo "\nComparing data in column 17 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_COLUMN); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 17); + CompareData($stmt, $i, 17, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 18); + $result9 = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_NUM + echo "\nComparing data in column 18 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_NUM); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 18); + $value = $row[18]; + CompareData($stmt, $i, 18, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 19); + $result0 = $stmt->execute(); + + // Fetching with the default fetch mode + echo "\nComparing data in column 19 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 19); + CompareData($stmt, $i, 19, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 20); + $result1 = $stmt->execute(); + $meta = $stmt->getColumnMeta(20); + $colName = $meta['name']; + + // Fetching with fetch mode PDO::FETCH_ASSOC + echo "\nComparing data in column 20 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 20); + $value = $row[$colName]; + CompareData($stmt, $i, 20, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 21); + $result2 = $stmt->execute(); + + // Fetching with the default fetch mode + echo "\nComparing data in column 21 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 21); + CompareData($stmt, $i, 21, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 22); + $result3 = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_COLUMN + echo "\nComparing data in column 22 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_COLUMN); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 22); + CompareData($stmt, $i, 22, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 23); + $result4 = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_COLUMN + echo "\nComparing data in column 23 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_COLUMN); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 23); + CompareData($stmt, $i, 23, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 24); + $result5 = $stmt->execute(); + + // Fetching with the default fetch mode + echo "\nComparing data in column 24 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 24); + CompareData($stmt, $i, 24, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 25); + $result6 = $stmt->execute(); + + // Fetching with the default fetch mode + echo "\nComparing data in column 25 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 25); + CompareData($stmt, $i, 25, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 26); + $result7 = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_COLUMN + echo "\nComparing data in column 26 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_COLUMN); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 26); + CompareData($stmt, $i, 26, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 27); + $result8 = $stmt->execute(); + + // Fetching with the default fetch mode + echo "\nComparing data in column 27 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 27); + CompareData($stmt, $i, 27, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 28); + $result9 = $stmt->execute(); + $meta = $stmt->getColumnMeta(28); + $colName = $meta['name']; + + // Fetching with fetch mode PDO::FETCH_ASSOC + echo "\nComparing data in column 28 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 28); + $value = $row[$colName]; + CompareData($stmt, $i, 28, $value, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 29); + $result0 = $stmt->execute(); + + // Fetching with the default fetch mode + echo "\nComparing data in column 29 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 29); + CompareData($stmt, $i, 29, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 30); + $result1 = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_COLUMN + echo "\nComparing data in column 30 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_COLUMN); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 30); + CompareData($stmt, $i, 30, $row, $data); + } + + $stmt->closeCursor(); + $stmt->setFetchMode(PDO::FETCH_COLUMN, 31); + $result2 = $stmt->execute(); + + // Fetching with fetch mode PDO::FETCH_NUM + echo "\nComparing data in column 31 and rows \n"; + for ($i = 1; $i <= $numRows; $i++) + { + echo $i . "\t"; + $row = $stmt->fetch(PDO::FETCH_NUM); + $query = GetQuery($tableName, $i); + $data = GetColumnData($stmt, $query, $i, 31); + $value = $row[31]; + CompareData($stmt, $i, 31, $value, $data); + } + $stmt = null; + $conn = null; +} + +function GetQuery($tableName, $index) +{ + $query = ""; + switch ($index) + { + case 1: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((-1), (null), (-13744), (1), (0), (1.79E+308), (1), (0.9132), (0.4765), (0.8474), (-214748.3648), ('£¢,å.uÐh.ý/BrýBãarýa,UåO/Üßh@<ÐuU+Oär.rvAߢ©O_ÄO,_ãýbUo.ÖO<¢.~£öUOäãßU¢ªaÐüª¢raîb¢Üý¢ÐoýrÜCað~ä:oÃ.ý©ä.hîuý*v~Ü_O~|ßß©z_aä£AC¢Ã:å@/ðßAuuu¢b.ÖãÄ£ýªã+ab£>,åüßr,aZ||<ÜhäÐo+z|O~ðÜOhýA_Üz/ßåö|ðb/aä¢ßãO+ö@håÃaÃ@:ð_Chobª@U+:|îÐZzvAb*ãÃÃZ~h¢bÜåä©:ªåýßzª/rð©AöðZ@Ä>ð>CBÃßãß~A:_Ð,>ßCZãZ@ö:£CðA£+©ãB,vü'), ('*îBhaÖ|üÃOv_oßý©.ýüb>ýÜÜr<ðO*oÖoã|baBvb*îOO/ßu.Z.rü,_aîaåÄZßv>ÐßUoOßbzävÜAÖ*|:~âZ_A_,¢ã£bC,üub.<,O£Aªrb+Ī>v~::C¢OüüÃvä_+OaOåîCbößý£î|ß.Ä¢¢,z@b,îröÜ©uÄävÜ,Ã>ª£A~ur+ý@ýörb.ÜOÖCýC|~©ö*.ÐBh+COBîCüªßBZÐ.,uý~ªãöÃ_©.~öaýbuß>UãoäÜBoZß~üaÃ,hr:üÜßîo:ã<Ü@üBý@|vß<|u+u£|.bC>/haÐ*Börbðü*å,rÐãÜzîÐ>r*Äö£Ärh/+©*å¢uUuÖoÜö@Öýu|azbÄÃCuhßü:åÖ¢/bb¢©_@ª>~_~Ðö_Ã~ÄÐbÄîbЩ|BÐßUß*ý_+åoC,ZªB.Ðo©ð_a*öC:ßÜUÜrªrª£u/.AbZb~|_OßhÄäBozªßåCOrUU¢~Ðü@OÄöäüzh¢£ãªAaCOäîÜAÃü+îýª¢.Zß:ðhrBZßÃß/OaCo£©åa,åh.*Co+Ähð£ü_åövruªä©ß@ª@*:B*v*Ä¢a/©b~AbÖª£Öh:©Coî/:vhÐb¢ßãßÄU,åªö*@Z+AvÖU*@uÖoOBh©îÃßUÐa£ðäzÜB,zöbÃ@ärBUðîör¢ã.ªvü_©AÖb£Ã<£/oöýhüOZÄ<ÃUð|£Öîz_.ß@vbß~Ðvrabß,~ÃbýðßU/|ÄhªÄ¢C.ýo*obäîîîO~uîbÃ/ää+å|Üü@ZýUßC:Bö¢åräÄ©*ÐÜObÖua@ªoa|z¢C<@ö|ªªãZ,ÜãðUýäoã.v_:üB~Ãîýo¢,hðbU~@ÐzO©v,Aãß©h>ÖýOobBÃ>.+äâéý£ðäý.+:@r¢UbövÜAÜ>/¢rýäý~BB+<>AªÄAråßhB,|©ö:ã¢_hªãðhvbߣbå/ZUbbÄ~©,ðbåu¢.Ab_A/hö~üÜ¢~îz©U|ßðÄßh<¢uäoU¢bÄ¢ZÐuüU¢£Ð>ÐvZ/hÃ+Щ@bZðªîªîß~üÃöîbavobÃýöOOubäOrboß_ö|£z,h*,+ßbÜßß+u.å@vAÃ~åCÃ_b/ÄÖ.hoý+/ÄåAÖz£hB/:v>ößr©|>UüÖrĪZö£bâ¢ßýßÄý>_Ðb/ßÃUU|>î_Öª©Ö.©>O/:+ýB/ßß>>hBuoAÜr_ü@<+Ð<Ö@öAübªhußbb+b*+å/Öb*ßz~|å_äÃüÐbÄîuª<Üä:îýå/Bö.Crª£Aª©Cör,AÜZ.BzÜðo|¢*C~öZ<Ä@ÜazîäªBb+ð.@*Ã_*ßßß,aßz|+A_CO,ßrr|aZBr:OãBOÄöªUvÖhðЩî>ýÄÜ_ãbüåýBOZÜÃUU~*¢|ªÖÜv+b:B|zbaª*©¢Cärr¢ÜZ<~@å*,b*~,Coßbߪã_a£ýЩ£ððÐ@ÃÃ,ýß:ZÃäoCÐ@ü<£>oå~zU,âhzÖîäü/£_©îýC**ÜCÖÃ/vhaUAoÜAor/zÄ+Ãh|ÜýOÃbüZ~£+ß|ßO@Ãv*az>åÄr,>*U~ZÜ©rr*Ü>ÐroÄÃ,ßö>+að¢:AÃßbåZýz~£bý+ªh+ªãÐr|hãªß@åUîhz*üäÐ>/C@Ö¢'), (N':ª>|åUvA+/ß,ÖÖBZðZ:ß|uCå~ßCbÄ~©Ö>,¢Ü/.ÜOB_ä<__ÐÃArãzzzðu>ªýbhO*ÜÖAî,Czä@@UÐA+£*AB/b>Ð+/o:.Zã/ÄbîrbÄ>¢*|Ð+*ßbªîäãUöýÄhãb.ªÐðÐaABßrßßvðã/Öå*h.~,~|_:~>Ä*Ü,Azîå.ßÄ*uüäÜb~ð.@uãßÄ£ãuo,O|:ªBåA:U¢ö_+b£ßoª@ß>ü_oü>@ýÃ.<©ÐA¢hå£äýÖ£*z:ýäÜoîooo_*Ãüð@O*A¢ß~£ý¢Uýîî+ð+üCb|ßB.hßýÐÜaBÜZa*Uý:å~vßÄ*ZZ£.avüb>ÖAãîuCOîo~:BUB*OuððB|ÜÐBÜBäoªÄ@bbZ_ý@+Ö*Ä©BuUUbrzrß*büðüZÐhhUAz:b.Aö|+Ãåo~.åÃ@ä@b£ä.ðoaB+~ÃÜu¢b©+£härB£ü@îo<|bZßå¢B¢Oî_ð,öªý©ð©|@Ð@A+ýzöaåîAý|_zvîå¢@<>U@'), (null), (null), (N'öã|v/îZh¢Zý@ðã/vÄÖAAAbubîh@b:*~zÜ,ªÃÜý<ýz,är<¢Z£ãuö_|rªîª>,Z.UÄvÄÃÐ|UÐ@ÐvbЩrAªvãäuu,AÖ|ÜÄbv¢O~bÃ~@ÜåÖ.BÄuU@>öÄ~îB:_a>î©ðOÜ_©UCB<£ü¢_ß,Bo<ãð+ã¢vÖAb,C~Ü<ßî:ob£:ßßBzbßbuåÜ£äü_Ährz,,ªZ|ý¢bA,~.ÜvOO©O££/<Ãßo¢<ÄßðbÖuãÄvã>vBÄaÜÄ£uÄ¢:+.ärAÐ||aß+*ߢöh©A|*._*Z¢,üZa:~ä.îüOð_£ÃvÖbý,bäüa>ãOhZbßb~ª£Öv||u@ðð©ÜrC.äb©:+ð|_ýv@¢,ÄBähC+öýöýr@ÖåoðzUZÄßvu.,Ä£ACÜaAöäßßb_£ãO.ðb<ö¢A.ª©auî~:å<Ü/Ðîåoýo~åB*äߪ:_,*Oö:B¢£@BýßAZ+ö¢ÜuCv_ÜbZ+ÐöbÐå/Ü,OBÃ_*zBv+ba*|ªÐ.Ua,zUä~C£b,©ªÜr|b£BvUb:ðaaoÄAößCr©<äA:Cz@~åUö:©ªä:Zî>rýÃýÐZ@ðÄ,ÐÐ<ü.>ÖîCb>A+£Ð.ýbö:£hhOarv~ªv*©ö_îab*åB.ü@~ao£bvzZ:ýUÖ~ªvüOroZãÄßBãÐÄ*©Oãbã|oã|uZZüî©Ãß*/ß,Äåu©©äObßz~ü£vhrbðABC£ýäaäßo.A.Z|Z>üßaüÃýÜÖîÐz*£@>Ð.,ÃUbå+£ßbA©Üðv/+ßO,bü+C*åBhÄ_O£îzýîäA/B:,o£üCýü<åüã*CÃ:A~ðOßuövå,Ü:ð,|z+Bð©ßÐouîCÃß|*Zߣ_åvCuZr:îÖ~+_Ab*£>UOa©z/ÃZ©|CÄ_Büß.@ZrhßÐuîü~å+bª>îäA¢¢å|ä:ö££zOu>,У~b:*<<ÜÜ+äßð~ü£ßÃb+O@ö/ÜOobîÄÖB>£:a:<@~oãCra£abÃÃ,_:A_h£¢©hz,åb¢ÖzvhüO*ob,üãrÐåO_üra£Ð.BãOã~+_ßr/+*oOß:r©Ðvãâî~>£U,bÜ*zOh>|bhC@|Ö.ã.ÐýÐ_ý*ð<~.uvvbÐv/o©AaüA*åo_ªÖvörboÖ£_äb/.äªÐvZÜb.UhªÖß,Üö.©ßßzÐz+Z/v>hÐÄÃ<îO.ª©£BðÄ¢bbzÜ>**u,CZC@z./î|ßã~å:@öååÜÃUa:/:/vü©Üoß>*|OÜÃBOUÖ|CåAO£ßߪ<||CBãÃbåC:uÜ£ab_å*orÜ~:>äü£Ö<£.ÃýB¢£_h£¢>Z,ªöÖC*:aB~|@©ýÃßÃ:ªîÄrÜýZä~ßßöüª~@CO_CãzZ:|ãh<:£öÄüÄÖîhÄ,ߣbäZÐü~ab*ZO/.Ö£+:CÃ|î£_Ãrü*ýAä_*å¢bZCöª~ãövbZb~üB|ĪÄUÐ/z¢@aUAA<Üã_ßÃ+u>_CÄö+:,h,+å:ªzßðßz:_ßZÖzrb~_ßbåU+¢åh~ZãråîA+ÖbðßîA|hö¢ªbhoüäbCB~CUbü/©+@*b,özü¢©öO>båbßCbOO/.B:bªßZavã_vîZ<+å~©ýÖ~ÐäO£b£a¢_£_ãbU¢z*_©¢.C©uoª¢:U>@rÃß+ªåüЩý£zåÐbruÖ|uCzhO¢rü<>A~+AC*oUåA:,ßa¢ÖßÜ>B,,ÃÄv.å_£Ð,î>u£/vvÜ.îö*ÄBäzz@/ß:>+B£å©ÖÐ<¢¢o£|å~,r<_ýbr/bý¢.b|arðö/CCý_¢_böv*Ob|üÖözüZ:.o©ªîb*£îª/UÄhÖ:,ÃäaðhðBäðb:*¢ÄU¢>aü<.£|O/A©ðö+/rZ|¢BZ@Uu~Ðüýîzvåo¢rCßýübZß@BUOð~£uAbbh*ýÜÐîé~vBÜßÖ*oZ/zª~ßðuýýîªÐZ©,UÐZ~:C>ü¢Ü>Ð@<,/>Ozvý,ßv~¢vUÃrãÃbÄÃuB£äb~öübarÐ@ª>B|ý,,äöaýßäUv.öß©ßßozãzuÄb/Öv+åÖ£rBvézãvb©Ar*©~Cr|ðÐ:ovuÖÃ*:rÖ¢öC¢bZ©Ca@üåuoZA+,ä£bßüarbãböüü,b*ABBЪv:b|/Ü|ðBöÐ*öª~Ãß'), (0xEC6077ADB74D7703D8702004DF829BCEDB649DCC4D2D2261447BAC0823FC3E796832A4884FB435E03B46CBFE47B3A046C323AECAB31AF1BE3D16683BA16EE0DC31A79CADDE127E272EBC910662231990DA6545004D73C54D40E54B394BFA3FCC66EECDA016F368B0A5879BF689EA5C1AFDD8603B28E08EBEE14F6CF8C745DB347DC4A073567879E7FD955BC6D7CEC35588313C739903A386600EBFC7866FE85FAC6779C54AE9C3845F783D9385C8E6128C176F9608679A79211A1247B9B2618EADFB197055FE0B9F6DB71420C50F019828AC3A6F), (0x5744CCFFA330D67BCBAC17AFE689AE75BC8E0BCF2851537AB311187A1F0C77F18E5BA4D4A4555ADAC9F2EC6709162977E9B3EFAC63302805F1524C92175B5CBE3426218BE5D9D0673331063664EB709DE7A1906B1CCB8E5D329EE35209C7C36D952D32E2F49E9110E288783229F2A105E1AEAB75CAF0A26997C35847F57E5E6C6D99A036B88DA84910808994525B3440FDDE715A1C4DA2A58BC94B3145491A642A0CC490516A4C530661654A660E173C8F184A170C034F20F6FBABEF427703C66525694697A3EE92911E54BAE8573E7DB20FA22CFE035727BCA49955BE7BE0B38B03E2C2EAD8B28E008FA25E0101545038E32F5C7423916799EF906D3E61EEBAFD0E85447E5AD00588E308B185A84E1D1BD2E26C4B833F68727B29FB48454F9EA0E761F37B10D2DD132AD4901BDFE1F0A172516B7F826E79CE1DD10331D4BD43936A109E475AA20B00045821AEAF4D9399395AD5F3E41E1D79E7245D3ECEAC3F6D91FCB1CE43A946D046), (0x294DCA1ED03E6AF879E750AF4BF2255F3544CC325719B31DB39BC4558F1405AC78A3F32B1C068D0063B4C586414C65DC1E49108B7938231099AD6B347A146865E1A337A7FEF9422FB7CCCEA3542DDC6635E83745037A323F80E3926F9A58854E4B5FA49D15BEDD2407E0D4AC697FBB6BD104FE92DD23616770F7261B67D3955E478057F5893E51770A123E23A586D50128269E955AF37128FD3C2D1E98C2BD334B7565E927C6E292B38787CAE43EC1EA2125F24035FF03530E2FF74C1132DE48550BD82F20ECD961430BE1D26BA643BF7A42FD73CF7CFD4B7112DAF8CE958180A83E75D96691CA344B91FC291D31E768EB38D2CE356B79D80D8CBD0E11E092AE8A612A2D316A59E878C8273E80D32E402A720D96C3B24B3CC59DCBD9D3BC236BFE4E9FBC07BA3680DFE9566D443B58395E7DE39FD3FCEBD715ED67949E7F14BCA5BB40313471396F3B8CB7D95CDDF88A44927B56D883980163C7A61953A2E25FD49AC888FB2605EC4374634644117B38F4DD4F0C03BEE321233ECE6CCB1F1C99AA5705C215708F1E8D8F13642051BC514C6E257652D55C16B6A764BC5798202357C99CED0CB923844D317F74AA7F00D2817C205DFAA646D4FBEF0B6D0BD2C6F46EEF0D57B5CF45924AA0282156E76B37A6923BDA77C328B082C7F21CADB5797B6A8A05BB72A91CB1378E125A7E257D1383D4C67896EB7DAAC75CD4EDDD28E59426ADB7CCE1B0C4FD7589A3E9B0CD325D4ADE99D8EF00E47E0D5AF315D42FD40EA87D33D06EB131D30FC9030D4794FAF92977A3FE8F0CA526C938BF87BCAF2946B49540339A62EF98ADC6C826FDD7377860FAEC5D8BD67F9BD70D0735F1AA16609FBC58B253C65FC7880A50C9447F29F2014626C2C2CCBADE15A55B8C5AB9A602AF6786407E8425999B24D48185B55757E41534AB84549ED3017A9CAD6BD26CCF634D1ADCF9E8737C208C269DF738585F4E03A84584FB7E5AC9B23F04E7782EC8B8051E379B7A5DFBA1B88BF72D00DCC8213C5F6729264A67CFBF1247A96E94A85D5EE5F11A20C4D3A6ADA3CC70862CC9D676E3AB18C79F91BBB668DC6816523B398069180DCFA1D1D0D28E), (0xAC0A1A39104D3AC0DBB7D2195FC6F99A86E7F70CDAEEAA4AE664091103ED297E9464964E1122AD619C7A5EC997FFAE08A73749EB9687B8BD60D053AB4429BDED8D5485FA30AB89643996706090E1B3E7B60069CCA43EE80CABD8ED41CF011C73BD703B437AB3D8FCADE5B1FF17582D7F1D6CF543BFF0494F6C784E57BD48943779FCA31FA5706C02286D1EBE8CE8F553AB6D23620CD9B4CC27EBD70165803E655079851AB50DD9ACFFF21B51D28AC28383FBAC5024312BA048D6F7BA996B9DA72EB86D1262DA9D9D9377631ABE62F99C28537E69D4EE411E4EE5D1DE161F1394547322191DB020A85D0EBCADCE10E2E6DC5B9145EC33D340774EC47D09BE877463CFC54A3FE07F7832D60CC53294503E02EB02BA8ECDA720A0E2A2CEECA39F9E874749FE4A98A6FCDC181316270D0EE2FE857B1DBCCCE59528A5AA5D3DB08C8F376D3EEA4A51F36A4405DE75BA91F992D3BFCF662781E2BA61B4D6A5EE49BE2B8C83DEB9CE2497AC9211D8B7EE70DA96AB17897DE12667B58B8488967B4D7BFB46B2E937B23FC4AF2723D316A82ACBF1D93FFAD6C0414A651B282E4A9C01314495DD703B87A3E46B53D956BD52510D60B896BD3B53A53B0568831B8911791326F28F81CBBA906A355B8E83E6EB8C12CA1BCD910544F5F0F0A26AD203885A5AD9C62FCBC3D041A5282C87DFC521541ADC70506D44E80DE2FC9E902850BD66AD0134AA20BBC04FFAD92768D9BC152BCF4D4FF69238EC15F0B84DCF253F19556BC5DADE9076345F1B2156941C803425E672C1DA00C6D475141EBA676DF129BBC0D1FC14E2379B5819F0DB70E158F9708428E8B37D3E55A5F065C972BCCCF129F89BD34B831F42AFD769C10FEA6393BC2549E34509763D827B56237D6D299B4DFFB2784684AEEC64E538DB147D46BB8DD20C4C72E9AB1F815D51CF2CFCC541AECD731037EA4C37775D7F60D09A2904359B95AB60DC29645392F7E933DEBF671FF62D46093683A385D4D414694B4FE7334C9991661AE1D1A40FBCA5F36304989E1740D96DE3FBFF18F2258C0714FA08430FB5E6452E0105155BA2F97ADDAF22D03C76520A757950519A7973F455CA8F15B361E3AD1E5370163BC4114001853A4303BBCB9DA7FECE956495ABB48D10C09112E4C67CCC74B2E8063BCFB068104F143E30F199FEF26DB5934D4988A8BB892A03802E1DBEBE778E09E3976C2F1D2D544596765C1E2793207611E87E2B29404BB2A4ADE8E1325680B9638F1B5A01857620F44312452C0CDBED0DA6ACD5F862E0A24CB98B6F4572133FF5A2BB9877C5758B725D36E62E058597D1FBD48BA9AAC001AF780E48FA06DA822FD24F8A504375473120639A2BF9744633F39E4A9E87954AE1BAE8FFB72D916B83B4B65BB5FB478D868C5A894726D5B504C714076890E3A83C05A8965B7590038FF7177643189038AC), ('46eb23a8-9377-41f4-8b05-9d93707c88e7'), (null), ('1952-09-02 17:15:00'), ('10/31/2016 11:46:28 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/0110/31/2016 11:46:28 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/00'), ('06:05:55.2800727'), ('5983-05-08'), ('2001-01-01 12:00:01.0000000'), ('3303-02-12 05:25:44.5483616+00:00'))"; + break; + case 2: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((-831496210), (null), (-28188), (9223372036854775807), (0), (-1.79E+308), (0), (0.9942), (null), (0.8968), (-214748.3648), ('büUüî@ßOb©:+Uî>äÜÃåß.AUbC£@Är~üZ©BOÜhBbvZCªvîüý:>>.rbã.AÄOãUýbü<ÄoÄhÐÐÄã¢@Z/üoh©brÐOÖußÄhböUßvrhh~ÖvãÐÜÜÄ~:*CªÐaĪîåuvöðî:~ÃÖ:ã*zhZ./åß+:ü>oß<~|zö|r,*îä¢ã,.hBavÖBaüZ*ªãß|/vßßC/åa+ßî¢üýU:Ü~>.r+üu/Ã<ªUý*Ä/ßî£bãÄü_hÐO,ChAÐã~A*,+bZ~+/övÖ,>Orhbª.ðä@åb¢ßäZ|/å/.z,b<ã*>v:@Ðbä_.*üOßoÃUvð:~@£ÄvB©+|<ÖüÜh||O,ßÖ./>äA,@,ÜzÄä@r~zã~o/bÐzb£+rAUª>ä:oboAÜ©å~vªUÖî<|~uÄ¢r.Ä©C.u©OÖ_aBZür*åÜ,©@î~ТЩ:ußU*buoÄ@|äý|bUoBbh:©ou£ÖZCß+ßZß.¢UöZ©bbß@ã*î.B:©/£~~rßb~¢~Ü'), (' '), ('Ub~ößzä/ab£o*åÐ*OÃhðOÐuOãO+ÃBvö/hhbäão£*|r~@|¢ÐÃåU~/U/@_ã,C¢öO*ÐhuArßä@AAß,ýoÄ£r:©ßäÃ@oÄ¢_A:AA.,äã,z~_åaÐ|.b¢bz~vãbaý,ýCz*z¢ðßÜChCÄ£boîÄCåCý,ßÖUvöªoÃ|.bb/bÜ:ZU>¢+î@+Ã@Cö+åCvaÖ_~b+üOª:ªÄ£~©BhU㢢,aåUî~:>uz©Zßb©_O.ðZzaðzßÄBäaßBA>££Ö£*hÜ@Ä,Cð.ã_ý~öäð*z,b~:O>ª>/u¢ßo.ðåäUhÜ¢£ß,,å|ä/äuCo*v©båövðröîZ¢ÃhbÄb¢Ü~ÄÖvrbzöÐb|ý@£ßhUßýZäã.ÃîbÜO/ÜÐߢ*~¢_.äߣ*Oo,îßüäýãåBzðÖZb+CZC_ü*Ü,obr¢ãüO©_£uZaZß*/öãÃZ/ã*ßBa>bBÖuäoßuÃhÄ..Ã>Ãî©büðÜ¢AÐvÄÃîaÄÐu*_/ä©ßroÃu@ÃéBäoªhAA|Z<Ð@ª/ÜzÄüýrzuo|ÜAß:@î.@uAh£ãvýâzÄßÖ<_ªoÜäbã:vC>:üz~ý_+ßz~ÐÐUäÃz_¢¢>o©>hAhðöãaܪߢÖåC/aßåzðßäððOÖzã©£zÜ>o_äª.BÃ|ühÃÜZðåzB>ðãßO_ö+ã¢Ä:@ð:O_ðvýüB+|u*üÖ|ªZA<©C+or+ö+rÐ/v.h~uªÜa¢*h.|v|ã|OßU*ÃüäßBa*|*£Ö,ðZãO._üãv/h@Äßî£@,o/ªbA*ªðÃß>Ü.zrbCßZZÐ_U:AuC,U,hOÐoÖýÖãß+ßýðªî¢/ãuA~,¢£hÃuhAßur©z©ZAu~uzvüö<Ъ©_/ßZoußÜ~BAbÖªßAC¢*¢ð¢ªOUýÄZ/©O.b|¢¢ýöb:>îÄör@>Ð<ðãã.üÖðߢAo@_+ðî£ýOýrbO~îhAî£ýÖ@ýã>ã*ýBßÄß,ðîðü_ßA|@ZÖUå©v@ãÄao¢v£_Ääb~£å+v/ÃUÃüO>C¢:Uru~ªOb>vBâ֣U.vÃÄ:î¢ð<ãr.ª_©uî|OA:ZCUî@o¢vªßð¢/~ðß.o_¢vÖÖ.Ü:ö¢åßb|ªßBh/ßöÖa©åavßOüÐ/@,©/o|+äCbuý.ßÖ+a¢<Ü.~uܪªåÖCývüåüaðoåã|âÐCßUCö@b<ßüðüãå+U~*ßð:+_a+BäOã|bZî*~r:üª|ýOAߣUrîh_ðbÜr@h|:<åbÜý*ZüÃarvÖUCððU*Ð,BåübZ£a_Äß/£a>Ä>uOÄ@©~rÃäÜ©öãZUßßb+aBÐÃÃZ:üOߢÖÜ//Ãb¢uÐ_ÃZO_ýbÃ/|ªãöCÄ,@@AruªaåUaî_.ªrr~*ã*ß_oÃuÖ|¢OÜߣÐbO|ªa>ð©.ö_b_vß+.Uªv,~ü**ýÐvß_r+**ßÄð/ÃßÃÖ+Äo,ÄÐvýzCîöýî<åO<*_ßb,ÄCzßý|z~ýã:a£zã>_Öb_oß_*îUAuuaAÜ||~ZOußÜb~AÄu+<åu¢/./B|b+b:+AÄ+*ZO¢Ðz+,Ãðß,åaBª_Uvo媢*<ý@ðhÜ:Ö+uÄ.bÐÖ|äuÐ>¢Ä©bv<îCÖ|<ЩãoÐß/Uî>bA©zÃ_ß@ÐZ£åhÐÜz:©ÃåzýßÖüÖr~aÐ/ýßa~ÃaÖ~ðB¢Avãä_C,.+Ü~Aobrð*ö*u>ýä©bhobzÄBÖÜÄü_ðBrbOª+©.Üã@B:ð£o|bzO_ÃAZªz+bv©ª.åo_rÄÜuoäZÜb,¢*î£öÜüU£Z|@üßAý/BvoBuv,/:ßãh*Uîüîüü::zî¢hUäýÄÖürO/r>îZ.b_r>ýhBzb£|bãb_ýÜ,|öý/.ÐÄߢöîA/bª|îðußî>bðß*hÃCo©//B+*/£C<ßO@h.ar>bîb¢b~AâzÄvCUßÖAÐüî_ßh©rãab©<¢Z/,AbåÜ>bäð.îÜOoåÜAðÜba@ßððßä:uãhÄ/ãÖrÖäUuð'), (N',UîAÃhZ¢|äu/århüü£ª¢bvO*+/©Ao©_hüå:ÃaßOzör~är.åýªåZª*/_Cb~©vü¢ÐBÄzßã~zî*U,å:UZ|ö|zrÄÄÃ~Üh//aÐbüåîU£ãÖabäBA/ªhÐðß|Uö©.A.AÖAÄhåî¢O.ý_ý£ÄO_U+ª©vü£.>¢:>CZ<åä¢BÐzîüO.uOhOöOãb..b/ÐÖÜ,z©v:hbC@©ha¢u|:¢/Äî.:Cäz*ýUÄÖvhb£@ßou~ObÃa<ÄýîbCããÄ,uBrAoZCuBéýîÄäÄÖv,ä@ßÐ:©oz:ðCäð~Üä_zÜaö<üüa+<ßZb,+|:ää.,Ü+ýüÃzýßÖ/Cäz.ýv:,|äÄbO*A.|ÖZ<Äîªîå@<'), (N'ªÃ|,Aãu*A@îÜÃ**ß>¢<Ãa+ð:BaBãhßO|C:ªAü+OU*¢o+ÄîÄör_ãã~åC/bbÜ.~*bußÜ>Öu<ãC/r*,r~ÐOzo¢¢ü*üu*Oüå~ÜÃa_ö|ö/öÄÖÃÐ.+_Ðu>*ªB.ÄÐaAýOö+>|£~ðªÃªrZ©ßªoB+@îråhozý*:bBba:+aå_uö,Ðaor:ð,,o*££@Bübrzu£u>|ª@räZßÄ¢å.CoOÄðÐ.¢üvÐ*,ðÃuýzoU<~Ãubbª<ÄÜu.öß,Z~ãaUrßCBÄ@îäA.åBüîÐCu@*_+ßAÄrB.ª|,£ozîb£:ª@_oÐ/Zåh/Abvbb><î.ß/å¢Czz£ÖÐßbäb©£ýß,¢,Ðßb©,_v*~©ãa©äAÄî/ä/ß_o*+Oýª.BÃBvabªßüÖÜAZOböbru£ðb.@rªO©ÐZO|Ãð<üÐУß/åð©B,>*B,zýÖüßUCåAC¢aoý/*Ü/å~oUza,ÜÃ+.£îv/b£z<Ðvî¢_Üýu|oýÜ<öA~Bü/ððBa|Ü<.r*~b*CäAoüü£übvAhha.ÃOöî£,,ÐhÄ¢îÃý,<äܪÐ:a~Z@*Zb©v,ürßýÄßO,_vüâ©,ZbZbäz©ÄrÐÃ,©r_bÖb¢ÜA@z,äBa/ö@~b:ßUý+¢b¢äܪvA/@oaOr¢aå+ÜÜðuÖzuÜÄhªðäü_ÐhýýðUðA+/ªã.ÖZüb~<£r>:b¢AöBäÐ~ßh>ãa.O_+.>zZЩÃZUAîððü¢ðZr*b¢,¢>äªOßßý>öîhuÐoo*ü*©*UA.:rh@©åB|::rCýÖßÃãb@o,vb+U@Uz~ß+rßB£ãaU|,£AuOßO_~BåßvÐåAooßvh£ßü¢býzîBý©~@h@+Aä|büÃuÃab>ðb>Ãuý.ÖäbªBßä£CÜ:zhb¢@,ýßÖýåî<*Ö¢a>ªhÜä|.a,aübAävAr_åÖÐvðaö_åÄ<©ÖåbÜa@£©:~£ªh,aÜÄh¢ävßååZ'), ('hßzÃÄ|ÄUBýUãÖ._bZBCh|ýOãÖBª.öýo>ßã@äCîöb/CðÜ:Zä:ÐĪÜobCh£>öªözZCãÖAîîÃü.hbÄ/b.,:uhÃu..zÐÐ+u£ö*BCãßöÃUü|AÖO¢C,ÃbuB>,ߣbÄ/:zO|ÜB@Uößý~:ÖßoÜBßÜ.zCrýª@©uöhU<ßýä£CãOö:U>ªbA:üA~ö>rChäB>o~äaý£ÖB©Öü@©U¢ãüb¢ªð|bÄO,rÖv©ãý|¢©¢b/Uääý,Äý@ðÃå,obu>~¢@hߢ/ýßýîüüAªýÖ_ßÜÖðO+Ä©:@äå>ÜÜbbbBðª¢/åo,ßA|åBU:Ö£/h:hoîCö@hOðý_îßðr+åÜß|©C~/üvä@zZ/¢ö><:@Öuå©ã£î|aßÄbBå䢪ÜAª>h©£¢å¢~îö*<ÖäC|öîß>Ãäüär..UzýãBb|/vu_ý©|ß|îåCö:b©ðü_©b:/CÐÖ'), (N''), (0x00), (0x), (0x57CDF160B1CD42B39A4C3844CF1719168847B6FECC6079994BD995251FC8961EFFA573173276CF40D35D8FD9E1ADB125E67F2A35C3AE6CF6545715D8381C93D2B619442AA302E31277752A6FD19FB89D276F3F9F9D615A50387DC3DB60F708B6C9BD22C7688781E49B18BD4D83FD25B49F77B1C8DCF43555BEF4F2253E08B2806D74A6137DF035A0ED5B0484A30DCC3B580F3CE84640D9D587CC0874955CA593653CDF0F561136AE4210DA81F0B0B1B127B6CD2FCCC93A2D9AD18BB0E7C10B91B5701F662DBCE446D88DA6356558E5D71B574CDE8E76C79B02B275858585C7800548CB7F914A836AD286563E2304C741ECB49590742C023FA883647B475FC053C5252F1D8D6545D0D3E20C08208878FDEA3499D1F0370CD8458BD70EE6082373C50379FDD5AAB4866EC5BC9220FA1F1F0A669013C5EF628E1956D7018D37E9CABE99421D0F7A283F7F12622B7A79B84B6CB803807710ADD5A0EAE47ED3B3F836DBF2D387B7010F903D5093B38D37139F9E8B94D5346953AEEB6C7C2496AA6CCB192B4F88A530936AA87242267CFA4869D744DF471E2302719E249B4B85EBFA93E4C79961E78BF96EF8292DC1272C32F123134D51CB25ADF629DEE9819448E5BD78AE42B460E6814B4A704FF5F5404A72F1AA27719DA8C8BE1AA33809A178D9AF44704978EA7E903D9201065AD5B700E893778FEA20741FCFE68399BDAE78A9D2979FB185F7D1887319EADF9BA26DE963A250752AE9EF8ECF50799166E4D38F195834C336067BF1C2EE1BD03694842D789A361B5E037EE8A710E1357D3F218F465BC5436DAAB35C275EB33CCD1E583ACB4B18CCAEB83016D94C1B2525457382CC8C8ADBCF41C0CEE7DAF3FF50421A89B8A669312755E18D2AAF69218F8D37E1EB306F41937C6B8A8B4AE4D3C431C69631926C7EDB415B77ABFCB01FD79F3F59A211131FD3D9F53B3ADFA6DC710DC9F8E8D2F0FC21B806A9D5F89597CC4BB2EC0B8B31B21DA7C3579D3044FB9B7E059E33B83704AF4237EA0B6A49DA435D6FEE2CB9D0E6DB17DA410E985FF1740A0D56F2F7641314AE3DC85347A6B35EE9133FC055CFE2AD1FC21BDDF5F516E34675015CF2E84CF95CDA7E7C0892947027E325851F301F3B2BA9D23548361EB827E50CD1A5566B01C745647A5065A58C2DABB612CF91C31556C630491517B125BBC6552286C3CBD07724BDDACBA4EAC50346079C7F0CE89522F67BDF2522EBAF943EBE8A1083066AE0E59AABD3E949B1DB6B45CE0F14A5AFDDBF008F48ECAB55380D458DE660B5803F9DBC9A713DC238ACC903BADD1DA07F980FAC51827721F43A4572C5A3BCCB7BB5C58E72570B3DA0FDC7E27EE8F0781B6DC6F4D6B364FDC87BC8602BCAFC95F0FE8704178E2D278DD10C54BB94A20B4ACD5757DCC7F4C851C7C5F68FAB12D50032F28060EB608C3F9E6705A60F4234D9BD165E73A4B82809BFB350C83798E1BF375389A221AA5E6DCA183E60EF214184911DD295B3615B79C977A0380FC4080685466FBCB992B712B7F0A98C7AC71A9E71CB371AC64C0BD5F80FD8AA658FCFFC770B581C5C0A1C1482C4C1555D14962DD2D8DC09BD2D6410A3E9F36A82E70C63872E296577DFED2B427DD0F7C760E4B1CA226C8DB3C63F4AA747787217B2919BB75ECD2EEA31B5F2EAB0C7405C6D484205419F607A7FB0C1EFDFBD2F677A148FEB353F6993B6C6996C79A2), (0xFEED3AFF356DD613312038AD0B3E0B73868B6F9C3754262DDFAF4F7A05F400DCAEC3F1F18330310D3D7CBA1507BB8777BB26E5EE3365C7A9C70C20861E5B0D3C1F5CFF484D19EF565A625FBA9A11262327D7BEDF0E12A949FC472A760FABEEE16F7DF5ABBB3F2AA1368F93D0050C0FB5E6FC5E469D2D2E0CB2B64C06EE7946AF737D4B1C1C7B68E8AD1CA97F7FD843EF377590134CA744414FE8F805F324B2BC48E45D88C0F3996A9476998084AE8DDA735CCE79E83362CB380ACD338362F48969EC379A7BBA2F44DC257FD60EC4BDA05B1E91975539D31582914FB62565E7318110169777F25EB82C899DC3B48BBD744C2DD864D15C3330182118EA6B791E763D4D54FEAE262868BFD86ACB9B1060F6D4DB69F88D836025DCE57E55DA744DB36A27866636BFC5260470A5F8F1679408C121158F0F26E8BAED2F21C9A7D28D4A8BB761748AF1310EAC79C31A6501776DDF7CDF65DD200F1F752287F782FE8698F7B0DE659B4D695CC78C22FFB9A89A1B5B3B9D31908E02105C1796EB93398CAEF22B22B42212D29112829D50FD0F65CC886B9B4661550868AECE0983B77EC88D00740D8E86E6F949690B1E0DF0B1124CB1C963289E454C9C23BA0554084EDA94B9104F8902D746A27151DD5636FA77FCBBBCD1ED1BA0EFBB27706BCBE0A4401A308A53E062884ADA83C0B64C67F666284209994C7CEC7819209462916C7AC1CD11C325ACA7979A3272F76A5AE9A254300A4289D70BF49D43113ABFCE26B86E08FEE93BA3D1B981AB6E66D985821E2F7E3F328729F0EAC11C25E19A6DD7CC0BB988615B0067B05F50C1DE68A41A18C1F30A418EF431451713281E7EF01F13D3A25A20FC20C22E168FD3366712821C010D20A762953E1787434947D848326E31AE0AC54AFA1BA030DB2BFF21FBDD05910B1C70D078B13E644398B8D67B56CD5C64), ('99999999-9999-9999-9999-999999999999'), ('4303-03-26 08:57:46.916'), ('2079-06-06 23:59:00'), ('10/31/2016 11:46:28 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/0110/31/2016 11:46:28 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/01'), ('02:05:50.7400842'), ('0169-06-07'), ('9999-12-31 11:59:59.9999999'), ('2669-10-09 12:27:15.2132842+00:00'))"; + break; + default: + break; + } + return $query; +} + +function Repro() +{ + StartTest("pdo_fetch_columns_fetchmode"); + try + { + FetchMode_GetAllColumnsEx(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("pdo_fetch_columns_fetchmode"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'pdo_fetch_columns_fetchmode' test... + +Comparing data in column 0 and rows +1 2 +Comparing data in column 1 and rows +1 2 +Comparing data in column 2 and rows +1 2 +Comparing data in column 3 and rows +1 2 +Comparing data in column 4 and rows +1 2 +Comparing data in column 5 and rows +1 2 +Comparing data in column 6 and rows +1 2 +Comparing data in column 7 and rows +1 2 +Comparing data in column 8 and rows +1 2 +Comparing data in column 9 and rows +1 2 +Comparing data in column 10 and rows +1 2 +Comparing data in column 11 and rows +1 2 +Comparing data in column 12 and rows +1 2 +Comparing data in column 13 and rows +1 2 +Comparing data in column 14 and rows +1 2 +Comparing data in column 15 and rows +1 2 +Comparing data in column 16 and rows +1 2 +Comparing data in column 17 and rows +1 2 +Comparing data in column 18 and rows +1 2 +Comparing data in column 19 and rows +1 2 +Comparing data in column 20 and rows +1 2 +Comparing data in column 21 and rows +1 2 +Comparing data in column 22 and rows +1 2 +Comparing data in column 23 and rows +1 2 +Comparing data in column 24 and rows +1 2 +Comparing data in column 25 and rows +1 2 +Comparing data in column 26 and rows +1 2 +Comparing data in column 27 and rows +1 2 +Comparing data in column 28 and rows +1 2 +Comparing data in column 29 and rows +1 2 +Comparing data in column 30 and rows +1 2 +Comparing data in column 31 and rows +1 2 +Done +...Test 'pdo_fetch_columns_fetchmode' completed successfully. diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_binary.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_binary.phpt new file mode 100644 index 00000000..2e38da2e --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_binary.phpt @@ -0,0 +1,105 @@ +--TEST-- +prepare with cursor buffered and fetch a varbinary column +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = 'asdgasdgasdgsadg'; + +$query = 'CREATE TABLE #TESTTABLE (exist varbinary(max))'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindParam(':p0', $sample, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); +$stmt->execute(); + +$query = 'SELECT TOP 1 * FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +string(16) "asdgasdgasdgsadg" + +no buffered cursor, stringify off, fetch_numeric on +string(16) "asdgasdgasdgsadg" + +no buffered cursor, stringify on, fetch_numeric on +string(16) "asdgasdgasdgsadg" + +no buffered cursor, stringify on, fetch_numeric off +string(16) "asdgasdgasdgsadg" + +buffered cursor, stringify off, fetch_numeric off +string(16) "asdgasdgasdgsadg" + +buffered cursor, stringify off, fetch_numeric on +string(16) "asdgasdgasdgsadg" + +buffered cursor, stringify on, fetch_numeric on +string(16) "asdgasdgasdgsadg" + +buffered cursor, stringify on, fetch_numeric off +string(16) "asdgasdgasdgsadg" diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_char.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_char.phpt new file mode 100644 index 00000000..f9f07b48 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_char.phpt @@ -0,0 +1,105 @@ +--TEST-- +prepare with cursor buffered and fetch a varchar column +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = "eight"; + +$query = 'CREATE TABLE #TESTTABLE (exist varchar(10))'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_STR); +$stmt->execute(); + +$query = 'SELECT TOP 1 * FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +string(5) "eight" + +no buffered cursor, stringify off, fetch_numeric on +string(5) "eight" + +no buffered cursor, stringify on, fetch_numeric on +string(5) "eight" + +no buffered cursor, stringify on, fetch_numeric off +string(5) "eight" + +buffered cursor, stringify off, fetch_numeric off +string(5) "eight" + +buffered cursor, stringify off, fetch_numeric on +string(5) "eight" + +buffered cursor, stringify on, fetch_numeric on +string(5) "eight" + +buffered cursor, stringify on, fetch_numeric off +string(5) "eight" diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_datetime.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_datetime.phpt new file mode 100644 index 00000000..7e48158a --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_datetime.phpt @@ -0,0 +1,105 @@ +--TEST-- +prepare with cursor buffered and fetch a datetime column +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = '2012-06-18 10:34:09'; + +$query = 'CREATE TABLE #TESTTABLE (exist datetime)'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindParam(':p0', $sample, PDO::PARAM_LOB); +$stmt->execute(); + +$query = 'SELECT TOP 1 * FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +string(23) "2012-06-18 10:34:09.000" + +no buffered cursor, stringify off, fetch_numeric on +string(23) "2012-06-18 10:34:09.000" + +no buffered cursor, stringify on, fetch_numeric on +string(23) "2012-06-18 10:34:09.000" + +no buffered cursor, stringify on, fetch_numeric off +string(23) "2012-06-18 10:34:09.000" + +buffered cursor, stringify off, fetch_numeric off +string(23) "2012-06-18 10:34:09.000" + +buffered cursor, stringify off, fetch_numeric on +string(23) "2012-06-18 10:34:09.000" + +buffered cursor, stringify on, fetch_numeric on +string(23) "2012-06-18 10:34:09.000" + +buffered cursor, stringify on, fetch_numeric off +string(23) "2012-06-18 10:34:09.000" diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_decimal.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_decimal.phpt new file mode 100644 index 00000000..daa88221 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_decimal.phpt @@ -0,0 +1,105 @@ +--TEST-- +prepare with cursor buffered and fetch a decimal column +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = 1234567890.1234; + +$query = 'CREATE TABLE #TESTTABLE (exist decimal(16, 6))'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +$query = 'SELECT TOP 1 * FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +string(17) "1234567890.123400" + +no buffered cursor, stringify off, fetch_numeric on +string(17) "1234567890.123400" + +no buffered cursor, stringify on, fetch_numeric on +string(17) "1234567890.123400" + +no buffered cursor, stringify on, fetch_numeric off +string(17) "1234567890.123400" + +buffered cursor, stringify off, fetch_numeric off +string(17) "1234567890.123400" + +buffered cursor, stringify off, fetch_numeric on +string(17) "1234567890.123400" + +buffered cursor, stringify on, fetch_numeric on +string(17) "1234567890.123400" + +buffered cursor, stringify on, fetch_numeric off +string(17) "1234567890.123400" diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_decimal_bindColumn_int.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_decimal_bindColumn_int.phpt new file mode 100644 index 00000000..99a144e4 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_decimal_bindColumn_int.phpt @@ -0,0 +1,113 @@ +--TEST-- +prepare with cursor buffered and fetch a decimal column with the column bound and specified to pdo type int +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = 1234567890.1234; + +$query = 'CREATE TABLE #TESTTABLE (exist decimal(18, 8))'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +$query = 'SELECT exist FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $decimal_col, PDO::PARAM_INT); +$value = $stmt->fetch( PDO::FETCH_BOUND ); +var_dump ($decimal_col); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $decimal_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($decimal_col); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $decimal_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($decimal_col); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $decimal_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($decimal_col); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $decimal_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($decimal_col); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $decimal_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($decimal_col); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $decimal_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($decimal_col); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $decimal_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($decimal_col); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +int(1234567890) + +no buffered cursor, stringify off, fetch_numeric on +int(1234567890) + +no buffered cursor, stringify on, fetch_numeric on +string(10) "1234567890" + +no buffered cursor, stringify on, fetch_numeric off +string(10) "1234567890" + +buffered cursor, stringify off, fetch_numeric off +int(1234567890) + +buffered cursor, stringify off, fetch_numeric on +int(1234567890) + +buffered cursor, stringify on, fetch_numeric on +string(10) "1234567890" + +buffered cursor, stringify on, fetch_numeric off +string(10) "1234567890" diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_float.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_float.phpt new file mode 100644 index 00000000..afed939a --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_float.phpt @@ -0,0 +1,141 @@ +--TEST-- +prepare with cursor buffered and fetch a float column +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = 1234567890.1234; + +$query = 'CREATE TABLE #TESTTABLE (exist float(53))'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +$query = 'SELECT TOP 1 * FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); +$ok = FlatsAreEqual($sample, $value) ? 'TRUE' : 'FALSE'; +print "\nFetched value = Input? $ok\n\n"; + +print "no buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); +$ok = FlatsAreEqual($sample, $value) ? 'TRUE' : 'FALSE'; +print "\nFetched value = Input? $ok\n\n"; + +print "no buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); +$ok = FlatsAreEqual($sample, $value) ? 'TRUE' : 'FALSE'; +print "\nFetched value = Input? $ok\n\n"; + +print "no buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); +$ok = FlatsAreEqual($sample, $value) ? 'TRUE' : 'FALSE'; +print "\nFetched value = Input? $ok\n\n"; + +//prepare with client buffered cursor +print "buffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); +$ok = FlatsAreEqual($sample, $value) ? 'TRUE' : 'FALSE'; +print "\nFetched value = Input? $ok\n\n"; + +print "buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); +$ok = FlatsAreEqual($sample, $value) ? 'TRUE' : 'FALSE'; +print "\nFetched value = Input? $ok\n\n"; + +print "buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); +$ok = FlatsAreEqual($sample, $value) ? 'TRUE' : 'FALSE'; +print "\nFetched value = Input? $ok\n\n"; + +print "buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); +$ok = FlatsAreEqual($sample, $value) ? 'TRUE' : 'FALSE'; +print "\nFetched value = Input? $ok\n\n"; + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +string(15) "1234567890.1234" + +Fetched value = Input? TRUE + +no buffered cursor, stringify off, fetch_numeric on +float(1234567890.1234) + +Fetched value = Input? TRUE + +no buffered cursor, stringify on, fetch_numeric on +string(15) "1234567890.1234" + +Fetched value = Input? TRUE + +no buffered cursor, stringify on, fetch_numeric off +string(15) "1234567890.1234" + +Fetched value = Input? TRUE + +buffered cursor, stringify off, fetch_numeric off +string(15) "1234567890.1234" + +Fetched value = Input? TRUE + +buffered cursor, stringify off, fetch_numeric on +float(1234567890.1234) + +Fetched value = Input? TRUE + +buffered cursor, stringify on, fetch_numeric on +string(15) "1234567890.1234" + +Fetched value = Input? TRUE + +buffered cursor, stringify on, fetch_numeric off +string(15) "1234567890.1234" + +Fetched value = Input? TRUE diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_float_bindColumn_lob.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_float_bindColumn_lob.phpt new file mode 100644 index 00000000..01c3d60b --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_float_bindColumn_lob.phpt @@ -0,0 +1,117 @@ +--TEST-- +prepare with cursor buffered and fetch a float column with the column bound and specified to type LOB +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = 1234567890.1234; + +$query = 'CREATE TABLE #TESTTABLE (exist float(53))'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +$query = 'SELECT exist FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $float_col, PDO::PARAM_LOB); +$value = $stmt->fetch(); +var_dump ($float_col); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $float_col, PDO::PARAM_LOB); +$value = $stmt->fetch(); +var_dump ($float_col); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $float_col, PDO::PARAM_LOB); +$value = $stmt->fetch(); +var_dump ($float_col); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $float_col, PDO::PARAM_LOB); +$value = $stmt->fetch(); +var_dump ($float_col); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $float_col, PDO::PARAM_LOB); +$value = $stmt->fetch(); +var_dump ($float_col); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $float_col, PDO::PARAM_LOB); +$value = $stmt->fetch(); +var_dump ($float_col); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $float_col, PDO::PARAM_LOB); +$value = $stmt->fetch(); +var_dump ($float_col); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $float_col, PDO::PARAM_LOB); +$value = $stmt->fetch(); +var_dump ($float_col); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +string(15) "1234567890.1234" + +no buffered cursor, stringify off, fetch_numeric on +string(15) "1234567890.1234" + +no buffered cursor, stringify on, fetch_numeric on +string(15) "1234567890.1234" + +no buffered cursor, stringify on, fetch_numeric off +string(15) "1234567890.1234" + +buffered cursor, stringify off, fetch_numeric off +string(15) "1234567890.1234" + +buffered cursor, stringify off, fetch_numeric on +string(15) "1234567890.1234" + +buffered cursor, stringify on, fetch_numeric on +string(15) "1234567890.1234" + +buffered cursor, stringify on, fetch_numeric off +string(15) "1234567890.1234" diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_int.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_int.phpt new file mode 100644 index 00000000..8c6f65fc --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_int.phpt @@ -0,0 +1,105 @@ +--TEST-- +prepare with cursor buffered and fetch a int column +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = 1234567890; + +$query = 'CREATE TABLE #TESTTABLE (exist int)'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +$query = 'SELECT TOP 1 * FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +string(10) "1234567890" + +no buffered cursor, stringify off, fetch_numeric on +int(1234567890) + +no buffered cursor, stringify on, fetch_numeric on +string(10) "1234567890" + +no buffered cursor, stringify on, fetch_numeric off +string(10) "1234567890" + +buffered cursor, stringify off, fetch_numeric off +string(10) "1234567890" + +buffered cursor, stringify off, fetch_numeric on +int(1234567890) + +buffered cursor, stringify on, fetch_numeric on +string(10) "1234567890" + +buffered cursor, stringify on, fetch_numeric off +string(10) "1234567890" diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_int_bindColumn_int.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_int_bindColumn_int.phpt new file mode 100644 index 00000000..623f3327 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_int_bindColumn_int.phpt @@ -0,0 +1,114 @@ +--TEST-- +prepare with cursor buffered and fetch a int column with the column bound and specified as pdo type int +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + +$sample = 1234567890; + +$query = 'CREATE TABLE #TESTTABLE (exist int)'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +$query = 'SELECT exist FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $int_col, PDO::PARAM_INT); +$value = $stmt->fetch( PDO::FETCH_BOUND ); +var_dump ($int_col); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $int_col, PDO::PARAM_INT); +$value = $stmt->fetch( PDO::FETCH_BOUND ); +var_dump ($int_col); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $int_col, PDO::PARAM_INT); +$value = $stmt->fetch( PDO::FETCH_BOUND ); +var_dump ($int_col); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $int_col, PDO::PARAM_INT); +$value = $stmt->fetch( PDO::FETCH_BOUND ); +var_dump ($int_col); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $int_col, PDO::PARAM_INT); +$value = $stmt->fetch( PDO::FETCH_BOUND ); +var_dump ($int_col); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $int_col, PDO::PARAM_INT); +$value = $stmt->fetch( PDO::FETCH_BOUND ); +var_dump ($int_col); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $int_col, PDO::PARAM_INT); +$value = $stmt->fetch( PDO::FETCH_BOUND ); +var_dump ($int_col); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $int_col, PDO::PARAM_INT); +$value = $stmt->fetch( PDO::FETCH_BOUND ); +var_dump ($int_col); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +int(1234567890) + +no buffered cursor, stringify off, fetch_numeric on +int(1234567890) + +no buffered cursor, stringify on, fetch_numeric on +string(10) "1234567890" + +no buffered cursor, stringify on, fetch_numeric off +string(10) "1234567890" + +buffered cursor, stringify off, fetch_numeric off +int(1234567890) + +buffered cursor, stringify off, fetch_numeric on +int(1234567890) + +buffered cursor, stringify on, fetch_numeric on +string(10) "1234567890" + +buffered cursor, stringify on, fetch_numeric off +string(10) "1234567890" diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_money.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_money.phpt new file mode 100644 index 00000000..e6502be2 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_money.phpt @@ -0,0 +1,105 @@ +--TEST-- +prepare with cursor buffered and fetch a money column +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = 1234567890.1234; + +$query = 'CREATE TABLE #TESTTABLE (exist money)'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +$query = 'SELECT TOP 1 * FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +string(15) "1234567890.1234" + +no buffered cursor, stringify off, fetch_numeric on +string(15) "1234567890.1234" + +no buffered cursor, stringify on, fetch_numeric on +string(15) "1234567890.1234" + +no buffered cursor, stringify on, fetch_numeric off +string(15) "1234567890.1234" + +buffered cursor, stringify off, fetch_numeric off +string(15) "1234567890.1234" + +buffered cursor, stringify off, fetch_numeric on +string(15) "1234567890.1234" + +buffered cursor, stringify on, fetch_numeric on +string(15) "1234567890.1234" + +buffered cursor, stringify on, fetch_numeric off +string(15) "1234567890.1234" diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_money_bindColumn_int.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_money_bindColumn_int.phpt new file mode 100644 index 00000000..328a4cab --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_money_bindColumn_int.phpt @@ -0,0 +1,115 @@ +--TEST-- +prepare with cursor buffered and fetch a money column with the column bound and specified as pdo type int +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + +$sample = 1234567890.1234; + +$query = 'CREATE TABLE #TESTTABLE (exist money)'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +$query = 'SELECT exist FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $money_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($money_col); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $money_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($money_col); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $money_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($money_col); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$stmt->bindColumn('exist', $money_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($money_col); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $money_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($money_col); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $money_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($money_col); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $money_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($money_col); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$stmt->bindColumn('exist', $money_col, PDO::PARAM_INT); +$value = $stmt->fetch(PDO::FETCH_BOUND); +var_dump ($money_col); + + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +int(1234567890) + +no buffered cursor, stringify off, fetch_numeric on +int(1234567890) + +no buffered cursor, stringify on, fetch_numeric on +string(10) "1234567890" + +no buffered cursor, stringify on, fetch_numeric off +string(10) "1234567890" + +buffered cursor, stringify off, fetch_numeric off +int(1234567890) + +buffered cursor, stringify off, fetch_numeric on +int(1234567890) + +buffered cursor, stringify on, fetch_numeric on +string(10) "1234567890" + +buffered cursor, stringify on, fetch_numeric off +string(10) "1234567890" diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_multicolumns.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_multicolumns.phpt new file mode 100644 index 00000000..096b3c68 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_multicolumns.phpt @@ -0,0 +1,271 @@ +--TEST-- +prepare with cursor buffered and fetch from numeric columns. +--DESCRIPTION-- +Uses buffered cursor to fetch from float, int, and decimal columns that have positive, negative and zero value. +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = 1234567890.1234; +$sample1 = -1234567890.1234; +$sample2 = 1; +$sample3 = -1; +$sample4 = 0.5; +$sample5 = -0.55; + +$query = 'CREATE TABLE #TESTTABLE (a float(53), neg_a float(53), b int, neg_b int, c decimal(16, 6), neg_c decimal(16, 6), zero int, zerof float(53), zerod decimal(16,6))'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0, :p1, :p2, :p3, :p4, :p5, 0, 0, 0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->bindValue(':p1', $sample1, PDO::PARAM_INT); +$stmt->bindValue(':p2', $sample2, PDO::PARAM_INT); +$stmt->bindValue(':p3', $sample3, PDO::PARAM_INT); +$stmt->bindValue(':p4', $sample4, PDO::PARAM_INT); +$stmt->bindValue(':p5', $sample5, PDO::PARAM_INT); +$stmt->execute(); + +$query = 'SELECT TOP 1 * FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "\nno buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetch(PDO::FETCH_NUM); +var_dump ($value); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetch(PDO::FETCH_NUM); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetch(PDO::FETCH_NUM); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetch(PDO::FETCH_NUM); +var_dump ($value); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetch(PDO::FETCH_NUM); +var_dump ($value); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetch(PDO::FETCH_NUM); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetch(PDO::FETCH_NUM); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetch(PDO::FETCH_NUM); +var_dump ($value); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +array(9) { + [0]=> + string(15) "1234567890.1234" + [1]=> + string(16) "-1234567890.1234" + [2]=> + string(1) "1" + [3]=> + string(2) "-1" + [4]=> + string(7) ".500000" + [5]=> + string(8) "-.550000" + [6]=> + string(1) "0" + [7]=> + string(3) "0.0" + [8]=> + string(7) ".000000" +} + +no buffered cursor, stringify off, fetch_numeric on +array(9) { + [0]=> + float(1234567890.1234) + [1]=> + float(-1234567890.1234) + [2]=> + int(1) + [3]=> + int(-1) + [4]=> + string(7) ".500000" + [5]=> + string(8) "-.550000" + [6]=> + int(0) + [7]=> + float(0) + [8]=> + string(7) ".000000" +} + +no buffered cursor, stringify on, fetch_numeric on +array(9) { + [0]=> + string(15) "1234567890.1234" + [1]=> + string(16) "-1234567890.1234" + [2]=> + string(1) "1" + [3]=> + string(2) "-1" + [4]=> + string(7) ".500000" + [5]=> + string(8) "-.550000" + [6]=> + string(1) "0" + [7]=> + string(1) "0" + [8]=> + string(7) ".000000" +} + +no buffered cursor, stringify on, fetch_numeric off +array(9) { + [0]=> + string(15) "1234567890.1234" + [1]=> + string(16) "-1234567890.1234" + [2]=> + string(1) "1" + [3]=> + string(2) "-1" + [4]=> + string(7) ".500000" + [5]=> + string(8) "-.550000" + [6]=> + string(1) "0" + [7]=> + string(3) "0.0" + [8]=> + string(7) ".000000" +} + +buffered cursor, stringify off, fetch_numeric off +array(9) { + [0]=> + string(15) "1234567890.1234" + [1]=> + string(16) "-1234567890.1234" + [2]=> + string(1) "1" + [3]=> + string(2) "-1" + [4]=> + string(7) ".500000" + [5]=> + string(8) "-.550000" + [6]=> + string(1) "0" + [7]=> + string(1) "0" + [8]=> + string(7) ".000000" +} + +buffered cursor, stringify off, fetch_numeric on +array(9) { + [0]=> + float(1234567890.1234) + [1]=> + float(-1234567890.1234) + [2]=> + int(1) + [3]=> + int(-1) + [4]=> + string(7) ".500000" + [5]=> + string(8) "-.550000" + [6]=> + int(0) + [7]=> + float(0) + [8]=> + string(7) ".000000" +} + +buffered cursor, stringify on, fetch_numeric on +array(9) { + [0]=> + string(15) "1234567890.1234" + [1]=> + string(16) "-1234567890.1234" + [2]=> + string(1) "1" + [3]=> + string(2) "-1" + [4]=> + string(7) ".500000" + [5]=> + string(8) "-.550000" + [6]=> + string(1) "0" + [7]=> + string(1) "0" + [8]=> + string(7) ".000000" +} + +buffered cursor, stringify on, fetch_numeric off +array(9) { + [0]=> + string(15) "1234567890.1234" + [1]=> + string(16) "-1234567890.1234" + [2]=> + string(1) "1" + [3]=> + string(2) "-1" + [4]=> + string(7) ".500000" + [5]=> + string(8) "-.550000" + [6]=> + string(1) "0" + [7]=> + string(1) "0" + [8]=> + string(7) ".000000" +} + diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_strings_to_integers.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_strings_to_integers.phpt new file mode 100644 index 00000000..ff0e1a8b --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_strings_to_integers.phpt @@ -0,0 +1,232 @@ +--TEST-- +prepare with cursor buffered and fetch various columns with the column bound and specified to pdo type int +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + + $decimal = -2345209.3103; + $numeric = 987234.9919; + $salary = "3456789.15"; + $debt = "98765.99"; + + $query = 'CREATE TABLE #TESTTABLE ([c_decimal] decimal(28,4), [c_numeric] numeric(32,4), [c_varchar] varchar(20), [c_nvarchar] nvarchar(20))'; + + $stmt = $conn->exec($query); + + $query = 'INSERT INTO #TESTTABLE VALUES(:p0, :p1, :p2, :p3)'; + $stmt = $conn->prepare($query); + $stmt->bindValue(':p0', $decimal); + $stmt->bindValue(':p1', $numeric); + $stmt->bindValue(':p2', $salary); + $stmt->bindValue(':p3', $debt); + $stmt->execute(); + + $decimal2 = $decimal * 2; + $numeric2 = $numeric * 2; + $salary2 = $salary * 2; + $debt2 = $debt * 2; + + $stmt->bindValue(':p0', $decimal2); + $stmt->bindValue(':p1', $numeric2); + $stmt->bindValue(':p2', $salary2); + $stmt->bindValue(':p3', $debt2); + $stmt->execute(); + + $decimal3 = $decimal * 3; + $numeric3 = $numeric * 3; + $salary3 = $salary * 3; + $debt3 = $debt * 3; + + $stmt->bindValue(':p0', $decimal3); + $stmt->bindValue(':p1', $numeric3); + $stmt->bindValue(':p2', $salary3); + $stmt->bindValue(':p3', $debt3); + $stmt->execute(); + + $stmt = null; + + echo ("Input values:\n\torginal:$decimal\t$numeric\t$salary\t$debt\n\tdoubles:$decimal2\t$numeric2\t$salary2\t$debt2\n\ttriples:$decimal3\t$numeric3\t$salary3\t$debt3\n"); + + $query = 'SELECT * FROM #TESTTABLE'; + + // prepare with no buffered cursor + echo "\n\nComparing results (stringify off, fetch_numeric on):\n"; + // no buffered cursor, stringify off, fetch_numeric on + $conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + $stmt1 = $conn->prepare($query); + $stmt1->execute(); + + // buffered cursor, stringify off, fetch_numeric on + $conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + $stmt2 = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt2->execute(); + + compareResults($stmt1, $stmt2); + + $stmt1 = null; + $stmt2 = null; + + echo "\n\nComparing results (stringify off, fetch_numeric off):\n"; + // no buffered cursor, stringify off, fetch_numeric off + $conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); + $conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $stmt1 = $conn->prepare($query); + $stmt1->execute(); + + // buffered cursor, stringify off, fetch_numeric off + $conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); + $conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $stmt2 = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt2->execute(); + + compareResults($stmt1, $stmt2); + + $stmt1 = null; + $stmt2 = null; + + $conn = null; +} + +function compareResults($stmt1, $stmt2) +{ + $stmt1->bindColumn('c_decimal', $decimal_col1, PDO::PARAM_INT); + $stmt1->bindColumn('c_numeric', $numeric_col1, PDO::PARAM_INT); + $stmt1->bindColumn('c_varchar', $salary_col1, PDO::PARAM_INT); + $stmt1->bindColumn('c_nvarchar', $debt_col1, PDO::PARAM_INT); + + $stmt2->bindColumn('c_decimal', $decimal_col2, PDO::PARAM_INT); + $stmt2->bindColumn('c_numeric', $numeric_col2, PDO::PARAM_INT); + $stmt2->bindColumn('c_varchar', $salary_col2, PDO::PARAM_INT); + $stmt2->bindColumn('c_nvarchar', $debt_col2, PDO::PARAM_INT); + + $numRows = 3; + for ($i = 1; $i <= $numRows; $i++) + { + echo "\nreading row " . $i . "\n"; + + $value1 = $stmt1->fetch( PDO::FETCH_BOUND ); + $value2 = $stmt2->fetch( PDO::FETCH_BOUND ); + + compareData($decimal_col1, $decimal_col2); + compareData($numeric_col1, $numeric_col2); + compareData($salary_col1, $salary_col2); + compareData($debt_col1, $debt_col2); + } +} + +function compareData($data1, $data2) +{ + if ($data1 != $data2) + echo "Not matched!\n"; + else + echo "Matched!\n"; + + echo ("\tExpected: "); var_dump ($data1); + echo ("\tActual: "); var_dump ($data2); +} + +test(); + +?> +--EXPECT-- +Input values: + orginal:-2345209.3103 987234.9919 3456789.15 98765.99 + doubles:-4690418.6206 1974469.9838 6913578.3 197531.98 + triples:-7035627.9309 2961704.9757 10370367.45 296297.97 + + +Comparing results (stringify off, fetch_numeric on): + +reading row 1 +Matched! + Expected: int(-2345209) + Actual: int(-2345209) +Matched! + Expected: int(987234) + Actual: int(987234) +Matched! + Expected: int(3456789) + Actual: int(3456789) +Matched! + Expected: int(98765) + Actual: int(98765) + +reading row 2 +Matched! + Expected: int(-4690418) + Actual: int(-4690418) +Matched! + Expected: int(1974469) + Actual: int(1974469) +Matched! + Expected: int(6913578) + Actual: int(6913578) +Matched! + Expected: int(197531) + Actual: int(197531) + +reading row 3 +Matched! + Expected: int(-7035627) + Actual: int(-7035627) +Matched! + Expected: int(2961704) + Actual: int(2961704) +Matched! + Expected: int(10370367) + Actual: int(10370367) +Matched! + Expected: int(296297) + Actual: int(296297) + + +Comparing results (stringify off, fetch_numeric off): + +reading row 1 +Matched! + Expected: int(-2345209) + Actual: int(-2345209) +Matched! + Expected: int(987234) + Actual: int(987234) +Matched! + Expected: int(3456789) + Actual: int(3456789) +Matched! + Expected: int(98765) + Actual: int(98765) + +reading row 2 +Matched! + Expected: int(-4690418) + Actual: int(-4690418) +Matched! + Expected: int(1974469) + Actual: int(1974469) +Matched! + Expected: int(6913578) + Actual: int(6913578) +Matched! + Expected: int(197531) + Actual: int(197531) + +reading row 3 +Matched! + Expected: int(-7035627) + Actual: int(-7035627) +Matched! + Expected: int(2961704) + Actual: int(2961704) +Matched! + Expected: int(10370367) + Actual: int(10370367) +Matched! + Expected: int(296297) + Actual: int(296297) diff --git a/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_unicode.phpt b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_unicode.phpt new file mode 100644 index 00000000..b8174cba --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_cursorBuffered_unicode.phpt @@ -0,0 +1,105 @@ +--TEST-- +prepare with cursor buffered and fetch a nvarchar column +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$sample = "가각"; + +$query = 'CREATE TABLE #TESTTABLE (exist nvarchar(10))'; +$stmt = $conn->exec($query); +$query = 'INSERT INTO #TESTTABLE VALUES(:p0)'; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_STR); +$stmt->execute(); + +$query = 'SELECT TOP 1 * FROM #TESTTABLE'; + +//prepare with no buffered cursor +print "no buffered cursor, stringify off, fetch_numeric off\n"; //stringify and fetch_numeric is off by default +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nno buffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +//prepare with client buffered cursor +print "\nbuffered cursor, stringify off, fetch_numeric off\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify off, fetch_numeric on\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric on\n"; +$conn->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +print "\nbuffered cursor, stringify on, fetch_numeric off\n"; +$conn->setAttribute( PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +$stmt = null; +$conn = null; + +?> +--EXPECT-- +no buffered cursor, stringify off, fetch_numeric off +string(6) "가각" + +no buffered cursor, stringify off, fetch_numeric on +string(6) "가각" + +no buffered cursor, stringify on, fetch_numeric on +string(6) "가각" + +no buffered cursor, stringify on, fetch_numeric off +string(6) "가각" + +buffered cursor, stringify off, fetch_numeric off +string(6) "가각" + +buffered cursor, stringify off, fetch_numeric on +string(6) "가각" + +buffered cursor, stringify on, fetch_numeric on +string(6) "가각" + +buffered cursor, stringify on, fetch_numeric off +string(6) "가각" diff --git a/test/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt b/test/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt new file mode 100644 index 00000000..2d0641e3 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt @@ -0,0 +1,134 @@ +--TEST-- +fetch columns using fetch mode and different ways of binding columns +--SKIPIF-- + +--FILE-- +exec("CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_nchar] nchar(512), [c16_nvarchar] nvarchar(512), [c17_nvarchar_max] nvarchar(max), [c18_text] text, [c19_ntext] ntext, [c20_binary] binary(512), [c21_varbinary] varbinary(512), [c22_varbinary_max] varbinary(max), [c23_image] image, [c24_uniqueidentifier] uniqueidentifier, [c25_datetime] datetime, [c26_smalldatetime] smalldatetime, [c27_timestamp] timestamp, [c28_xml] xml, [c29_time] time, [c30_date] date, [c31_datetime2] datetime2, [c32_datetimeoffset] datetimeoffset)"); + + $numRows = 0; + $query = GetQuery($tableName, ++$numRows); + $stmt = $conn->query($query); + + $query = GetQuery($tableName, ++$numRows); + $stmt = $conn->query($query); + $stmt = null; + + $sql = "SELECT * FROM $tableName ORDER BY c27_timestamp"; + $obj1 = new PdoTestClass(); + $stmt1 = $conn->query($sql, PDO::FETCH_INTO, $obj1); + + $obj2 = new PdoTestClass2(1, 2); + $stmt2 = $conn->prepare($sql); + $result = $stmt2->execute(); + $stmt2->setFetchMode(PDO::FETCH_INTO, $obj2); + + VerifyResults($stmt1, $stmt2, $tableName); + + $stmt1 = null; + $stmt2 = null; + $conn = null; +} + +function VerifyResults($stmt1, $stmt2, $tableName) +{ + $numFields = $stmt1->columnCount(); + + $i = 0; + while ($obj1 = $stmt1->fetch()) + { + $obj2 = $stmt2->fetch(); + + echo "Comparing data in row " . ++$i . "\n"; + $query = GetQuery($tableName, $i); + $dataArray = InsertDataToArray($stmt1, $query, $numFields, $i); + + CheckObject($stmt1, $obj1, $i, $dataArray); + CheckObject($stmt2, $obj2, $i, $dataArray); + } +} + +function CheckObject($stmt, $obj, $row, $dataArray) +{ + $j = 0; + foreach ($obj as $value) + { + CompareData($stmt, $row, $j, $value, $dataArray[$j]); + $j++; + } +} + +function GetQuery($tableName, $index) +{ + $query = ""; + switch ($index) + { + case 1: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((-218991553), (1), (23494), (1864173810), (0), (1), (-3.4E+38), (0.3607), (0.6259), (0.3426), (0.7608), ('bAB:+:|©ßÄ.:B¢UBüÖÄýÃ<ß©ÖaÃbvßZö+/AZ:rzvbåvh_£+Ðr<Äbý.ßãr@výߣöÜ£|B©~ö©Ö/:ðßr©CB©öäßbuöö£rBðö*:/ö>>ý|ÄoUßö¢|>ý£_.O*åÜCÄ.ªha,ãÖßBý>äÐOC_üߪah¢.:©~~@Öý|£ßßb..ÃÜ.bC@|ýCî+v£,BOB|ßrÃ+ßvubC:äAöBÐCZarüaBã<|©uUC¢~.|~ýÃbý>£@Ca_Ð.îÖäÐo££.ªZ¢bU£CýðUå*vCoßý@©bAz£_ª.obüÄ¢öÖhüÄbaÖä_@rÖ£.hß~bУü~'), ('B:ä.ß.å.bbCÜO,UÖ+¢rBßoð>zÖzZzä+Ö<Ð:©oÐ@,ýÜößbüAä**U@b|Or:ýbAb~brÃ+Z:öªª£BãBauC.*Ä<öý¢äÄð_uro£>Z/ÄÐCЩ~ðÖТ.u:oýz~Ð*¢¢bªß©,ðÄ,:ý¢_ÃZ>ýéC_rva:>>+Uå~îo©ß£hZbZ©r,Z¢ßÃÖä>*îbrb/ß~,oå|:>äb<äî©+Ä¢,:zãü,©ßÖ>Ü|Ããöª+ß~ðî/CÖå/_*ßãrh£vÃÜÖªãOÄ+CbÃðOU@v_ãÜÜÐöz|ö_äzýîZ¢ð.hOãÜý,r_Z'), ('UZß_Z++ä_Ö:r©Z*,,uÖbýÜB*:Üä.~r©U>ýZ|@<ªövbä.Ö¢CÄAßobzaü>r>b_@öZ*bOÄ©aÜåî¢ßßðîÃ~ß©h+ÖA~ð>aß+a,b:zß_*|ð:ÄvðU:@rAª@îvä+ª_z>vr©r/å__+>h,+Aîu:Bßßh.ZýrãCCrbÐUuãvÄß>ãbÐîoÐîuoä+£î>Ä_+åð©a~Ö/zvÜåh@ÖO:öUÜbuU|BýÃUÖ~Ou_Ä<.ÜC>vZZ~åüab£BOOöUö:h¢+~Aî@©ÖB_+:b+.z<ý<Ü£,ªBîAh~.uüªU*Ü.|<+ߣãÜ/£¢ªÖÜä>U>Ã>UÜ>Cä+b+å|ÜrÄ~ýÜý¢ÃCߢO:Z:.ß_ÐÐB|ã_£b+@/|>åäå.åoÜB@£ß~ãªAß/üvßoã©©åb+Ä£CU~ZÐz|å|uA:rBZühåba_b:v©ÐauÐaB/,å@|ý.îv>ðäÖU,öÖ+ÃÐOZÃBAßZ¢azzo|h¢b*~ÖýÖüß_ªrÄAZÜvåAZðCÃÜ<ÖÄÜîüoÜzª¢zªo¢Äür@o@ãBv*ub:Cvu>,~BýZýbã+ßîB.üoBv*zĪ@v££//©îУA,hðAb¢/ýhÃßÃðaä,böoßü@ååz<+ä|/ð¢oäÄvAhA£î*~ðÄ~У|bªü|äbåUbЪv~/ÄÜo@bZª*öð_î¢ðüoÐÖÄZ@ãübzßãaü./,*îäo|b©+öÄ£AAü:öZîýä|vîåßBrv:ª,_@h|ÐbߢÃb**ßOðA+*îÄb.~ßbýý*:ä.ð+*C_î.Ðå>ZzÜuöBÄåbäð_åzhöz@Ðb,Bßö££<öîz@©|AhÄ,ãßaßßOA@£+|>ýüZªßýüöð£ÐO/vÄ*UüUª@Ãå@>BÃz>@>+/£oBÖî'), (N'üªrobã|ß*Ã@hrUãÐ.vöß©@ßz_uu,Ä_,Ã+hÜ*ö£uª£ä£Ãå<Ãvb:Ä<ö@o<Üå£ãªObßzãÜb<@ÜÖb+Oßäh:b_ö*vãîÐäüz|ba,ý<Üã,Aâß|Ãö/ª*+ª£:~z£äßü/BðCoöÄÖ©+ÄAAÖ.,îhߪå>h_ªÐð¢:b+¢üåöðÜ@£ªüOÖu¢*~åbO.>buböü,@:ABz~aBbhß<ª.@O¢ÃÐîîUoA|UÃä_Ðßaü,ýr~äuÖ:C~Büý.ãZßÖ|ðÜüý*_ªCrr.å*¢Uaðßb>ß:©|aüî@|Bð£öä'), (N'ZO*åuÐãªî,~+,Üb.|v.>:u.:ÄhbÃÖ<ª+a|ÐOðAÐu+.|CÜa@BÃ@|oh|~ÜboavÃu,ðîÜ<©.Aä¢|rbuäOrZÐhA~CzB|:©ð¢ð>+ðäöÄ||rßߪÃîO,Oã_hÖ:Oaå©ð+£~îOÃobCBýb,äð.ubÐä:OBUã.O|zßåZ:åCÄߣððîåBhé|£ÄAoBÃo:,ö/ýßU*bAvß>A*îoüäÃ'), (N'©~¢@©ß.OO¢z:ÖÜ+£CÃ~ãbäîo_ZßäOUýÜv¢vä,Z~_©äBoO~ªhß*ÖaßzÃßÃ<ý+ý_ýö£_oC@@vßBAOrÖÐýöÄ©bä@ýöüð:oîzu_Cäo_£.<£Zbðîßh>büC:£ªrOb*+@å>b*ªr+_uaÖß~boß:.r/v|r¢ÐÐåbrv~üÃ/Azbh:ÖC>:zÐzä~/,Ö©Cðu*Z/¢åzZ*ö:ßChbäÐzöåöOÄ@~£/Щ¢¢£aöuhßboBroCÐ>¢C*~/h£b,ýãÖhÖ|£+£hã~.~üäu£+zzßUuårü<ߢ*ý~£ü..£Zª<ã_üîîu_+ÄO@vÐbr~_v|~O~AßäaÖ¢~<~*ÖäoAî>îÜUªo@åªözrZªhb>ªýhoOäܪrªÖ~rÜCZb>|*ãß©röüß@©boåuÖåoîÐZAa£,~Üz,orîuBªÐ©ß,ÃbBhüð£u£ðÖO'), ('ýß/Zãz~üöߣh:©Ð|ßaa@@Üh.ÄUÄh©*uaUb©a£Ã+o:ß@Ü'), (N'OäACaÜuÄ+.Ov@ÐÖZö.Üvåub,zã+ÃÜð_Ö>ýÜß>+uUa,z¢<.ä/Ä@~+ßßýöÜ£ßo.|ö/ä/Ü@üöߪO_v@ð:ðîýðýU+oðÃaãaã|bã~ÃbÃ@B@hðüAhÃßbAÄåv£hª*öý+ÄAvÄrðoßöårÐ@bbBÖo|>¢*ö£Üða@:vußZ@+ÖÐßÃßÄC**_CCÜ.bäO.@Ð,b¢övð£+é.:aÄz,ü>ÖÜ>ý¢C£ßUB,.A.ßÜýÃBã.îbÜ©ÃCÃBbBÃîÄ£Ðu,:@Z<©_üv¢ý©uÄ£vCbãÄÐzðýöÄoaÄ@*Üb|ªuª_öu_ß,bCAå,b@@*Ö@_vÖZu~ÃB.©~hªoððäåðãÜ_vÖÄübb_ÄBOOÐa_£Üî:OZbÖr>rã~h©£/ðÐýýa~v_£|Ã~,üåo.ZBü<£><__uÖu>,b<ÜUýA|¢r¢ð|Ð,oß@zÃOä|©övu@:<*@zoO¢C¢ÄåhäÃå+|BÜB+ÐÐ:_z_>ßßýÜ@BhäC¢._Öbrß:_*OÃz.o:/bÖ¢bÜã©hzßÐC¢Ö_AuöÃU*z@ðaýü+ß>/ßzBa.uAã|å¢Ð/b*ð~££b:>rbÜuÄbÜåZzã+îß_ÖhßrBh+ЪBã©b.ÄOC|<*Ouåå£*©zÐb£Ðb_*ý£_©Ð~ª/aÃãr©bðªCÐvZÖUZ<ÜäÜ,>/ªo/ÜüîªîÖÖ~ßåOßCýuß:_aÜuªÖ*båäßröªB©ßîBaarý@Äö,B©£ä_~Aåª,OOu~_aåö,ävBC*Bhß:zAv_£,u,UýZ£îÄ>v¢Ðå:>rB~zBBý|ZÜ,Öª~~C£_ä*hÜî©Ðö~¢ãÐ,U+öCðÃr_vuãöuîåzhðoüOAB*:Oðªh_,Ä~,C.£<£:/a~BZß©Öä£/ÃÃÖruv¢©ÐÜ¢.Öäh+ãÜ>©©'), (0x69C465C11581CB199C423F5ADAB10D08FBC98CA537ADE0F745A40A46E7EBF13678AE4020751008ECA0A59DA462FD031A024E5DAA578E93BBE36D7C70778431B15008E22B71ED93EAC0A005920F3B9548A1EF44998D47DEEA1B843C089397741B74EC545A1AD8A815BB72EAB28BDC95E087D8B022B9638070135CE9526220C976972887D7543078AE083DDDA0A6FBC5F3290CCE9A1A2F6408C3D27BB9BDCEAE4527A23B6354BE8C575F01197B85E0CA1796F62956522F5B68C4), (0xB5BFF13A8F987804C0629E6A7F763A1816E882E3076322C6A557F2F746D39ED5AB0AA2A010DEECED67C6573457BED6DCF44352DBFC2F79519C2A9537A580BEEDC3C8BF75D49A0837FB957B410BEE6FE68016030281C3D97B905004E649B447E6086A708EFF61075A3B1ADF67B33845AEBBAEB78F7DBB6C00EF44E4839622EE474D9539A17DE0E71F0EA8C766E371C7580A552A9AFDB8684DFE709FBA7C84C4182C981A1EFFA431DAC79780D5F3051D7D7D8C7840F75349942CEBA30C402CA28AE3AE394A57A858662450D365389359F853213DEF0C8421D942F6116302D83057244BF49BA56446ACC22AA085B4B9454FE721BD907B3C2938A988F49A96F9C13464E468E3D573B75C62FB87A2ABC7D458B0C17CE01BA0DBE821BBF21C6FAB16C9686B20D0506279736A612E80B4878CE1B39BEAD47DE119C40880C219809BECB7E5BE3A40A604084D470876), (0x5B33992F1168D4C0414C87515CC7D0A060F2CE75779BF64C4B3AAF939C8D746A5F53AD4D28EA6F3318F6A372B3EE4A3D38ADE716A844FAC03C4DE3D7B84024B3D71CF2091A827875839B5D80F784995DEF725BEFA65EC21A58ED10DB06987AD7D2F09A96FC4122B53571920CE779C8B8B386E746E4350D795AD7B02C729C6FEB2185AE07E90260AD4FA6B935CBB533DE2457277FB033F29DE2203C2591F6439801A7EB61CBC1CEB0263AF73E7BD8AEA4A3D1081A75EF3BED921024C9C63B1C5963213324C9D11362897659763CB4210A729781BFED28221260CCB318AC9AA23B8A73706F8495DAAA9F6ABFD2A58F22DC3066DB9712B4CE930E9B04D96E63906F9B871585067738C1143FFB3D03D5C8A0AAD7E745BF0B29D6A4DD60E891013B3BD94F371E7A9DD5B92D1983D2B9919038D2632F967C4BB08F5D3EE0291650301303BF63B39EAA260C012E17A587561C124D27162DD1A4BF8E9EA7C03BBCACA2DDE870CFF92DDC9A273C22944D330B080722CEDCEE3902415FFFD515F7EAEB50558B3ABD178723E5708DEEF0AE0F725B939EB919EED0B3A3E52E6FF958C7A33D51D23FC2F94DF07FFEE96B614B11924F8D7A815830D26285D796D7E817298CA8E9B09FDCD0AF6145AE391A05645157D49B206BDD464A0C7F6FB9EA62760768D55C), (null), (null), (null), ('2079-06-06 23:59:00'), ('10/31/2016 11:46:19 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/0110/31/2016 11:46:19 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/01'), (null), ('0001-01-01'), ('0498-06-25 12:16:38.7590909'), ('0534-02-18 04:26:43.4190075+00:00'))"; + break; + case 2: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((1), (1), (22772), (-9223372036854775808), (0), (1), (-2.3), (0.9047), (0.6343), (-922337203685477.5808), (-214748.3648), (null), ('ä|uÜC£r¢@Ü_@vC~bzCOBÖrö~ÖÜ/Ü~ýÐüðvbvªbÐ.özoÃÃå£rb:>åãvb©ª©A*u/b@A~ªýAz>u.Üo/AvåährÄC@äC£r/©ðb,O@U©£CZ£OÖöv>bC£¢zÜoraÄðaO:üߢ£|oaª£O,¢Ü~>AüOu,b@|ß+oåOoßz£<©*ä~C.B<><_ð+B_îü©îã.hv¢vý,UÜZî_ã+ýÄaßZv@.>>rÐãUßäzªabaBãåo+£*ýÃu@Abýª¢ÃÄß/å@rßb:AÃ|h:ChðZßv/ãBb>ßvÄÃßUuu¢îu©h~¢bb:_Ü,îhÄß*väÃ*,ÄöC.©Ü:z@_î>ßÄOr*ð>/Av>zß©|:î+b_*Öå©üÃUãrCaU>.*aªhððOî+/<ߢau'), (' '), (N'oAß_U©Ã©uîvUã,z_ãÖ:C+/,Ðha+ÜåOäãÖözýrã|ðaAo,C@£ßÖß©ß>¢uÖÐuC©*.ß_@oüÄbzåÜåBAOUBª¢+_ab@A+hÄ|vüÐ媢u£åö_öÜ:öîö,~b@¢£>zß,Ða<ßãý>Aîbý@/öªu£b/Ö~Cªb'), (N'£A£*u~ßU>¢.hÄöv.AäüäoCî.ý/îbߢ/,ãU*:.~ß.ÄO:ýã@+<>oBÐCC/_hU*ßÃÖ:åraz:ªßab/~£@ÃvÐãßzhuaÄüÖ|ÜßOîä©o£Ã|ü:äuÃäÜUOß,_Öª¢ß@äubäOuChÖZh~î>åýÐÖ.üB<,u<ßöOUbªZboZ.£ÄÜrÐbð~¢Ãvz>_aãrã..äß..ZöåÃÃã@Ör>+aýruA|ߪÜ@£ÄÄbÜBuÜå_ãz+îßOUåßo_BaOaãaª,äbr¢å£UO¢ðÖvýzö@Ä¢Z:ãzðߪý_Ã@bðB£,A>¢Ã>U_a¢übî_ßb©ZuÄh|_C.aîC|©~ý££ªv:îAhAîßÃüÖÄ:Ö£äÜÄ,>ã>+uÃ~hý+bü.Uö@¢Üvb©UÐäo£Z:+ðßßZvhÜÜB©*|åð<£ý+:@UAb@¢ÐUC£Oª,Cßb|UÃhCOÐ@Cð_ÜzvrOväöCîÐZÃ_î£hîbü/Bãî.zu*z@@öªBöðã:a*@OÃOßöýªr_ª¢böBÃ<+ßäb@¢ª.ßĪªüßÄîÐ:U/©B:z¢Cßðåå£ýv:>¢hva|bÜAU.aUÐîÐ*>äUßU,|'), (null), ('Ã@îbªz@zbÖ::Ä+åbOî¢åbao|oha£UU~ã£Ä,vCrßÃÐ.ãüåbhîîoO£ß>ÜÃ**å:zäbãOªßýý¢öýBðh©OrÃCýBÐbböAhöÃößz++å*aßvuoðZur|î*B>h©Üä+ªuÃobuÜh>ªZ<ã©h>£ÃabÖö£äo+_U*Bv¢rßÃ>|@ðzüåî+¢bîÄÃAîv>ܪ©ªãýÄÄ_ß.zÃUAü©öhÜÄCåbZuB:ß|zOah/OAîÄ@rª/b@AöãäÐABÜ>ܩТZªîvåü>rz|ãäöÖu¢~BÃß_vZv~B_u/@+Ü<.ªB¢ðÐ<ªZÄ,ýaßvähî©üöbAz£Äß:vßßrb,_oCa©,urU|îv|ZU,vªÄß_z|ßüuöAz@îOö£UCÐhUZÖÐã<.,h>Oî_r@Uäuª*A::råÖ©Ð*ðAh£h*.bß>B,ªb:ã/üéÃÜA@,Bßä|ãbß©bu10/31/2016 11:46:19 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/0110/31/2016 11:46:19 AMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/00'), ('01:21:33.8136175'), ('0413-05-13'), ('2016-10-31 11:46:19.9521697'), ('6050-11-19 01:00:40.4482745+00:00'))"; + break; + default: + break; + } + return $query; +} + +function Repro() +{ + StartTest("pdo_fetch_fetchinto_query_args"); + try + { + FetchInto_Query_Args(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("pdo_fetch_fetchinto_query_args"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'pdo_fetch_fetchinto_query_args' test... +Constructor called with 0 arguments +Constructor called with 2 arguments +Comparing data in row 1 +Comparing data in row 2 + +Done +...Test 'pdo_fetch_fetchinto_query_args' completed successfully. + diff --git a/test/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt b/test/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt new file mode 100644 index 00000000..f85a0e35 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test client info by calling PDO::getAttribute with PDO::ATTR_CLIENT_VERSION +--FILE-- +getAttribute( PDO::ATTR_CLIENT_VERSION )); + +//free the connection +$conn=null; +?> +--EXPECTREGEX-- +Array +\( + \[(DriverDllName|DriverName)\] => (msodbcsql1[1-9].dll|libmsodbcsql-[1-9]{2}.[0-9].so.[0-9].[0-9]) + \[DriverODBCVer\] => [0-9]{1,2}\.[0-9]{1,2} + \[DriverVer\] => [0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4} + \[ExtensionVer\] => [0-9]\.[0-9]\.[0-9](\-((rc)|(preview))(\.[0-9]+)?)?(\+[0-9]+)? +\) \ No newline at end of file diff --git a/test/pdo_sqlsrv/pdo_nested_query_mars.phpt b/test/pdo_sqlsrv/pdo_nested_query_mars.phpt new file mode 100644 index 00000000..26cf0c7a --- /dev/null +++ b/test/pdo_sqlsrv/pdo_nested_query_mars.phpt @@ -0,0 +1,108 @@ +--TEST-- +fetch multiple result sets with MARS on and then off +--SKIPIF-- + +--FILE-- +SetAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $stmt = $conn->exec("CREATE TABLE $tableName ([c1_int] int, [c2_varchar] varchar(20))"); + + $query = "INSERT INTO $tableName ([c1_int], [c2_varchar]) VALUES (1, 'Dummy value 1')"; + $stmt = $conn->query($query); + + $query = "INSERT INTO $tableName ([c1_int], [c2_varchar]) VALUES (2, 'Dummy value 2')"; + $stmt = $conn->query($query); + + $query = "SELECT * FROM $tableName ORDER BY [c1_int]"; + $stmt = $conn->query($query); + $numRows = 0; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) + $numRows++; + + if ($numRows !== 2) echo "Number of rows is unexpected!\n"; + $stmt = null; + + // more than one active results + $stmt1 = $conn->query($query); + $stmt2 = $conn->prepare($query); + $stmt2->execute(); + + echo "\nNumber of columns in First set: " . $stmt2->columnCount() . "\n"; + while ($row = $stmt1->fetch(PDO::FETCH_ASSOC)) + { + print_r($row); + } + + echo "\nNumber of columns in Second set: " . $stmt1->columnCount() . "\n\n"; + while ($row = $stmt2->fetch(PDO::FETCH_OBJ)) + { + print_r($row); + } + + $stmt1 = null; + $stmt2 = null; + $conn = null; +} + +function Repro() +{ + StartTest("pdo_nested_query_mars"); + try + { + NestedQuery_Mars(true); + NestedQuery_Mars(false); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("pdo_nested_query_mars"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'pdo_nested_query_mars' test... + +Number of columns in First set: 2 +Array +( + [c1_int] => 1 + [c2_varchar] => Dummy value 1 +) +Array +( + [c1_int] => 2 + [c2_varchar] => Dummy value 2 +) + +Number of columns in Second set: 2 + +stdClass Object +( + [c1_int] => 1 + [c2_varchar] => Dummy value 1 +) +stdClass Object +( + [c1_int] => 2 + [c2_varchar] => Dummy value 2 +) +SQLSTATE[IMSSP]: The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. +Done +...Test 'pdo_nested_query_mars' completed successfully. diff --git a/test/pdo_sqlsrv/pdo_query_timeout.phpt b/test/pdo_sqlsrv/pdo_query_timeout.phpt new file mode 100644 index 00000000..ec56fbfb --- /dev/null +++ b/test/pdo_sqlsrv/pdo_query_timeout.phpt @@ -0,0 +1,92 @@ +--TEST-- +test query time out at the connection level and statement level +--SKIPIF-- + +--FILE-- +exec("CREATE TABLE $tableName ([c1_int] int, [c2_varchar] varchar(25))"); + + $query = "INSERT INTO $tableName ([c1_int], [c2_varchar]) VALUES (1, 'QueryTimeout 1')"; + $stmt = $conn->query($query); + + $query = "INSERT INTO $tableName ([c1_int], [c2_varchar]) VALUES (2, 'QueryTimeout 2')"; + $stmt = $conn->query($query); + + $query = "SELECT * FROM $tableName"; + + if ($connLevel) + { + echo "Setting query timeout as an attribute in connection\n"; + $conn->setAttribute(constant('PDO::SQLSRV_ATTR_QUERY_TIMEOUT'), 1); + $stmt = $conn->query("WAITFOR DELAY '00:00:03'; $query"); + + var_dump($conn->errorInfo()); + } + else + { + echo "Setting query timeout in the statement\n"; + $stmt = $conn->prepare("WAITFOR DELAY '00:00:03'; $query", array(constant('PDO::SQLSRV_ATTR_QUERY_TIMEOUT') => 1)); + $stmt->execute(); + + var_dump($stmt->errorInfo()); + } + + $stmt = null; + $conn = null; +} + +function Repro() +{ + StartTest("pdo_query_timeout"); + try + { + QueryTimeout(true); + QueryTimeout(false); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("pdo_query_timeout"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'pdo_query_timeout' test... +Setting query timeout as an attribute in connection +array(3) { + [0]=> + string(5) "HYT00" + [1]=> + int(0) + [2]=> + string(63) "[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired" +} +Setting query timeout in the statement +array(3) { + [0]=> + string(5) "HYT00" + [1]=> + int(0) + [2]=> + string(63) "[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired" +} + +Done +...Test 'pdo_query_timeout' completed successfully. diff --git a/test/pdo_sqlsrv/pdo_statement_rowcount_query.phpt b/test/pdo_sqlsrv/pdo_statement_rowcount_query.phpt new file mode 100644 index 00000000..f0c8fe7c --- /dev/null +++ b/test/pdo_sqlsrv/pdo_statement_rowcount_query.phpt @@ -0,0 +1,181 @@ +--TEST-- +test rowCount() with different querying method and test nextRowset() with different fetch +--SKIPIF-- + +--FILE-- +exec("CREATE TABLE $tableName ([c1_int] int, [c2_real] real)"); + + $numRows = 5; + for ($i = 1; $i <= $numRows; $i++) + { + InsertData($conn, $tableName, $i); + } + + FetchRowsets($conn, $tableName, $numRows); + + for ($i = 1; $i <= $numRows; $i++) + { + UpdateData($conn, $tableName, $i, $exec); + } + + DeleteData($conn, $tableName, $exec); + + $stmt = null; + $conn = null; +} + +function InsertData($conn, $tableName, $value) +{ + $query = "INSERT INTO $tableName VALUES ($value, $value * 1.0)"; + $stmt = $conn->query($query); +} + +function UpdateData($conn, $tableName, $value, $exec) +{ + $newValue = $value * 100; + $query = "UPDATE $tableName SET c1_int = $newValue WHERE (c1_int = $value)"; + $rowCount = 0; + + if ($exec) + { + $rowCount = $conn->exec($query); + } + else + { + $stmt = $conn->prepare($query); + $rowCount = $stmt->rowCount(); + if ($rowCount > 0) + echo "Number of rows affected prior to execution should be 0!\n"; + + $stmt->execute(); + $rowCount = $stmt->rowCount(); + } + + if ($rowCount !== 1) + echo "Number of rows affected should be 1!\n"; + + $stmt = null; +} + +function CompareValues($actual, $expected) +{ + if ($actual != $expected) + { + echo "Unexpected value $value returned! Expected $expected.\n"; + } +} + +function FetchRowsets($conn, $tableName, $numRows) +{ + $query = "SELECT [c1_int] FROM $tableName ORDER BY [c1_int]"; + $queries = $query . ';' . $query . ';' . $query; + $stmt = $conn->query($queries); + + $i = 0; + while ($row = $stmt->fetch(PDO::FETCH_LAZY)) + { + $value = (int)$row['c1_int']; + CompareValues($value, ++$i); + } + + if ($i != $numRows) + { + echo "Number of rows fetched $i is unexpected!\n"; + } + + $result = $stmt->nextRowset(); + if ($result == false) + { + echo "Missing result sets!\n"; + } + + $rows = $stmt->fetchAll(PDO::FETCH_NUM); + $i = 0; + foreach ($rows as $row) + { + foreach ($row as $key => $value) + { + $value = (int)$value; + CompareValues($value, ++$i); + } + } + + $result = $stmt->nextRowset(); + if ($result == false) + { + echo "Missing result sets!\n"; + } + + $stmt->bindColumn('c1_int', $value); + $i = 0; + while ($row = $stmt->fetch(PDO::FETCH_BOUND)) + { + CompareValues($value, ++$i); + } + + $result = $stmt->nextRowset(); + if ($result != false) + { + echo "Number of result sets exceeding expectation!\n"; + } +} + +function DeleteData($conn, $tableName, $exec) +{ + $query = "DELETE TOP(3) FROM $tableName"; + $rowCount = 0; + + if ($exec) + { + $rowCount = $conn->exec($query); + } + else + { + $stmt = $conn->query($query); + $rowCount = $stmt->rowCount(); + } + + if ($rowCount <= 0) + echo "Number of rows affected should be > 0!\n"; + + $stmt = null; +} + +function Repro() +{ + StartTest("pdo_statement_rowcount_query"); + try + { + RowCount_Query(true); + RowCount_Query(false); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("pdo_statement_rowcount_query"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'pdo_statement_rowcount_query' test... + +Done +...Test 'pdo_statement_rowcount_query' completed successfully. diff --git a/test/pdo_sqlsrv/pdo_stored_proc_fetch_datatypes.phpt b/test/pdo_sqlsrv/pdo_stored_proc_fetch_datatypes.phpt new file mode 100644 index 00000000..2936e9ca --- /dev/null +++ b/test/pdo_sqlsrv/pdo_stored_proc_fetch_datatypes.phpt @@ -0,0 +1,320 @@ +--TEST-- +call stored procedures with inputs of ten different datatypes to get outputs of various types +--SKIPIF-- + +--FILE-- +exec("CREATE PROC $procName (@p1 BIGINT, @p2 BIGINT, @p3 NCHAR(128) OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(NCHAR(128), @p1 + @p2) END"); + + $inValue1 = '12345678'; + $inValue2 = '11111111'; + $outValue = '0'; + + $stmt = $conn->prepare("{CALL $procName (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindValue(2, $inValue2); + $stmt->bindParam(3, $outValue, PDO::PARAM_STR, 300); + $stmt->execute(); + + $expected = "23456789"; + $outValue = trim($outValue); + if (strncasecmp($outValue, $expected, strlen($expected))) + { + echo "Output value $outValue is unexpected! Expected $expected\n"; + } + + $stmt = null; +} + +function ProcFetch_Decimal($conn) +{ + $procName = GetTempProcName(); + + $stmt = $conn->exec("CREATE PROC $procName (@p1 DECIMAL, @p2 DECIMAL, @p3 CHAR(128) OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(CHAR(128), @p1 + @p2) END"); + + $inValue1 = '2.1'; + $inValue2 = '5.3'; + $outValue = '0'; + + $stmt = $conn->prepare("{CALL $procName (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindValue(2, $inValue2); + $stmt->bindParam(3, $outValue, PDO::PARAM_STR, 300); + $stmt->execute(); + + $expected = "7"; + $outValue = trim($outValue); + if (strncasecmp($outValue, $expected, strlen($expected))) + { + echo "Output value $outValue is unexpected! Expected $expected\n"; + } + + $stmt = null; +} + +function ProcFetch_Float($conn) +{ + $procName = GetTempProcName(); + + $stmt = $conn->exec("CREATE PROC $procName (@p1 FLOAT, @p2 FLOAT, @p3 FLOAT OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(FLOAT, @p1 + @p2) END"); + + $inValue1 = '2.25'; + $inValue2 = '5.5'; + $outValue = '0'; + + $stmt = $conn->prepare("{CALL $procName (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindValue(2, $inValue2); + $stmt->bindParam(3, $outValue, PDO::PARAM_STR, 300); + $stmt->execute(); + + $expected = "7.75"; + $outValue = trim($outValue); + if (strncasecmp($outValue, $expected, strlen($expected))) + { + echo "Output value $outValue is unexpected! Expected $expected\n"; + } + + $stmt = null; +} + +function ProcFetch_Int($conn) +{ + $procName = GetTempProcName(); + + $stmt = $conn->exec("CREATE PROC $procName (@p1 INT, @p2 INT, @p3 INT OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(INT, @p1 + @p2) END"); + + $inValue1 = '1234'; + $inValue2 = '5678'; + $outValue = '0'; + + $stmt = $conn->prepare("{CALL $procName (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindValue(2, $inValue2); + $stmt->bindParam(3, $outValue, PDO::PARAM_STR, 300); + $stmt->execute(); + + $expected = "6912"; + $outValue = trim($outValue); + if (strncasecmp($outValue, $expected, strlen($expected))) + { + echo "Output value $outValue is unexpected! Expected $expected\n"; + } + + $stmt = null; +} + +function ProcFetch_Money($conn) +{ + $procName = GetTempProcName(); + + $stmt = $conn->exec("CREATE PROC $procName (@p1 MONEY, @p2 MONEY, @p3 MONEY OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(MONEY, @p1 + @p2) END"); + + $inValue1 = '22.3'; + $inValue2 = '16.1'; + $outValue = '0'; + + $stmt = $conn->prepare("{CALL $procName (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1, PDO::PARAM_STR); + $stmt->bindParam(2, $inValue2, PDO::PARAM_STR); + $stmt->bindParam(3, $outValue, PDO::PARAM_STR, 300); + $stmt->execute(); + + $expected = "38.40"; + $outValue = trim($outValue); + if (strncasecmp($outValue, $expected, strlen($expected))) + { + echo "Output value $outValue is unexpected! Expected $expected\n"; + } + + $stmt = null; +} + +function ProcFetch_Numeric($conn) +{ + $procName = GetTempProcName(); + + $stmt = $conn->exec("CREATE PROC $procName (@p1 NUMERIC, @p2 NUMERIC, @p3 NCHAR(128) OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(NCHAR(128), @p1 + @p2) END"); + + $inValue1 = '2.8'; + $inValue2 = '5.4'; + $outValue = '0'; + + $stmt = $conn->prepare("{CALL $procName (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindParam(2, $inValue2); + $stmt->bindParam(3, $outValue, PDO::PARAM_STR, 300); + $stmt->execute(); + + $expected = "8"; + $outValue = trim($outValue); + if (strncasecmp($outValue, $expected, strlen($expected))) + { + echo "Output value $outValue is unexpected! Expected $expected\n"; + } + + $stmt = null; +} + +function ProcFetch_Real($conn) +{ + $procName = GetTempProcName(); + + $stmt = $conn->exec("CREATE PROC $procName (@p1 REAL, @p2 REAL, @p3 REAL OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(REAL, @p1 + @p2) END"); + + $inValue1 = '3.4'; + $inValue2 = '6.6'; + $outValue = '0'; + + $stmt = $conn->prepare("{CALL $procName (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindParam(2, $inValue2); + $stmt->bindParam(3, $outValue, PDO::PARAM_STR, 300); + $stmt->execute(); + + $expected = "10"; + $outValue = trim($outValue); + if (strncasecmp($outValue, $expected, strlen($expected))) + { + echo "Output value $outValue is unexpected! Expected $expected\n"; + } + + $stmt = null; +} + +function ProcFetch_SmallInt($conn) +{ + $procName = GetTempProcName(); + + $stmt = $conn->exec("CREATE PROC $procName (@p1 SMALLINT, @p2 SMALLINT, @p3 NCHAR(32) OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(NCHAR(32), @p1 + @p2) END"); + + $inValue1 = '34'; + $inValue2 = '56'; + $outValue = '0'; + + $stmt = $conn->prepare("{CALL $procName (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindParam(2, $inValue2); + $stmt->bindParam(3, $outValue, PDO::PARAM_STR, 300); + $stmt->execute(); + + $expected = "90"; + $outValue = trim($outValue); + if (strncasecmp($outValue, $expected, strlen($expected))) + { + echo "Output value $outValue is unexpected! Expected $expected\n"; + } + + $stmt = null; +} + +function ProcFetch_SmallMoney($conn) +{ + $procName = GetTempProcName(); + + $stmt = $conn->exec("CREATE PROC $procName (@p1 SMALLMONEY, @p2 SMALLMONEY, @p3 SMALLMONEY OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(SMALLMONEY, @p1 + @p2) END"); + + $inValue1 = '10'; + $inValue2 = '11.7'; + $outValue = '0'; + + $stmt = $conn->prepare("{CALL $procName (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1, PDO::PARAM_STR); + $stmt->bindParam(2, $inValue2, PDO::PARAM_STR); + $stmt->bindParam(3, $outValue, PDO::PARAM_STR, 300); + $stmt->execute(); + + $expected = "21.70"; + $outValue = trim($outValue); + if (strncasecmp($outValue, $expected, strlen($expected))) + { + echo "Output value $outValue is unexpected! Expected $expected\n"; + } + + $stmt = null; +} + +function ProcFetch_TinyInt($conn) +{ + $procName = GetTempProcName(); + + $stmt = $conn->exec("CREATE PROC $procName (@p1 TINYINT, @p2 TINYINT, @p3 CHAR(32) OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(CHAR(32), @p1 + @p2) END"); + + $inValue1 = '11'; + $inValue2 = '12'; + $outValue = '0'; + + $stmt = $conn->prepare("{CALL $procName (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindParam(2, $inValue2); + $stmt->bindParam(3, $outValue, PDO::PARAM_STR, 300); + $stmt->execute(); + + $expected = "23"; + $outValue = trim($outValue); + if (strncasecmp($outValue, $expected, strlen($expected))) + { + echo "Output value $outValue is unexpected! Expected $expected\n"; + } + + $stmt = null; +} + +function Repro() +{ + set_time_limit(0); + StartTest("pdo_stored_proc_fetch_datatypes"); + try + { + require_once("autonomous_setup.php"); + $database = "tempdb"; + $conn = new PDO( "sqlsrv:server=$serverName;Database=$database", $username, $password); + + ProcFetch_BigInt($conn); + ProcFetch_Decimal($conn); + ProcFetch_Float($conn); + ProcFetch_Int($conn); + ProcFetch_Money($conn); + ProcFetch_Numeric($conn); + ProcFetch_Real($conn); + ProcFetch_SmallInt($conn); + ProcFetch_SmallMoney($conn); + ProcFetch_TinyInt($conn); + + $conn = null; + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("pdo_stored_proc_fetch_datatypes"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'pdo_stored_proc_fetch_datatypes' test... + +Done +...Test 'pdo_stored_proc_fetch_datatypes' completed successfully. + diff --git a/test/pdo_sqlsrv/pdo_tools.inc b/test/pdo_sqlsrv/pdo_tools.inc new file mode 100644 index 00000000..959fa51c --- /dev/null +++ b/test/pdo_sqlsrv/pdo_tools.inc @@ -0,0 +1,446 @@ +getColumnMeta($colIndex); + $colType = $meta['sqlsrv:decl_type']; + $colName = $meta['name']; + + if (! IsUpdatable($colName)) + { + return true; // do nothing for non-IsUpdatable fields + } + + return CompareDataValue($colType, $rowIndex, $colName, $actual, $expected); +} + +function CompareCharacterData($actual, $expected) +{ + $matched = false; + if ($actual === $expected) + { + $matched = true; + } + else + { + $len = strlen($expected); + + $result = strncmp($expected, $actual, $len); + if ($result == 0) + { + $matched = true; + } + } + + //echo "Expected: $expected\nActual: $actual\n"; + if (! $matched) + { + echo "Data corruption!! Expected: $expected\nActual: $actual\n"; + } + + return $matched; +} + +function DumpMetadata($stmt) +{ + $numFields = $stmt->columnCount(); + for ($j = 0; $j < $numFields; $j++) + { + $meta = $stmt->getColumnMeta($j); + var_dump($meta); + } +} + +function GetColumnData($stmt, $query, $rowIndex, $colIndex) +{ + $skipCount = 0; + $data = ""; + for ($j = 0; $j <= $colIndex; $j++) + { + $meta = $stmt->getColumnMeta($j); + $type = $meta['sqlsrv:decl_type']; + $name = $meta['name']; + + if (!IsUpdatable($name)) + { + $skipCount++; + } + else + { + if ($j == $colIndex) + { + $data = GetInsertData($query, $type, $rowIndex, $j + 1, $skipCount); + break; + } + } + } + + return $data; +} + +function InsertDataToArray($stmt, $query, $numFields, $rowIndex) +{ + $dataArray = array(); + + $skipCount = 0; + for ($j = 0; $j < $numFields; $j++) + { + $meta = $stmt->getColumnMeta($j); + $type = $meta['sqlsrv:decl_type']; + $name = $meta['name']; + + $colIndex = $j + 1; + if (!IsUpdatable($name)) + { + $skipCount++; + array_push($dataArray, ""); + } + else + { + $data = GetInsertData($query, $type, $rowIndex, $colIndex, $skipCount); + array_push($dataArray, $data); + } + } + + return $dataArray; +} + +function GetInsertData($query, $colType, $rowIndex, $colIndex, $skip) +{ + $data = strstr($query, "(("); + $pos = 1; + if ($data === false) + { + die("Failed to retrieve data on row $rowIndex"); + } + $data = substr($data, 2); + + while ($pos < ($colIndex - $skip)) + { + $data = strstr($data, ", ("); + $pos++; + + if ($data === false) + { + die("Failed to retrieve data on column $pos"); + } + $data = substr($data, 3); + } + + // Is it's XML type, we can't use the closing bracket as the next delimiter + // because a bracket can be part of the xml data, unless the data is null + $str = ")"; + $pos = strpos($data, $str); + if ($pos === false) + { + die("Failed to isolate data on row $rowIndex, column $pos"); + } + $tmp = substr($data, 0, $pos); + if ((strcasecmp($tmp, "null") == 0) || strlen($tmp) == 0) + { + $tmp = ""; + } + else if (IsXml($colType)) + { + $str = ">')"; + $pos = strpos($data, $str); + $tmp = substr($data, 0, $pos + 2); + } + + $data = $tmp; + + if (IsDataUnicode($colType, $data)) // this includes unicode data type and XML data that is in Unicode + { // N'data' + $data = substr($data, 2, strlen($data) - 3); + } + else if (IsLiteral($colType)) + { // 'data' + $data = substr($data, 1, strlen($data) - 2); + } + else if (IsBinary($colType)) + { // 0xdata + $data = substr($data, 2); + } + + return (trim($data)); +} + +function CompareBinaryStream($inputFile, $actual) +{ + // open input file first + $stream = fopen($inputFile, "r"); + + $len = strlen($actual); + echo "Comparing data...\n"; + $matched = true; + $numbytes = _CHUNK_SIZE; + + $pos = 0; + while (! feof($stream) && $pos < $len) + { + $contents = fread($stream, $numbytes); + + // if $actual is empty, check if $contents is also empty + $contents_len = strlen($contents); + if ($len == 0) + { + $matched = ($contents_len == 0); + break; + } + + // Compare contents (case-sensitive) + $count = ($contents_len < $numbytes) ? $contents_len : $numbytes; + $result = substr_compare($actual, $contents, $pos, $count); + + if ($result != 0) + { + $matched = false; + echo "Data corruption!!\nExpected: $contents\nActual:" . substr($actual, $pos, $count) . "\n"; + break; + } + + $pos += $count; + } + + // close the data stream + fclose($stream); + + return $matched; +} + +function IsUpdatable($colName) +{ + $pos = strpos($colName, "_"); + $type = substr($colName, $pos + 1); + + return (strcasecmp($type, "timestamp") != 0); +} + +function IsDataUnicode($colType, $data) +{ + if (IsUnicode($colType)) + return true; + + // This input string may be an XML string in unicode (i.e. // N'...') + $letterN = 'N'; + $index = strpos($data, $letterN); + + // Note the use of ===. Simply == would not work as expected + // because the position of letterN 'N' may be the 0th (first) character + // and strpos will return false if not found. + if ($index === 0) { + return true; + } + + return false; +} + +function IsUnicode($type) +{ + switch ($type) + { + case 'nchar' : + case 'nvarchar' : + case 'ntext' : + return true; + default: + break; + } + return (false); +} + +function IsXml($type) +{ + return ($type == 'xml'); +} + +function IsBinary($type) +{ + switch ($type) + { + case 'binary': + case 'varbinary': + case 'image': + return true; + default: + break; + } + return (false); +} + +function IsDateTime($type) +{ + switch ($type) + { + case 'datetime' : + case 'datetime2' : + case 'smalldatetime' : + case 'date' : + case 'time' : + case 'datetimeoffset' : + return true; + default: + break; + } + return (false); +} + +function IsLiteral($type) +{ + switch ($type) + { + case 'char' : + case 'nchar' : + case 'varchar' : + case 'nvarchar' : + case 'text' : + case 'ntext' : + case 'uniqueidentifier' : + case 'datetime' : + case 'datetime2' : + case 'smalldatetime' : + case 'date' : + case 'time' : + case 'datetimeoffset' : + case 'xml' : + return true; + default: + break; + } + return (false); +} + +?> \ No newline at end of file diff --git a/test/pdo_sqlsrv/pdo_utf8_stored_proc_unicode_chars.phpt b/test/pdo_sqlsrv/pdo_utf8_stored_proc_unicode_chars.phpt new file mode 100644 index 00000000..1bc223ac --- /dev/null +++ b/test/pdo_sqlsrv/pdo_utf8_stored_proc_unicode_chars.phpt @@ -0,0 +1,105 @@ +--TEST-- +call a stored procedure with unicode input to get output back as unicode; also test with xml data +--SKIPIF-- + +--FILE-- +exec("CREATE PROC $procName (@p1 XML, @p2 CHAR(512) OUTPUT) + AS BEGIN SELECT @p2 = CONVERT(CHAR(512), @p1) END"); + + $stmt = $conn->prepare("{CALL $procName (?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindParam(2, $outValue1, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 512); + $stmt->execute(); + + var_dump(trim($outValue1)); + + $stmt = null; +} + +function StoredProc_Surrogate($conn) +{ + $inValue1 = pack('H*', 'F2948080EFBFBDEFBFBDF48FBA83EFBFBDEFBFBDEFBFBDEFBFBDEFBFBDF48FB080EFBFBDEFBFBDEFBFBDF392A683EFBFBDF090808BF0908080F0908A83EFBFBDEFBFBDEFBFBDF48FBFBFEFBFBDEFBFBDF090808BF0908683EFBFBDF48FBFBFF2948880EFBFBDF0A08FBFEFBFBDF392A880F0A08A83F294808BF0908880EFBFBDEFBFBDEFBFBDEFBFBDF48FB080F48FB683EFBFBDF0908080EFBFBDF392AA83F48FB683EFBFBDF2948080F2948A83EFBFBDF0A08080F392A880EFBFBDF2948FBFEFBFBDEFBFBDEFBFBDEFBFBDF48FB683EFBFBDEFBFBDEFBFBDF48FBFBFF0908080EFBFBDEFBFBDEFBFBDEFBFBDF48FBFBFEFBFBDF48FB880F0908683F392A080F0908FBFEFBFBDEFBFBDEFBFBDEFBFBDEFBFBDF2948FBFEFBFBDF0908683EFBFBDF0A08A83F48FBA83EFBFBDF48FB08B'); + $outValue1 = "TEST"; + + $procName = GetTempProcName(); + $stmt = $conn->exec("CREATE PROC $procName (@p1 NVARCHAR(1000), @p2 NVARCHAR(1000) OUTPUT) + AS BEGIN SELECT @p2 = CONVERT(NVARCHAR(1000), @p1) END"); + + $stmt = $conn->prepare("{CALL $procName (?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindParam(2, $outValue1, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 1000); + $stmt->execute(); + + var_dump ($outValue1 === $inValue1); + + $stmt = null; +} + +function StoredProc_Unicode($conn) +{ + $inValue1 = pack('H*', 'E9AA8CE597BFE382A1E38381C3BDD086C39FD086C3B6C39CE3838FC3BDE8A1A4C3B6E38390C3A4C3B0C2AAE78687C3B0E2808DE6B490C4B1E385AFE382BFE9B797E9B797D79CC39FC383DAAFE382B0E597BFE382BDE382B0E58080D187C3BCE382BCE385AFD290E78687E38381C3AEE382BCE9B797E2808CC3BB69E888B3D790D291E382AFD0A7E58080C39CE69B82D291C384C3BDD196E3839DE8A1A4C3AEE382BCC3BCE8A1A4E382BFD290E2808FE38380C4B0D187C3A5E3839DE382BDE382AFC396E382B0E382BFC3B6C396D0A7E385B0E3838FC3A3C2AAD990D187C3B6C3BBC384C3B0C390D18FE382BEC4B0E382BCD086C39FE3838FE4BE83E382BCC384E382BDD79CC3BCC39FE382BFE382BCE2808DE58080E58081D196C384D794D794C3B6D18FC3AEC3B6DA98E69B82E6B490C3AEE382BEDAAFD290'); + $outValue1 = "TEST"; + + $procName = GetTempProcName(); + $stmt = $conn->exec("CREATE PROC $procName (@p1 NVARCHAR(MAX), @p2 NCHAR(1024) OUTPUT) + AS BEGIN SELECT @p2 = CONVERT(NCHAR(1024), @p1) END"); + + $stmt = $conn->prepare("{CALL $procName (?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindParam(2, $outValue1, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 1500); + $stmt->execute(); + + $outValue1 = trim($outValue1); + var_dump ($outValue1 === $inValue1); + + $stmt = null; +} + +function Repro() +{ + StartTest("pdo_utf8_stored_proc_unicode_chars"); + try + { + require_once("autonomous_setup.php"); + + set_time_limit(0); + $database = "tempdb"; + + $conn = new PDO( "sqlsrv:server=$serverName;Database=$database", $username, $password); + + StoredProc_Xml($conn); + StoredProc_Surrogate($conn); + StoredProc_Unicode($conn); + + $conn = null; + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("pdo_utf8_stored_proc_unicode_chars"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'pdo_utf8_stored_proc_unicode_chars' test... +string(47) "Je préfère l'été" +bool(true) +bool(true) + +Done +...Test 'pdo_utf8_stored_proc_unicode_chars' completed successfully. + diff --git a/test/sqlsrv/sqlsrv_bind_param_out_string.phpt b/test/sqlsrv/sqlsrv_bind_param_out_string.phpt new file mode 100644 index 00000000..4cb11131 --- /dev/null +++ b/test/sqlsrv/sqlsrv_bind_param_out_string.phpt @@ -0,0 +1,459 @@ +--TEST-- +Verify the Binary and Char encoding output when binding output string with SQLSTYPE option with different size. +--DESCRIPTION-- +Tests different sizes of output string which may cause ODBC to return trunc error info. +With unixODBC 2.3.4, when connection pooling is enabled, error information maybe returned differently +than older versions (or with pooling disabled). +The NVARCHAR(1) section would cause an ODBC call to return an errorinfo to the driver causing the statement to fail. +With unixODBC 2.3.4 + pooling the statement executes without error. +--FILE-- + +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect($serverName, $connectionInfo); +if( $conn === false ) { + die( print_r( sqlsrv_errors(), true )); +} +$conn = null; + +$conn = sqlsrv_connect($serverName, $connectionInfo); +if( $conn === false ) { + die( print_r( sqlsrv_errors(), true )); +} + + +$bindtable = "#BindStringTest"; +$sproc = "#uspPerson"; + +// Create table +$stmt = sqlsrv_query( $conn, "CREATE TABLE $bindtable (PersonID int, Name nvarchar(50))" ); +if( $stmt === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$stmt = sqlsrv_query( $conn, "INSERT INTO $bindtable (PersonID, Name) VALUES (10, N'Miller')" ); +if( $stmt === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$stmt = sqlsrv_query( $conn, "INSERT INTO $bindtable (PersonID, Name) VALUES (11, N'JSmith')" ); +if( $stmt === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$tsql_createSP = "CREATE PROCEDURE $sproc + @id int, @return nvarchar(50) OUTPUT + AS + BEGIN + SET NOCOUNT ON; + SET @return = (SELECT Name FROM $bindtable WHERE PersonID = @id) + END"; + +$stmt = sqlsrv_query( $conn, $tsql_createSP); +if( $stmt === false ) +{ + echo "Error in executing statement 2.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +$tsql_callSP = "{call $sproc( ? , ?)}"; + + +//*********************************************************************************************** + +echo "NVARCHAR(32)\n"; +echo "---------Encoding char-----------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), + SQLSRV_SQLTYPE_NVARCHAR(32) + )); + +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) === false) +{ + print_r( sqlsrv_errors(), true); +} + +$expectedLength = 6; +$expectedValue = "Miller"; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +echo "---------Encoding binary---------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), + SQLSRV_SQLTYPE_NVARCHAR(32) + )); +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) == false) + { + print_r( sqlsrv_errors(), true); + } + +$expectedLength = 12; +$expectedValue = "M\0i\0l\0l\0e\0r\0"; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +//*********************************************************************************************** +echo "\n\n"; +echo "NVARCHAR(50)\n"; +echo "---------Encoding char-----------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), + SQLSRV_SQLTYPE_NVARCHAR(50) + )); + +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) === false) +{ + print_r( sqlsrv_errors(), true); +} + +$expectedLength = 6; +$expectedValue = "Miller"; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + + +echo "---------Encoding binary---------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), + SQLSRV_SQLTYPE_NVARCHAR(50) + )); +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) == false) + { + print_r( sqlsrv_errors(), true); + } + +$expectedLength = 12; +$expectedValue = "M\0i\0l\0l\0e\0r\0"; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +//*********************************************************************************************** +echo "\n\n"; +echo "NVARCHAR(1)\n"; +echo "---------Encoding char-----------\n"; +$id = 10; +$return = ""; + + +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), + SQLSRV_SQLTYPE_NVARCHAR(1) + )); + +// with unixODBC 2.3.4 connection pooling the statement may not fail. +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) === false) +{ + echo "Statement should fail\n"; +} + +$expectedLength = 1; +$expectedValue = "M"; +$actualValue = $return; +$actualLength = strlen($return); +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +echo "---------Encoding binary---------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), + SQLSRV_SQLTYPE_NVARCHAR(1) + )); +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) == false) + { + echo "Statement should fail\n"; + } + +$expectedLength = 2; +$expectedValue = "M\0"; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +//*********************************************************************************************** +echo "\n\n"; +echo "NCHAR(32)\n"; +echo "---------Encoding char-----------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), + SQLSRV_SQLTYPE_NCHAR(32) + )); + +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) === false) +{ + print_r( sqlsrv_errors(), true); +} + +$expectedLength = 32; +$expectedValue = "Miller "; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +echo "---------Encoding binary---------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), + SQLSRV_SQLTYPE_NCHAR(32) + )); +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) == false) + { + print_r( sqlsrv_errors(), true); + } + +$expectedLength = 64; +$expectedValue = "M\0i\0l\0l\0e\0r\0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0"; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +//*********************************************************************************************** +echo "\n\n"; +echo "NCHAR(0)\n"; +echo "---------Encoding char-----------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), + SQLSRV_SQLTYPE_NCHAR(0) + )); + +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) === false) +{ + echo "Statement should fail\n"; +} + +$expectedLength = 0; +$expectedValue = ""; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +echo "---------Encoding binary---------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), + SQLSRV_SQLTYPE_NCHAR(0) + )); +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) == false) + { + echo "Statement should fail\n"; + } + +$expectedLength = 0; +$expectedValue = ""; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +//*********************************************************************************************** +echo "\n\n"; +echo "NCHAR(50)\n"; +echo "---------Encoding char-----------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), + SQLSRV_SQLTYPE_NCHAR(50) + )); + +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) === false) +{ + print_r( sqlsrv_errors(), true); +} + +$expectedLength = 50; +$expectedValue = "Miller "; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +echo "---------Encoding binary---------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), + SQLSRV_SQLTYPE_NCHAR(50) + )); +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) == false) + { + print_r( sqlsrv_errors(), true); + } + +$expectedLength = 100; +$expectedValue = "M\0i\0l\0l\0e\0r\0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0"; +$actualLength = strlen($return); +$actualValue = $return; +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +//*********************************************************************************************** +// NCHAR 1: less than length of the returned value +echo "\n\n"; +echo "NCHAR(1)\n"; +echo "---------Encoding char-----------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), + SQLSRV_SQLTYPE_NCHAR(1) + )); + +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) === false) +{ + print_r( sqlsrv_errors(), true); +} + +$expectedLength = 1; +$expectedValue = "M"; +$actualValue = $return; +$actualLength = strlen($return); +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +echo "---------Encoding binary---------\n"; +$id = 10; +$return = ""; +$params = array( + array($id, SQLSRV_PARAM_IN), + array(&$return, SQLSRV_PARAM_OUT, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), + SQLSRV_SQLTYPE_NCHAR(1) + )); +if( $stmt = sqlsrv_query($conn, $tsql_callSP, $params) == false) + { + print_r( sqlsrv_errors(), true); + } + +$expectedLength = 2; +$expectedValue = "M\0"; +$actualValue = $return; +$actualLength = strlen($return); +compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ); + +sqlsrv_close($conn); + +$status = true; + +/** +* Compares actual output to expected one +* @param $expectedLength The length of the expected value +* @param $expectedValue The expected value +* @param $actualLength The length of the actual value +* @param $actualValue The actual value +*/ +function compareResults ( $expectedLength, $expectedValue, $actualLength, $actualValue ) +{ + $match = false; + if ( $expectedLength == $actualLength) + { + if ( strncmp ( $actualValue, $expectedValue, $expectedLength ) == 0 ) + { + $match = true; + } + } + if ( !$match ) + { + echo "The actual result is different from the expected one \n"; + } + else + { + echo "The actual result is the same as the expected one \n"; + } +} +?> +--EXPECT-- +NVARCHAR(32) +---------Encoding char----------- +The actual result is the same as the expected one +---------Encoding binary--------- +The actual result is the same as the expected one + + +NVARCHAR(50) +---------Encoding char----------- +The actual result is the same as the expected one +---------Encoding binary--------- +The actual result is the same as the expected one + + +NVARCHAR(1) +---------Encoding char----------- +Statement should fail +The actual result is the same as the expected one +---------Encoding binary--------- +Statement should fail +The actual result is the same as the expected one + + +NCHAR(32) +---------Encoding char----------- +The actual result is the same as the expected one +---------Encoding binary--------- +The actual result is the same as the expected one + + +NCHAR(0) +---------Encoding char----------- +Statement should fail +The actual result is the same as the expected one +---------Encoding binary--------- +Statement should fail +The actual result is the same as the expected one + + +NCHAR(50) +---------Encoding char----------- +The actual result is the same as the expected one +---------Encoding binary--------- +The actual result is the same as the expected one + + +NCHAR(1) +---------Encoding char----------- +The actual result is the same as the expected one +---------Encoding binary--------- +The actual result is the same as the expected one diff --git a/test/sqlsrv/sqlsrv_client_info.phpt b/test/sqlsrv/sqlsrv_client_info.phpt new file mode 100644 index 00000000..9fad7661 --- /dev/null +++ b/test/sqlsrv/sqlsrv_client_info.phpt @@ -0,0 +1,25 @@ +--TEST-- +Test sqlsrv_client_info +--SKIPIF-- +--FILE-- + +--EXPECTREGEX-- +array\(4\) { + \[\"(DriverDllName|DriverName)\"\]=> + (string\(15\) \"msodbcsql1[1-9].dll\"|string\(24\) \"libmsodbcsql-[1-9]{2}.[0-9].so.[0-9].[0-9]\") + \[\"DriverODBCVer\"\]=> + string\(5\) \"[0-9]{1,2}\.[0-9]{1,2}\" + \[\"DriverVer\"\]=> + string\(10\) \"[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4}\" + \[\"ExtensionVer\"\]=> + string\([0-9]+\) \"[0-9]\.[0-9]\.[0-9](\-((rc)|(preview))(\.[0-9]+)?)?(\+[0-9]+)?" +} \ No newline at end of file diff --git a/test/sqlsrv/sqlsrv_close_twice.phpt b/test/sqlsrv/sqlsrv_close_twice.phpt new file mode 100644 index 00000000..d28c92e6 --- /dev/null +++ b/test/sqlsrv/sqlsrv_close_twice.phpt @@ -0,0 +1,61 @@ +--TEST-- +Free statement twice +--FILE-- +"$username", "PWD"=>"$password"); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $tableName = GetTempTableName(); + + $stmt = sqlsrv_query($conn, "CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint)"); + sqlsrv_free_stmt($stmt); + + $stmt = sqlsrv_query($conn, "SELECT * FROM $tableName"); + sqlsrv_free_stmt($stmt); + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); + +} + +//-------------------------------------------------------------------- +// Repro +// +//-------------------------------------------------------------------- +function Repro() +{ + StartTest("sqlsrv_close_twice"); + try + { + CloseTwice(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_close_twice"); +} + +Repro(); + +?> +--EXPECTREGEX-- + +...Starting 'sqlsrv_close_twice' test... + +Warning: sqlsrv_free_stmt\(\): supplied resource is not a valid ss_sqlsrv_stmt resource in .+sqlsrv_close_twice.php on line [0-9]+ + +Done +...Test 'sqlsrv_close_twice' completed successfully. diff --git a/test/sqlsrv/sqlsrv_complex_query.phpt b/test/sqlsrv/sqlsrv_complex_query.phpt new file mode 100644 index 00000000..e4e07397 --- /dev/null +++ b/test/sqlsrv/sqlsrv_complex_query.phpt @@ -0,0 +1,103 @@ +--TEST-- +Test a complex query with IDENTITY_INSERT +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"UTF-8"); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $tableName = GetTempTableName(); + + $stmt = sqlsrv_query($conn, "CREATE TABLE $tableName ([c1_int] int IDENTITY, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_varchar] varchar(512))"); + sqlsrv_free_stmt($stmt); + + $noExpectedRows = 0; + $noActualRows = 0; + $stmt = sqlsrv_query($conn, "SET IDENTITY_INSERT $tableName ON;"); + sqlsrv_free_stmt($stmt); + $noExpectedRows++; + + $stmt = sqlsrv_query($conn, "INSERT INTO $tableName (c1_int, c2_tinyint, c3_smallint, c4_bigint, c5_varchar) VALUES (1324944463, 105, 18521, 2022363960, 'üv£ª©*@*rãCaC|/ä*,,@ý©bvªäîCUBão_+ßZhUªî¢~ÖÜ/ª@ä+ßßar~Özr,aß/,bCaü<ÖÐhÐbß<î/ðzãý+bÜ:Zßöüª@BÖUßUßaåab|¢ª¢|ü£/ÃßzzuªãA.ªZUöß<©a>OzübBüÜ|bZ./öbvß*rbö>ß©r//~ÖCÜhð¢bßz:¢Ä+_Ã__£ý£Uýh:v¢bý,©Ü£©,A:Zh>ßåvö+Ä>Ã.ßvC|:Ü*Üü*åz|b.©©üAý@uU.oOÜýAÜÐÜð©OB|rrîbU<övoUßäZÃÖ<ÄåªAÄ.Ua*ÖOªB,åîzB:ÜhövÖZhýðüC')"); + sqlsrv_free_stmt($stmt); + + $stmt = sqlsrv_query($conn, "SET IDENTITY_INSERT $tableName OFF;"); + sqlsrv_free_stmt($stmt); + $stmt = sqlsrv_query($conn, "INSERT INTO $tableName (c1_int, c2_tinyint, c3_smallint, c4_bigint, c5_varchar) VALUES (-1554807493, 253, -12809, -1698802997, 'ßöîÄ@v+oß+î|ruý*Ü¢ãÖ~.*ArABªßOBzUU£ßUuÜ<ðýr|b~_äaü/OÖzv.¢ä>>OÜ+¢vªzöªoB_ä+ߪrÜö£>U~ãÖð~ðýur,ÖvZh¢ªZ>vªUäåîz,>ÃräðäýðO_ä*a,ö+üÐß~bÃü¢<<+îýÐöäªO/zA+:îZ,öBÐü<î£îåhBÖzßÄ~,:>bð<~aÐãö¢*¢våvÃÐåî@a_Äu£öa~våu>¢Bã©å:Aßã£Üvåö+aä£U<¢b@|zbãÖ@ÃãUb|ÄB£©,~ßð©ðUßöZÜöî£Zz<>åäZßð©ßaÖÖ¢bð£ßÄ>îÃÃ.~z>h_ý~ÜrüÖBruß+ª©CB©O>rå,Chro,£ßbZ_ß©,ÃUu|ßåüÄ/ý*åu|~Ö.ßZUoä:~A~CZhðU|öZ:ä/£Ä*î©ÄhävhbrzîÐ@.rãß©@uÜ©~>ÖÜööCÄzÜCü+>oZÄÜ/ABßA_b|b¢bÜh<|uBr.B*rü>aCª|AÄ©@öÖßÖ~ÖüoîzÄ¢zz~Ãýö|vUå>|CÄUü~>buÃv<ä~Ö+.ü*ªbuî_bBC©.oîCbåîÐÖUa~/U>öAäÐBu~ozîZ/zrOOä:ß©bßo.ü©A¢höÖoßÖü>r+A/ßaªrß:ª@|bhhªª/oå<Ö:rüa+oC¢~uÄü>/.ãbOöª_b@bbߢ|uzߪ֢~uäýub©ãaZäC£ÄrÖ,üöäu+Ãîö|||,U.BråãoýbüåöÃburöoî+>öä©î,u_öb©@C:ÜåÜîÜåAÖzýbð|Z<Ãý.£rîZ|/z@¢£AýZ,ßuZ*:b.AzТä¢üßöbvbväð|<**~Uv.Ð*Ä©B*ýCUöa¢åO©Ãß*ÃÃ|ÜðA@îÃßaBöüahUUA+ߣ_u|~äö.©hr£oBo<äãüO+_åO:Z~Üoîßzb£ª£A.AÖÜÄ._O_å£ß');SET IDENTITY_INSERT $tableName OFF;"); + sqlsrv_free_stmt($stmt); + + echo "Number of rows inserted: $noExpectedRows\n"; + + $stmt = sqlsrv_query($conn, "SELECT * FROM $tableName"); + while ($result = sqlsrv_fetch($stmt)) + { + $noActualRows++; + } + sqlsrv_free_stmt($stmt); + + echo "Number of rows fetched: $noActualRows\n"; + + if ($noActualRows != $noExpectedRows) + { + echo("Number of rows does not match expected value\n"); + } + sqlsrv_close($conn); + +} + +//-------------------------------------------------------------------- +// Repro +// +//-------------------------------------------------------------------- +function Repro() +{ + StartTest("sqlsrv_statement_complex_query"); + try + { + ComplexQuery(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_statement_complex_query"); +} + +Repro(); + +?> +--EXPECTF-- + +...Starting 'sqlsrv_statement_complex_query' test... +[Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Cannot insert explicit value for identity column in table '%s' when IDENTITY_INSERT is set to OFF. +544 +23000 +Number of rows inserted: 2 +Number of rows fetched: 2 + +Done +...Test 'sqlsrv_statement_complex_query' completed successfully. diff --git a/test/sqlsrv/sqlsrv_data_types_explict_fetch.phpt b/test/sqlsrv/sqlsrv_data_types_explict_fetch.phpt new file mode 100644 index 00000000..bc52d2db --- /dev/null +++ b/test/sqlsrv/sqlsrv_data_types_explict_fetch.phpt @@ -0,0 +1,100 @@ +--TEST-- +Test insert various data types and fetch as strings +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"UTF-8"); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $tableName = GetTempTableName(); + + $stmt = sqlsrv_query($conn, "CREATE TABLE [$tableName] ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_uniqueidentifier] uniqueidentifier, [c16_datetime] datetime, [c17_smalldatetime] smalldatetime, [c18_timestamp] timestamp)"); + sqlsrv_free_stmt($stmt); + + $numRows = 0; + $data = GetInputData(++$numRows); + $stmt = sqlsrv_query($conn, "INSERT INTO [$tableName] (c1_int, c2_tinyint, c3_smallint, c4_bigint, c5_bit, c6_float, c7_real, c8_decimal, c9_numeric, c10_money, c11_smallmoney, c12_char, c13_varchar, c14_varchar_max, c15_uniqueidentifier, c16_datetime, c17_smalldatetime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $data); + sqlsrv_free_stmt($stmt); + + $data = GetInputData(++$numRows); + $stmt = sqlsrv_query($conn, "INSERT INTO [$tableName] (c1_int, c2_tinyint, c3_smallint, c4_bigint, c5_bit, c6_float, c7_real, c8_decimal, c9_numeric, c10_money, c11_smallmoney, c12_char, c13_varchar, c14_varchar_max, c15_uniqueidentifier, c16_datetime, c17_smalldatetime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $data); + sqlsrv_free_stmt($stmt); + + $data = GetInputData(++$numRows); + $stmt = sqlsrv_query($conn, "INSERT INTO [$tableName] (c1_int, c2_tinyint, c3_smallint, c4_bigint, c5_bit, c6_float, c7_real, c8_decimal, c9_numeric, c10_money, c11_smallmoney, c12_char, c13_varchar, c14_varchar_max, c15_uniqueidentifier, c16_datetime, c17_smalldatetime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $data); + sqlsrv_free_stmt($stmt); + + $data = GetInputData(++$numRows); + $stmt = sqlsrv_query($conn, "INSERT INTO [$tableName] (c1_int, c2_tinyint, c3_smallint, c4_bigint, c5_bit, c6_float, c7_real, c8_decimal, c9_numeric, c10_money, c11_smallmoney, c12_char, c13_varchar, c14_varchar_max, c15_uniqueidentifier, c16_datetime, c17_smalldatetime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $data); + sqlsrv_free_stmt($stmt); + + $stmt = sqlsrv_query($conn, "SELECT * FROM $tableName ORDER BY c18_timestamp"); + + $metadata = sqlsrv_field_metadata($stmt); + $numFields = count($metadata); + $noActualRows = Verify($stmt, $metadata, $numFields, "utf-8"); + + if ($noActualRows !== $numRows) + { + echo "Number of Actual Rows $noActualRows is unexpected!\n"; + } + + sqlsrv_close($conn); +} + +function GetInputData($index) +{ + switch ($index) + { + case 1: + return array(array(-1512760629, null, null, null), array(167, null, null, null), array(-28589, null, null, null), array(-1991578776, null, null, null), array(0, null, null, null), array(1, null, null, null), array(0, null, null, null), array(0.0979, null, null, null), array(0.3095, null, null, null), array(0.8224, null, null, null), array(0.6794, null, null, null), array('~Ö~.üoö©UßB.|ÃÄ£*/v|U/*bZ£ÄUÜß*+ööî*©ðü©bðr@éåbOý|©©hob/>Cz<Äå::Ð<¢ß+ü/:ª@zrß.¢Ü£bÜU©ÃßÜßðoß©r*bÜböOUvãahub£ãäªb>_ã£BOÜA©ãü/ߢß.ov:Ö<:_+uÜC:£oöü*BzC,Äö~Zî@/Z/r@/©<~.ã¢Aaü>ý_zz@rÖ¢aU@,ABð/¢ß>z/ã@/ªUA~CoÄ,>bö|Ö>A,v+©CbC/Oo>©ßa©boAîÐvOo>ã|Cåöo+ÃhÖBAbo,+<ßã/£@å+ßAÜ@äÖÜOBäß~öu*ã+a<îoo|¢üýoBaÃÜ£ãCaC@ha,äzäî¢ü@å£b~råîUbßr©ãßÐ:@UhAO>u*uýBbäZ£aý>v:ðC~ÜöåðzZ>O|Cä+£>öz./Ö+uÜ', null, null, null), array(',ßhr©+|v@,Ã+BZ|îAÐß_öýða_AoäAOÜ*ýC@hoBßßaä+ýöCäAä_Ä¢/Uî.äC©¢rÃuz¢*,ýß.Ðöðý@b£öb.OCý@>hðÖrCZb/Oªz¢A+ªÖäu<ßÜÄ/ÐßÖîbU:bÄÐã>/£ÜÃBÃ@Ð.r:ªª>©zî_ÄÄ:@A.+.aoÖ@¢åOåOBB|+Cvüa_+hz|~COoACAî¢+*Ä©*ýî~|.Äz|u+o~:<@>Arb:~£z<äbãv>Ðr©:ðýCößÖ¢UAîãý:Ã~.C*C¢uÖ*~CÄ*äAb>h@h_>,|u<<.,vå,.BAuo£_ãB.Örö.Ä>zoba~C©hArªB£Zü~oÃbb>î+ääÄCbÐýª*Üýburäßv/åOüA:Oß:obvz©ý/ßroäaª/bªvz©rÐ,ZäߢªÄ.ã.@z¢|ð*aCý©:ýÄövã,öAbö+ÖCb~uÖ£züî|_ö~*CÃ>+ý/_ß+ãÐz/|ÖÐAv©ob.©£vöÃÃã:ðvC/@£Zð©O,åã@.u,BAÐbO£Z,Oü|Z/UBAß©ªA@býUuz.UýÖUöv|ª£B,Тßb*ÃУUðZäbãßOäAA>ãåUb,/Ä¢aä©:ð.öýr<Ðz@~b<öhÄ.CBÄ|ª¢|ä+ÜÃîüC¢,vãö£îãü.ZÄüUßßBuÐz>ðoBãZäB¢:ßã+.üÃOBOb@ZCZvî©ZA~©:O©£ÄuOý£Üßr_¢<+|ZhªýA©ßOßäãö£~bAÜ*©ÄÜv:aB|ZÄbªÃß|BýåýÃ.@ä:ð+*baOÖOÜvߣßAÃ~å>,bCOoa._~obböhãz*b©b|Büîã/aîzür.ßîhß*Ðr*£<ª~CÃz©ß>êîãövCr+Öa@u¢ü£ZAabü¢å*.b>,ÜhB*Bßvª>üüäÃh,:zhUý¢<.v£Ö@bÐ|/äÜÐ/r£Ã..*,ozbo,ãU/Avã@ÜzÖãÄ+zräö|CböÄoߣÖã©uzbr>+ÃãZ_/ruÖ©Öß.ðß/|ýÜbä/Ð,ßr:Ü~Ðßüh,ßî+ZoÜ/©oaÄ:>O|CÜov>z_£vÜhý:©aåߣaBðbUßa©OUЩ,Ðvr<ö¢Ã©bZUB_aßüaoCîß_ª£UzåO|O¢äb,©a@Au*/að,ÜUÜ£ã,_ßåbv*A£+:>|:©+ßvbäb>/ÃÖv<¢råý_U*o£ßüýC*ýäÃÜãå|UaöýÄÜÖîCCCßßÄßÜ,îurZa|ãvö@B¢Öîî.ð+Oðaã<~v_Äär*£oBz墩vUößåß*:CÖ_Ðba©oî_ßB©/C:oäßövåaåäß,rO/oßä.©~Ð@.vBrBU_*Ü_,@£Aa.oUЩäU£:ü>,ýUUÖ|Ä©:_ãbÜÖ+_Aãã©Ühß:U/ãb£o£©u¢å_B_Üßßý~>|B<ýz*rߪzÐuOßa*Ä¢aZîOãoäß/@baäÜ~zîhüäÜZöãbð', null, null, null), array('54e16f51-64f1-4d62-a028-582b553c2de5', null, null, null), array('2130-04-16 14:12:00.131', null, null, null), array('2032-05-10 23:32:00', null, null, null)); + case 2: + return array(array(-1111886816, SQLSRV_PARAM_IN, null, null), array(27, SQLSRV_PARAM_IN, null, null), array(-20174, SQLSRV_PARAM_IN, null, null), array(-840346326, SQLSRV_PARAM_IN, null, null), array(0, SQLSRV_PARAM_IN, null, null), array(0, SQLSRV_PARAM_IN, null, null), array(0, SQLSRV_PARAM_IN, null, null), array(0.4880, SQLSRV_PARAM_IN, null, null), array(0.9184, SQLSRV_PARAM_IN, null, null), array(0.6916, SQLSRV_PARAM_IN, null, null), array(0.7257, SQLSRV_PARAM_IN, null, null), array('<ö©ååä,ääÐ*bhîvr~aöb/BzZÜ@öðß@_Ä££r__£>£Ð£ðbUB~/ãbo.>îzöã*,ßå/+zuu.+BZßzA,aÖzüåão£©BãÄbä~ýooÜ,+äßÐ:UÃrz|vä,Bå~¢ä<_£uÜv<_O|ßBC¢_£Ahöª_¢oözCßýzöüý+zÄUÖhB@Uîbh/u/©zÐbÖ¢A*ã,Ãî£<>rUªßÐßîZîåb:+¢|A_BÃo©ªäu,*ýååbU:bÖÄß|¢>¢ÖaãrÃO©Äv+oßöZãª,+/.ãa/㣪,¢ðÐ<î¢b.£Ü©_r©vª@î:>ÖðB:OrBÜЪý|bßbÜ|åUOåîOãÄãuÐ|/îörB£ÃßZZÄ@Z©bÜB:.¢@b££U¢äÐvÐ+ý+uzÃb+üo+öv~_©~Uhbª,ßCb+UZö>Üü', SQLSRV_PARAM_IN, null, null), array('|öÐob*+ÐÖ,..Ä¢ß@>îß*äî|å>~Oo+/o+*/ü|îî,ðö*ýåãob:zb|Äßîvb¢,Ã,UªbbrAbZ©uªª@ä,_ð©A*>Ðävä:|:oîö_rý©+vî©ßBßßb>üOö@Öoö*+î@ÐßrÖ<¢hÜZb._raUaýUUÄößßîU¢ð.ÐýrãBh¢>Äðz<©AÜ/|©Ö@>hüBCO~öýZ>äÄÐAzä~/b.ÜzbðÜbða++ªå/ð~ACÐî~©>./<Ööý<~ýuÃBÐãåo*h©ö£öîüZß:ZÐä_>Ðvî©_äbb©ö¢*b@BÐÜb+bî+åßAåîu|/A.Ä.~hvb:@zå|Ä,ªÃZß@v©ßvB@Bð:£öß@uðr££ðü<Ä¢äÖaßO.:rª/Ao,ª:ZbA+¢ß|>,*ßoöA+ãb|Aü@bÄð@a:+,ouªýª+£ðr*Bã¢+rCðUU_ÖÃ>îö>r©v:U_v@vCÜ>', SQLSRV_PARAM_IN, null, null), array('ývC|ãv:|öbÃAÖ~oÐA,~ßß,b:+ba¢+AbOýzvÄvý/a_är<ßüÃ,~,<¢Z/_î|/bv£¢C*.bÄABz@výb¢.Ä*ä,.AßC|/î~Ü,hãðo+îÐäbÄv£vUäO£rßhOu|Äå¢ßaÜýohvÄOÃ+ãÐÄ©ävoCÖÖ|v+ßÄÖ¢rîvuvÐBÐbb_C+~bOãßzö.~CåUÐ.O<.©>v.h|ýªöÜO¢ý,o>B_ýu£hîüå£oääý©îbÐÄBßßÄU>@||Öß~ÐÄÜ£åruÖ_ªz@>.CÖöBZ_vÃb©zãÐÖC~*rBb/hB+ßÄäüO>@vrä+ܪ:£öUB//üý©~zª©Z>~ob£ä,+ýîÖãÖ,@ýAÖu,*UU.ãÜå,BaCäªa|bðv.ªBvö+*ªÃª<©||+ZöU*ÖräüýUüÜåUÐßr,öÃ@ÜîÃßaobZzªîh@ðãÃ*Üvo+U>CÜöAO>,b.vvuÄB/Ðäã©hBA£ohýßä¢Ã@h_/|AbCåßð_@åuÃh~ðb.*ÄB,BCÜîrÃ@<ÄÜCÄZ|.©Öüãr|_Üð:ãuö|vo:/A<îZu©h/bßCBýäA@.©,äåß|+ã:+:|î:ªÃvUB>,_ZÄß>Уß<ýr/o¢Ü~|rî_:ÜZÃäo+@/ĪöAACbhbzãÄaü,uhÐßßBÐbOöðÖaAö*,h.Ö,v.h>Ä:<åbß/ßî:Ð/_Ã*ý|ÖbB+bChý<~¢uaüÖªzC©UOCC>öB~uîã:bÖã@zbh¢z*î¢Uß~î>üãr~©Ö,£ü¢/ý|îb¢C|åª><@Ö.u:bÄߣßbo,ÐU>£ßZÜßAîB©A:rUýßÐ~uÜÖ<~Öî£b©.îAß,ZuÄýªb<|ðOÜð.Ö|:BbA<ÄvîCäÐîª:aÐö,CðAZßh@ÜrãZßß@î@rzãý*©Az~CäoäCî<ã|Zã@ovö_Ã>/ªzbuabuÃîöb<åÄÄÃv*îBöãýUrbýbbå<£OªÐvb¢oßîr|å@ãöß:ªÃOß.>Ü*ßaöbo£a>.vvý/¢£OCrÐvBÖ,COªoA>,Aü.:oÐãã:+~>oUOª*:ÄOü+ßO,ÄO|b>Ü_ªýAUТÜ_/ß,_üéö+îãªÜObªªvÜa>bÜö~bîouðäÐÃoÜOüZuÄbUOh©OÖ/ö:©<ãÖb+A:öîÜ>Ð/bBðª.å¢ZbbüUå¢Z.Z>uÜv,îöC/o_+zð~@ÜB~vhabß,/ÜÜß<.o_¢Zvöo©rü>ýÐbðÐo.ÖC_ÄCh>oüåöho*öÖ,ßãZ|ª/,Z©a*Ð:ê,A*ä:.<ßBývÐbßhAüÄBA@BO_B*ã>>ßU:v_ÜZªîð.uãO~zÄz<@ü,A*£BvãßÜC~ýzvÖã:_Aä/bÜÃß©î¢r.a+ü@:uB¢/>:ßhßãU_îãýÖ~AABß©Ã@ÄÖßz~åz@ü.Ö<*~ãäßOÜÄv~Īb_ör*bvÃÖýZZ<ö¢.|Ð>ÜåaCAîâãßu/aå|@U*¢Bb*+bZr_.ã|,h_BöÄb.ðZ©//î_~v/ð/,bð¢/:@öãß+vÜv/båðöã:ã/z:£î<_ÐöC>.Ozrð©@rC~Bö,£o<:Ã*z_ªöÜ,z,ªboB,+öCr*¢î*<£~ýb:U|©Bh/ãÜÖý:obhå£+Z+r:o|v+bÐhãåaüÐöbãðöAÃ|ªOCÖO|Ü<ãvv¢ãýbý.ÐbÄÃðåü>/BbbÄ/véäý:@o>öÃaªÐ+îüýã_röýä©zhvÜ<Ã/CäaðoCB|å~~ÖaðvuC_hBrOrzÃßO©ZU.AvvåÖÐ/ÐãåZ©£,UãÖAîhUzªrö£Ãu+ð/v¢o_<ÐA@', null, null, SQLSRV_SQLTYPE_CHAR(512)), array('ÄßZrð@~ö:ü:£,CoÄ©böBAO,ð:aA>ãÜBÐ@./:A.Z/bÖÜ,>ßî>ýßß©b/<@/,Öî>BBÃäÐCüÃÐÃvÜ_AZ.ý/©C_>aö/£Böða©£,öý£B_ÜÃðßvh|î|.oB/öBÜö¢BÐ/bAAÜÄa£.ªA©z<£ýOÐrå._bÄÜß~Ä_ªý,|+BãîA~Cî@ü+@ÜüzCªr.rzåazUöCzBߪ©Bö+ü*ZãÖ@AC*UA¢..aÜü*ArÃz£B:ßßÄ+Ã/ãª+ßZ_Ü<ßäîýýî@ðÄÜßÃÖðova£ªOöÄzÖ©ãrabªÐUrår+Ü*©OöåBö|a©î:bß©ð~_C_o*hÃ@åBb|<åÄß@©ý.Ubª,O£Oz|üßbz£+bã¢a@>:aaîý_Ür£|hÃ@z<_hüÃü,öîZýuã_¢üå£<ðßAª>rC.Bî.©,ß*å|é*_B>CÄîÖÃU~ÃÃ>rª>/ð©Ö|~ZA>¢¢/@£bZuZößzðå~:/h@uÐoOrã<¢aîßüß<¢BZzO¢@.:rvÜo>ABzC/ÜßÖ::r©O/v*@üaäzßZhU@aßvüî:©ü~ðª©_b£ä£ãB@:bhCÄZÜzOUßoåîÜý><', null, null, SQLSRV_SQLTYPE_VARCHAR(512)), array('Üarä+Öã/ür:h:AÐbðÃov¢©+ß*C/ߣZ£,ozz@UuÜ,ääßBÐB+hhãäz*rîC©/@u*bßÃ|býhÄrãÃîÜ/ÜäUbuÜv£ß:*bü.räßÐ_bßbC_©Cr:ðao/|ßhU.+v+aö+ЪÄÖÄ,C|ãÄßvð>ovü:<:bý/båz£B>ö~ýrbãO/@ÄüoðrÃrüz_ã:@ð|@ZÐO©ä,Öabîhßbä©ýã<Ð>vhßÐäîaBUA/©:ÄÖBaÜ£zuhäz|.ð:åä¢bObuhö<ßö,£Öa<>**C|ýbß.UhZãÄü*,>ÖÄO@.*>ÃOåbvÃ/ýb/äCîuÜß>¢C+|<*Üã|*.Ü*båöîv/O/*äÖÖÖ©+CA£Zr~ªz,ðA@ãuCzªO*ÄCzuoÄ*UüäöaUCÖO~£ö,uC,Aß>++Äbh/ý.o>Cv>ªäz¢:/ðß.BA_éßC~ÖZ:©*ý|ðÐ@AãövbZo+_î>ð¢äÄüvÃ@<ÜrZ~ÖããßvåÖÄÐîîö:ßöU@zvrBã©~ü+Äå/UÖã|,_U<*b£hßOÄüÖ_:Ðvö<ðb>îoUäîOåZ>ob.bZÄÜOu¢bZ£üªrãUohZZî<å¢brÄ|î+ÜuzaOvr©©bðÐhOäoðOâ~Äbhßßö.Äã+bh£@bª:Ä*å|ßC_|+@ÃÐ/Z<.ðbOou,zßößÄã~>Ãß~<>*ÖaîöªU::Ðü/bªÖÄ:å/uzAbªý.Äüäãåoößh£ðoU|ýöåbý.¢uãÜuðoUOz©.ð_îvª¢äßãßãOÐ>Bb+*åbuåvu@¢üã,äzÃ>/ªCbåvZ*rCÃOÖÄÃbßýã/+/:ðbßäuî<,*aãCªB,£*ª©:©b@ÖîZß~åäÐÄB:å+~ð_uÄðÄ*ãzrão|ü::A,+¢bbÖªZ:UäCru£ÐZÐöåvvbb@å~:/ðã©rä£ßªÖBß,oýß@UvÜã,Aoz¢Oð/_|Oohå_/rî*ªãh_¢h>Cvv|v¢ßªbîÃ*\/@Ã~bßar|ýü¢åbîÐUðO£Üãî~+ã,oÜîö>+UbÄ+ÖðÜ£Äý@>üÐßabîCð¢Cª¢CuU.ã:©b>U¢ýO+>ý.abã.B,B|oÃýã|Öh@CCüzArß_ðBÃýÐ*ýªÐbhbÐäuÃ__C_©UbO¢zÄ~/ß>.vuZðhoC*Öb>å,.ßUîZß@bväÃa>+.£.oð|Ðß>|Z_+.ÜÜ~Ürßu_:ÃCýýza+ªð©ßÐÄ+î|z£¢ZâÜ/Bz>vðßÃhäOÐAö|/ßCü+å~Ãb/ÃuîO¢uÖb+~åa@BÃhü£ä@¢äªÖußOÐUåä/UbªÄî@_£/©Ã,©.vAvuîöß|ßߢhüßbAß>Ä<Ü:>ýuüÜÄ£ßOýA~a<Ö@î_.b.¢hð.Z>£Bߪ+¢ªZU/@@äB.orÐîå¢ð>*<äAv~,ß@ýü~+~*Ðå¢ý_å_bÄb~_<<ßÄ:o¢zrC<ªa~BäÐýA©ßB.Uhîß+rAÖå¢ýö.îîaýåUC¢/Aüöß©zÄaðÐ,b|||+vCO+~üA£ÄöãýbÜ_üßãCðã|_ÄbäÃU~***<¢¢höUãbözbÄ>Ðühr.vÐ:£_Ä~@/o,a_abý_>ßr*å|bob¢îãBî~<üBÜouÄBar_üß.ÐÐ,©ýuÖUöäÐÐîZýªÃ*vß|~îZßZÜÐrh*~UÜ*î@OBÃßraUb:*/B/@OÄaãoßBãÃhöBb@Uªý*|£U+ü*¢ß¢häUðOb/*.rßOrÖåüO<ý*aöCa@ªoä>ÐC.UO+ZUrÜA©Oã,ro+î+,Üå¢<¢rÐu.ªî|ãÃB,ða,ªÐüÄü©ßBÖß+uv>BöußÜ|h|aßohB*ovãu+@Ü£ßO©ßßBý:b+£bÐÖäªo:UAÐo_ã~>ö<ühåÖÐî>å*v¢ßa_>/ZÖ:åbbäz¢Ä*Ü¢ÜåubvÖUî@Ã:¢<Üß_.*Öh,o/uz.B_/Äã|Ü/öOÐ.ÐßU@ßbav~zAßu+ª£U¢<ýÖä©Ä>ßãåäbã¢ßýªöåä,*ubßß@¢><üCozÐЩäC_aauC/_<.ýuÜ£Ö,uCÜÃbåräZ,ðÐî@îbzÖã+ã,CB£ZzB¢vÄ*+Üb¢üýßU*oÄärãü@öîaß.|äý©bÖ|BuA©ª,C/ZB*ð~aÃÃvîü©+ªÃ+Ã_öuu¢ZöbÄuð©O¢Z@_uä|bu,äOÐÜbBr@|Ãüb/îr©ß.ååßÜabZ©hß+ãߪz|+Aå@äü>Ä+ýu|å¢z|bhr*ªbO©>/ö,hÐå+Öå_OßZ|ð,b.AäÐß_@ßß©.üüZäuA/aC|£CäßýbhÖÖªZö@ÃhßÖ£/å*örüßðU*~vhðv_Üðýðß<+bAbBa:ªÄ/vܪUuÄîîabãßO>:,Ãýðußßäö@vîhäÜ>£¢hý+zAZbaBðУ|å|ªÐ*:Ã>ª:ð£ÐüßÖbuªOOA>Bb©ÃärÐîhzö,+|C:Aö', SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_VARCHAR(512)), array('|:ÃZ*UvAÃC:+ãb@ÃÃÖãZä|Üzß~:rðß~ub>ö£+++ªðÃuaå~|zA:ÜvCãÃa©,@ÖßbCå|rz+Cåzz,U+OzÜ:rAhO@ÃîözÐÃî©åvBb|Oî*<Ü<@¢ýzÜðßöAz|.UÖZUbb_åBüîÖ©O~Z©@o.vv£|ÜäÖ:~CbîzÜoA_OaÄuOÜÃobð_¢/îßã@zC*ä£öuuߣîª/îa_Ü>@:OB>ü.ÄörßorUbßý,hÜ|£A@b©/z.*u¢+<öaA.£/©_¢/üãBö:üOÄ+OãbîÃÐ@ßð©>U/+r¢b<ªÖÄCAaª|U|>å:o@vo£äB*©C£ÐåhO_>î*.@~ü£bßbzh>üzbr*ÐhO|ÖöCð¢b,oå.AÜ£o>ö~_£BãßßOOo*©©ß:,Z*ª:,£+|ãoîCÄzo~vÖO@bÐðoðÖ:ÖÐ>bü£äOB.bUåu¢©Z:îîßüoBöZZ>_z©A/B*£vîöý*/öåC|ßro*~¢|Ärbä@/~,>b:bü££/ßzCC:_Ö@Aü.åAðêÐb|Äîhãa,>£>*hrÃðbZ/,@CO<_ß_AÃ+|.ä+îbö_ÃÜZªý_¢övÜhZr,ðÜzÖ,öh¢höÄÄo+OCAãßO©Chr©bÐ_ßÄ>~av~u:hßrBrörv£OvC©£uãÜî*oahärzãý©b©î./aüv:o~|öðAî:/hbU', SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_VARCHAR('max')), array('34eeff6c-7d28-4323-9e28-d6b499fde336', SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_UNIQUEIDENTIFIER), array('4191-02-05 02:41:51.953', SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_DATETIME), array('1975-12-01 15:24:00', SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_SMALLDATETIME)); + default: + return array(); + } +} + +function Repro() +{ + StartTest("sqlsrv_data_types_explict_fetch"); + try + { + ExplicitFetch(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_data_types_explict_fetch"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_data_types_explict_fetch' test... +Comparing data in row 1 +Comparing data in row 2 +Comparing data in row 3 +Comparing data in row 4 + +Done +...Test 'sqlsrv_data_types_explict_fetch' completed successfully. diff --git a/test/sqlsrv/sqlsrv_data_types_fetch_binary_stream.phpt b/test/sqlsrv/sqlsrv_data_types_fetch_binary_stream.phpt new file mode 100644 index 00000000..6780bc37 --- /dev/null +++ b/test/sqlsrv/sqlsrv_data_types_fetch_binary_stream.phpt @@ -0,0 +1,111 @@ +--TEST-- +Test insert various data types and fetch as strings +--FILE-- + "tempdb", "UID"=>$username, "PWD"=>$password, "CharacterSet"=>"UTF-8"); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $tableName = GetTempTableName(); + + $stmt = sqlsrv_query($conn, "CREATE TABLE $tableName ([c1_int] int, [c2_char] char(512), [c3_varchar] varchar(512), [c4_varchar_max] varchar(max), [c5_nchar] nchar(512), [c6_nvarchar] nvarchar(512), [c7_nvarchar_max] nvarchar(max), [c8_text] text, [c9_ntext] ntext, [c10_binary] binary(512), [c11_varbinary] varbinary(512), [c12_varbinary_max] varbinary(max), [c13_image] image, [c14_timestamp] timestamp)"); + sqlsrv_free_stmt($stmt); + + $numRows = 0; + $query = GetQuery($tableName, ++$numRows); + $stmt = sqlsrv_query($conn, $query); + sqlsrv_free_stmt($stmt); + + $query = GetQuery($tableName, ++$numRows); + $stmt = sqlsrv_query($conn, $query); + sqlsrv_free_stmt($stmt); + + $sql = "SELECT * FROM $tableName ORDER BY c14_timestamp"; + $stmt = sqlsrv_query($conn, $sql); + $metadata = sqlsrv_field_metadata($stmt); + $numFields = count($metadata); + + $stmt2 = sqlsrv_query($conn, $sql); + $i = 0; + while ($result = sqlsrv_fetch($stmt)) + { + echo "Comparing data in row " . ++$i . "\n"; + $dataArray = sqlsrv_fetch_array($stmt2, SQLSRV_FETCH_NUMERIC); + for ($j = 1; $j < $numFields - 1; $j++) // skip the first and timestamp columns + { + if ($j < 9) { // character fields + $value = sqlsrv_get_field($stmt, $j, SQLSRV_PHPTYPE_STRING('UTF-8')); + CompareValues($value, $dataArray[$j]); + } + else { // binary fields + $stream = sqlsrv_get_field($stmt, $j, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY)); + CompareBinaryData($stream, $dataArray[$j]); + } + } + } + $noActualRows = $i; + + sqlsrv_free_stmt($stmt); + sqlsrv_free_stmt($stmt2); + sqlsrv_close($conn); +} + +function CompareValues($actual, $expected) +{ + return (strncasecmp($actual, $expected, strlen($expected)) === 0); +} + +function GetQuery($tableName, $index) +{ + $query = ""; + switch ($index) + { + case 1: + $query = "INSERT INTO $tableName ([c1_int], [c2_char], [c3_varchar], [c4_varchar_max], [c5_nchar], [c6_nvarchar], [c7_nvarchar_max], [c8_text], [c9_ntext], [c10_binary], [c11_varbinary], [c12_varbinary_max], [c13_image]) VALUES ((1), ('üb_rÃß|AZ_Uß.zðýªu+bz©£+_ü_©+o<öoý@îÐbÄÖðîä.ª>ärBãý>brCZ:O©ÄvåÃCbZ¢/ßAU__ªh.ßuÖAB*|_Oýîüär:|,ý>AuÐ*Zä:.b~:ö:bÐ@r:¢¢özhöb.©|ªªÄh|CCobî:O©ö>üÐA<îhÄuBÖ+_ÄBÜUîråß>åZ.zö|/zý:öaãOubðÜ/î+ª.O~.bZ©|ýßhab/¢hö~vðCz/_åð|Oü/:îCvîrÖ|>h£'), (' '), ('ßBß/ßãã¢:>bÜA@hÖ<~|*uåß_/.,BhvÃãUÃbãÖÜO~ªZÃßß/ÄA*,OåîߣöZva.:ÖÜÜhäß@ý|î+~A.<<ðBÄÐv|aÄBaÐbý*bz*Ü>ö¢ãåCÃahb*ÜÐ*¢*ý++.vöÖbAãB:ýuãbUUð_äBr~bU.ð+zZ,:©*Ðöö@_O|ßAauaðUZ_©bÜßö*O:ªOª>ý+ãbîÐU,ýz_:ÐB¢//ýß,äðrý/zvoårÜßO+A©zÜo<å_bhUß+åh¢ªäbOOÄAßðCCåUv|Ðv+büC©ß*>*äbÖ¢ö,aU¢ðÖýo*£_ãb+ývߪ_|aßO@öAÄÄäãöAzoÐåa©¢üvOÐ*~ßÜý@bb>rbö|ßÐÃîbZz>Ã>@î¢BUhü~vßBäöCUÐOßÜ<ãUrobvüÐðüåu©bzä+.ßð@£ßh<üß~£zC@Ðr©vÄ.Bªä,@:/bhî+ãÜZý>u¢ÜðÜuö*@åU>ß©Üö>ÖCb:_*Ö:ÖU>.hb©Oüßbz~£å©_ä£OðU>C.+aoöbÄvAuîBßvB£îOª>ã~£.¢/Ü/îî~hA~<ö>OBð~ÖCbðÖî+ß<+OzoåüåChöu+o~©ÜbußohÄB©ðB<~<å@.¢/|,ßåÃ>b,AoÄr~_.oé|©*£B£uÜ@z£v/ßÖÐaZöO_zBÖ©ðüÐ@ðOzrääöBîvO£îýÖÖÜÐå:ä@Br>Cu~ªýoÃ@v~hzb~üߣCAÜ|£**aåOBv£/bCÄТ,>ðB>C£/¢/Äoî©u.b/ö>îU~Bü~U¢obr+ã*/hOðv/ýîÄ~OAOª/aýÄÜUÐhîãCðåav|u+*~uBuoh@>ý_aðö.£rhb@~~ªZu*ß+|ý£o+ßâ+zÜã|UÖÖ¢îðO_ußC>ÐßÜüöäå+ßü@*+£BvÜvå,Uözßä©z/¢ãzO_ãAð£/<î<ÜZU.UvßîÖüîÖuÜ<öBZ_.Äb@r_bah+.Zðª>h©£rhÜÐCÃbãh_C>o£býý@~Að*£*îB.ÃÄå~Uªö©å_ÄöhýäZhB£bÄÄa*Ã|ßÃ*îb*~o.ÜÖüåîrýr:v|:bbA@uUaÃýÄ_åÃÜvzü<,+ü~b|ouÖåÄ@|bCCÖ¢Z,ßb|>/bubOCðÃUüîO<>ý>+ý@hüoa*bb£öAv~oö£v£ßAã~/@ööÖz¢Bv©©z~,ýzýhvöärör|ZßöÖaO_ßÖ,üub~Ö:_©,ªUª|£ü£©ÖåÄr~Ð,<@©+vÃCUÜO.î>ÖÖ~¢äÐBu,,©aCBÖU,~îö*|ßߢªuöhOüÖýv>¢ü£u>*ý|ß|ÃÃU@ßahZuu£Cv+_©Ðü+ýðãhߪð/Ab,b|:Äðu:+Oã<ÜAä@ÃvÃã¢aOߢaACÃObÄUoäª|ß©¢/vªoo<îªZZî©ÐO:+äÐßÃîCÖOCCr¢OUöå¢b/*/a£oC/ã,Z/übÄ>Ðã'), (null), (N'Z@/bAUZ/A+AA©ÜBöãb©~ÐÐ:ªÄ|,Ã/ö@ãßCÐbß@Ä,£.z<ÃoBBbZÖÄ~ðaÐb£b*î@Ö|ü>bbU<ߪrO©C|að_C©Ö@rbbäª>h>.våObÄ@å¢î¢*ouröu:©oãÄ_<ß>Ð:./a£bzäªrãý:ð>äub.+üåC¢oßý©ÄÖ/,|ýb:U£ß,A©@£Ößbhvßäîäåð,h_b~ý:ðª.zZßäbb::BZЪu.~ÖîU:_ZZß>*>åªZ>ýäå<ÖågetMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_data_types_fetch_binary_stream"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_data_types_fetch_binary_stream' test... +Comparing data in row 1 +Comparing data in row 2 + +Done +...Test 'sqlsrv_data_types_fetch_binary_stream' completed successfully. diff --git a/test/sqlsrv/sqlsrv_execute_twice.phpt b/test/sqlsrv/sqlsrv_execute_twice.phpt new file mode 100644 index 00000000..34161c5e --- /dev/null +++ b/test/sqlsrv/sqlsrv_execute_twice.phpt @@ -0,0 +1,72 @@ +--TEST-- +Free statement twice +--FILE-- +$username, "PWD"=>$password); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $tableName = GetTempTableName(); + + $stmt = sqlsrv_query($conn, "CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint)"); + sqlsrv_free_stmt($stmt); + + $stmt = sqlsrv_query($conn, "SELECT * FROM $tableName"); + sqlsrv_execute($stmt); + + $errors = sqlsrv_errors(SQLSRV_ERR_ALL); + $e = $errors[0]; + $value1 = $e['message']; + print "$value1\n"; + $value2 = $e['code']; + print "$value2\n"; + $value3 = $e['SQLSTATE']; + print "$value3\n"; + + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); + +} + +//-------------------------------------------------------------------- +// Repro +// +//-------------------------------------------------------------------- +function Repro() +{ + StartTest("sqlsrv_statement_execute_twice"); + try + { + ExecuteTwice(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_statement_execute_twice"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_statement_execute_twice' test... +A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute. +-23 +IMSSP + +Done +...Test 'sqlsrv_statement_execute_twice' completed successfully. diff --git a/test/sqlsrv/sqlsrv_fetch_datetime_as_strings.phpt b/test/sqlsrv/sqlsrv_fetch_datetime_as_strings.phpt new file mode 100644 index 00000000..8e8c3495 --- /dev/null +++ b/test/sqlsrv/sqlsrv_fetch_datetime_as_strings.phpt @@ -0,0 +1,120 @@ +--TEST-- +Test fetching datatime fields as strings +--FILE-- +c3_datetime; + $value2 = $row['c3_datetime']; + + if ($value1 !== $value2) + echo "Data corrupted: $value1 !== $value2\n"; + + $value1 = $obj->c4_smalldatetime; + $value2 = $row['c4_smalldatetime']; + + if ($value1 !== $value2) + echo "Data corrupted: $value1 !== $value2\n"; + } while (++$rowFetched < $numRows); +} + +function GetQuery($tableName, $index) +{ + $query = ""; + switch ($index) + { + case 1: + $query = "INSERT INTO $tableName ([c1_int], [c3_datetime], [c4_smalldatetime]) VALUES ((2073189157), ('1753-01-01 00:00:00.000'), (null))"; + break; + case 2: + $query = "INSERT INTO $tableName ([c1_int], [c3_datetime], [c4_smalldatetime]) VALUES ((-920147222), ('3895-08-29 00:41:03.351'), ('1936-01-05 21:34:00'))"; + break; + case 3: + $query = "INSERT INTO $tableName ([c1_int], [c3_datetime], [c4_smalldatetime]) VALUES ((-2147483648), ('1753-01-01 00:00:00.000'), ('1915-11-08 19:46:00'))"; + break; + case 4: + $query = "INSERT INTO $tableName ([c1_int], [c3_datetime], [c4_smalldatetime]) VALUES ((1269199053), (null), ('2075-04-27 22:16:00'))"; + break; + default: + break; + } + return $query; +} + +function Repro() +{ + StartTest("sqlsrv_fetch_datetime_as_strings"); + try + { + set_time_limit(0); + sqlsrv_configure('WarningsReturnAsErrors', 1); + + require_once("autonomous_setup.php"); + + // Connect + $connectionInfo = array("UID"=>$username, "PWD"=>$password, 'ReturnDatesAsStrings'=>true); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + FetchDateTime_AsString($conn); + + sqlsrv_close($conn); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_fetch_datetime_as_strings"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_fetch_datetime_as_strings' test... + +Done +...Test 'sqlsrv_fetch_datetime_as_strings' completed successfully. diff --git a/test/sqlsrv/sqlsrv_fetch_field_twice_data_types.phpt b/test/sqlsrv/sqlsrv_fetch_field_twice_data_types.phpt new file mode 100644 index 00000000..54ca0ff0 --- /dev/null +++ b/test/sqlsrv/sqlsrv_fetch_field_twice_data_types.phpt @@ -0,0 +1,141 @@ +--TEST-- +Test calling sqlsrv_get_field twice in a row. Intentionally trigger various error messages. +--FILE-- += count($metadata)) + { + $value1 = sqlsrv_get_field($stmt, $idx); + PrintError(true); // errors expected because the idx is out of bound + } + else + { + $colType = $metadata[$idx]['Type']; + + if (IsDateTime($colType)) + { + $value1 = sqlsrv_get_field($stmt, $idx, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); + } + else + { + $value1 = sqlsrv_get_field($stmt, $idx); + } + var_dump($value1); + + PrintError($errorExpected); + } +} + +function PrintError($errorExpected = true) +{ + $errors = sqlsrv_errors(SQLSRV_ERR_ALL); + if (count($errors) > 0) + { + $e = $errors[0]; + var_dump($e['message']); + } + else if ($errorExpected) + { + echo "An error is expected!\n"; + } +} + +function Repro() +{ + StartTest("sqlsrv_fetch_field_twice_data_types"); + try + { + set_time_limit(0); + sqlsrv_configure('WarningsReturnAsErrors', 1); + + require_once("autonomous_setup.php"); + + // Connect + $connectionInfo = array("UID"=>$username, "PWD"=>$password); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + FetchFieldTwice($conn); + + sqlsrv_close($conn); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_fetch_field_twice_data_types"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_fetch_field_twice_data_types' test... +string(79) "A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute." +string(63) "The statement must be executed before results can be retrieved." +string(52) "An invalid parameter was passed to sqlsrv_get_field." +string(52) "An invalid parameter was passed to sqlsrv_get_field." +int(968580013) +bool(false) +string(25) "Field 0 returned no data." +float(1.09) +bool(false) +string(25) "Field 1 returned no data." +float(3.4379999637604) +bool(false) +string(25) "Field 2 returned no data." +string(23) "1756-04-16 23:27:09.130" +bool(false) +string(25) "Field 3 returned no data." +string(52) "An invalid parameter was passed to sqlsrv_get_field." +string(52) "An invalid parameter was passed to sqlsrv_get_field." + +Done +...Test 'sqlsrv_fetch_field_twice_data_types' completed successfully. diff --git a/test/sqlsrv/sqlsrv_fetch_missing_row.phpt b/test/sqlsrv/sqlsrv_fetch_missing_row.phpt new file mode 100644 index 00000000..261c0231 --- /dev/null +++ b/test/sqlsrv/sqlsrv_fetch_missing_row.phpt @@ -0,0 +1,68 @@ +--TEST-- +Fetch missing row +--FILE-- +$username, "PWD"=>$password); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $tableName = GetTempTableName(); + + $stmt = sqlsrv_query($conn, "CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_nchar] nchar(512), [c16_nvarchar] nvarchar(512), [c17_nvarchar_max] nvarchar(max), [c18_text] text, [c19_ntext] ntext, [c20_binary] binary(512), [c21_varbinary] varbinary(512), [c22_varbinary_max] varbinary(max), [c23_image] image, [c24_uniqueidentifier] uniqueidentifier, [c25_datetime] datetime, [c26_smalldatetime] smalldatetime, [c27_timestamp] timestamp, [c28_xml] xml, [c29_time] time, [c30_date] date, [c31_datetime2] datetime2, [c32_datetimeoffset] datetimeoffset)"); + sqlsrv_free_stmt($stmt); + + $stmt = sqlsrv_query($conn, "SELECT * FROM $tableName"); + $result1 = sqlsrv_fetch($stmt); + $result2 = sqlsrv_fetch($stmt); + + $errors = sqlsrv_errors(SQLSRV_ERR_ALL); + $e = $errors[0]; + $value1 = $e['message']; + print "$value1\n"; + $value2 = $e['code']; + print "$value2\n"; + $value3 = $e['SQLSTATE']; + print "$value3\n"; + + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); +} + +function Repro() +{ + StartTest("sqlsrv_fetch_missing_row"); + try + { + MissingRow_Fetch(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_fetch_missing_row"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_fetch_missing_row' test... +There are no more rows in the active result set. Since this result set is not scrollable, no more data may be retrieved. +-22 +IMSSP + +Done +...Test 'sqlsrv_fetch_missing_row' completed successfully. diff --git a/test/sqlsrv/sqlsrv_fetch_object_class.phpt b/test/sqlsrv/sqlsrv_fetch_object_class.phpt new file mode 100644 index 00000000..9b1db31d --- /dev/null +++ b/test/sqlsrv/sqlsrv_fetch_object_class.phpt @@ -0,0 +1,140 @@ +--TEST-- +Test insert various data types and fetch as strings +--FILE-- + "tempdb", "UID"=>$username, "PWD"=>$password, "CharacterSet"=>"UTF-8"); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $tableName = GetTempTableName(); + + $stmt = sqlsrv_query($conn, "CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_nchar] nchar(512), [c16_nvarchar] nvarchar(512), [c17_nvarchar_max] nvarchar(max), [c18_text] text, [c19_ntext] ntext, [c20_binary] binary(512), [c21_varbinary] varbinary(512), [c22_varbinary_max] varbinary(max), [c23_image] image, [c24_uniqueidentifier] uniqueidentifier, [c25_datetime] datetime, [c26_smalldatetime] smalldatetime, [c27_timestamp] timestamp, [c28_xml] xml, [c29_time] time, [c30_date] date, [c31_datetime2] datetime2, [c32_datetimeoffset] datetimeoffset)"); + sqlsrv_free_stmt($stmt); + + $numRows = 0; + $query = GetQuery($tableName, ++$numRows); + $stmt = sqlsrv_query($conn, $query); + sqlsrv_free_stmt($stmt); + + $query = GetQuery($tableName, ++$numRows); + $stmt = sqlsrv_query($conn, $query); + sqlsrv_free_stmt($stmt); + + $stmt = sqlsrv_prepare($conn, "SELECT * FROM $tableName ORDER BY c27_timestamp"); + sqlsrv_execute($stmt); + + $metadata = sqlsrv_field_metadata($stmt); + $numFields = count($metadata); + $i = 0; + while ($obj = sqlsrv_fetch_object($stmt, "TestClass3", array(1, 2, 3))) + { + echo "Comparing data in row " . ++$i . "\n"; + $query = GetQuery($tableName, $i); + $dataArray = InsertDataToArray($query, $metadata, $i); + $values = (array) $obj; + for ($j = 0; $j < $numFields; $j++) + { + $colName = $metadata[$j]['Name']; + $value = $values[$colName]; + CompareData($metadata, $i, $j, $value, $dataArray[$j], False, True); + } + } + $noActualRows = $i; + + $i = 0; + sqlsrv_execute($stmt); + while ($obj = sqlsrv_fetch_object($stmt, "TestClass2", array(1, 2))) + { + echo "Comparing data in row " . ++$i . "\n"; + $query = GetQuery($tableName, $i); + $dataArray = InsertDataToArray($query, $metadata, $i); + $values = (array) $obj; + for ($j = 0; $j < $numFields; $j++) + { + $colName = $metadata[$j]['Name']; + $value = $values[$colName]; + CompareData($metadata, $i, $j, $value, $dataArray[$j], False, True); + } + } + $noActualRows = $i; + + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); +} + +function GetQuery($tableName, $index) +{ + $query = ""; + switch ($index) + { + case 1: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((-596308883), (0), (1), (-856437494), (1), (0), (1), (0.2409), (0.4671), (1), (0.1303), (null), ('zöhUarCßZOÖ_O@+Ö¢ZvÐCã<_u£O/*U~uÜv©Ð,_oÃ/B+ßzC£O*UzÐrª|buvðäaÃ:bª@Üðä¢,BhªzA>©uCýýbBðäAoÄ~@BªZ©Ð£zOß*Ð*ÄäuªªZ@ª|bßåaª+CÖ>ªuvCZ>ãð~.O>/ÜU©,uÃU/åOýöbÜ.©CvO/zÖ@bª~ÖÖÃÄC/b£b¢*uh:@bÐAZOUÄ/UÐob|ä.ÐÜzåhÐðva<Ö.*A*/Ua/a|aåÐ:>@az~üÖzãý©ð~üvhãAvä+zîozîbCãäÄîª/©rîö/@aB>b©>ÃAoåÄýªåBO¢äaÃî¢<üvÖ*hzbuåªÜävßä>ßrAüöhÐZÐ*>ªböÖ¢,¢_Üðo|ÐB©ã.ãAß,Ö+r<å©_ã|:¢ßã:oöãh<©ZB~©©ZäZzz<Ä_CäÃÐZÐÐBÃu<_Öå©>å|Ã|ob©ß_ß~ßU@uýðvЪ>r_/Ð~ªäCCî.rß/ÜÜh+ArîO>höãOa<¢>obaÃavߪ>ß©/îOª:B.'), ('ÖÖ@zÜ_.uܪü*,B_uäª<Ã:©üýAäböaö>ö<Ã*ÐÐÖr©UßÄ,Ãä¢@:åä*@äZuu~OýýO/ýîrCÃbzÃUh~Ä.:ãb©@îî.ÄZb£¢ACð~uÃb.Ð>>Ððbvavªh>bAOÜÃðî¢ýÐ+~ð~:ÜrBCh<ÖUÐ.bÖªß,:å+Ur:Ö@å:ÐvöOoîör.bA,ªö/ãvv£hÐîho,:.>Ö*~/üüª,COh,îÜî~~~<ß.©OÜåÐÄßCßßÐ>:ª/£*AÐbCUUüý|~ßý¢BBhß|uvßU*,/å:zãBýhß:+ðB£¢>Öß~rýÖå*+ðÃAr,|ß.ÄÄ+ß©o<~©ooÐoAoðÖ@ü©C,ÃýCöî.öö:î|@.ðvÃB©C¢*~ТOî@C:äªCZza+ÃÄvb£ÄÄUo£¢U©b/z|>bCv@Öýîã@ªz:uBîîý,,bý¢ä<:|'), (N' '), (N'Z|ßUîBOüvoßrCöÐ/_*ÜbAð@>:UßbrТ,Coaå/ãaßß/Bår.Ö@ÄCÐöÃz,+Azß©ZCÜ||/BööãýÄzüv¢ß£ZA@Z+býÃÃßÜ©ãCa*>©£_zÜzß~ähA¢ÄAÜ_.aßoaÜÃ~åÐZoZaäÃý<ªüZz@ÐÄÄ>ööbýÜaC:CuO>îüö:ba¢Zäv+£u/ßUb>b:ZZÖ£ßÖ@oo|Ð<ßýÄa*ä<ÄB/¢©ª/Ö+uä+£hÖr¢rz£z@£:öoªÜ.*öCUUz*_ßC:~ß<¢üåýåOÄ|*ü+ß@_<*A>_£:hÃÜüßäB>rÃCßAa>boЪÃÜ¢hî:ßßbbv~bý|C,:üÜ*ÐCÃîüaÃOB£Öüßö~a<©ª>éªa@¢_z|bîzzä>b.Z,öbb,/@ªvAüBðÜ,Z£b@©.*>,¢bÖ:©ö<@¢oZ£/£Zo>î+>*:hCöAr:_ÐîA:Ã/*Ãböz~<>Ö>ÐäaÜ+O>ýaAOî¢>¢ÖAüåvvrÖrö>/öbîåü,£rorÃO|.A/£U/b~z~|>ª>r@BßßäÖ|ðBor_üZ:_+ß*äuÜCö@:ä¢>+,,î:Ãb.ä*ßäB*<_aC:£å¢+~Ö:hÃCv<öB_|'), (0xD35A6A86054875A44046409962581502342C6344FD8635EC1395F8357448F3F191003B3130746C9B9EC07B4C32D764F9AA94050A90AB2A4D257D736F4F4030F8534E07621DE4D1BFC0C8A53C0AE8BA3B31FE8AE042E961E5D6ED0BB201FD4B5E9083F9E3CF02DCC6C3464AC806EE423DC3373E51B64FB56AED218C30D81EBDC37C46AB87EFD66B2E22C41CFC9E98DFE7AACEEFC9BEFAA840CD8FF5AD76ABEDA488874BBF42BB4EEDCB0F2EF133B9CB5542A485092FF404829E6ABA60E0BEB876D67873174C1FA5EF8001FDAFCBB60D13C3A06AD226E3759382C821D800ED0C0D56B81BBA45B30823FE96892DF654B7E85CA69C3F53E650F203DF9484FECDEE92F5EF59139A52CE109847D3905A479C2E11387B5951CDBA706728B7651E842431A7F63807FBA34D0AD88CA60B13C098BB426672C55194412735E9AB729A68F11422A1E8630E95336DEB7181C526AE5CA790FC23B8115FDF12B510D112F8DB6F709A92E971D734ACAD3304DAE0137B01868C03E19E453903C3C1D6AEFEC421751001112B93C5EBD05C4F75840C85FEFF3A0E4A85F4FAD02B608F26AF0185AEE9128FCADE02506DC1A033844ECFB4F63EC3D774EF799B801631E5CA049F3BCED6A08C727DBA20FCE9A189E4C075815BAC98E8EC2A11BF6F760EB4AD1C8668226465ADAD42417883A3DA8A4EEFB6BFA0AF8A1D6BB64FA4C5E11CFC6F0672E0633866), (0xC7D3196A20644950877180BF4E13FE9268FD6B15C1E8CC2545097C30F92715883664D8DB70B34B5106533E6F9C3C20F3744EFA3AFB6A9ADF07F42517E818565F5BF789EA3B2221B5B1190F6DAA5C8808E988BCAB2DDA8DCCC9F8D37C8E3469EDFDDE14A4B335B8512C4489E15B2D35AA6B1C2D5F5CAB32806ABD21424EA5277DB02170CB48E355B3D06B7EE851AC709CC329117106099151AB933AF6147502871D0C3039BD696287C67A8FF12D4A2FBD4C857E2594B576A7901E1FB4A85F9F5A68F7CB457B37DE59C689AB8C79A02461647DAA8FF6DCF3679589A99F4AADDE67DA3488367FE94214A6255F0A0AEA2DF0FABD502BB510638C0463B0F28E3F0F18AB44B14517EEE27B3A95E8D17DE85DAB5BB8A48C5DB2B1CAD0FC321B3950866715465EC538E8D80BDBD5F11F27920E1B060496D59A83BBF992E6741BBCC741EA0F81BB3789977273DF99B188C092A23EED161434B9B0CC928A65335173BD4F13930710A6D6D7916F9223F501D233380044352DA5B20D305EB429AC246320FF22499DF4B982FC681685A5A196BACBC0572D330D3E90A96186D7B1F1AD2A6D0C7224D3B7353E7E7E47CD16200D6B99A4E96855F73012C0929F3B6EE2D00ABBEC17992F72E7138B19AEB5C5717989531B3D0D0844D576DB5F634018F9CF9CAD3D112A08743B27BCD6DD3B487A4D4F1916E853A4C3027716A1BDD00EA6FA92491207), (0x80DFDF16503166992D6112132702117CF7E93B9A1466B76A1816DB4BF13572C4F0CA9508738E73D0107C745BBEFB7EF880CD1B1D4DC43A1A2875FE39551BA8F7EE4D235D6C080869766A8A97F25BAC1C97E3CA241A55772B907BE0FE8F397BF4D47C6D71D8218B78C5B2A7E23AA227CCC9E1A2725184DD99D5F7CCF1270AD93DEE95C376ED3A447557525F68AD95F0ED531335AE9609A3509ED49CE0549A50BAD969F4A5DFB1DF8E9D2C1AFE2575EB4006DF9D13D42AA6E5F173D7199E6320BC01A48F44A14B0EA234A61169EEA42A29E95CD975FD3CE6AEC30EB36BD9C5F0390C5EFD81E5A69FBBD1FADE2D193CF74C48AD2F418AABBFC4BEE1819297A2C96EB6D85D8FA7A94342BD1EA4035DB894861666232B96AEAE619F38C304250C232E306F7741DF67A59E6AED846B06BC67054CFA7B307F9DD80729FCF06FE63E1CB0FA75D2ED4052A7AFC184C1A829435AE3D1BDB82B90F2AF92976F18E37736B82BD0D37091E979C4FA53CC6FFADC2FDDE5A10D9D2986F12008C63F56D13AC3378ACB96D756D2C9F957F8BDD7C382B4B66791324169988FA4DFB368CBAA22F3C08F95E52E46588713D8144530C8195D851189EF3F0782CEF23CF1F8A26AFE4E3505FD472E9D856F0F091F6955525EDBC656E2B92133B3B80DC6F2F2555FE4A8D9C5102BCAD12EE78B19E035E719789FDE15324C87442D0251453A67AA931AAF25768F8F423210F427FB2E07537ED1F34D53133F2D982195970EDA8597C63CF7FE3010708AE85CAB043D258B1625AFD27447195680DB818D55FAF834588B5C60BE3609226D606865F7683AEF72957E725E071150ADE57722AF831B90F0285379E1C4CDDF1C8C6513DEB45F17A0911ABF7B86D5507A0CB3D8BFE672EA44D874CE14C10EAEC79F55D1A2FD71F8865F821D695B156A5302FFB09B27E324E6C58CBA56A387FCA7FE715972416FA69363), (0xC648FDACD01D6A0B6039F39937DDFED9AE5F8F380AAFF91E79E5B3D32FCE1C38DFE25E9EC7E0EA38387B9C6E0AD821954295F833D30FF6164EDE32EA6A96DC911CBAB9557F6012CE7557FF8E138B14A1C31E9199D6E865CFEB484FE9ADFFCDA29E0B514F1ECC16E1832ABE7C0F6703C3CA90274B0542BB3455EA693610282075895DF00739C583F608AF15AC2AE8BF83458C1E1CD8829C5B501DF93362DFD15A0948F3B277FB89040D3043552C0E096D2AE5F1830A049D07C094B71BCB4F9328DEBBA3E1F50D0C4A031A0FA73E472E41138C097DEE4D5005ABECF2E3436D38BD9C1A77716B28771DD9A680F0F4F5C8796576083F46163A916A9D35091A280795E1520A23D0BDD6EB14EBA347CD6010B523797ECD65F97686E0E606CDF26E02B09FEA0EA2EBA5A9A79F1C2A5B72F2C30A8D7FE5A1AA3A30E7DB8442EAB608F8A6E8F3A8E2516ECA7DD0D62D48CF1B13E22FC407388610565B927C76662BEF1972484727F4AE1B81EB567D1D40CE717BF7054C8B9C7851D87ED422929A7641B11E495244BA994A4404321BD208F3058D10F07F50EF637CF9F66EA9107B78E77504CC0F077C3CC16DCB08775A6C7FCF8BCEA57199F6E0C1AADC584E4BF029C89B5A96DD3CE40E10E43F1185E6FC129D869F1D41D6074D8E240A643B13EE756DA13FDC0FC5A28297B0848D6AE55AEAAEB9BAC7BCD2D7E2840D18A9328FD2FCF5C9A3E4A71002D7D41C75914179E298D92FC5715926C89AC94D5802ACF95017DD8797A14166338EA6E2E3905D46A10A1A7FD012CA619E397E3B475B39876CEE7F9F967842190279C1528437A4109EA2B5ACDA29D349C43EC30E5FF8C9B2088CAD860DC94576EF50B6D50D4C4A66956D9CA4A68CAD421B35DB58B434B90AAF0E49F78A1D02D55D3FBC8973FD3C77F45D78F25B250C7CDF6B6BD8EDECC1BA96AB26FE0352), ('9d30ba7e-f6f0-4aa7-a7b1-bd074f458078'), ('2016-10-31 13:39:46.362'), ('1938-07-12 22:15:00'), ('10/31/2016 1:39:46 PMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/0010/31/2016 1:39:46 PMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/01'), ('05:35:51.9401724'), ('2016-10-31'), ('4685-03-06 12:32:44.5405972'), ('4052-05-14 01:15:44.4184521+00:00'))"; + break; + case 2: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((2147483647), (6), (13193), (499351064), (0), (0), (1), (0.1990), (null), (null), (0.9601), ('üo.hý~BZ~|.b+OäÐbäv©,~<ßAöªB*ßuC~oÐözB@ÐUö+Äz©BAu>£BßßÖ,Ã~BAýýüÜ'), ('r:î.ý*_//uÜ@,ä~åãoCzÜ/+vÐý>ÖUý~+Aoî~zb£Üv+*oîbÄO.Ch¢rüÐÜB£ÃýäÜO:£/>rüý|Ö@*b.bO+©ZZ+Cäühb:C>Ã.£î>Oî_>ª@r.~,BåðzÄîvaÄ|/b~Ãv/Öä@ä+å:|uO©åbÜBö|<ävCh/ª/,ð+bhzäuoÖ+îÄau:+B@u,üö<>>Cßýz¢:ð:üöB@båß©îaÐbã:£ãÖbî+A/ýå~©rAuähðývbv+äa£/ÐbðÖ:£©Oh||_|.|örð/©Ü:Aåßb~.+>å'), ('z*bhha|äbZ~,/ä>*hðªü£ZAAö,bUß©©_v£Bräbß.Cð+Öz/£Äða+ü/zvAzbOÖözA/¢ACßÜvßüä@raäã©ãr*h.uvîCÐ::ÄCåä:COZýBAbãÖ@höbðOOhöhB>O/*îh/ABãý£Üî©_©ÖOv:Ü>ÖÜo©_v@üöÐ+£aå£O_î_>OOU©_.Üä£Ðé~Ð<åܪäbð.v©£A|v©ßUªuvý>Z¢ý~oäÐîzrb~ßðªa>ã>OãÖéî:uÖÜÄ:CÐZÖª@B|U~_УUîh_Ī|/î|h_r.ÜðC©O,.ßrAßhOba<ör+©äZ@öB¢ðã¢.ü~ÐZ.ãåBb|rCA¢BB**u+.>©._v+/ðå_¢_î|Obb@bªãoß<ÖvåãaZª¢va<ðzÜC<:OOA~~CbðbvãhÖ|*äößý<ªð~öavîbvÐZ,ªo©ýÜîohßðöaÖ<@:hb,ýCýävªB|zuý©zü<_ªå+bA¢ä©©ü|O_Ö:ðr+zU¢Zß,oßz*¢ãuvª//îða+__ãüU/ÐC¢aUß/vCöÐßoa¢r@ZruU<ý:Ð~>¢ö/Ðð|ö/ÃÃz,Aßý><ÃbÜýÐ*a_ÜAOvhov©zß*ÄußUr~ö@Oðo:Uý_Ü©ß>åª.rh_Öo_@AÄÄý£Üuäã|.ßß㪢ªöZ¢.¢äãðÖzA©Ð:/.ýhu::ªrZîßvbBÜ|ª_ðßåðC/äåbîߪÖßýÖ/hߣ|<äÐ,~,ÄÖbÄÐoBÄöär+ß>ߢ£¢UCUðÄÜbZ+~výaîuÖ+h.ýÐubz£:ar:ÐîC>ßßã|zzý.~,v¢hªðAoÜ>za~ãðÃ*_ZbA£©o@OB.rAã/~oäßÃ@ãbovö/ß>uüðäüßÄ*_Öª:£/ã£üЩî//ýCýßÃrðÖÃU+aöC>*bAÄU£CO'), (N''), (N' '), (N'ÜîbßýßähC>/Üb+b¢<Äo¢©¢OOÜäöÖövü.:<<<ühhÃ>ððuãåbßoUü_+Ã_hÄåbCäBrä_a*ªî¢©*~|Bü~©~å~U~£ÜA£v@++ObB©:orhîa_öÃbäu_~äüß+©a..uÜÖßý<Ö~ßzhªhO_v_<:ÖhÜzÜ+_b*U¢hC,ð~z:OîðUCääBoªbubÜO©üÄAUÄ¢£vîA>Cãßz¢ªð:*Ðö+hAð:ðZA:ý/ß/u_bÐ~v©+B+ÖU*Z:£h*ãðA*îußoÄýåª>ÃA.BB@OäbbªUÜZ©C£ã,Z©ªå,å£ãö/|Ã<@äbröO+Cö<Üä/BCöCAbh>ZãBhau:ÃU/@.CÖîU_ýuAöböüð:ÃAU|ã©o~~©/vrh@åäÖZ.Ãüî_üÃUýUªoã+.v£hC.U<Ã_,a©|Z@Ca,r:ª/*ü~h/o*ü@~ðßååÖÖ+böa/@@zCö>CªÜ£îßãZO_UãZ*~ä/ÖÖ.¢,UAo@ªaã:vA<Ãu,ÖöaaAª~rCu£©ä£b~,_äÐ*Ð<.Oã:Ðvu+¢COC<ÐÐ,AßbîöåöoO+Ä£ðã>ba>¢ãrhãöîäA_©_¢ã©oßðvã_@u©v,|~/Ü£hBýBîOåüBabã/@©ÖäýÜa|ÜOüBª_å<,*:Uå@/¢ÐÖýöüoã:©ßoª¢Äª£ÜB,ðAaZ£a>Ðâª_ýUhCußb~ª+@/ß+vä©Ðß<ßC@ªÐ:*ð>:oãbvO+>'), ('Ä@ß|_~@ZУböãCÖäÐ_,Ü:hBhrÃ+_Oî+b+ðÜv*öboöbrä.raö@z¢örª/~/©+ärýCãvüªî+>_ß>rã.öuÖý©/aäv.äÖ@O㩪rüöð+~/¢C~AÜãu~v.>|ubÜBUßüürßüUUZ£Ab>~Oh@h¢våߪßrßA<ãZr'), (N'Ob|ÜUîÄ:oo£Uö:CC:Ðv.Ü|+OCOß*o*å+Üh£>*üazðð~bßÜð¢boB£uª*ßb@b_ÜZaha,O>BoA@Ðü>äüv.+UOUýuuhoßä.ãð<|£ð:b.ª:_îCªðUC>ßßBÄv~AaîÜUaÃUÜrªU/äö@rýÜß©*å£zåbzA¢Örrüªî_Zo~Övhðý©ãU|z~Bö<îÃhv<+ü,rh:,bO£ßzO¢>BßCz.+vÐBaªÄzO>AA©ßU.,+@+_.ª¢a>.Ð~î©>zü¢Uª|vo©uzêߩ.+bÄ:¢üäü©BB_OýÐvß|ÄÃoaßßB*ÃzBîArãa+@rrä*B©~><ÐýÃ>AäAbüu+vå*vZ|äÜbåÃO¢ub¢Ã,~>:z+,+ýßbAaCäÜauzåªb|ßßåýßZîBãä>.,.ZzÐZUC.oü|oÐߪîOU~rðbЪrbßä:£/öýbOîðo@<ýÐ|ßOh_zßvÜr<ªv£AOår<,ß|:¢Ã¢z<@<Ð~üîÄhvÖ<+.bC_å.*©>AråhßßbU,*CßÖÃAOäB©ßîîhÐBüÖ:îÐZZ©ýBäOäüåÜvßÖ**zB|ZÜ:|Ä_¢b.ýð,h|rvß,Ðu>ÃÄ£oäZZÐ,a_aãåvb~üü>ÜÜäA>£Ð~<|öªäbªbß,äUz+Aü,,ZÃöru/ÜÖÐî䪩r|ÄvãböoÜvrvOBÄO/Öå©C*A£vz_ãC*ÄruУêBOî:r_/ÐðAzb~zhüð|ä@bý@_Cb/|ab<getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_fetch_object_class"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_fetch_object_class' test... +Constructor called with 3 arguments +Comparing data in row 1 +Constructor called with 3 arguments +Comparing data in row 2 +Constructor called with 2 arguments +Comparing data in row 1 +Constructor called with 2 arguments +Comparing data in row 2 + +Done +...Test 'sqlsrv_fetch_object_class' completed successfully. diff --git a/test/sqlsrv/sqlsrv_param_floats.phpt b/test/sqlsrv/sqlsrv_param_floats.phpt new file mode 100644 index 00000000..dadb092c --- /dev/null +++ b/test/sqlsrv/sqlsrv_param_floats.phpt @@ -0,0 +1,112 @@ +--TEST-- +Test insertion with floats +--FILE-- +$username, "PWD"=>$password); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $tableName = GetTempTableName(); + + $stmt = sqlsrv_query($conn, "CREATE TABLE $tableName ([c1_float] float, [c2_real] real)"); + sqlsrv_free_stmt($stmt); + + if ($withParams) + { + $stmt = sqlsrv_prepare($conn, "INSERT INTO $tableName (c1_float, c2_real) VALUES (?, ?)", array(array(&$v1, SQLSRV_PARAM_IN), array(&$v2, SQLSRV_PARAM_IN))); + } + else + { + $stmt = sqlsrv_prepare($conn, "INSERT INTO $tableName (c1_float, c2_real) VALUES (?, ?)", array(&$v1, &$v2)); + } + + $values = array(); + + $v1 = 1.0; + array_push($values, $v1); + $v2 = 2.0; + array_push($values, $v2); + sqlsrv_execute($stmt); + + $v1 = 11.0; + array_push($values, $v1); + $v2 = 12.0; + array_push($values, $v2); + sqlsrv_execute($stmt); + + $v1 = 21.0; + array_push($values, $v1); + $v2 = 22.0; + array_push($values, $v2); + sqlsrv_execute($stmt); + + $v1 = 31.0; + array_push($values, $v1); + $v2 = 32.0; + array_push($values, $v2); + sqlsrv_execute($stmt); + + $v1 = 41.0; + array_push($values, $v1); + $v2 = 42.0; + array_push($values, $v2); + sqlsrv_execute($stmt); + + sqlsrv_free_stmt($stmt); + + $idx = 0; + $stmt = sqlsrv_query($conn, "SELECT * FROM $tableName"); + while ($result = sqlsrv_fetch($stmt)) + { + for ($i = 0; $i < 2; $i++) + { + $value = sqlsrv_get_field($stmt, $i); + + $expected = $values[$idx++]; + $diff = abs(($value - $expected) / $expected); + if ($diff > _EPSILON) + { + echo "Value $value is unexpected\n"; + } + } + } + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); +} + +function Repro() +{ + StartTest("sqlsrv_statement_exec_param_floats"); + try + { + ExecData(true); + ExecData(false); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_statement_exec_param_floats"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_statement_exec_param_floats' test... + +Done +...Test 'sqlsrv_statement_exec_param_floats' completed successfully. diff --git a/test/sqlsrv/sqlsrv_param_ints_with_deletes.phpt b/test/sqlsrv/sqlsrv_param_ints_with_deletes.phpt new file mode 100644 index 00000000..9ed080aa --- /dev/null +++ b/test/sqlsrv/sqlsrv_param_ints_with_deletes.phpt @@ -0,0 +1,162 @@ +--TEST-- +Test insertion with floats +--FILE-- +$username, "PWD"=>$password); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $tableName = GetTempTableName(); + + $stmt = sqlsrv_query($conn, "CREATE TABLE $tableName ([c1_int] int, [c2_smallint] smallint)"); + sqlsrv_free_stmt($stmt); + + if ($withParams) + { + $stmt = sqlsrv_prepare($conn, "INSERT INTO $tableName (c1_int, c2_smallint) VALUES (?, ?)", array(array(&$v1, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_INT), array(&$v2, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_INT))); + } + else + { + $stmt = sqlsrv_prepare($conn, "INSERT INTO $tableName (c1_int, c2_smallint) VALUES (?, ?)", array(array(&$v1), array(&$v2))); + } + $values = array(); + $numRows = 0; + + $v1 = 1; + array_push($values, $v1); + $v2 = 2; + array_push($values, $v2); + sqlsrv_execute($stmt); + $numRows++; + + $v1 = 11; + array_push($values, $v1); + $v2 = 12; + array_push($values, $v2); + sqlsrv_execute($stmt); + $numRows++; + + $v1 = 21; + array_push($values, $v1); + $v2 = 22; + array_push($values, $v2); + sqlsrv_execute($stmt); + $numRows++; + + $v1 = 31; + array_push($values, $v1); + $v2 = 32; + array_push($values, $v2); + sqlsrv_execute($stmt); + $numRows++; + + $v1 = 41; + array_push($values, $v1); + $v2 = 42; + array_push($values, $v2); + sqlsrv_execute($stmt); + $numRows++; + + sqlsrv_free_stmt($stmt); + + $idx = 0; + $stmt = sqlsrv_query($conn, "SELECT * FROM $tableName"); + while ($result = sqlsrv_fetch($stmt)) + { + for ($i = 0; $i < 2; $i++) + { + $value = sqlsrv_get_field($stmt, $i); + + $expected = $values[$idx++]; + if ($expected !== $value) + { + echo "Value $value is unexpected\n"; + } + } + } + sqlsrv_free_stmt($stmt); + + DeleteRows($conn, $numRows, $tableName); + sqlsrv_close($conn); +} + +function DeleteRows($conn, $numRows, $tableName) +{ + $stmt1 = sqlsrv_prepare($conn, "SELECT * FROM $tableName"); + $stmt2 = sqlsrv_prepare($conn, "DELETE TOP(3) FROM $tableName"); + + $noExpectedRows = $numRows; + $noDeletedRows = 3; + + while ($noExpectedRows > 0) + { + sqlsrv_execute($stmt1); + $noActualRows = 0; + while ($result = sqlsrv_fetch($stmt1)) + { + $noActualRows++; + } + echo "Number of Actual Rows: $noActualRows\n"; + if ($noActualRows != $noExpectedRows) + { + echo("Number of retrieved rows does not match expected value\n"); + } + sqlsrv_execute($stmt2); + $noAffectedRows = sqlsrv_rows_affected($stmt2); + $noActualRows = ($noExpectedRows >= $noDeletedRows)? $noDeletedRows : $noExpectedRows; + echo "Number of Affected Rows: $noAffectedRows\n"; + if ($noActualRows != $noAffectedRows) + { + echo("Number of deleted rows does not match expected value\n"); + } + $noExpectedRows -= $noDeletedRows; + } + + sqlsrv_free_stmt($stmt1); + sqlsrv_free_stmt($stmt2); +} + +function Repro() +{ + StartTest("sqlsrv_statement_exec_param_ints"); + try + { + ExecData(true); + ExecData(false); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_statement_exec_param_ints"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_statement_exec_param_ints' test... +Number of Actual Rows: 5 +Number of Affected Rows: 3 +Number of Actual Rows: 2 +Number of Affected Rows: 2 +Number of Actual Rows: 5 +Number of Affected Rows: 3 +Number of Actual Rows: 2 +Number of Affected Rows: 2 + +Done +...Test 'sqlsrv_statement_exec_param_ints' completed successfully. diff --git a/test/sqlsrv/sqlsrv_param_query_array_inputs.phpt b/test/sqlsrv/sqlsrv_param_query_array_inputs.phpt new file mode 100644 index 00000000..6174d36b --- /dev/null +++ b/test/sqlsrv/sqlsrv_param_query_array_inputs.phpt @@ -0,0 +1,158 @@ +--TEST-- +Test insert various numeric data types and fetch them back as strings +--FILE-- +$username, "PWD"=>$password); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $numRows = 5; + + ExecData_Value($conn, $numRows); + ExecData_Value($conn, $numRows, SQLSRV_PHPTYPE_INT); + ExecData_Param($conn, $numRows, true); + ExecData_Param($conn, $numRows); + + sqlsrv_close($conn); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_param_query_array_inputs"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_param_query_array_inputs' test... +Insert integers without PHP type +1, 2 +11, 12 +21, 22 +31, 32 +41, 42 +Insert integers as SQLSRV_PHPTYPE_INT +1, 2 +11, 12 +21, 22 +31, 32 +41, 42 +Insert floats with direction specified +1.0, 2.0 +11.0, 12.0 +21.0, 22.0 +31.0, 32.0 +41.0, 42.0 +Insert floats without direction +1.0, 2.0 +11.0, 12.0 +21.0, 22.0 +31.0, 32.0 +41.0, 42.0 + +Done +...Test 'sqlsrv_param_query_array_inputs' completed successfully. diff --git a/test/sqlsrv/sqlsrv_param_query_data_types.phpt b/test/sqlsrv/sqlsrv_param_query_data_types.phpt new file mode 100644 index 00000000..0f319efe --- /dev/null +++ b/test/sqlsrv/sqlsrv_param_query_data_types.phpt @@ -0,0 +1,65 @@ +--TEST-- +Test insert various numeric data types and fetch them back as strings +--FILE-- +$username, "PWD"=>$password); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + ParamQuery($conn, "float", SQLSRV_SQLTYPE_FLOAT, 12.345); + ParamQuery($conn, "money", SQLSRV_SQLTYPE_MONEY, 56.78); + ParamQuery($conn, "numeric(32,4)", SQLSRV_SQLTYPE_NUMERIC(32, 4), 12.34); + ParamQuery($conn, "real", SQLSRV_SQLTYPE_REAL, 98.760); + ParamQuery($conn, "smallmoney", SQLSRV_SQLTYPE_SMALLMONEY, 56.78); + + sqlsrv_close($conn); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_param_query_data_types"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_param_query_data_types' test... + +Done +...Test 'sqlsrv_param_query_data_types' completed successfully. diff --git a/test/sqlsrv/sqlsrv_statement_cancel.phpt b/test/sqlsrv/sqlsrv_statement_cancel.phpt new file mode 100644 index 00000000..4efaed9e --- /dev/null +++ b/test/sqlsrv/sqlsrv_statement_cancel.phpt @@ -0,0 +1,105 @@ +--TEST-- +Call sqlsrv_cancel and then sqlsrv_fetch +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"UTF-8"); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $tableName = GetTempTableName(); + + $stmt2 = sqlsrv_query($conn, "CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_nchar] nchar(512), [c16_nvarchar] nvarchar(512), [c17_nvarchar_max] nvarchar(max), [c18_text] text, [c19_ntext] ntext, [c20_binary] binary(512), [c21_varbinary] varbinary(512), [c22_varbinary_max] varbinary(max), [c23_image] image, [c24_uniqueidentifier] uniqueidentifier, [c25_datetime] datetime, [c26_smalldatetime] smalldatetime, [c27_timestamp] timestamp, [c28_xml] xml, [c29_time] time, [c30_date] date, [c31_datetime2] datetime2, [c32_datetimeoffset] datetimeoffset)"); + sqlsrv_free_stmt($stmt2); + + $numRows = 0; + $query = GetQuery($tableName, ++$numRows); + $stmt3 = sqlsrv_query($conn, $query); + sqlsrv_free_stmt($stmt3); + + $query = GetQuery($tableName, ++$numRows); + $stmt4 = sqlsrv_query($conn, $query); + sqlsrv_free_stmt($stmt4); + + $query = GetQuery($tableName, ++$numRows); + $stmt5 = sqlsrv_query($conn, $query); + sqlsrv_free_stmt($stmt5); + $stmt6 = sqlsrv_query($conn, "SELECT * FROM $tableName"); + $result1 = sqlsrv_fetch($stmt6); + sqlsrv_cancel($stmt6); + sqlsrv_fetch($stmt6); + $errors = sqlsrv_errors(SQLSRV_ERR_ALL); + $count1 = count($errors); + $e = $errors[0]; + $value1 = $e['message']; + print "$value1\n"; + $value2 = $e['code']; + print "$value2\n"; + $value3 = $e['SQLSTATE']; + print "$value3\n"; + sqlsrv_free_stmt($stmt6); + sqlsrv_close($conn); + +} + +function GetQuery($tableName, $index) +{ + $query = ""; + switch ($index) + { + case 1: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((-740876517), (null), (-13014), (9223372036854775807), (0), (1), (0), (100000000000000000000000), (100000000000000000000000), (0.5369), (0.8851), ('ðªÃªÃz>£Ã>Äbßvß*ý~:*ã©BoÃCrß.ЩÄ:h.Ö£<¢rÄöÖZ++b*hr©Z:ö@v@@ð|C©Uöãa£©äbÃ_üU¢åðßßzbåÃrb+v>¢huh|bbAÄß<+Ð.>b@C*Oz£+ÐĪ>uýbå<ÐhAÄã|üðo~oCz:~£.ªÃöAboýß@/ö|äb©ðý¢ª,ÜUüo/aÖ~ÖîîOO~rC+oaabb£äÃv_,ªv:ðuB©¢b@ýäAuzOÃuav¢ÜübCaOö_*O*ÜÃßuܪ.vOýãö_<¢u|©r©bu~~br<îß/_OäUߣ©a>Ar|b>ðA@©.ö.åC@v|Cro|h@<Ü:©bv©ü~_OO<@OB©aÃß>äA©b*ýU_:äðåÄBvAUüUa:+äÃöbÜ+A|oä+aªÖÜ¢Ðv/:hÃvzÜ/îb.b/ßb>oýBZuå@îÄb_Ã>_U¢uü~auO/£Z+:ühB:~üBªß@:b|öuýrßbð,ßßß*¢ÃaB£:ÖÃßUî<Äb~£ßÃääoäÄOA>+ýÖÜßb©Üaýßu©vrbßÄ£vä©@|¢Cb¢OÖýßboBÜða:äßðA~äªaª©Cbý|Ö£ÃübÄývZ@/zÐåßåÜ*ß~î:@/rýZÖÜZÄb_>C|>b@|aÃäýÖh,î<îÄ~ã+ß~ªbÖb/U@aß+ÜUÖU.Ããu|r~_©b¢,ZåªhuOÃ_ö/*:hÜoãO.+hor@a֪ߪýb.Ãub+~,¢,B_åA£ýBß_/@Uܪöð_£+@a|UÐCüüßåvãZ_u<Ã/ðÄ/ýü@zãaöîZoãðAB+_vhîÃaZðUªrÐ|Cv>¢öö_AböîBAb@Ð:îv/äö.ZaA/ÃAUТZ.ÜA©rýb~z<*Uß©ðÜOßÃöZÃOårªî_ãaßrvߣz>buðOä_îä/AÃýÄ~o<ªb|Ðz£CýýaªvC~ÜÖ:oÄßo._Aöh,o~ãðÐ*¢£ã¢hrßðbu+î¢Ðävð__å*Zr|_Üovö.hBäö@ööhZöååCooCz/_î©Z,äa*/vB+A|ü©bvAÃuî*ßU~ý:Ob,.<ÖÐbÐäÄýðO_©böð*ð:bîb¢O¢|åbßå|h,åÃîZßððu|bÖb,©Öz©£_Ðü>zä|C>äßîB,Aoß+A¢_ý@ã*AähUîCÖhUA/.,+bÐÄ/b:©ö,Auå,vbC_rCoZý/äªÐbÜä+ªÖ.aýîß~:.©B>CAAÖ>bC>rZubbzãã*,îbA/<<,vhZüÜÄ>O@zã~ý~ÃÐä.h>ÜüÐÜÃ>+,Aoa,*äuZrAÐBU:~:+|î:u_î,îUÖ.ý>býCbßðB@ABÄa|åÜbu*.v.C~ðh@v¢hB~ÖüÄuß>*£aüÜ/<.ävaCOåÐzÜ+ßAßAö*:.h/åCý£ÜoÄ©ÜZ~|.,Ð|ä,üuä:O*/ÜÄzÄÃ_+vA@Ðv,+AOb|>ßU¢.z.|_h><,<ª/zbzÄB>Ä,oªBhUhßüzÄ<©äã.vz©BhOååuß|£hªB~Ã@bvî|*Z.*_oÃýð|<©A,:,|OvÄå*Ã@©+rhÜoß~>Ðü+Äß©Ðruîýo>|ÄCzüåýrÃa¢îÃoö_uÖÖîoz/B©Ð@|@£:Ä*Oß>a*:_Ü|ß*åv<+äªýßã¢Z:Bä~rüãý~hªý,ßv£OzCUª:î/öãÃÜ¢,£ã/v<<£ÖU~ö|urZZb_~üß<Ð,ÄaO/£,vb,r>Ö~B+ßý/b|zArÃh©ÖoO£~OCA|:vrCäZåOAÐ_ÄüoÜü,,obÃß>vü¢BäТZ¢_|î_ßzrv/äOü¢öÜa/Ãrßüüå©ÜðoUOA<.:îO/UUOboî£ßª_©aäB:A/>©~©UÖåhüb.,ß_ÄÜ*Öo@C£bzbbUUh,Bª/üöU¢Üa>zðOßU:ßaöu@*v_Zä~Uªr@¢a~ýßüß~:o~ÖÃrr:Ub¢uuZðåÖzîÃb©+OßüÃð¢ã:>a/>bðªª*B/*OüüZzbÄu>bh@ßß.öÄ@Zã.¢hußßäüoÐ|~£*OrãÃ,ãðªü/åðÄU_£åãߪð|ãÖ>@uÐbÖ@|ä:¢OBaC:ðß©Ä+åbª/@äÐÜb/¢,Brå¢O@rðzbv£+|äÄ+ÃCîC,ª/¢z~îÜÜßaýßýßUãÖZ:ßAßßüböýa/+zÄUĪ:ýäözh_UU£_|A<@öß~|*_.ö|ð/>Uö+ßã+¢z.,Oß|+ývßÃÃbUAÖvZÜýý_Chåu+Aðv>ªßAîb/OÜß+z+*,ÐOß/~,~/b*oBßzr©OB*:ßo:+üzB_ý£©OßÖhb|ª+îîoÐÄÐý¢ä|CouAî~~Ð:~+ðhýã>~bb~öuã/åªãa:£ãÃðhÃäZC_|äåü~_U:zU£_|ßh+ãîUÃB~ýhãÖÃaaZüO:__ÐbUvå¢o@ÜßBvîßßäüA:/üäÐCãäU/aZ:BAU.Z~£,böbu+Ö<Üur:ahaB~ä*h:a,Ð><+aU£,>Z*©,>î£,,,bAr~vZBðÃ|ö/r,Ãu~ÖýÐrhãÐhÃ>¢¢©uüÜbð+@ðrbðzöã|åCvÄvzî,£oöO>:ªßhÄOö©*äU@ªA~/_ßÄüCv¢uaÃZ|Ãîbv>oÄÖ*Uä<.Ü@/@©Ðh>haoüAvU¢UhAå.ý/ßüa:*ßbZöu~+ÜrÐ*öob|îåz<å/_ßbÐ*:_ð©||åÃaoB£ª|êr©£+.>o©ªaöýÄ|zÖä£+|©åCÐðüb|hbä,ßßzh:+öö+vb_ªåü:ä*ßörCð*_î///¢__h~Oîuoz@Ã.r.ß~CAýªÃßvîoÃåýª_ð__ÖÖ_,AÄbrÄAZUßð_@üÜ./ßðÐUu.@Au|Ü~oå:©ZOB*ð,ýÖý£,Ö*>£Ö_CÐãaZo¢rbýhUÄBð<årÜÃb>/ðýU~AÖÃÖîAbbo¢£aA>Cå.ä@Z|üA|Ä/Uboå_ývß_b_AýoÜrvB¢îCªuUZvBãÃ/,*z+o@:,C+ßCÄ~ß:<ª/O>ð~ªß.£bObzÄoßrÄvUåA>äZBbUzßzÐzö:ÖÐ|Öå+rAbBöðBÜöUîhðªåbÐ@Z|ãäZýüääaUÖ©ö.åÃý:Ü*oü<,|/ª.*~ÃaAÖ~,:@:~Ö¢>b+*U,£uBåöOö:ã+/Ãbbä*ÐOåý>Ö©~£ö:ý<©Ãª£UzöAÐ.ZßC_bbÖ_ÃÖo+o:Uîî~_uaÐüýOÃãbßÖîZaÜ~r+r~>î/Öuzz@h,*.,@>.|ãh+.UÃÐðää+.åb|Ãab*ªýЩäoé<*_Ü@+a+uäourÃã,<öCU*ßÐ,>u|AUªzBãAäÃzßãßhîo+rðb:*âhz+|/vÐ,ð©O:Ub,Cb,£<ð_£að/ÖZ~OCÃ~üoÜ<>:/~:îhö:îZãßübÃrâ©+¢a@:ßÖ|>åv媣ªöuå<¢@b<@<:Üuba+Z|CCä/ðh£oª<~£.uh,B<*uýoZo>býa_<>@ý*>_Ð_zÖß<îªåäbu¢ãý|ab~öãÄöäh££Cýb©bÖ¢£~o~._UÃv¢aÜ,Ã>ð>ðÜ_Ãa~:¢OãÜ¢ßrübb_bvÐã_~U~vöåh~u:©Ub~/Ã<:+rBüîãbä<|@@>ª..AÐCÄvß*>uü+bZî£ä~ã,¢/¢+ÖrB/~bÐübäÃ_Ã|Üo|å¢Öð<ý|~î©ð©rÜü/,,>O,Ü/@avr¢,üzß©ßÖßä/vu_ä©.ßzäbÄBzOvýoBrUîrÜöOö+Bý.Ao|öBU£_BaÄÄ.hîv£C|©©£ª¢@b<ª©ÄbÃzoýhî.r:,B+ýZbZbr~>:vå@.£babîßÜßzUÜ~¢b¢/£BvB©OÖð,ÄöU¢ZТ£~>ß.h|~*_©bvÐ/Cübßo¢.Ö<*b©Oä@rA@oðA~ßa@|ÄÜC@ã©vßZ|>|ýUãA>*<+Äß*¢_Cß,|ObZªî£¢ö©/aðbu£|üoÃr|zÃAOýößîh_ååvC>OüB¢rBßî<:©ªÃb~öBoýå+v.îÃãb@åhAÜCo+ýbba<>ß~Ö*+Bß|ªÜÄvuCð|Oð,AzAz/å>_Ä~åao_.~é~zÐuüZ,z>åA::åh¢/oÖU*:å~BüÐÖ|Öã|u墢©ß+ÖöB.ª>vhýä|Buß/*C:hCý~¢ÜÖb>r|U_ü¢ßazubvBããÐåa£¢Ðß|@o_Ã+o**£|ýb~Ð*ª>.ßCCbOAZhãÐÖB/Ü.aÖ,/ö<ãüå.ÄBÜvý|/orðÃ::ý@.u*¢~|Cu©~ýüo|öühu©>ð@äão*öuäÐ|z.ð|u@+Zä~åCÄaªUªvü_rÜ>~å+>:Uªî,öaÃ|uýÄ:Üößîb.oã|B*üîO*_î+©äaðbv©@OÄ*Aaª|@Övüª_>r©îoUö>Ö©h©å*ä:äî|u_r:>@bÐ/,/b~ZßO£*AîaÐA/Ar+CvÃbî,/+ývÖO,b@_Bzª~,borBãa_@@Ü/Zhö<Ðz_Äoöãîåo£ß@rðB:£ýbC>üb,îä<>*åuhbßÜßî//uvzöOOaZªÃý©oå+u+>zUb+.|Z/Ã<.oz_r~ãäOAäOå|åß+rbUÄ~/Z~o+ääÜ©Ü@|î:+::~b@b*£h,_>~rb+/ÜßÖaübuüý/,BÃz©:<îÜ>rO_b©:Üã,Z|~.bßbÄ+<,+b@£ü_:bäACZzåu~Z©*_Z.ªÃäßâ_~ÖC:,CCÃîBCýÄü*Ovv_ãußAaöBü>ãÜOäã_ZZaäðÜBî.|r|ª/U@~@oÜzÜÄB>Zh£ãCðvhoÃAo/*ÖÐz.<ß.@|uzr©Ãbu@~î+£¢vAUh*~UäAA@,+++>~Öbb@¢ªrUîüaav_åöBhou¢ö@~Azß_¢,v£o_ö>ðü@BªhÃîÜZÜooOªÃÃå¢ßuîö<>ªC/öüö©<@UAh*ä>ð@r£::ÃUÜBz/ruãö,B/äüãÜbCBZUå~@Äoßßß|vU/Zu£ä£@ßhZÐb/ÐðUää:vAubã£î|oî<ÜîßÄîÃCåßZbßhBÃüü+öÖÃåУ..~Oð©+zðÃ@ßUÄãuhüboOrBüo©öOuhÜîOoz@CðöhöÖî:~åÄU_Ä¢Äß~+ÖÄA|voðbOÃ+.AÜC*zîöýß,/ªü|./zO/©Ob@@@|Ðü:zÄ~zoÐüäßÖÖu@|,Z/~ÄÖ>ÜývÃrývªßÖoü|>©äZßã/~@/©/Ã>CuBýAuö_ÄZ¢üÐÄUüý~vübÐ:hÜåzðU~¢v@@h,ÐÜhÖªöö|©ãBÐz|/boÐ/.ßörhîAvh©+:u|åUu/_+CÜå<öܪªÄh|BßBЪÜ~aã~Ãßîz.åo/zr££+Z£îåUObuÜu*Тozßh£*OrbßhbA|+Ã@äð:Öüªh£îðÄ|Z_.¢_ÃU/ßð@<ÐUö*ßu£Ä_U+.väOУ>Ö/<©ÜUÖ:@äCvÄð.ýAУßßÃvaaboý*©@@ÜzOBýb<Ã~,ÄߪU|:ýZã:bã©bh<£ÐBÜBã*ª@,ÐzýüUüu:ü>.Ü>Äýbãb/ð£hö@/Öªßvå:aÐoÐÃZärCªuB©båu,Übavo~Öª+>|>,/OBA,AzýZÖüäz*/bZ:_@rAuäOão/CzßzÄÖobý£Ããð,*,Uzbö>/vz,*ßa/b~öÜvÐo+:ü.vZä£Ð~zbª/@©äoBã@ý*vüb*Ä|AUUÜOAß*©©O|£ªßAÖ/ý|îbªOî£+BÃ:OühbÃzbZ..Äzr>ðvå.@Ur©+o©ü£bå_ÃЩuavУÜzBB_ßUÖÄhrýö£/oOãüboåäîîB~Ö<<~O@îßÃz¢COoBaã<ýbb:hzoz©>î:Or:u@ZU~A>AßÖã~ÄÜîbåa/îA@Z*o.äã/ðuh/Ãü©ßÖv:@AUÄZÐ:_Ü:A/ÖZ/Äröboo£¢å@ÄhAßUuCÖý.ývªA/ö*býîß,CvU<ä©BZä.+rªª/Ä_rîÃÜî/:ªð©uß~£ýÄ*AÖ~Ü+:£Ä_~î.:ã@¢_ÜýCªuãßäöaåU|©hÖäãÄOAåý~ÐßîÖð/:ÜuUß@îOvä>ßî@b_åýv£:Oovvå.ð_ÖÖ,ß:AvãCAazZ|äÄuü<>röðßÃÖöv*o£z~Aðð/zßãð_<ÜÃåhCî*£Aܪv>ýÄ@OüröߢZ,öar|*ÄUO/ýÄCߢã+ý@¢_üÜ/ðÐߣäĪÃo,@OBÃßZz@/ã@Z~Ã|hãüvaÖ.Oªvã@b,<ãz_ÖZ:ÐÃOuZA|ðî£hCbÐå£.o¢Bý@/üÜUCöýOÖã,C.oÜbÄÐãvâÃ,ý©ªrð,Cr:uUu:Að¢ðÐîzÖü*Äå*:îvÐßßZ,v<*ßU+ÐO©Oî_ª©ßu*üÐ+£a*îzªörðÐ.<öbÜ:vC<|ª|zhß*ÐÜBA.äbðÖUh:<©/@vðÜ:ª_î+ür>OÜBÄOBýäoBå*|.az_zß©ßCbZ,,+ÐOåÖbO+åh£|ö*>ßU/,ªrA~r,Ah|ÄbÄßzßÖU©Z*ßhr_åÃü+Ðð££ß:ãuoªuhüb|Ä_.©©,ÄOAå_£/BÃߢå>|Ä/ãÖråöãîª+zoã::Bbvvð£/öÃ@oC:o:Ãü<ßvÄ©<Ü/åZ:_u>ª£UÄ~¢u¢baîbÃÐã,ÐoÃ+ÄAOÖ:ª>ãb*ý/|Zääß|<@vüh,|ÐA<ª©h+Ovv¢¢äzðöäã/u~ßÃ|ªuaaÃOîð+výu*ýBðUZvauUðU_~/hÐhýð*ðý/h*@b,ÄU~zÄ¢äÐ@ýv_Ã*+ZÖbvbªÜZo|UUbZ@aÖªª+_@zbb:bÜ.îh¢,|ü>üv~¢bb+ãßoßÄ@öZb..oäoar¢_ýåv.¢å_*aAvî£*v<ªbrü,CzöbüBbOB/ovv,>ðãb:BÃÄÃÖzüOÐ.©*.îhuÄb.båuZbbB¢+üÜ|ÜaÜäÄ.£rÖaî+Ü/BÜ+hhßã:a|ä.£££Ãªuöäîz¢A:ßbä£býå@¢ö*vuãýÄzauaZÖý|b.Ã.î@ßhzÃ*Ðüßü~ýåAÃz/,,üo~Z.Aobbzª£z>¢CoOö_>UÐ_b£a.ÐzU£AZ:Öð>Öv_v©©rzU@vð_h<ãîÄ/bUÖAÖzÖbü¢AAäO©:©au~b*Ð,@CßÜCoO.ÐzåUýUßöðªuÃur<î:b_:bîĪCÄßÃbßÐß©Ü|åã¢>oüÖzCA~u'), (null), (null), (N'CýOÃ:v*Ð:Ä|~ÐCðÄ£~bß~,>zª~|_<,.ßßä©Ãªä:|ZÖÐvrÐ,î£_ÜÄrrZÖ>äBäÐßUbzÖÜå_ß©uCäO£åöªÃª¢~îOÐÖ~ã<@©ü:ü@:./ý~Ä©ðÖ|rr*vßrå+AüAb¢hBab+ýå:Üã@ZrOÖC._¢î@BÜð.b.Äa.©.B/ö,*r*ÃÄ<ßÐ>ZA+B¢~zCU,.AöZåoZurä.bßåÖªz~öý|@å>@ý>üvöãªCB_*.ð:_.UªäbUu<+aUÖoUãîo:ßî©*övAßöÄ*.r.C/ªZða.vÖa¢UÃB/äv£Oöh|äbZzãvZ@Cößã¢Ö©_ýhA_h©Ðêö+b:ÄuUv|~_aªOCüuå©:ã<ÃOУbîª/OzªÐåðub/Ð|~bOh_,üð~îÜbUÖ<ÃåÄbo+©h,r.åZrÐo<öåv.>brvbýA@/v*ý.u*++|rððßðh@©ÄãÖzC,ÃhbÜ/ZrÜChü@+b@ã|<äzZäübªßÐ:örÐÐh~>Ã:A©b.ããßB._ðåü|ðßArßOýo¢|Üãð/u+Ü|ã,@ßÜî:AoýrUß|ü£ßîB,Ã@<åA_£að,äAÖ+b~ACAhÖßaýö,Öräv¢zv.å*ð£ÜUU:BÜho@:@.¢Ãåü¢å>>ð©Ü/h©Ü_Ü*>Ä©,*ÜåZÄuäÜäZÄ<©ð:zßaä/ÄB<ãz:aª.oößÐu*BbzoU£hß>ЩaãB<|>äu©UzbývuU,Ä+îÖ/~*u<ãªß~zýB£:O@öª<ö@|BßazîA,ÜBCßð~UÖuB£U/Bözü+ãAÐ:BåüUå<|uhö~/ßrävÐäðÜÄ@ßÃb:o>bübý©zýUäÃÃÜOaüÄ©aZÐ:âüCöЪbvO/ZbooåªÖ>r¢.bOh~Ä|CAbo:A++BäĪ.Oª>U>CãßßbUß@ßv~ßü,AbðUCväAßßÜÖbåß:ß|ð©:äÖzüß*öuBÜzî*|+U+,ý¢BbýAzrðÃ,ªuOýãðv~,_+£Ö_©übvAvZö/~.Cî*b:ßÃoBßA©£ð@zãý@U/Z@A*v©_rO©ßªªÄAö@öÄ©<£Ð£Z:+_ö<üîzüß,ZÐohoüîhubAr<,*bß~ü©ß.bãzaUÐÄîv£î©£ßÜ~@>Ö~/u.ob~CîBÐao~å©*ãoÄO:*ß.zßÐ|Zub<îrýã/höêßß_£ÐbÜC:Ö+ß|hb/aävä~¢å©_U+îãîÃrA~£_UÖ~Äü/ä>ÐÃ*bî./Z©ãAOðåB<Ääv_bhÖý>|oUv|îhÖ,öäðZa:>.|r|<ª,©CýBoz/~ð+ª*uoaBöå/_öu|/äãb£~|:Aöߪo|ßý:ö+ö|Ä©r*.:v©Ab:/~~Uzvßý+î|.:|ä_håý~~ZýÐr¢ÐUhZÜýå+o~ý_©Oo+~£hUÜÜ_îCÜb>äC~ãbÃ|_äbC~@B.v|¢rß/uÄ*ªöäöÐUOaö.>Aao>ý¢£>@_~Щ,v+@~Ub£/uUCߪߢåC¢Cª+v.CuOÜÐrßArr~:åÃ:Ä*ä¢Üî,oAãå£.ö¢baýü,*bZîu/uî¢a+ÐrÃðßÖÐ@>Öbhöä|/bªC|<ðZB£bü.*Ã<_A>h/ÄAß*Z><£vð*b©ýhz@:åßîªvÖß*oßZ|C:ÄCÄbÜßvü:©ãÐÄîÖ+_hA~ªz_CÜb|ü*©*Äßî*_vB_ÄOð,UÖ_bå~ßvh__ä,ªªvÄUz£ÜzCðß~rb/ªz.Ãöv+ua©,@,OOvböðÃßã:äZªð£ªUZ,Öä|rho:*ubÃ~öC.Cð©Ö¢v,ªÜä|.++ТÜ+aÜ_£ýCå@ª:ÜhäðorßÄö>ÃB+Uß*ªÜBý@/ðuö*å>ö_åCÃöߢý¢uh|aÃvÃð/vä£_B.¢ð+u£ýã*zÜ.¢|//:O*ü_ö/@Щ_Aðª¢ZaÜÄba<,+£.ßUuZ£C>ýãÃZ||b|Üîßvh~äåZu/*aho|Ð:ýüÖ<ö£¢@ßCýÜ££b¢@|uC,*îbÄrÜîã<ÃU@U>CßZöh©¢ÜÐî_ªÜv.¢¢*ªb©bÃÜÖäÄÃ+:ÖU.oðßu¢b<Ö|ooo¢a£/ãbîîOãbh_rvüö/ZovO¢*¢.ýUhZBå_ªhäbU@,ã+rzä©Ã/ß>ßhåaî/.Zß:b£ÜîÐo¢öb/îÄîîzðüZ<öÄ~:£>üa_ÃÄ<Ü/bzO~zªÃbÐ*.ªBuüÃürh.ÜobU+öCZã~C*£ÄÜîýubA,ÐAãÄu+ü|*ãý:îß_ü>ý:>ß.,~£ß.AüÖUhBAbCU|h~oÐ>ÖhîÖåUö@Uª|~*£vb/üðªª~ã+åÜ.Äðãäzbî£/Öb@ßaUý__:Ð<ü<Ä¢UhîoZ_äCãu~.Öu@/aã**o|/|ÄhCbOßÃa>@@Ö<ª*ÖBü.<îh<ßZbv~zzh,äð/ýUbðÐä|@rî*/îÜ@.Ooü£|©ZßC|CÄO+Cå|o+@AA©ª*£Uîö¢,ßßîöuåvåzöZßOÖäzäß<åÐ/a,o>,zð..ã>OzðZ.îývZA|Öu>ä>z.C>Ö£,Ü_ßÐ@ööî|:zý/ðA:å<ª/,<£öý@ãªaZ+Ö_ðhÖ¢O>*+CýZî>/|ZüuahraZåäÐýC*îvb~:@ÜvßÄüvbv<*A@bZzAb*ª*ðäåÖ:>rarÐu*AðÜAh@AåCÐã|ãuðãAårÐüCªð.bv>bbbÖBÃ.ÜrAü+|£_@o|ÃA@hrß>Öö*îãä<ßÄB_A©ãbîöö~ð+ßÜÐÃ,r~Uüð|:ß,Cö:+.bý>ý|Ü:¢,.BöÜ~ÐZßA@.Cîî*AÃz*|hååÄ@@ÄZÖ*ã|a¢ZªýÄåbbzîööZoBoöå:BbU@ruUbAabÄÃãBZ¢ÐAü:v<ßýöÜu*¢hß<,:ö+Ü.Ü:ßAbvrvBrå>/îîýãª/_U:AÐ/äUuZ.hðãzv_£u,|ßéa¢voAýhb>~_,ãzb©ZBîvöAðÃvßýb*ðåßbÐß_b@äß©öBª.ÐÄZbå_¢b©~:êîuabÐ~ÄÐzB_|~ßUC¢aðzv|BÃü:.UÐ.o+ã>¢¢ß~r_îÃ.¢Ü_zÐ+üb~@+z*CzCZO:ýîöoÄbO¢zAß./ªo>ð/C©îÜ'), ('ã>Ov@<:î+:Ã@©ªãu|Ö£aÖCöÐO¢.Äo@|u|+*uîîBäªîbüð©ãzªåBhh*ãÄ_:BîbÖÖãî<özU¢*£©Ü/äbüß_ßBCv//br@£Ã:ßrßãhrb:Ä@bü|<<,ðü¢~£~Bö£>/>,Ðߢðîã<ö~Ðäî+ßZbhÐüöÄZ|aCßUBär©ß~büÄ/b©¢a/*UÜ©vÖßî@Ö<_Ã/U¢ßBª+B,bb*Zä~¢Bb*ðvüzU<_>vöÄî*A,_<ßu|A£züOÃzýîUZ/ÃC¢/<ö++~BBhÃhÐZðzCßä@zZöaC|h_ªA£vuu/üb@<äýð|OvAüAb~C@|Oß*<<ßßuA@b>.ð@äüUª*ý@,ÐÜÐB~B¢Ä££ö/>ÃåCo+ÃåBÜ<£åÃ+~üUBäüCö,©.üo.ý@ßåý/@.ð*zrU+,¢Ä¢>rB<+ªåÖBoOßÜ,@öz@ouaU*+rC~öÃÜuCüüÖu,ð:/ßraz¢/äýî|oªB:Уð>öî*ãüü_/ÜýAU~,bÐ.ßUaÐßU~bCª¢A_uBzåü_BýUbßO©ä'), (N'ýÜ_,Aö,ðÖo©ÜÜä+î.UÜððÃ/|£/|Or|h.ª,hßB£@vÐ.hßäzr,ÄåÜ.BbÐZ+üÖ¢.rÄrAvo.Ð_ßÜÖ>r_|¢Ãzro>*ÜÐZ_£ã*UÜbÄÃ:BÐä<>©ß*@:£@bßB/hßýO,CårvaÐ,C©vUüzÜ*ß.+üä@B./£*Äuã¢UÐ>büã©O>ð/uãO.|ÖaÜ¢Ð:AzäA>Ü¢©AovAa_huåA/ZvrAB~Ã:uîßÃorö*å~ßîvößoäb@rv<@|ýî~ÜZÐ@b*ÄbhuãzAäoU£bho*OÖ,uÄv¢ObobÖ|~î©öAÜ>*Crä>äÖuZU|@aã@ZÜýhA.ÃBBªz~~:ßBhU~ob|@Öã_,åߪ|ÐÐb¢åüåÐb©aCvovoß@o/U/Ã:ÖUUZ@aßZ>Aªä@ãðhU@@@b/u~~+ª,£oZzA¢å¢ÐªrbäÄ¢U~.£ü~AoÐîO+zA©Äh//©zÖvãåüZ/©Z:©+ßýýOУߢa|.äU~ªbåb£/bZ@z~ÖaZ¢@/:_a>A©_/ߢbzOåü¢,Ä+ÜußuZ_>êü¢@ý>Cö*Ub|,*©ãßaüz/ªC¢ªUÐr£ZÄü|ýð:oCbãbð£,zhü/~ü/Z|oUaãaðÖÖðzA_Ca./rÃÃühv@öÖz:vz£©ä/.,Ðð¢orZbC¢¢äOh~a,ZÖz/îÖbBÄ<<:Z£oBТÃ>ª_<©bhå*îCzÄZv,BBü>üå_:Ðߣãü:Ö<@î_ÜÜZ£,ÖäÐrªhÜhAvbýÖCÄ¢bbÐä/zðåh£ßßÜ>ªß<ªÖCo*ä~b>ßÃ.ÖÄ.rðÐÃ|ßö:öãbö+ü,|©u_h,ª:Ðroßð>u>,ð.ªãÜðv©£ZozvðubCUaou~aª<ý<,aAöÐ+©aö+_ßÖüå>B/hßbUU/+.£ððBvÐ+/<öhCOorh©ßßzýð~îÃ+b_BÖb£ä>BbUÜCð¢ßzýýÄBz¢ðª:ªb.Ä+a.î©ß*Äî>UÄbhåîÃîÃO¢ªîýUÖðooªbßb.ÄU+rUß<ªb@:.U@~îZa.üÐ*_Bî:Äz_zazuÄ/BÜðîO+rB>CÐãBv,ðüBBaß_äb~bßu,Ua¢a©AöAUãAo.©|rö¢äO©UAbbý||:*h|ýÄuBBß:u/åÐvbä.äzã¢/üAÐaý_üÃ<.ªðÄ>oÄ::BCO¢ÖÜ@ÖaOªÄu¢ÄÄ~öürC~äÖÖavß~~ÃÃ/åÐ>u*vö>u_ÜBÜOürýîuÖuz_v/åÐ>ÄO/@îv+årrÜîîOBªÜbübßÃý©ÜC|ðãöß>ýã|bboBb|._Ö*Aü~zªZäªr,OaãAr/oÃzU><ãoböîÃÖb+î*öbOãÐðÃ*îÜ<+buhî£.Ðu>ä|C/u©,oãhaZÃåöaª/Ö£î~Ä,ßAÐÖÜB©£©ãb@b/@vZob¢:b,*rîbüýª~väÖ¢rö¢.Ö<,£äß/üOð<*Ð_bðÄ©,|åAÄ£bUߣzßZ*©ÜZhb:__Üöåb@vãÐßßî||b_ÄÜÐ~ªýß©,¢h©üUðävhäßöo©Cß.bä>åÖߪU~,*åð|ýZ>/,ªå>åü£/Äv~_C¢ÜOðöo@å+bЩ|_.rZß©ÃuÖr_~ý@U*b*©AvÐBÖ£bCü_A_oãz/ur.<ðäåå|©*ö£U_vÐb¢ZßZ:©Ö_ÄÄÄÃ+ЩBaCÃäÐO,ã|BZB<ÄýÜßCuA¢öÖÐÐ,hOoåZªZ|U/~åz/îüÖ+,©býÄðåU@¢uýABÃBob¢Öü,Ävb|ÃäO_CÐü<Ä<ßåüªbbîÄ:©ÜªBßZ_~Oß.:,BäÃOh¢,@A~,uZZBa©ð>ýã/ÜbÜ,u~hߣZA/ÜBðåðv*ö£rrå@a/~@åð:_Ãbª/Ö.ß+uðbOå@ýbÜau£îÜ~ååäuo~vÖ,~åBa*:>ÖåÄߣð@Ð_åO£ÜöoĪa/üÖZä*Bz+vÖOUÐÄB_åÃÜuO@ªövÃðßC,/üãÃaÜhh>ÃZ@ðªîzä,£A£bÄOÜãa_ß~,ÖßAüö/£:ßCbbbu,OÄÃ_ЪäBÖ,v£îoÖ¢+ðz_Ðî¢üårößî~a£ü.©ZäUãßîÃvª©ßÖvb@ÄCÖéZBÄ©_©AOO,£Ã>@<ÜBb@¢A£aZB:©Ö.äååvÐ,rh~zhä>BuACUܪ֪Aub¢©B.|býZßz>Ö.Ühv¢_Ü,Ü__ßvýOîh£vã©rBÄBÖAUh~ÄÜÐZÄ>¢*hbvãZBCZh~åBhhA©Br©/oå@_UOãÜbüZÄvß@r~ßîaªßª:+b:|Ðbr>ãrOv~ЪCU+UoZoß*£ãbvZ£ß+vboOÖvAýÜßåb¢U¢ãrßÜU|:hruBîîýðOO,ßa@~/,ü*U.@ZÖÄ.uub@å~ßßðüü£,CaãªÃÃßuBvß/ßðzA£î<,@©Öã©zßAOª¢:ЪrU|î¢bðoCüZBB£>ßvb>UäÖv/<*|Ü|~ro_.*_ö<ÖÖý|üUOhB¢zö.ðð/Z_ßbÜ@î<:+oÐu©OC,u:ýbðßAU*uö<ªoZUCBßÖåÃuã@Cªb<î::r|O<ö*,ÖÖã:Züaãrå:ÖßubO*äz,ýh@Oýr:/ÖÃ+ÐßO|rBaU*Ö,ü£åU,ýuUª~ß_*rA~ÜCu_<+@<,uÐÖ*äoAO_býä.îÄoao|ßCo+o@UCBrî¢C/äov>îÐ.Ü+ßCbÐ+u@+ÖC:bÖO*BobÄ£ð£_ßA*ßr+C~öÜh>ýZÖuzßýÐuö.©+Ä,örbUC~Ü|z:.Ãb@ªAß~zߢ*ob¢ß|_.¢zUýÃhÖ._aðuÖ:ßðCðu£î~bbåÖ¢ba¢ZAuhÄChÄ,+ã¢Z|.ªäå+o>î*î>AurBö_Ã.ðÐåa¢ÖÃÜ/_äßvZ_©ZåÐÄäC.Ãz©©åöª:ãou+ã@~Uh©,b+>BrÖÄ_ßbßü|ßzrÃöbßßßBA£bðÄoÄ£~AUÄuA/,ZÄruÜ|ö|,|ªðÖ_OZÖ>©,@:îbÜ@||,öªa.:bÖã@ÜåCbBA©OÃÃðbAzaa£o~b/Üoa.Cbv/Äbb|äÜOoaãb~<:O+_/ýäv@ýurßOZä<ðb_¢bA>z£_ßä¢*@@özü©äÄb.hrbä+b~îz|îAbä,h'), (0x878FABDD9DE03A221ED7C36A8309F81B39EC15FB3C12D30D864ABD06F3A869C4084A5DAAB5E56C1EE8CE0CE57D6FCD74FFBE92F03072A074BB9D575323342F756DFE6138AC4D655480F7136E06EE90167C49063416799496AAE9EF178D19FAB49E96787E0E95C7B2583D6F385E316ECC0D41D6D92DFB5FF369CB8DC35E4DC767FC5ECF6F761108E78154DCD8095B37787A220A4B422186A3378414420704530A76083C0061FE56FB39EF58D91F87E90D9414483C437B24DBD82C38F2FCC5DF7D00D161011F37167BCB0E1253C9AD84B09A07AEF3BC17C7FBE3602735C664FF730FDAD84D25E8EE5CFDCC703468D0122AA88757F517ECC1E90A651DBF0FE1B5670F9E43EED4368AB11851F5EFA79292124EFD8D3E0C095C2319EF43BFCA2E4D1C170825CA8C245671FCA5DCC5F6D14177D30A711254765A064120D4E5F3CBE7EA04D9D89F6CC88629A61B42662C962C47ADCBECBFBD3AC7A0AD1F886C4F707E31BEF2C11DF9A20B0A84476B3834F3D9ABF38C3727DE272CC91137F14D3F19DA369D10A83AD50F4E0BC0E13B75CF), (0xB945DD78C239565397C5F70BC1B1BDCA6B6D359B234B9FD5E43E2B66FCEBB6FA82CA370862A117FC46BD1074DD048C5A0316D00899F4486DA50794840DEF56AB0FFC88F779B468CA563854663B2BF7E0B1D163E51BAE608E81C9625A0C92EF56666565E07B95E04EAE06B2619FE2327A10B3D23B8C708F609CFA75AF520EBDC349FDFFD5C616651CF0143715FCFFEE9F97E84A59C0B3CAF6D50444801F31FC9F49EE17A2E25CB4F94EB64A77397A649C0BD51869C3630395320900C1D2E4FCBF1897CC0DE74CBD3A34510A0A6852BD5B9FA875491892DBE5EB826FD95CF2266AB98A5FA1AD57B4BF18DE357259F92CE04BA422296EB3A785BBE72165DCE4DD412A9F50DDF31BAD48F3B531395C698BF2558F8DB7D0FDFC06E3D9E5C6364FE33400F448BAA84FEC30F6D936B9816D29525C2E104D5626712DA34AFA1A35E712A3D2FC386820D14A06A25DC0F6C6428475AF936A073F96DDDA821B165D54D1FA0FC792E68535C0318AE309AAF9657B6660C6856AA27D0A643C1E5DE605BAEDB1D063B12E335CFF40EF61DF2435ECE47507B4B696CF675791BD2DAA66D7DA2CD0F658D996B34C3204C7457B409EE52F503AE8DE500457E1690B207105B1986814350E2EDC124EC1BB81B1CE8AEB17F2D7FE5A2CEFFBDCCC9E766F8A93DA2C522FDC670B8FB7523DEB2623CE5288577DF32D9907576C791858428F66A3C32DE0555E), (0x0B621FAC1D40C8D80D79161EF197654025FBC2634EB24C28EB9928F94379F3E8AAF9E7CA7A5A3A2CB1404AC87847B12CC79F2F63DD4A355915F6C08318CF9494862E74F8AF9BC6CBC196B20E6783B0A4E7FB31A44A900B60C38EB48399B613E2ABB6424AD3B97C2CD863A1469827F191461C9B1C9DF04AF622143421E643310B82CF62B14165D12D7F04CBE6EEC4C79C9D223C7D1AF744BB6C4C27BE855B46953296C4C8C44050E04B6E9D992463075E4C9884C306B3F08B8BA744F43D12A823BD36B358FFFFD75CD25C32AC5AAA970845C972EDD7838E7EA6393D1DFB5BA238C579DBEDB0AF5E4DC027F0AA814330E0103BAF74A0BB4B39A734B2C6C2BA6076B9F9E7E0CEA958956A252CB092E4FD62085C0DC0AC2E980A2EA2457BFC988A06C67AF1885C501166E219097A8E9BA7C33750BD65ACA0C98A60DCB22C63DD99D29A26E918F37505B84BA6DD92241D7C92FA8243ACDA245925B06627809EE403058036E067B18FCFC24A051A05C3376642601F31E09AECF875CCC3678D4D851A66A3D812EE3D14E06BB28062D50A546279FFE41F7EB2226A943E6A9BED274274C5EBB462EB2478054C3FC62D1989236B7861ED8D69659CB3806D7DAFD378F244700B9DEA56F1775487DFD33FECF5510A1BFF228C39B50B5F4F00E27DA7C832D34C81BD0DEC381587E9990589925CC78B1C7393D355650ABA003E9A550EFAADBEADFFF26A37431EE2E99BA3A0AF81CCF238576133345E7EE677030DAE9EBF78EFEB46DC936A5C53D955B1EAB9C26A0B7E71A81DB798798A4F810CF334356F442A23B4B468A53887DB0388DE8373C4230DF78E3CF5A2C05671BC8D125D691FA3E56CC0960EB5A3D9392041B3109B06DBBDE8E53CA7334F87625D42AB17262972E3E4F916B32E155854B503D4600D45D6B5B2557583E3FCB11D3833B072F7C113F6CFD02CA1F0278FD28DE8F2307A2655186CF565D0B4EE00E847F87BFA65E55DF3EFDD913C1834C58AC6A3F5FB0A80A311DD9706015C4242E1CF4359F7AC946DA88AFF409C9518FF3730DA0A11F804DEC755691CCDDF5B96AD9BB06DD5DBCDDE31354D700B40067CC598CED1813831E9CA6592A243779FC82F689301E5AB2A5B63FCD8DBAA94D3874FEC5735108A15756AC6A26F482A9ABA968540A2AC0496196D726E845F563E36920B5356206AB8A5531E971E09BA1338621E45255330DA4A563EC79D1C901514886BDD3CD7449A2BCC9B4F8E6D7810B25F3522411019E261310800E2A21A88C20883969DDDD6601D831958B7E14BDDDD2D143DE66DD3036F887C0BCF44B889B2138C85E9E272D589D505BDF7C02B80E52B22B48EDCA2543580261561CA91D9CB90A6A454C5F3FBBB3FB41068150BAE5DFB25A32203FBD570385069BB5C74BE442BF26719862C203726F84E2891EB1F7578B765A4EDB7C795D1331C748A405FCAF6CC3C23A394D6CF056CFB0BACADDF4C31F442A40E1957742AD2485210ABADE5998432C5AFFBD1E3E1959E80BBBCE92454CF4395FEBABCDC74C7F9D14C83BF5ABE1672277B199D35225FC3787AD6B96DE924FE406817E7DEC75550E70200975DD644C2A449C1A93A320D729EF643619E0E8BE727857FF2A72E936025C9B63322C866A236141424287BC30D5AA448E884F4B2D876186FD757511B75B56183717CDA50D690A12A89F967BFACC1719802E6ABDB3017DDBC0A598E4BE5F99F6EC7F752917F547257E3C928DF0F58C216E5B9188F0985525A9F1E457D0155459059F8F8B46EE018AD7354F867EEFDACB19678652BE00F6441A4F8F2610FF5558D84988517600EA705A16B1CB58DEB6BA17293E689F019AF4A93B61076EEB69E7DFE6BE25677955188D1608A75F3AFB4B88D50EF7B0ECC9D6BE83B83AAFD3117EACC9EB721091529DC425EB88F8F3A57E30157E99DC7226E31CD776D3C11A78AA0499FA854F892AF77271239B0140DDCADE49FA8A6477923AA30855392A7ECCB9BD41845EF9102EB6EA77176B4E9BE1DC09BC05872FEE358D07CFE24733E1DD03A479EDBFCC8EC1CE1B439FED6036FC0716793F6E794599CF1C0757293DE78308AA13B6C4BE321F8AEA377BDC5E481CC0D3DB20BAB26E8A6EE01EA3B527A88023C097434C702758E469B48431D9BD70C3EA965552C6C8563B9957E49F8E5A5341B7481BB704C040BC723C50FDEA4885229DDFDA91532D6CF84107B32832C9560F51428A8191B9CF1A4598E08A77584140C2B1F1BE8FC1EE89DB1375FD0D2879B8D6419CA7CE0390D410B05BBA4CE280E8B6725580C7A78F9F53F0A67E43C20D1D1F27A435CBA972CF211D9DCF76344CF8928D369AD2F8F12EB1FD53DD5D12904EB33E9B153E5B2D1D0F965FAA19C0291FB3ADEBB69B724A26367573CE5595620E8D5F1C379927CA4760298B4CA94714E1D43429AF36D9B330E474EEB8D76EC76A24411CBAB3E1868BE3945671E70D8CEF6091FA117BC2D7B3ABDB532449017FF1622118582A5A2DE2BD9DBB92733149D6F7FF583574A5EE5179691EE89E6242D224651FC5E524C506641763EE0735306A817C3C49A93A8CBC32C6EEB13B45444B4AD97E1E2173E1C33A51D16D32004A3428B45722360938E9D853932E4603CB1EC318932DC722390D8A691F57692F8A227CAA8886EE221541A08435DDE0C37D2686B29A196D1C7D067B4997CCE9D180B8D970A1AB7B3430A0F3518D32FC6B2CB6FBE8C0B68560B5503D820B948A5857DF037CF46021D1BB4281F9161B9F3A0A102CD0D10100DA3378BB516DE9389C16AAF9AE2F7F66BC952A3D7E248C4A99B0F377FB77A98756CE39498BB4D063C219F5E27B11F11A9AA9603BA6C382A5A72E11906A714D146D13A2DFF635DB07C4B66B643EA9CF87CFE8ED42076DD0834B93A25D933FF950DCD175577F112454CE6876E0682E66FD5E99C081840B0CFF331F9452938D6494CD1A976D247808AEC48873AEF6E02B2D2CA50F096F77DCD1C486145F7CC1C14103E2234D8709C8E68111BCFB876DF842F47735EE8E6B25554DC598AE53A92202C722AB14CCDD9622DC18784DBAD14A668DD16B058BD2C298BCDE0FC1712A92E7EFBA3464C2D38616D1031265F2D79A6C7B2210724E2C76E4500257FEDDC4B1A175906E009CD593AA4B442488C06CA5E53393464F1A1099C3335004C08B87490C5160F0627999CF958E6636F9F7FE13F36C62CB3A7E729754F69ADD4CC0D32EB40B28276F6C337B99C3A0B0A3E99FDB353C115418CC1E8DEEC99B6BDECC82C4BB3772FEDFD6A48728F72166CDB1E37B56BF9800BF8F15E7EC9A3895D0C79CDD40EC057C66A10B6E979A3B21AAB7B02CE610151F3472152C218B02459E0EA6515A4415E1BC7704A5B429792553F44D34DA1BBFB0B28DA8DEC6D94ADBAEC5D088E3D9D75691CE706F0B6F6B0B08FFD83D4DCD6B825EE5AEDCAB472AE32C3906FE48F0317A2CE6A30B9B40B543229DD5C6F11D9DD15A5E39FE2B0ABB5AA38D42D2686CC208CE5E10672A6FE70E305BBA0D7163F20EAB2593EAC6335F222B58681E0DD2EAB5DBCD08930D36B6FA773BCDDF50BDD94F9B343732C8D7428B0EFE3BC207D8F05D60F431B45B5F01746EA9739A758FD17D7F2872C9ABB7ECF47A7D841EFAF4F2AE3B606E5C8C24E43C32B2C41F63B8158F2CBE42DF5626B1B9173D39EDCAE27979B6C55F052174F15A443686E8E3CD90C26804F24CA2135804BEA5454EFAF67150495A68AE59006DA7BCF89B1FAFC8D549AC6775C5F02CC7A37D5A567C3FA26F4DC5369F8964F26957A74A8FB9783864E5D00BA28370AB601C82C5210EA9E1780A995D5A69394BE4BA0E2E2EA5AD1E2B44E5709C340FDBCD19D672F6AFC15ABDB5D275102ADE0BBC9416F875528CCA7F7499EC292955340ABD903092B64544E8A36078C327FE0066E9C4081D07666A9D1BF38205D53184609E7A628AAD69064ED20FD1F213F7577689853F654D26F35725A376B2B33F1361EC0824BD9B75C970CDC93B0784787914AC1B7C50022F1377771B54F9F2438BA2EC60160BD2A9DB03CBCE9773FBFBC2BD2A6216A7B2C36FBCF4BD367020CF8E7A3D881B24627D41F230689433879F7B8B48A7CF903CE3E028F0591715635AE2FC301D4663D0AE5ABD1014674A3F2C355531BB3778FFFE4D49A7F22409562C8ED58598036A5917AAAA4D1270188CA5F9C423C1E3B82379FC41D00CCBEE8A4156E39581C49728CAFC2A008D2BED7E24BD01F85FE59448C6B20BABC18A5C5BCC2FFBCB8C830AD152AB58C5C9B7964E1EC3BB4FC9E62A6BC2AACEDFB8FB6730AFCE346D3021F191B220A5B48CCE8F1F1BCD8CFDDF1C81E5F59FF8E1C43B1867342825FB73390DFCD2198D994075EEF41BBFC3FB6E6951933C5E77A0290FDD87184E7064A8939BD2BF902BC58EF8958FA104C614672CF8DAC692BF24B16DD3C23C0670F1F57A5B8823E13BDE4D8C0443B8E7FF2439C91FFB55A59E05D4672141BC59997877939A36A94DF085E11A8D3A8E7B6BC97EBF8872E685AC4FEC45A3BCCFEA11E3F29BCC155DB87AFBD89A0A3B18638768FB57D30CC12691E1F74A5A539A33C606F8A947F2AF567BB6349C9A5C8B612788CE4AB168171512DBF9DA2E136E175194C3F3427F1B94C9A4DC1CC8348C35668612C1955F825DEFE3866BF8C52858481D4AF588882EB86D29810C9C0A4CEE5FB473A7F0AC72021276136EE8BFABF48C73C4AAF986BCDE576A647BDE75C1E5A6A81C34518067AB9B38E31274CFCFAD8D8C80183591F4C907954096380623ACB42DB840A367930C1C191FB0A3AA69979AC6FE41E5B9A004A3533CBC3FC1739505365E47078F0A15F983FC2C8244156D3BA90B7117E1B70A7DA39D503AE6CC4AA57AAB6D4C12900BBCD85851BD83826F3DCF8086C6027D237F53C953F1B9EB12C7A1870798B8111918FFB44F99654CFA212E126B56358492FD87EDE8594C1A02FD459A1C0C8AD685AE777DD65E3D9A1A16DF32C3977FBC822CE40A10CA38CFDCD5B14253CB30ED96579CBCDEB7AE44BFEDD3D17A19566B7D8917EF1870B5CB75B7A100ACFB365EE5E19C7C0AB2DDCA7EAF4A22E66745B38F45C4CAB341C52591D1472716ADA7C4A774EB987CDA60CAAB4D70792692D51E61ED8157267BABA41C66EA297BB6B3D0F7757B717BBD46A4D50A2F65C15BFF0F0C28B5292C40AE3BD9477C15F0374EF489AF71A11763A2B4A6DC721C4C35D2C077DB2D52F18AA4B6D74FA8243120B4ADCB3AEC21DA4DF7A885F0824FDE81FF9374BB9E1519FBF2FEFACAD9D7BB7E837627D4511159A0E629E384DCF10854D8454004BF75929F83DF37ED370F641FED077D0C27FA30DE49C3C56421852F01E695F29487B6BF1DE3884081E96C79E6C5D3A7C5840C3CEE0471BAEBA02E7E0EB025F6911CDF9826586F189FB9BEF4DD59EEAE5DED598DEA690480160BC36DB51A41D1B375E8D9E63), (0x4E3AD24557CDD91C180201156A80C28085F822A84F1637567DD41677104C8126BEC68F6CBD02A992E319C34FA208B0B69B0C186B956E8238027CC783B7620BCB), ('1f7a31bf-3955-4aff-8aa2-526b140ddf40'), ('6416-03-01 22:55:30.055'), ('1927-09-22 16:45:00'), ('10/31/2016 1:39:16 PMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/0010/31/2016 1:39:16 PMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/00'), ('07:09:35.0713400'), ('9787-11-17'), ('0341-02-07 07:17:21.4060236'), ('0904-05-22 12:00:53.5170304+00:00'))"; + break; + case 2: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((1), (145), (24152), (9223372036854775807), (1), (1), (0), (0.7225), (0), (0.0540), (null), ('<*<ãÖ<ö+AÐ_rßäAzAh@bz_ÖßÄ_büAb£b'), ('bh£o.Ößã,|O>ãrÜrüÄä£ßßzÜu.ü*üb<î@uöo,ýB*@Ã,U@buÜUä©+<£ßvßäöýß<åZBZü¢Uªßýª@hh/bvoOrbB.B/ä/©+_AC|bßborb¢@©bÃÜö@ÃãÖªÜߪ.ÜåaåCOªUZö>übz*îbB~Ã/AÐ*<*Öߢðvzü©AOh*ö/ð/ÜÐvý:+>ã|ÃuîüÖoÃ.ß/Ü¢+Ãoa/ZhÖÄhhÜavab>î,zÜ,~bbÃZA<öoýhß.ACüab*Bã_Bhü+h:ýÃ~>Oý_*zÄ|hªvÜ@B¢zÐßÃ>/üvãZOý_üO/Üðå©ðß+BraÖäýßî~ãåb/¢ZOh@îhUhzOªöO@ã¢/Ã>+Ð<<ÃCZA.¢h©îÜß<ÐÖhA©BöîãhðßZbö.O|bîßÐ:.ªBä_o@Ão,ABäZ©ßUÜ~¢|r@Ö*©Ö<+Ðî£aZÖ/~U/Ð*:Ö/UöBC_ÐßCUBîrB+@ä/A*:<.b|_uUa+äZB:b>.~¢ö¢öß.ð.|zrÖ|ZZCzöää_~Z>ßÖ,ßßÖaäCZßBÄ>ÃaooýîAUß~ýýhh£_AA£hhAahªßz+£U/©>¢©+ÖîböU,ubÃßZBb<ÐCzãoî|ßÃ*ßuzb<ßU©ÜýßBÃbäªÜ+ö:bÜr@Üz*©<ÜßörÄoabh¢å_£*Bâ:vü|hOäUZaAßZo<ªOýoäåüåã|¢Öbî//BãoäbÃð£¢Bü/hýUä:/ö:*ðãÐ>.ha@/ß*ä~ðühz.îv+|Öî_ÖãZÜz£öüBoa/+©_îCrz£||bZÄhaboü,bßö£ZÄÖBh,å>ÖöÄöÐ++U*:výOßabO¢îßCßöª/aUr,Bov©bZ*aOZÃßÐA¢|ü<@ÄÃð@©ba*ua,Özb:|Ð@Ão©ßªÄ**î£~|£äß*_©+AA£ðoUbýrÃbbo.£Ob£rÄ.o*o+ÜÃ+_üý@ßOÄ>bU>|_ÄC©ã~.oý©ÄÄzööäÄ<~.|Ä:UÃ_výÃohßh©OÄA©zÜ,@,åUZOäuãÜ~ÃBýßväüZZ.<ß@å|©ÖUßAhßab*~ý.+/Öã.~hb©+/OÄA>üA|++Ö_BýaÖ/åbö>Ãå~öä©£/öÖ>ý.ߣ*CzAaßu¢£ß*ÜߣhUýÐåÖzCb¢z.C.¢U_Äo,/Z~AðÄ@U@b*rAÃТ©ßa,Z£ª:uä©ößovbOüC.~*ªCÖvOBAuvîzzA£u|ãåz,|öß|<*ßvab|zA*rå¢a<@.aªÖ£¢ß~zÖü©vßÜaü£¢>ð¢,Uzo+ýC*öh,îÐzZÃUÃAÄî:*ªýaäB*r£|ããÖ£ãbî~©uð/uUî£@zªð/,bßÄB¢BãÐÐCîÃ:ýb*:zOåbb<@uüßv./~/Ä¢öößÐ:ßÖð>ß@_ßðªOäZÖ|*ý*rä©v|zz|>ýßAC|Ää,CC|©ýÃhîÃr|Z©oZöª~äß_OªOUA|ýå<£h/,/r/ÃuO@+äCÜUßã@b_üäbãð@,.öhoau|.åZÖB@î+ÃZo*>.auÐ<ßðÃZ+|>ÃbZvA++£|,,_b/|£¢oÜ©ªAý.Ð/ÃrZåB@ÖU.<¢b£ößÃöA¢Ä£bUZbhªåå©bî|¢Ãh+©våÜãABA|@ããðBüÜAÜ£üýãüðä,Ð.Ä@AvÃ*vÖÖðßaov@¢Übü~BC_ãAO+ãzöð:zÃzA>|oªßraBü¢ßÄ*+oß/oZªýOu@ü:/ßý+Ä<üüßCAh,ÄbZ*©¢,ÜrB¢ð*Ðäuöðb..<|zßhzöü__z:zruv+u>:,bÄzbZ|ubZB.ZÐoÄ+ßhaöä*Ã:îßÄBýhß/r+Ö<©Ã©ÃUîu/îhªA_î_B<>_ÖAäh@ähBoZüB+.uÄC<*ÃüCU_Ör©~bªÄöÄãa:ß|oßBßbhðbu£Zr/¢hß~£Üz,ZO~a|~a._,ßî~z@~obBªÄüAvåOBßîv,©u£CBÐh/U,ä:äÜoAbhA¢b*_|bÃAß/rhÃîð+ßÐ.:üðã|,Ðåö.@ßÖäÐå:oÃbb.ÃzabuO|ð.*Z£ÃbÄUßhãOÖ¢zª:Zöa>rh©*Avªã@o|zaaO@Co©OO+_Uî©ba.ªöu|/Or£z£ßC/ZýZäТCB*CZßZãª//,O~/îrzÖ~ßüåß@åbÜb|üAð<åzvãöÖh*¢uîvª_ý,ÜüCoßou_ª.Ð@Üb<,:ÐÃC£,,a:<*£îUZ_ÜÃÜýðÜÃÐBÐu_a¢~~Ü@ßääåÖ,ªCÄzß~£*COÐbv<:ÃböAîoãýãîÜå.//Ãîö,,ßb+î@Bå.ßå>ZZbhßîäãuÜCå/bÄUðÄ@ãhåb££Äb@ßv/oö.<~*ZªßABh¢ÄoðäßäüÄzA+/r/@Cä+Aü*öða~Cî£BÄßAÐ<ö<ÜOÄöZÐäðbb+îß>ðÃbOvÖUîZÃözªªUzÃÖ>A*©£B©¢Baü@Üýä¢ßCü|OOuäîAýýCUÃÐ,¢/ÖhUAü@©©uðÜðü<üßC+C~Zß>Ü¢BÃ>ã*Uho~B>_býA@_ýðCCA_h/|+UAhö©>/åãoßåâÖ.+>Zb+BbÖß|©@bo*/ßîîC/oßb£Br+ÐBU@+£o©h>ýhý,hCßîã©ß,BßÄ£üO©U.UåÄAÄZho.ýÃvZîÖhbAÄuÖv__*a<ßÄÃýýîbÐvZ*/ß©Ö/ÃhCßåö@îoüa©ý+oÃÃã+:ÐaÃ,ro>ãĪå|ýäã.ur~ãrÜãAå/Öðh£ÖýzZÃÃ:ÖªÐ<©O|b<ÃC@Ö<>U@UöO©>ß.~ÄbäîuÃOoü<ªã,_ªvü/Ö£/,OUÐßbÖ@ßUßzåöCÄouýö*ßýå.CÐ+îÃbbÜo*å¢o/£:£Orß@>~>|ßOÖÄbªO+zÃz.ªBrÖ|ТuBýß*@âßBî¢hå+îåZbbÖÄUäÐzAbbabüÖüU:ß,.O,ü.v/bO_Urz:>©å@ãhBA<£_ÜAðåO¢îB>îäÃÜzý/©h@ßåêOýî*Bß|¢ýBräÄ:åöã£b:<ßBîZ/u@hðzÃÜöbÜ_BÜüÄ*+är.£©C,_<,a*a_Cüðbvª/Bvý*bOý©Oåß<~ð+.,ÐÖ£_ruAhAU/Cäî/ÐöÖö.Z¢ð,äZ>rå:ªÖ~_*@üåîAörAö~|*ü+Ö¢o/ýBCßUÃîÐãUª£©_z~ðÖbäî:bªýßZü_ãhß/*ª:rÃÐraZCö/Zîaa,:ý~z@|a_ã/,örýCßrzöå,uAäãª*hoÃðvüh,ouCßb/©äzßbBCã¢b~bhauüÐ<ðuå+h@Cýb¢ö|B@î:Ä+ÐAÜ@rZ|¢*rßCoO@£îß_zÄßÄBa@åbüboÃzåUÃÄ.aªß_oðoö:ÜA<ªbî.£ð*Ã>Ã.>£UAhßvöãð@ßö>O£ßöbbCa@>BÜðuz*aîzO*åîåahOßr_BvüîÜoZÐîððåzßü.ãßaÜAßv<ÃåäÖhö£UCOÄ¢>ÜOÖo|©ß@Üý£o,B|åvªÐ£CßåZ_~öv©ÖhC*îb£U¢>C:üðîu~Üäb,ä~üý,öZ<¢üÄ_äaä_ã>Að£©CªroîübößBrZî|h+_B*ßý@/îCîbÃÖvßoÐO<,Ürbz.©h¢*|Cz,<ööªäêoZbað,r|vA|ÄbÖ~zba,z+ß||ßÖZv*å~å@årUb|Ä,@o,:z,_/rr<~¢uCaz£OðuÃA~_ýå@*bC>,,üЪ©OB,bܪOãðZz<ö.+ÜoßCßîz@AbCBüOuU./©r£ÄAðbZövrAåÃ.rUðßrý,ã~aBߣîª@rCZörb+_ZßãÜä.Ovî+_¢©_ߣC+bÄöC.,*ÖýðªÖrhî,_,hªüoßãîv,ýäÃz£ßãa©ªO/<:B/v©oo,Ðvý:>_,îåCrCa+îZªöð,huß+üÃoåÖ|oÐCýv~ÄåoZüZ:BrC©ÐUÜu>>£<ß+@o*ö>aAbzªÐߣðb*ÖßBb.+ðöö/_£oУý~>îîbü:z:Üub/>aCOÄ@*ÃÜ©bäÃbÐ:У*hB,Aü~>ß:_Cý/bý>a,<:rzzßßäb.ðÄäÐhåîzBýh¢uÄåo£©£ßÃ|ÄbvOhßß~rC_båÖ©.ä_h©.Auãu>:hoorUÐ,aß:üÖB_@ßabhãh@UßB£ahÃã/ö£,£.Aðv+>ÐäöîßbhåUuÜB£z£ð+B¢Äu*üAaBÄåBÖbßß+.£Zðz~äv/ª©äýbý>Ãß©£v.îîåÃ+UÜ@oöZZða¢:ßAãüî+,oB©å¢bvohbýÖ,A£u¢>ð:Ä|ÄhýäUA£ßz@__Ð~Ã+h:åßhröUU.hßu<ýð©ª.@îÖßu@+Ü>rãzb_U*<|ð.@OÖ¢äåã|ä@¢åCOo<.£©vß/:/åuîroÃb~££,Ö<_îbüU*OCza:ã¢ÄªßîCvª+Ãß~ßUÖOuUðZ¢CÖäü,rðÃuhÖAüÄß>ÖÐ_ChBãBöÐä@ð*ðh|b|ã_üÄ<@.O>AðÜ@rO*Ã*b©åÖrãÜîrªÜuöZAð*<¢Z/~ÃüÐ,©Bzhö~Zß.vb£Ö~åÐÐ.ð>ýðAÖßß|îåÖýhߣUUh<*ãvî|<..OÜ:ýZãîý.Öb~*Ca,î£vðãC*¢~Ubbub+|+/¢ß<åuð>uðaOBÃAåß:aBãU*b:,åä_+©>/./AîCÃ*uЪüýÖbß_r¢,,ðö~:UöC*böäU_ÖÄUýüÃÖÃåB£+BÄZbz*ÖZB>:䪣o|ýoßÐÄ:ý,||uð¢ÄbäðboUãvÖ¢Cü:UU,,*_OÃÄZî©£Äð_ÜBã©,:ªð_<ðÐÐ/O¢ÖaÖCOoî./a*|A._|ðÖü@Zzbß©:ääª<üZCvî¢üß+ªr:u+BAîCö+ßý:©*:üö:ÐzAßübb/Ö>ðªC,+zäbÃÄ¢|ÐðU©üã:ö<£A*¢Zo£vzAªo¢aÜ©ªb+>+ZuãO~C|ZbuýßAv~ÜÖbhrß+©a,Cî£ýãZã>ýbßüß©@UåzÐr@*£ÖãZ.îAå~ÜBªoußÜCßüÃO£rr~Üa*Üܪä¢vãîo_îð:C>aäO@Z:Brðußoa_£Ð¢åÄCßOUýå,î¢~îAUbhO*,vÐå©|©¢ßCã+_ßvbÖýÃã¢r¢î|>£Ar~bå*Z**oã:U@¢oÜ|_bÖ¢ý@b<+Äo£ßzOð£/UAüAu>ðåî|/zabã*A.å.rba/üªoÃ*@vzåOðÖª@ävÃCª_AbÄCßZ¢öÖÄb:rö<~rvð¢åaÄÐÖu._hÄ|oCÖv*©oÜßßå+,Ãb*©U@vrvåbßAoîz<ÃCðb>Bßa£zAÃåÖZ£:+ÃßÐuh@Ã@öÐbzußÖåüC~rÐ+@a.åCß,hßoªßr@Ð<ü/+üobözbUðaÜobu¢*zÄßv,åCßöîÐ/~Uh@ooäÖO:bbßåå,+¢B+OäOa©ÄA>ä+CAª©©©äUaî,ähå£Ð<@ýÖ:zß+uÄbhährr_BªOýzÄz,ðU_>Bîßuu/öªÄ.|ßAüz~ªUzZßu*Ä¢,îýÖÜörªåßð>ßvÐvªßÄb.ö+.ÐßÃðuÄå~Bªã@öäö<*Ooo,bÜüªAO.£©u/|ªaýUZ+oî+.u/rzÖäuÄrßOa©AÄ*r@>ö/,.ü+ãürbßýUA©__,Ä:|.öa¢,~+ö|öo©//ýãAUüðåA©.ßðüý>oö>bð_<,ܪÜ>a©B£*ã¢ÃÄ@hÖ_©,Z¢ß:¢+<,aÃ~>å@B+zß©ßÐ_voOÜühoC_vBüÖ*ÖOuv>~BßaÐäz+UAÃÐrübuÄzÄZ@AzzB<ÜÖÜ¢h|.zh:|Zr|ªv:ýo_h,ÄÜÖ~>UýCÖ~|ö,ßä>aßß:C@ar£@oBC~ÜßýäªZz¢¢Öî.ÖüÐvOß.öý+ÖÃvrÜ_ðýBåaoßÖö@u~o.rb_ÄÜA>,*+Äoª/bZª,_ßâ©A,~Oýz©|ãß~hÃo|/Ðb|©Ðoã~+©©îãb,Ä~.>*rä>b©ßä,aZ,CAüßÐhööä_ýßz©ÖÐß.ä|Z©uuä~@aðZ<|bZ_åäU©~£<¢ßrbo,îbbüoÐb<ßÖðo¢öa|b:ÐACåä_ÖZðãA:aß.CBãu@.+rbbCz>öÃ:bÄÜzhvar|råßU/,ÐCÖ@ãO*|b,ãbãÖrU*U.üÐU>väB£ÃZ_|ö£Ã,rÖ:ävÐoÄözrO:*ö_|ä<©<£>©Ö_©U@ßå.h+a@î,hU~__ü£îO*+ýaBOAð|UÜBÐbîrh/ßbBz:£oÐrbðB_aä*zî_ßÜrB::UCZ.O©*._ãäBAî+ã,üöý<ÐÜU,Öý©*©ªö©åO_åzA/~ÄýOýUZaOü,uo:ý@+Aa:ÃÃbåuî:oÄßübö_.ÖÖ~Uh:ý>+v+ÜråÃoãZ~Ðî£CöbßCZ+azZ£AuÜÜÜåýÄrêåhuvA©CÜ,ß@B¢¢oz*ÃC+übho©î.b/ªAÄü_böªäUÄî|îã_Coh¢~©ab*£ßýb¢CUb+ßÖßhB|rÐ*_Üä*Bã+ð*ßhu/£AߪªBoÄOhbz£zÃo¢ãÜuö>ã¢O<ãö¢Ozhý,î¢O:@/z¢+¢ßA:@ÃÜÄü,a>hOO/CUrBýãðzv/büoz+îzhrßoa*vä<*ÜýîÖ/aA,oC_+ßåÄObªß|©aBoaü+vCîðu@>o*,©£öhðrßÜÜ/ö:ãv©bð©ÖåÐÖ:©ÖvUOZãZÄ+ß/üä/Ößz,u|.ßZ£vOªß.:£ðOZ,©öª>öhuBaäÄr>ðö~*@îUöüªðCUÜ|bBäÄîZ.OÐvß|ýBî<åZ.vb/Ã*Ã,Ä©öoä+ð©:ãbä:u@brÄß+hh~ãbäB>>¢._A_ªÖU|/:¢z£b.aäoÜýrü_zU|BBCýv:oã/_ãð£Öß©Z+Ðß>_/býðÄ>.:ýÄZA©h:/OOoߢäoö_Ao|Ão~/b|Ãbý+Щ:ý|h¢ªBÃräO|©Ãð|åb,ÃzãÖßCäüB:,/ß<>,¢zazðð>+ãýAÜ.£hoöüA*ªUÐAÜ£ýUß:+boZZuåÐ:Öã>ðãbb|:Zr<ä/hÜ+~ý|Zðu_,¢ö¢B*îZr@v@C*åöÃb|ö¢ü@©A_ö£åOoã¢ü_uC<uýÄÖ>uAU£ªöÖrÐößr©@ZªÄ,ý|BÄ_ý*ÃÐ.rUZîu*ývÜý~Uã,AaA>ývz+ö©/_|zð~b:AC/*.:vuãB/bBa+<ä_~urCrhz£<Ãß©*OîhuÄöÄ¢~rÖ/uðî|/ob<.ýäuðîZaÖüª:oÜuhüߣ_oðaa<Ãýr.Bb_ÐrðrAîZߣÃ䪢zv:ÄÖ~£__Z>ýãÜuîb¢£ðbÄöãABb>ãå>b..o/|@£@/aOð:rrCbå|brÃ|ªzª©©@héß:ª:¢ß+~üh@b_A.ßîö/öz:ãÐã|Cöãýavz_Ca¢Ö@U_ýBbo>bvOäÖ|ÐÐ*+ªäªbÃ.ÐühuÖað.Oh:o/**Ö|*@äÜýU:|ªOÖ.Üh<~CvCööÖb*ß©¢C|zaaäzv|ã£ÐÐÄ©hßoa/ýüªZbuߪ~:@üz£zavOÐ_<åîÐ>_.uZ¢uOÜU¢oüaößAÄÃB*©/B.©ä£:<üBbåß_ãªzürzrbaoAUuö@Örß,ãBZã¢ÖªÐzZ@BBu/ü:,Äåa<+bU¢ª/@örîU>ü/_ÖÃu¢~åÜ@böAßüb,>ð++ÖuªahOu+bªãªUý/ÃÃ>U/~~|O©~ª_åß/*CªÃzoÃB:+_Äîu*ßã|,Äa©ðßo©.+~~Ä>£zC_a,ßbAå©özÜÜý>C/ÃOß@©Öz_å£b:ÐÖäh@/äâäãÖva.Cä@ðhüUäz_~_/bîöZ.~aĪ@ÜĪaü£|oo¢¢¢bîãÃCaÐßaªßvîbarüa/Ü<ÐÖã~zrä.~Ä,<Üåzoåß|ZZ<Ðåýýä¢Ã©b~o©r*ä~züzzð*UÐC~obBOä>OUAüª**UÄð¢ßZCÃýZZ@ªüü|,¢Uäv:£~îÄÃ>özîýý¢bªaÐ<:aîOÜßÃU@rÄ£**¢:*ÜÖ'), (N'îî,*,>b_î_ßbý¢/>Uä,å*ª:ãvý£Ü,ü|Ðh*>h*or,Bzb::z.åÐa@åü_BZ<_b,äZhðüBö©hZhB£ýÜa_ý,üªÖb,<ÃÖ¢uOÃßbß_ßoh<+,ÖÖ/ä.UýÖBÜ./|Üv,öC@üB¢u.ä~ý+Uz,ÃÐA+©/@¢:oa|:CuªÃzªßîåöªu~|aöå:ð/>©£.o:ÜbBuvuå~:Ðåzb©@<ßÜ/hBä@©|ÄbCü:ªr_åßÐß©Ã,*ýBvb~.vîaäåüA*î|/©|hÜ¢ah©ßoãÜ©hBAßCÜuªýzßuö~ÄU>_îO,aCu~,Bh.ßu:C+ÐÜðUuhzßÃU*|Ã///Z¢ðªvhüßuÐÃBrOb+B~äðzUzÜä|*ååAÄ©Äü@¢@_b~ßCA>oü:B~>CbzhÐÐßBðOAa,uÃ<ä~>vCOußz£ðãÄbrä:z+ß+ÜÄrã<:ã.ßÄCäªz,ðã>ãBßü.aª/öO+uo>ßh>/ý,*äß@AaÜÐaÜãð¢ß@Ä~ßZOub@©b~|£b<ßöö:hýZvö.uå.o*/¢*üb~ÄhßoZ*h<<:h©|öîýo©_.Zäîü+£*z¢örÃýîCßhZã£hß<öBuär::<©/Ãväuãß©ýßä_U:©£ãbãäãîU>,öåhÜhåo>_Uz¢v~z@o£*ööã|u++ªªã_/ÜubAÄb+B/CoA</,¢Öv©ßîÃühÖöüÐAa¢îÄßßüð_Öî©aBÖª>_bb|Aö>Ð@Ü|oÜ*ã©zýbªöÄ¢/Ãr_ÜCå~u_U.ð£<üÃ/üZb.ÜÜ.baÃCÄa_ß/©åß/Т¢O*¢@öðaäa,Oý/|Aî:CÄBO<__Ä~>ýªhUÃ*©äbov£ÖÃüÐO©|C++OÐoOª+¢Üö©ßuhUBCC¢üª>bªvZU¢ü.£.üU|ä£.,ßbîä£ýãåaüîC/ßÐßBUªhå@aÜ:Z<*Ã|oäÄÖßåÄ¢/¢//u©bvrªU::¢bU>ZbC.*@@O<ßA.©ýãC<ãüÐ:v*ÄobåbUB|ß>auöð*vö:+üBäå,/BUC,Ð|U.aßÜÜC+*++obv+~ur,©h@£vBÜ,ÜuðChäv+ÃvÖBª|+~.uZ:£.BÃ//U.bß+.a.*C.urZßî©Z>CÜBßöubßzÖ,CUÃТåvîð*Brruooaý©ÐÐßrz£hªC|ZªÃ©üao|>Ãð¢Ü,hA~å~ßbAß©a£zA,bUßa£*ÄCO.u£ðî©îýBarußä+ö.îî@å:CBB£|£©©A¢Ã|bÐzBÖ©ßhb.a.©|/||ߣ/.ä.Ößãðý>Co£ÄzhvBAhAbÐĪÜZÃbÃÃoöÐv©ã~U/*>ð,Ä@.¢ªCr:_©ª+Ãhßü@håZßürî/©ZÃäªß_o+ýB,ÃBðOöã@ªðåbO*hĪßbüߪ*ðß@Ã~î~êð|ßUßå>obü¢Ä/AУ~bÃBh£ßü.,ÄÃUUªAUvo£_åîÄ*îåäü£@ÖÜAaãå~__ýA>,o+å©ð*©îßr>ßäb|Uåê¢:ß*ýr.£Ã:uUÐ<ªª@bUZîBuäª.bãööZ£z:åö,Aå<@Äý<~öÜ+U>bo£,Ä@rðЪC|ÃÐoªöZ~©uܪv~üå~@/:£bü,üuãbªý_uðÜoî/ãrã/b<Özªü*:ã.üA_î>£Zz£ýð|/ä//rö¢O:*Äîb©¢ð**:Ü@~aÐå+å,£uA@>_AååOÖ.ã£ü£*£ããA+ýz_obääÄ_äbU|uzr._¢Ðbh~böäª,.U@b*vUÐü*rOUÃÄ+ÜäbvAuãAü.ý>Cvî~å,|*ÃãÜÖîouÖ£/*bßä/./AUCoåZ~~Ööß|CaCC+©öBZ+åýB,_bðÄvã@hãh:Oß_roor/O@/rö/O>ZhÃ+Cb>Ü>£A©öAýoýÃOå/îCO©|ðåCäývB+rýoÜzCZÖÄ@C|Ä~h¢©£©Ö~ªßýar+b~©O¢bCbÜ*ä¢ÐbßvßýÃbÖü©Bbä,üå*|UAîoÐ/r./~>,ZßaöÄß*aý*h¢Cî|hÖÄor:ܪªßåb䪪U>U~,zbî©o|z~ðîvbßåb£Ð/vö@O/CßÐ<..b£+__UbäAý©CrÃÄ_ãvý_ö_bý£åC£ßã:bîoäUð@v¢aßÃ:ªßZ@Īý:>å:_~,î£C:br_ªuAã:¢zý+_oã¢,ß~ªðOäa~aÐz©|¢£@|Ð>ðaªUÃUý©oÜäð~rZ+ã.ÜrZU.hU¢Z/öÃã.rOa.:ãbU,/ß+:>UCüvA*ߣ,åª.zU¢Ã|<ð:Ð.rîîaz_ãä<îhao¢Ða@U,Z£>zÐZÄU*ýöo<ü>aCªvßü@_|,Ã@vöü_,rãªOð|/v,ä@>_Ö©A©ª¢.u//ßBööbß/Z,<~ö¢+zB~oý/BýAa¢a<¢**ChÜ/ßbýª+>ü|hAößbÃ*@bUuýzßbðzb*ßöb*/OO|UBüß@<_äabãüßvãZåîßBaabZzhhªBÃ@¢äCB.b:Ð_ßöbä>B|_CÖ<@,B|UbÃ>,zðÜz|rB©O:r/Ovzbörßu~ª>*rauÜ~ã*.ö*räoÖZvýå+©_bzCÜ>zãåvßÄ:/B|ý*B*Bß<äb<îOî@uÄUßßoÃBýB_@@Ã|>/УÖZ©ßa~Ð~v.~Üh/Ahhã|ubãZßvhbU¢ßa|.BãßaOvß/_+O|©vÜöz£*ðb*ÐCv¢ävzZh©£+ÜBzÃýuªz|ß@U.îOÄrÃßhußu+bªOðßÃ.ý©å¢>ð:üzO>ýĪarübªb:å>Cüäü£.bÃ>å,_ª@vÄßÜÄ>üÖß_ÜA,uãrªüCoÜ£¢üß~v@ýBzb/ÄÃ+ßzÐ._,o*OªaßãZZ/./Ö¢Bßühr<ÖÄuâÃ_>.vuУ,zª:uB©A.h~ß:v~CZC//ßî©+uZ©BäaüÖßoCÐãã,>B:ö:u*bãaað>Örýª©uuð/~O,OãüvoäZ:Öð*+örhåUãBãB*vÐã,b¢ßÃãvåßB,üÐ_ðb_U>+ßr:_U£A¢©_ÖOO|>.ÖOB:o|ÜB~býuv¢A¢ho<~A@©£>özozoåbý_**oAOU:+ür©ß+A@CbaBî,U@:öuUªÜ.oîuÖZÃüîå:Ã:u+äÜzAzCð_>ý¢>hª_ZbCvüz>+Aª*ÐUBUýh¢¢Z/Ö~_¢üßU.ßÄ++,ª,~oUA,.v:ý,©/Ua>å@ZB_@ª©a@uüuîý,å@ä/ü+ªðA>C|UZ.a,zããbZbåü@U_h:A_ýaßî,ª@@ð,bb,b/ÐBu~îbozª<ý:£ßßzîÃua>ðB_/ðåC<ãÄO:,ob,äª>ªß>Or¢~ÜBÖU¢,<@@+ª£äÃzCÐ_*AU£ÖöAbÐ.Ã:ðzß*CAÄ~bßýå£ÄÜrÐßZÃÜ£åªzC*ßr|©bßb/Zä_~Z.h<_A,aboã*ðCaÃv/OaýÃî,_Cöb~v>+ruýv¢O*~öCv£,rÖöß,hä©_ªª>Üö.B*/ß©,hUuÜöÄo+ö¢,aaÐåUÖr*ý¢+uU.~BoÜa,ߣÖýð,Cbäb~ÖÜZöð£Öî.Aß>ÜÜaü,+ªaªöU~,vÐöå¢h>ðb¢<ü<£:.å¢+ýhýuð/ÐÄUbýß_Ä|©ðßAÜå*|Äo|ýðÄoªÄzåªuZ>A:å:uåbßövîý@¢äã<£~vb+/ßýB£Bz+/Ü~î/Cã+¢ÐAÄ£ýä>/ö©A.ÃAb.auo©*AB_ßöüvãbz|:b|hª_+/Ü.Ãö*vrÃö.vbaîZbÖÐCB|¢Ü¢/h*Oî+ß>äÐîð*or|ÖZ*:ßu||ß>,ä.Uäh¢b@OöAܪ~ßuvý+Ъ_~+Uv*ßOªÜ,/üübªåÜzv£aªÄ_Öåaav|/ÖÜzãî¢äZU*ªo£+¢¢Ð¢.ub<ü@ü©Ð_<ßOzuü©r£uÄvîaAz.:bOhUZ.å*,Zßßäuî*ã:hZöÃÄ.ü>îh*A:©B_~_,r@ß:î~+/ã+<+|O~oC@,>ÖZß>Ða*ähÄ*£+ö¢ö+ãAB*ýhh:ÜÜää>üArb£ðÄZ'), ('u>vCAßa¢|£ßva£ÐZ:ZzÄubChääÄAB¢ÖhZuÄÃ~ðîåßbUÜ©Ö>¢,Ð+Äzä.Ðoz.ZbB~/rBOßöAÐ_./ðOß+>C+B©+Ubã:ßü_U@¢ãÃ>ß|~|Ä©*aãzåðuðßãZðö:rOhUÖüý@ãªr£ßbCÖ@a/¢ÄB~ªzazÜ©aOr£.Zßý_*©zîý>,~hªa.*ªßACý|orð<¢.äOÐB.ÖåÖÃU£ßO~*@v©|îO©üZ©,îü.ÜîäUoäUäÄO/bÜ.bÖBãhü.rvðÐuru:/aäð+ð*ß~AÐB£~Cð++ÄäbBbbUzz|©ýßr|AÐzBUöãh¢CÄÄ>.Ã:ubBýC<Ã+ß@Z>uuãhC+ßÖßßB/åÄ/ý+,Üð¢C:ðÃr'), (null), (0x60476FFCA499BDDBA9CF815CB0FEB6FAAA4FC769CC408EAB832902E0BB655A552BF5B6B24DE9A4B5BAD9ABB5EC05E7D46A5D7739726A411FB9C4787B96DC2043742C31BA4704ACC0AA12AEF394D9BF7E412E755DEE21E2C1B396F98971281EDEB96841C0D42C8C66B1B68E60BB4E8BDF05FA3A6766C25452496F06B10B234F81F3D0392C97FEA914A20FAC38938206650B198476EEB677F713EF766425DA5B8D670C6CEA155E48065025C556161CBEC769A1A5F19FD8923D584219A88845EFC7B41ED186151B4FACD17D26C9F121E0F630AFFA833569EE8E792F92D2459F7D0B36FDA2F74B37B14E52F82021B2C94B37612277D94A6B924B30420DE5897C6731001A93F37FE9ABF8FDAC313C48CDDBB0E35A712227A4C1CA6B56C7082ACCC5D591EF21973658B878777FDB9E39550262675D012E0B44095B59C8872822A3CD31E75CAB59AC44798BA2F9ABA8C82391155364A1CDCA96C94CE6C53E6ED09056D4B1AAB6DDAAA9B623E3768C12996193363672D4BD01006750925E73E6050BBA11A3D7F61D6CB46BAAAD7DEA5309B7B3843207375EBB98D2B143664A4C36CF1DD7BC7A6FC26A75CA82072A1083151347DBABEEB4FED45B560A0445DA06691E73DD6013968E48F11ABC3DB45791884E9B2F1E6FA0481B1F02602464BD249BC418E8B63223027CB892BAD3F7C02004C482B6A9), (null), (0x0F), (0xC24110366AE333F521799B6FDA552B99B366DCE675235EE25B5FE2067BF1DC1D6A8C6FE1F2464DAF86DDD6478495EA2C8B0FF7801E97B59FE32DDA0A012D7A309C7D044EE549A76C192D60974D165EC675B57B6BF95CFFD2D6D0FEC46B43AD5963A5AA06909B4641688A67F0371D52CCCF14944BB10E363F7E3177154BE200D57F0634A76C5735DA6E39026FF50E9BB97E31F557AE61A7556F6A885E2431F68689917130E9AA6077DEBADD780BC74A6F96BE961AEDA8271E5EDF3801CFD7B63D437CE5649D6BD0D3D07D79F4842F49A72018C8659ADC99E16952D109725FA806049F348FE2E6D22AEDC249702E33992F9590E0B60E50A267E304FA4A99C66171D472F0E4FABAC53A469D78D89228B236B9A274E27FF1F8B3FCCDACDEEC87902761D89BBC43B434DBCAA66F70D8CAD4BA51C391F7517EF2E27A3CE82D0EE74EBF0D5076BA8DF6F3046D9918D88187DB32F8F18C28F698D5E3E5B94106CC07CCE258B0638754192D5BCF8C8AB8661960F260B6341C5B25C9747B93D4D3B4ED3766CC3580D12454DDDEC43059690E1C7DCC584FA616D3B08632E7EF0CC14559A7E25524EC30998FCD0CD855582BCC5F655E16087B644621931BAE4DE935471C996B887614EA304C32C981993D7D585B668789AEAB3CDE439B3391D0814BA698C4BCBBEDA2554B7B2EC9D5B1E2ACE415C352E661AB99715D8C45DAE0595FCAE723E9D581C26BB9113C0E80E1662C60EF4F7C0C2E6444ED2A4A5D605D7A6A22CEFEEE8EF9D762525899D5259C198FF760E4EB91DD529B82DEFCAA36640A5B96D1ECB1E2779D6617EC29B3948F1F5011BA65FF788ED9447B636CB0FD2C2BED4E3E3F53B067AD80C27ED3A4B39F07F2BFAB997CFF0AE307F8A7367F0AED69EBC13B655650CBA0A90DE53DFABB3491DC5C79F15193AFEE435B375B08312E75D514E8E37839B0A2F30BEF6E19CDE84CC316592391FC1758114C22FB18709878DF7068E5D25018A3ABD7FD46A340D57672EB97F1D70AE4D8FD90D22C16B5898273778FA965037A72FEB678B9FED0B4D076AC892DD8A6A0D48B8FE7417EEC8CF64D7BD32BB02C106E7125F320922ADA2DDB01B56A2C5CAACCD7BB1DD51B38F3122A816C83FC89204A9B1ABCB3B37F4261B5BA657D3621CAF1D00754F50E3A533831E1F610BE563052E16ACE119FF6195779BC2060D276DB08B77BB2F5342A3C60C78382D28931A35E8A129CCB53B851D9EDFC7C511AF265BA05ED5942A43A1EB62E6E4A1B7447CD4719C1D5C0D90FB5FE29D80737ADEA46D41EC7CC9BD760BA50F4E69DE56E3564A62F3438581CA5E1F6385A18CB32E608442775FDAD7A326FB90785CBF372D090F473600879B2EDB778E5D6627D47EE1D0EDD59BDF837B72CFCC47F5097D9ED8368C92458D1DD8F913ABE84C8A4CFF2711967BD16181398BCBC340CB4339D), ('99999999-9999-9999-9999-999999999999'), ('1930-05-27 03:16:08.180'), ('2079-06-06 23:59:00'), ('10/31/2016 1:39:16 PMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/0010/31/2016 1:39:16 PMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/01'), (null), ('0012-05-06'), ('2001-01-01 12:01:00.0000000'), ('0194-08-22 09:42:54.8280810+00:00'))"; + break; + case 3: + $query = "INSERT INTO $tableName ([c1_int], [c2_tinyint], [c3_smallint], [c4_bigint], [c5_bit], [c6_float], [c7_real], [c8_decimal], [c9_numeric], [c10_money], [c11_smallmoney], [c12_char], [c13_varchar], [c14_varchar_max], [c15_nchar], [c16_nvarchar], [c17_nvarchar_max], [c18_text], [c19_ntext], [c20_binary], [c21_varbinary], [c22_varbinary_max], [c23_image], [c24_uniqueidentifier], [c25_datetime], [c26_smalldatetime], [c28_xml], [c29_time], [c30_date], [c31_datetime2], [c32_datetimeoffset]) VALUES ((-1), (0), (8483), (761407419), (1), (0), (1), (0.4844), (0.0452), (0.7332), (-214748.3648), ('ã+å>h@ýð£ð|ß:.Oh|ß~|uÖ|rZäåÜb,ü|.rßÜ<Ðårܪäü/ðAÃäîO++Ðubß©rv¢uävöý,U¢.Ããð+Ü¢îªß+ãrîOOªO©ýÄýß@ªª|Ö~a/_Üb:u+:aü¢oråä:.z©b+ßÐvî/åzzÜuä*~,ßbh<öãhOÄ+_Üð£hOuB>£¢ß@oa*AhuÖÜã£@A.vãÄUu¢aî,©üÃO~ýÜãðÖa_ý<¢ßaöüªÃh/ªÖð.,u/aߢß+,+ö©ÜaaÄÖAäv,'), ('©bZÃ@|b/£ª,ª@ÖrîrBU:bvrªÃ:©~å£avßr*ÖzBäãZ_AO¢/rýãöbZäaO,äÃýbå>|ý~CßZ.<~BU/o_UAývu©Öð~Ð/ÜAüå£BÜößüAª@ªîª,,üA/ä£CãÄÐ'), ('£/äÃAv:~öý><+|u/>©zb+|ªÃað,ãbð,ðÖA*¢Ðß|¢hß/ý|ü>Bî.rb@C,bßOÃCî¢@a¢|Ä*u¢Ä©BÄö|h¢hA:Zö£Ðz.£©Uß|h:Ð@Z.+aUoävÄ|ößoßöOA£Ã¢©äZZhbÃýßh@©ü/vÐvCÜZ/©öO+ª>ãZ¢@@ÜBä/Bbär<å<|AZãÐ_uðîä.OaðCð|~ãÃO£aîå~ä¢Ov~ß|r,hbÖÖZbĪ£+ðã@ýhz¢¢Ovða,üoêУZ~£ß_üãßã|¢>A,_üÐ+£böüO,ð:hÄܪª<üö*ßvbüaZ@ßb|Üî¢äa/*ö©Ä*.UåbãhaUöh@bAÖÃÃ,¢Ö/OBßh|@äÜov@ZUa,aOåOh>_C.Uß~B*Ðö*ðvoZ:Ð_zZUbãuãA+©@@:ªð£ý/åÖðÖ:/zO,üãB~:+åäU<:ÜAý|Щu/åz+_zAãß,_ߣ+:ãäOvbbo~ÖÃo_£äÜ,aÜrîZb¢zÜ|¢~>z/ßb*rC+<~ovz|©+Ü,ýaý_b:übÐÃUC/azb*+öüä/äouZð>+ßb~©äýäCrß|ªhZöÐ._öÃBzO|ß_üÜAr~|<Ãö£Ã<|A<üC~.,ÐCî/ãÜüßv<äAä*ABbýuýC:*zböüðuCOªh/ßãä¢,ߪ©.<Ü£ªua,îh+.vvܪ+åßA~oZUå/@Ou:¢/,a~bUO@ÜOÄßh:@öAßuîbö_bBÐðUahvýCüOh.ß.ßÄa£oABÄÜ|zÄ+zoOzöü<+zð@£îÐÃov+_r.:rbz,AaaoÄÃ/ðÃüßäO:*b<Ü@avz.|>Oß@üåöhý~ö<*oußrüAÐ,.Obä+ü:_zCĪ:©_îðößa£ä¢@åÄz|ÃßZvÃOBýÄ£¢î@ªÃvAü:|:h©üU¢Öo£Üü¢ðoÄaÃzrÃ+.vüãäbbA|/öãABb¢,©åZa__brå©b>~ÄÖÖObäÃC.~©hb*AÜîb£,z©*.*åÖð£ü,A|üÄObãbröUUz¢å:OA/üª.Bvb/+ãoCÖ>/O£~C£ãÄbA::b.aÜ/ÄöOð|bÄä,ª.äb@ßu£@CÖ*o,zåüÜî/AßåãoOßýO<©ßräb.©öãZ.>AuÖb|~äbªzß:*ß@AðzýU:A<ðCzzîraö¢A_Ü£Bb£UCCå*aÜ@Uî£A@ZoAaÜßüUvöã~,O/ßAäöã©ÖCzhCî+ßba~ý/*å@ÜåvC.üoZzüß+ÐuAÄOã_AÄä©î¢:OöUªöä_Zvý©ÐvÜüAãhBýbäýåߪßãßîu+@öÜubÄAöß/öbA<~ÖbÃa@ߢ,*ö,ß+bÄ@+ÜzuzaÐýhChO£îöã*AbÜBîÄ_å|:v©îÃåCð~vÄUUÖ*Äå,©:Ö:a_Oä.£ö.,AC+.,_Ä>a:Äîåü|roßb.årr/rOZC~a,,ßüvvÃ+äîÃÄ,OðÄ~ü<~ªü>öÖrߢð_ßC<ßvC/¢ðbUýüoîaüö:*:bý@.vaãubߣåßu,/ZoðÃÄ|OßhA+UÄöåß_|¢Ä|Ö¢Ä.ÜîböZÖ.ÃîßýAðÃîÃ,vÃåu<üäZbZoÖAðßbä©.ZvvAÜåßo~Ö|üßåã/oð.Ca©B*©ßv__*+äO>£h.U~Oåürã©*v.Ü>ðZ>Ð.@ßCÖAããÜ>~*v,|ß©ßðruA_+ArÄU*båäöãh,Z@,b_::@öBÐB@üßZ.>,z,äOßÖA*U£AßrvÃböÄBßßba.ö>¢Ð,uª:<:ãý/z|Z@COåðZ@ßãåå~zÃ|v,:U*CZüabª*Ü/ã¢oßZÄäh>©üßî>+ACßå~¢ýbrorooßzöÐ.huU.@,ä.uü~+*.<©a<|åäb©|ö¢B_ÄzoÄu.ÜUB¢¢.ao.a>äãßÃ@ßä.ä_b|Ä<åZ*Äîßåhð£r|.äoåé©bBßZãO@zz,åÄÃ.hÐCÃ<@r¢Äðo<ö.aUüã.î¢+Bý>@,>|ÖAö_oUCAOuo::,äh,UCZb/bZßÜߣhröCÄbüårößhårܪu*ý£Ö,<>îývAÐ..ýö/<ý/ä£:bzªß.ZbÃä_|£å~r/©.zÜzOÐÜ~UîÃßvÜ£Ã||/ÜÃbªîªOrãÜB+uAza|äCîªãuo..åªÖðö*aÜ£ýzÄäÐa_ðßh_<ÜöÖBã/©ãð:ðO@Ä~<ðãÜ+:.*Äãý.åbî:aåðC£ýãBð*öÐ_,_+a/baü,oA+¢AvãAßÜAaîz~ªðÃß*Z£,:ýå<£ÄîÖAö~vå|OÃ_Ü.*v_<|åo+£.ÖЪäý+ÄA|ßÜðã@,ä/ZvzO,~UAC©ã¢Oh:Aäo©C.ðb|¢ðü£/:abÃ:äb*r+o*ßåÃå/+©*îü+ÄÖA,OÜ~:¢¢¢©©ÖðÐZ+ãß@Ü~A.©O|ZZ@<©+ö.>î~£î_ÐrraC@¢,ýu*_Oü:*ßB,åz*Bra,ü*ßvau,BýaÐ_Z~_C*£uîb>._Äö,aßbîrý,©+¢ÐîÖ©Zã+ßh£+ßOß>öbå£Äö~bã,vðC@|ððO>ªÐãbý©uaÐßh£©Ãv£.î_ß:å|öÜÖÜ.ýªª¢üb©Üb/Aü:oBCh,U|böz_bäÜäðîzuåãahr@©ªA>~üå|,vÜ:ß.@u+Avb|BZßãOCßbªhüî¢Ä©_|,*©@<>BÜCß_oÃãÃß©ð.A£ßÄ,brb.hoBÜ©h+zîý>~AÄ+vöðuåý©zöÐöð@@vZ£ab£üÐÜ@_>,î©:ãaö/:/hýr/>äobÐuãߣÃã|üb.|O/Ã,ªÜ£>åðýbbãh©ß£oB|Äßz£ªUüªvbåaåUÐZßßÄr©oßuÜÄß|OÖzîza<¢,Ða@©åªåCbr~/+aß:åå|abåå://_ÐüÄåßBîbßr|Ü|îCÄ@uCßÖ>ªuÃhzz<Öü*O:ß+ß>uBýð_*©ÜãðåZð£b|îãäÄ*baß@*aäOß>uüÐvrÜüüv|îz./Oãzv¢_ü@CoobO.Ozß~A/©zO,äßöå~h/Ö¢,*@Ä~~U_Ää~BC~|ÜüÖ*vüÜä©ð.ߪBöüÖ.<ßb©¢uaßîö<¢vÖr,B¢.b<£ª<äÜoÖ£:ÜåßÄ©Ü>C*@*>bã,b£©ªrý*~©ðßrªßäbUh:b*¢©ßUb~ozabÄððu/@ýÐ_CÄaÐv£Üuîa©Ü/ãüCv|AöýC:,¢uä¢C,zÖîü¢¢ð_¢Z*UZ*vvUvvzU:Īhª|*©.ð¢Ü£+A@ÄA+OÐöТÐßCC*ý~*ßCåbzãAbªår,Ä>£ýUÜ_UðC@>üü.Ü:rz/*vö@<üOåýZ~ãÐ+îuOCå~<*/â,B*/U¢,~ßb/_C@Z~£U£_ÃÜ¢boªz*ÖüýäUîbäã<ÖÃrvöZ>Ãbª~ýßbßoð¢>vU©ZAãßb~Zu//ÐO@ü*.ßzª>|ÄÖÖhðbBü<,~rb,o/CUî.ßOov£~ãß@ßoÃÜCªZBÃh>OUüAoü/~oýÄbÃbÐ_ª<@*Uv:©uå:üßå*:C/r~rCC©hU£_BBaÃ,bâoßUðßZßv_<ßZ©UüUåhz<+ärý¢ð~häÜ~ÐOÄZzoZßðzãh©:+åãCÖßUÐuvzªUZåÐÃ/ÜåbBöü£Bª+.h_Ä_|vAÖaCö*ýü:üÄðýðr£.,Ü,Uðä>©uå*bЪbÄAãýÃZß|bh@aöoª/r@C*ÖoaBå@:Ð:©åÐ,£rüBröC~uZB/ä_>¢Cåã©uu|ýCCaª@ßÃBbbOîüCäö©@avÃüåÖÖU.ýbaßÖ.Cä/z_ª>aäzA¢+_aaîýÄuvzoUUãU/ÃAAZ/>äv:£hð@vß©ãh,Ð.üoAb_CßÄObÃ*bzßvÖräÃÖ~UäßÄa>@.C<_AÐäauî,b|Uª<ª:üý,Bö@b£.ðoZßCZOÜöußb~üv@Z>¢Oz©.Ovb@CzOö>,*öãöbvÜ,>£uß,,AC~£A:ßhrðr|:zÖhB:Ua::hîã©vz©~rðh©*UÐßÖO:B.Ü:ãåÐübZã_ýîîªÜOOu><¢Ðv/ßý@.ZÃb~*A+<ýß|+~Z+.hÖ/©ü¢~üö£Übo/BÐ:ÃaouhAã>U¢C_ýüzÄrî.@/Ð~Zü+uªb*/a£Ö~A*ö|¢/O:ro+/öö*ßüÜu<ã:OvÜßu.îrCîu,v,v+ÜBuZ,CªðZ£Ö_:u.ZÖªhÃÜãOß_ab:O|Ãö_ªýCU|bb©ã@~©+Öå>ö£ÃU~ªöð:Ö+boÖÐüßaäuvbr@bAð*ªCovoüUðaÄöabrßßÄý@BÜ¢å+î£rî:oo|©¢z*~bðO>ßübbãObU/ßb_ß|üUß,@Ü*>ã_.*:åÖOßroarr¢z<@zb¢Ã<>:,>,_,/ð©î:ª|U£|ÄüäßObߣ/b¢U,*ß>å£bZa+£.ßU*zÜaÖråÖ+©ß£Z,|ãö>Üo_*BüÐîo+|*zå/Z.,£ýh©u*ÄÐo/h¢¢ÐaußZãb_ra**¢+£bü_r,ßî*üZ>~<¢.ðoä.rÐvBC:+ä/üßz¢ýb©råöUãß©v>ßvªîßbo>rhðßä.ÜAö<+Oýå~.öObu¢~.baªzoÜö¢å¢ß.ãCå*Övbä~b,ÃAÖðb,<~ýaä©öÖBUBb,~üî@ß@+:öðåÜ<å>öÄCärÄhÃýã¢îuЩaüßO>/ß_ö.Ðð@£BU,ß:ß,hUä©/vî©O¢CÐZîðüv,boOo:<Ü¢AîªUBßîvåÄö@boåäÖܪrrÖovB.,©Oüß,îßuabö_*ãAAuAÖzîvz/©å~.ZÐzhÃ.Ubîî|¢UðäßßUU,OÄöU*b<,å|h@z.öZÜßãð~öh*ü*ðÄåCo*@ÄýuýÄÐÜ~/ßrýã@ªAZ/üAovªª:îä/AorãbzAo*b@ßA+.ß>Cß*êÃrz*/.£_ßä*_U¢@+ßO|äö*zÄî£@ßU£:>CÜZh*£Üã©+ÐBr>OåªbßUOÃCAAbZð*hBßÜ©ß/>ß/.z@îoÖu@.:£rrãrZ/£vBU+ä>@Uðz_ÃÜ.ߢ.ð©Üüvß~å<Ãöîý~+,ßrÄobªBrã_åoÜ/Äb*CbßýUª_+bzª,Ab,/<_äü©üÖ+/__~ÖãÐb+OO>>Ð>@Ãb/Z>ãß>@ÖzåãCÃoz<|Ðbü¢U~_aãäåðÐb+vª_<åªÐbbý@äuU@aOAOÜ/Ä:OÄüãü¢¢ãªã<ãb£ã~üýAî¢ÐÄZÖ*ãÄå|vö~o+ÖîÖÜ/:oîAÜ//,*:rÜ.ßß:aÃb¢ßðöb|_UðvBÃruª<<ãÃ_*î:+:*@_Ü+ðÖuÃh£@uä>uo@CUÜa_ß*+ª*oÐÐ*~>ÐAý>~+*:_BüüuýrZO,*C©züÐ:ýz/bö/ZðÐh:u©öÜîz>:ß,C¢Cä|*a<ã.bUb~Að,~ÃÃA>äÖb@:~r+ã@îbåÃZr,bZ<îÐ+üª,ÐüðîÜh*¢Ü,ßAî:U>||*ýÄ+£OOª,|ªC,Bîbãu/öÖÜÐUîð.*Oär/Oߢ¢Ã+*ÖÄOÖÜA©ýÄhãªabªß~v::ÐÜä._*oð~U£bãð>>ð~.ðBCÃîªu|orBZö>ýãvðrßÖCUÖÄ/|©ãbC/_<>zb|ÄBb©ÜߪO~rä~ÄÜßÃ>Cb<äå.¢äªh*,/Uü|:+a/_äoååü@U>üý©zOvO_:_uÜ:å©vßhöåAv.ßvüUå~ýîî+,£hÃbUýßAýãߣbý@¢bã|,<ĪäbuaZö:¢*/bU@ªÐîöBýv|a¢îU,üîb*ýîð|ßaaý@b_r:rýÖýa*ðªv<:Aä/*Öîü||a:OzöÜÐåüªðÐ+|.î@,ö|hÐÄ:>_ö~ÃuªCý£:Üß:AUªb£Ã|ÐÃZa/U_aý/vÐÐß/ãhOÖýßÐÄC£Bb*ÄöBBaåÜä©~vübBýuobb¢¢/:äîÄÖýüo,*OOZA*OÄãb~o~ðÜðÃÐ<öªa:ÐZ>urzhÜå>OðÜîßüåý*'), (N'bUäðCßbU@rßzÖ:a_Bü.ÄvÃÃ.ýýaåÜ~üUßäÃb¢zb_b@,v.¢A:ß/ßað@Z+~<öCÜo>ðU__îÜöîßåå£Üz.a,ü~B+ßö*v~z>o+üÜîAðåvrob¢B_îä@©>:AÐåbU¢*rvb_.@@za¢AuÖö|ýrÄöB.vðÄBßãv_örbå~©ýo¢©BªuåU<:Ð@>ÐÃ+z@ðö++/~ÃuÜb.ü*ov|ã~bã@O,Ãu©B*ßßvãÖ,£Ä¢ßrb/+:ãã@B|uðîBZ>aÖ~ÃCÜ>*/üß~rü_|ðA,ý.hÐ|ãÄ|_Bäîo_v+îhöÃ/ªbö>AUzCAÃýåÄzbäaAã.röU~b£bðª£/ý+ü,zru@ª./>/åA:rO>åBBvu©ã,/.r,_.Ua@ÖöÜ©Ü@UýÜ<ßÃAOãÖBÖzåBÐÐ<ãßC,AîBoä@Äv/ãB~äv:OaÃ,r+¢/£öC/auZãü,äüä+©ß@>~zv©ÖAßZ_:h/ða.>:uÖBÃACýCOaz~ã+'), (N'ÐßzhýrBªÐÄãÃ|<ã|,ð/b/ß_rZ_|a@ÄÄBZ|OuB+ö*ZZ~ÄoÖrððÜ:ðUCä:£bbaaÖ,aZªa,~.ö*_ð~/|ZO<_+_bArä>ßuz>u~äîb¢<_:aã@îå,b,.Z>*z£o~oöߣ@|+B:Cüb©,öߣa@@aOoßßC.ðãhîöÄ*åÃÃîð:>/<Ö/aÐA*©ÃZC£|ýÜüã.o/UZra+z>vCÖBbA~Ð..Ü,+ªo<ªßbÜîîÐb_Zboo_.©.|b_örb¢ýU+|baßãBã¢rz_~+.Ä|Üý/|äaBýß@£_,Ö/ߣ_Oý_>ubzðr+äýÄCu©Z£|Ã>_öaýz:zu:åÖðãuÄavÜ*uößÐCßß@ZÐ<ßuöA£A@@î¢aÄÖ>Cbåî_£Aär<|Ðrîo@öA>ýb@üߪ©@ÜäÐuUbUýÜö¢ÖB/ðA>>ðߢ*.ÃýrÜ/¢bbzåCva>uZ©üî.|*v.|h>b©>/ßÐa<+ßî~hî|~ðÜÄßU+b©~<üAÖÖîå*hÖ©îZ|åßa£ã|ÜbvÐUb*<|_..Ã+ßåîö+ßÃ:hÜÖãrCß/Äz>Ä|/ܪvªÖåh*,<+îzßߣä*,aßBðýß+Cz©Ö*~zÖ,hîÄ+ðZ+ðA*£BýߣÖhÖhªZÃöÄ,ÜßâCߪ~.©Ðhü:Ä/oå©hvC@aarßohb:öaüh<Ãzî_b@~hðªoZ/ohßZ|C£z©¢ðü/:*ußCýZUÖäýCBÖ_,B.A@Öýö£©<ä>hAÃý|¢bUðh,r:>bBö¢ä*aßArbÄCߪüÄã>¢vbðBåÖÜ¢¢©/îvÃð<_b/CöCßz@ä_ýðbß|ÄßuAoãÖC|,O,Üo//îC:©.+Ãou¢ªarßÄ~åäO<<åZÃZz~|ð:|+:Bbߪhh+hbß|UÐðÖâ@ðüüüAãÖ+bÜCußbU~BÄîýÖÄãz,bÐ:~.ßåzð£,>hbüãßb~Aãa>ýoßbÜ¢Ürh£~öBZÜÃüUðä/åBü©öhü£vZ|buß|ä+Aåäã+BOoUÄa¢v/|Bªöv©@£ðO*+Ð,vO+å,<@uaÄ©v/¢©:b@ß:ãÐîävOä,üÃ@ý,ßåb*Cußî*bîZaUåý¢O¢CUzO©å¢ÐªBöva_@£a~vöü.>~Ã/CU+Öã@AOÜbÜÄ¢C¢o,+o||ÐbåZð*U©ÐÖÖ@uÜ,,äU:ußÜ/åZöo*ßah.Cå,r<<ðoß@AzAr.*UÜ_BÐ*Ðü¢ýaýOîäÄ¢*,+.Oî,*v/zÐBäB|ßBA>A©Üh.~><ðýî©*ÄÃßAöbåhÜ:ð/u_hî/+ßãuªÃ¢rý¢/ð.Z@©..Ov@äAo*vuäÐOA*|@¢BßBBarßü::r+h.Öãhü£ð@ÜÃå*:Ö>ß~b,îÃ/ãZzObÃî~ÄCðb:ü*î,ðªCzhuÖ|:*oå+ã::ýð|ãß|uªOýÖ/+z_<ÃÄ£<öÐä<êCBöbý|ªr/>|b@Ãî*Öªãzß//@££@a,AÄ©Üã~Ö,@ªzuÜuBÜAßð:a_:ðbaÄu~Ã@CbîÜBb£Ð_*<@UÐÃßå@ßßBOOZAª.å>äa:zäߪCöh@bÜ~ÐaÃU©<@oðÖ,>özªÐ>ÃÖuÃzaO.ðBÄö+ZÜz¢:Z++Ö/ýÄbÃð+äv@r£ãZ_î.ý>ãÐ+ßu~<¢O*äz,hð*uýUzvbbßOÖÄ:<ÜÄbZä@abO<@uZO_/rß@îüZðo©rߣ|¢<äah.¢ßa_¢ðvb@ÐBüå|.ýðr|@>ßÐ,@~ý£roÜîªrZåýÃU//~Uîbh£|£<¢ðãð,åî_Ü<@båßB£ßÜ£*|ãu,uäU~h|ärîÃößåUýzü@b,.>@.v|büîîZ:,>ðÐ>UC.îU>*Ðu>>:ÄOBð+~Z_@ãåh~C,Ð<£UB~h/BZub>~h.|ãa|å,Z¢_Orý.Uv*CC¢Uªu,ä~Öý£C>ßö+:*z|~>+OaÄð,hAãaåü,öåßz:oýC,©>b,U¢öîüß_¢vb~@<£aä,ª,ü__©_h_böbB/ÖÃßußüÃ|/uAU_öUhzîB.öUZ_bäö*î¢r|hBÃZÃ/uîÄAöÖ>vC~å,ÜAüÃ<>.voða+©ßa>C/Uö:îÃOÄîz|bÜÐ,<@äo~:¢rî/UZå*+oÐaoÃzððå+Üý~Oär.r£îhöãð©ßbbA£.z+_oßbªAb_~üßß:bAbräBu:uråb>åvåîUü<¢AaAoßo~+|übCrÖaýßÜZbðzöðüCoß©ÃÐãzãAaabbö£b©Uãüvhvåb<©.ÄÄbBrÃýßÄåuuß,ÐýüÜÄ+/ã©ÄBýãA@Ü¢voðCzöðã,öä,Üa¢oî.vðZÖzüåA,aräüvöUübäv.Abðz/~:åÃ<ãö¢uä*_ãüuZ~î£_ü~:_b|Z¢aUä+/å./hÃ_+ð+Abð/rîbCv£ZBAzðoðb>|öÖAAbÃãAßßbåÃOä¢Öb~Cýü,BÐ+|ü+ðZª~/uÖ:uðÃüuvö,ßöovð@*bUö>£>*ª/Uv.@bOÜ¢öa+<ÄîUA<>Ã/.Ä:.ü_*<ÐBüå*UÄüå/z¢O|zðhä+*Ähböäo<ä|ãåßö/<+hÜ*UZbB*Cr:©ªåßAü:©h£Cb©r>CUß.aðäraCßßä<@AªßÖ+Уª>¢~hÃ+U,å,£ß+ÐOÐhîÄ,C©/BCýCööu>¢ðA_ð¢ýßäÜÐî/r©oz.b¢:Av>ãr<ЩüÃ.ðCÄ/ý:¢>~,ruÃÖOÃÐ|B¢~ýZ*ü>AªÖäððÖ+¢öhAUZîb.ßßü*hU_ÄzßÐ@ª@_aAÄvýäßbÃ_ÃCߪÖåå*Ã*_>hObuZßbzz*zã:<ÄÖÖã£rßß:CÖC~ðäOîOý@î_OÖ~aa,u+ßöovoC~ãÃ*Ã+Öö©ß_Üuv.ßîðBð©CuÃü|ª~bhvߣ¢©rð+Ä_+©oBA.*ÄäãZ>:o©ÃЪßrý>._|ÐCªªrä_r|~aöh©|råÐUäÜ+>üýÜBvC>:îzUå,*_/CbüÜð£B>ð:Ü~ååå/ýªö@*©å|ZðÐz,ão£O~~Ã*ã:£,ZOã*ð|A:ßîÜ,abB>ð+<ĪOB|u@UÐUÐÖ¢bîAýhrB¢uð|hîãußrÄoßÄ¢_å,@ýäCü¢ßOÖuv©¢ü£,Z:_||öð/+h¢¢uU>UZÐöh¢ü*B*:_ãhäU.äª~©:bvuãßîz>Ð>a+u+åA>ÜzC:rB/bZ>îuÖ¢Ããßu<Äzßbaª¢ü_>î>++,ßU|aåoßÖ>./Uüäz@/h/*OöUBãªhåª.ý_ä,ß/Zß+zå|z©ª+£Brü>+öAOåa:OvaöoB.î,ªbîuªßB:¢¢~@<öb©¢î£~ãrð.ã¢bZö.uUÃübAzÐå~ã~|U:öÄ*üv_OÜz|.h>.ýÄß@u.zß@îa*åAbÖU£ÃüîðÖ*©äb<,.©îã*îäAb*ãCCO/'), (' '), (N'~zÖ:@>bü<ã++ã:BUC>|COÄoZãäßÐ*.ÐrîÖßBî:*äãî~Uü>vÃboã:BA_åh:ª©Ä*£bBo:B£a+©oÄZvCð**~B£o|ZCäuðü:ã©h@v@hßAOAbßov.ÐUÖrCvBªÃ<@ßbªåÄAðß@|ßa¢ß@Z.b~+,AÄhüOÜz~~|ßßý*Ü:zzAå+ÖäöUh/ªüÃ_CCCÜ*<î_vªUðýür¢:ÖãbÖ_vO>ðå¢büU_Öîzä©öÜOð:arÃ_,ä@vuhbßböÐuZä.>ÜåÐ@Öb_vöãß:obü_ACßÖC<üä,C:©îß:©hOî©Ä.*¢zOrÄå<*ßä*åî>Äz~/bÃaähýBoBßu/üð~hA/hC©>@ÜßO<*v©ßäzu£Ä*ÜorB_ߣC+î/ZÖb_CÐrÃ*¢+¢ÖUoh/örau/¢îZ~~©<Ä.U@ÃUUu*Ã_bvÃ@*vîOhî¢|ý@::äou_o_r¢uBBbaðîbU©öüÐB|A©,_|BãUýUß,bCh,_bbvUoãOra|Zz~<ÄÖªOåÜa£_,ãBßr_ãüCZ/îÃãzåãrz|||ö/ßB¢<ðv¢@/_å.bAü©o~OAo|AðZzA|ÃbýãîÃABð,bãÃî>oöîßßU£.ü+îvubÃoªUbß*_b>,aäÖÜ_+*Ubäý,@uzA©A>ý.ßAur:~¢bh¢î//ß,î¢/a|AorÄÜbaÖÜ©@|>ýrã@~bÜÐoîUBüB/ðhbª>/,£ß_/,~åZ>C*ð/ª|,b+uãäUü¢ßüªß£¢ðbb¢~aýhÄrãä/hÖbÃã,ÖýUUª_,äî¢.üb©äOUäßa_.ð£AaýßãäÖZÖ:å£ÃãAÃv.+ÖzCÃzÃZaU£:ß>oorZÖCßA+äåßr_ß*A**@ÄOoÐüZzüävÖðhªz.:£/~/.åÜÖ©ªü,ãoö/¢CäOh>ý/zAãÐü£¢r.å,ðÜbZZ.üzÄ+ªACß@_>:rzh:hC+AbßaýÖüüÜA©|ß~ßoOZbÄaÐðaz,rC>ÄãrU¢ößЩ*ÜUrÐðuCbvo£<_ãÃ_üz.*roýrªähböÜvvªÐÜ¢ö::ÖªUäãbУ>Ü¢Oa.Ã@ªu_<©/ÄU|©ÄU@_Öª£_ÐÐüZoCåu£zz~Uäðro,/ýß*îýZo,>CüAa|>©ö|Zb.ª:Ü.öü/ã*z+,O£Ö.ãüaýÖ.BÃZzAÜrü©äZa.*|@bzÖ|hߪ£ãîÐvCªZB©O*BC¢ö©£,£>bou.ä*îÃuOUZB£>hAöãb~r+ra<,/,BîÜoB~©©z.ßêä<öbýhýbß<Ð_b,ü*åvö<îZ~uoãÃßb<@üu~AÖ+uhîå:CßA_ACr*oA*@bÃUbUîUö.:_r@BA@rªubö+,/hhª~ªÄZ<£Ä@+>züU©öb©vßaöbÄh~ßv~ÄZãðBzÃ*Ãhb~ß~Ãaå¢ý/azÄhzC©>î~ÐÃ,hßb£.äöüª+_rUr+.ÄUüßã|.~Īü©r/C/,r©Ü¢+>Öb:.öOðbðZbr*Üä_ª.<ößåZÜoUBãA|ªÜ*ÖrÜ.o:,ß_b+CuÜ©@©b+h/ÃåÜ©UÄBbA+Ãzä~|BC:üBovãð©ö<:z,>u¢ðb*ýÖîÐZÄ>Ä©ß+rB.|¢.bU/bãaZU£Ü©å<Ãö_uB,_uß<ÜCUä~v*îßäß~©£ZoÖ¢>£ðããrh/©äzoBovߣðªoÐüzäãa©Cvîöö£îÖh_öZbÐoª>ª©Ö£Zåý+|ªî@*býß_ahÜî+_O/Cvâ|:|,ãUOUaß.î~,bauß:ü<@ÄAß~UC|uOªzÖao*C@ßß~_rÖåzü>OOZßÐhAÐ_|UabÃܢߪã+î©Bz¢hîü>vöUv£,AîÜ~ªý/ß|UßÖåÖªöZ<¢ª@býr~UbUU¢aÖ/zÖÐaaU,<ä|bß,Auî*£Ü©båüî~ý*ðo~ðub*~aäðvuuC£_ð,Orã+ðýUB+/Üß:AðUZÖß+ðB¢hÖ£z@¢Ä©/O+äuuß.>£©ãßbzüýðCU©Cå@ä~©+/@h*vZ|öåOUA@+*ÄvUãb:©*ª>z~>@,Bß©bÐzßoUªÖ~ðÖo|ãßZU¢AO~rßö>ÖrvÖb¢b¢©ð/özî.aåO££/BªÐ<:ß©<ªO_v:öhZz_ðãßbÜBrZ>_ü¢AOZb£ª|v¢ß/obÖî©äüvÐ|©ÐߢAã¢vozh>üh+ü,Z_ÐbðB.£ýAr_/@UUb,:>:¢Üðý:@Z©Bª_Ä>ÃÐa@Т.ýAß/ÐÐßåUîÜî<öÃÜßb<~Cüî:©ãöß_u|£ßB:~ü>vÐÄ..Oߢu|aür:vbZah+Üîßý*a*hb,>BßzßÖåÄ~Ä£Aª.ß~AC+Üß::¢Ð+ðÐb.ä©|ãbîoî©_O~:/ÄÐZ.oÜýuÜZ~b©o~/ßh£oäü>Ü*ð/h:ãªuýaîýb_~Z/¢åUUauýzuÐAöv|ÖU_bu+ãÜöÄ+b©vª+v~*ÖUbU@*Ä/ßuB~vÄ*ö~äö_åö~ÐÐå<:ª@z©ýaÐ_ða|ãCB+OCãßßu,O,hÄ£,CÄB*£C©Oh>Ö|ÖßA|ZB/ߪ,ªîbÃuu*ð_Z,*.rðrOa/oA¢bUA/+£BAZãßÖBrvüî:äuüa_|©CÄðuCüZß©¢b¢A_aýCå<+.C.<*C~o.<<£åÐÃîoÃZ.C_zrÐAÖ*Ãã+CbOvaz<îa,h/rh:ÖzãBß:ªbuåßOÐZ+U>|B+~Ã:Ã~ÃßZAýCBbvZ/>~að@aö.rhЪ£h£äBßäbäh@h£zauÜÃh:aðÖî_Oã|Öb>:ör£î+UaU/u~*båvrýÄ£@ýZªÄ,Ð*ðüzzß©bO:©Cå@ÃåAÜ+.ã©ð£~A/ý:'), (0x1BD3592E27D31FD6B0FB1A576E3F59A69A7B7C55823CF28F413F3598D7F2810C502D6A3E1DFF8DFDB5F6E89DDBAEF96DB2C756150666EFDFE0C9EDE89D86FFBE790E0740D41B899AEE7E9F8B5355B49245C8E6109C6C06EA3E8B5E5312A55F2FF72A23CDA7B898973DBB2687C7638B8B155AA2D5A0EF220D081D0249BD3688481E6F942931E6EA8D2F4ED2E0457E22DAFFD23DB703898F070716C58AB54EDA0733DE6857CE62592F6F14A5511D41C789FA284AEAF8D04594E08D211B2160D8E174CAAC6715CB0B9C780E48EEE43DEAC54E3B908C04EF0F6398D37641403E0D0F3BC4DD529560742E1DAD71E98756B0A3A25BCCE5CF4BDD84BB49CE4154C95C3A0039B31F609730EDC7626F567E26DB4223D39EE571283FEFE758974C9232D9535137E84EA391420A2ADF1D6DDC8606956D8C1E812A77C8D8F9C3380700E45B95881B05473D8C9F929093BA920AD113615201DE06145828DA6E0F54DF583B0A468388E5C180BF57E0EC8CF097BC36CE28B0D7B5FE0655812FF22BD82E38BF395589740F81EC51E092AAFD198768911D37412C2F81850B402D07B5CE54E629A82D529A0D48274C3C5EF62254083CF9C7600EA0EC0B8FB8D07E34E17936838BAC8154EB35CE84D410F4F0D18A62B737F10EED6D46453C9D9D9ACA8E1446D205DA6B76D2B350C3F9DC3990F81BBE2E6DC7FCEF6B4815456645B74D9A04F5BFCA21EA), (null), (0x0A), (0x4AF4910CB54381DF6B5CC71856), ('1805b74e-0bd8-46d8-a647-0af16fd18c1b'), ('7041-06-04 22:06:45.738'), ('1993-04-30 03:32:00'), ('10/31/2016 1:39:16 PMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/0010/31/2016 1:39:16 PMThe quick brown fox jumps over the lazy dog0123456789,.;:?[]{}()-+*\%^=~!@#_|/00'), ('05:20:41.8865910'), ('1766-07-10'), (null), ('6153-12-09 08:49:27.7938112+00:00'))"; + break; + default: + break; + } + return $query; +} + +//-------------------------------------------------------------------- +// Repro +// +//-------------------------------------------------------------------- +function Repro() +{ + StartTest("sqlsrv_statement_cancel"); + try + { + Cancel(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_statement_cancel"); +} + +Repro(); + +?> +--EXPECTREGEX-- + +...Starting 'sqlsrv_statement_cancel' test... +\[Microsoft\](\[ODBC Driver 13 for SQL Server\]|\[ODBC Driver Manager\])( Function sequence error|Associated statement is not prepared) +0 +(HY010|HY007) + +Done +...Test 'sqlsrv_statement_cancel' completed successfully. diff --git a/test/sqlsrv/sqlsrv_statement_query_timeout.phpt b/test/sqlsrv/sqlsrv_statement_query_timeout.phpt new file mode 100644 index 00000000..2416463f --- /dev/null +++ b/test/sqlsrv/sqlsrv_statement_query_timeout.phpt @@ -0,0 +1,77 @@ +--TEST-- +Test sending queries (query or prepare) with a timeout specified. Errors are expected. +--FILE-- + 1)); + } + else + { + $stmt = sqlsrv_prepare($conn, "WAITFOR DELAY '00:00:05'; SELECT * FROM $tableName", array(), array('QueryTimeout' => 1)); + sqlsrv_execute($stmt); + } + + $errors = sqlsrv_errors(SQLSRV_ERR_ALL); + $e = $errors[0]; + + print($e['message'] . "\n"); + print($e['code'] . "\n"); + print($e['SQLSTATE'] . "\n"); + +} + +function Repro() +{ + StartTest("sqlsrv_statement_query_timeout"); + try + { + set_time_limit(0); + sqlsrv_configure('WarningsReturnAsErrors', 1); + + require_once("autonomous_setup.php"); + $database = "tempdb"; + + // Connect + $connectionInfo = array("UID"=>$username, "PWD"=>$password); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + QueryTimeout($conn, true); + QueryTimeout($conn, false); + + sqlsrv_close($conn); + + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_statement_query_timeout"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_statement_query_timeout' test... +[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired +0 +HYT00 +[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired +0 +HYT00 + +Done +...Test 'sqlsrv_statement_query_timeout' completed successfully. diff --git a/test/sqlsrv/sqlsrv_statement_query_timeout_transaction.phpt b/test/sqlsrv/sqlsrv_statement_query_timeout_transaction.phpt new file mode 100644 index 00000000..9eacc6ac --- /dev/null +++ b/test/sqlsrv/sqlsrv_statement_query_timeout_transaction.phpt @@ -0,0 +1,90 @@ +--TEST-- +Test sending queries (query or prepare) with a timeout specified using transactions. Errors are expected. +--FILE-- + 1)); + $errors = sqlsrv_errors(SQLSRV_ERR_ALL); + $e = $errors[0]; + + print($e['message'] . "\n"); + print($e['code'] . "\n"); + print($e['SQLSTATE'] . "\n"); + + if ($commit) + sqlsrv_commit($conn1); + else + sqlsrv_rollback($conn1); + + sqlsrv_query($conn2, "DROP TABLE $tableName"); +} + +function Repro() +{ + StartTest("sqlsrv_statement_query_timeout_transaction"); + try + { + set_time_limit(0); + sqlsrv_configure('WarningsReturnAsErrors', 1); + + require_once("autonomous_setup.php"); + + // Connect + $connectionInfo = array("UID"=>$username, "PWD"=>$password, 'ConnectionPooling'=>0); + $conn1 = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn1 ) { FatalError("Could not connect.\n"); } + + $conn2 = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn2 ) { FatalError("Could not connect.\n"); } + + QueryTimeout($conn1, $conn2, true); + QueryTimeout($conn1, $conn2, false); + + sqlsrv_close($conn1); + sqlsrv_close($conn2); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_statement_query_timeout_transaction"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_statement_query_timeout_transaction' test... +[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired +0 +HYT00 +[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired +0 +HYT00 + +Done +...Test 'sqlsrv_statement_query_timeout_transaction' completed successfully. diff --git a/test/sqlsrv/sqlsrv_stored_proc_varchar.phpt b/test/sqlsrv/sqlsrv_stored_proc_varchar.phpt new file mode 100644 index 00000000..9daefcf7 --- /dev/null +++ b/test/sqlsrv/sqlsrv_stored_proc_varchar.phpt @@ -0,0 +1,78 @@ +--TEST-- +Test stored procedure that returns a varchar +--FILE-- +$username, "PWD"=>$password); + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { FatalError("Could not connect.\n"); } + + $procName = GetTempProcName(); + + $tsql = "CREATE PROC $procName (@p1 VARCHAR(37) OUTPUT, @p2 VARCHAR(21), @p3 VARCHAR(14)) + AS + BEGIN + SET @p1 = CONVERT(VARCHAR(37), @p2 + @p3) + END"; + $stmt = sqlsrv_query($conn, $tsql); + sqlsrv_free_stmt($stmt); + $retValue = ''; + $stmt = sqlsrv_prepare($conn, "{CALL $procName (?, ?, ?)}", array(array(&$retValue, SQLSRV_PARAM_OUT, null, SQLSRV_SQLTYPE_NVARCHAR(38)), array('Microsoft SQL Server ', SQLSRV_PARAM_IN), array('Driver for PHP', SQLSRV_PARAM_IN))); + $retValue = ''; + sqlsrv_execute($stmt); + echo("$retValue\n"); + $retValue = 'Microsoft SQL Server Driver for PH'; + sqlsrv_execute($stmt); + echo("$retValue\n"); + $retValue = 'ABCDEFGHIJKLMNOPQRSTUWXYZMicrosoft '; + sqlsrv_execute($stmt); + echo("$retValue\n"); + $retValue = 'ABCDEFGHIJKLMNOPQRSTUWXYZ_Microsoft SQL Server Driver for PHP'; + sqlsrv_execute($stmt); + echo("$retValue\n"); + $retValue = 'Microsoft SQL Server Driver for'; + sqlsrv_execute($stmt); + echo("$retValue\n"); + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); +} + +function Repro() +{ + StartTest("sqlsrv_stored_proc_varchar"); + try + { + StoredProc_varchar(); + } + catch (Exception $e) + { + echo $e->getMessage(); + } + echo "\nDone\n"; + EndTest("sqlsrv_stored_proc_varchar"); +} + +Repro(); + +?> +--EXPECT-- + +...Starting 'sqlsrv_stored_proc_varchar' test... +Microsoft SQL Server Driver for PHP +Microsoft SQL Server Driver for PHP +Microsoft SQL Server Driver for PHP +Microsoft SQL Server Driver for PHP +Microsoft SQL Server Driver for PHP + +Done +...Test 'sqlsrv_stored_proc_varchar' completed successfully. diff --git a/test/sqlsrv/srv_069_fetch_empty_nvarchar_buffered.phpt b/test/sqlsrv/srv_069_fetch_empty_nvarchar_buffered.phpt new file mode 100644 index 00000000..c1568d99 --- /dev/null +++ b/test/sqlsrv/srv_069_fetch_empty_nvarchar_buffered.phpt @@ -0,0 +1,44 @@ +--TEST-- +GitHub issue #69 - fetching an empty nvarchar using client buffer +--SKIPIF-- +--FILE-- + 'buffered']); + if (! $stmt) { print_errors(); } + + $return = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); + print_r($return); + + // Free the statement and connection resources. + sqlsrv_free_stmt( $stmt); + sqlsrv_close( $conn); +} + +test(); + +print "Done"; +?> +--EXPECT-- +Array +( + [Empty_Nvarchar_Max] => +) +Done \ No newline at end of file diff --git a/test/sqlsrv/srv_223_sqlsrv_fetch_absolute.phpt b/test/sqlsrv/srv_223_sqlsrv_fetch_absolute.phpt new file mode 100644 index 00000000..f7138e4c --- /dev/null +++ b/test/sqlsrv/srv_223_sqlsrv_fetch_absolute.phpt @@ -0,0 +1,59 @@ +--TEST-- +sqlsrv_fetch() with SQLSRV_SCROLL_ABSOLUTE using out of range offset +--SKIPIF-- +--FILE-- + 0) + { + echo $message . "\n"; + } + die( print_r( sqlsrv_errors(), true)); +} + +function test() +{ + require_once("autonomous_setup.php"); + + // Connect + $conn = sqlsrv_connect($serverName, $connectionInfo); + if( !$conn ) { print_errors(); } + + // Prepare the statement + $sql = "select name from sys.databases"; + $stmt = sqlsrv_prepare( $conn, $sql, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED) ); + if( $stmt === false ) { print_errors(); } + sqlsrv_execute($stmt); + + // Get row count + $row_count = sqlsrv_num_rows( $stmt ); + if ($row_count == 0) { print_errors("There should be at least one row!\n"); } + + sqlsrv_execute($stmt); + $row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_FIRST); + $field = sqlsrv_get_field($stmt, 0); + if (! $field) { print_errors(); } + + $row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_LAST); + $field = sqlsrv_get_field($stmt, 0); + if (! $field) { print_errors(); } + + // this should return false + $row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_ABSOLUTE, $row_count); + if ($row) { print_errors("This should return false!"); } + $field = sqlsrv_get_field($stmt, 0); + if ($field !== false) { print_errors("This should have resulted in error!"); } + + sqlsrv_free_stmt( $stmt); + sqlsrv_close($conn); +} + +test(); + +print "Done"; +?> + +--EXPECT-- +Done diff --git a/test/sqlsrv/srv_230_sqlsrv_buffered_numeric_types.phpt b/test/sqlsrv/srv_230_sqlsrv_buffered_numeric_types.phpt new file mode 100644 index 00000000..895c463e --- /dev/null +++ b/test/sqlsrv/srv_230_sqlsrv_buffered_numeric_types.phpt @@ -0,0 +1,208 @@ +--TEST-- +Read numeric types from SQLSRV with buffered query. +--DESCRIPTION-- +Test numeric conversion (number to string, string to number) functionality for buffered queries with SQLSRV. +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password", "CharacterSet" => "UTF-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo); +if( $conn === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$sample = 1234567890.1234; +$sample1 = -1234567890.1234; +$sample2 = 1; +$sample3 = -1; +$sample4 = 0.5; +$sample5 = -0.55; + +$query = 'CREATE TABLE #TESTTABLE (a float(53), neg_a float(53), b int, neg_b int, c decimal(16, 6), neg_c decimal(16, 6), zero int, zerof float(53), zerod decimal(16,6))'; + +// Create table +$stmt = sqlsrv_query( $conn, $query ); +if( $stmt === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$query = 'INSERT INTO #TESTTABLE (a, neg_a, b, neg_b, c, neg_c, zero, zerof, zerod) VALUES(?, ?, ?, ?, ?, ?, 0, 0, 0)'; +$params = array($sample, $sample1, $sample2, $sample3, $sample4, $sample5); + +$stmt = sqlsrv_query( $conn, $query, $params ); +if( $stmt === false ) { + die( print_r( sqlsrv_errors(), true )); +} +$params = array($sample4, $sample5, 100000, -1234567, $sample, $sample1); +$stmt = sqlsrv_query( $conn, $query, $params ); +if( $stmt === false ) { + die( print_r( sqlsrv_errors(), true )); +} + + + + +$query = 'SELECT TOP 2 * FROM #TESTTABLE'; +$stmt = sqlsrv_query( $conn, $query, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED)); +if(!$stmt) +{ + echo "Statement could not be prepared.\n"; + die( print_r( sqlsrv_errors(),true)); +} +sqlsrv_execute( $stmt ); + +$array = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ); +var_dump($array); +$array = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ); +var_dump($array); + + + + +$numFields = sqlsrv_num_fields( $stmt ); +$meta = sqlsrv_field_metadata( $stmt ); +$rowcount = sqlsrv_num_rows( $stmt); +for($i = 0; $i < $rowcount; $i++){ + sqlsrv_fetch( $stmt, SQLSRV_SCROLL_ABSOLUTE, $i ); + for($j = 0; $j < $numFields; $j++) { + $name = $meta[$j]["Name"]; + print("\ncolumn: $name\n"); + $field = sqlsrv_get_field( $stmt, $j, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR) ); + var_dump($field); + if ($meta[$j]["Type"] == SQLSRV_SQLTYPE_INT) + { + $field = sqlsrv_get_field( $stmt, $j, SQLSRV_PHPTYPE_INT ); + var_dump($field); + } + $field = sqlsrv_get_field( $stmt, $j, SQLSRV_PHPTYPE_FLOAT); + var_dump($field); + } +} + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +array(9) { + [0]=> + float(1234567890.1234) + [1]=> + float(-1234567890.1234) + [2]=> + int(1) + [3]=> + int(-1) + [4]=> + string(7) ".500000" + [5]=> + string(8) "-.550000" + [6]=> + int(0) + [7]=> + float(0) + [8]=> + string(7) ".000000" +} +array(9) { + [0]=> + float(0.5) + [1]=> + float(-0.55) + [2]=> + int(100000) + [3]=> + int(-1234567) + [4]=> + string(17) "1234567890.123400" + [5]=> + string(18) "-1234567890.123400" + [6]=> + int(0) + [7]=> + float(0) + [8]=> + string(7) ".000000" +} + +column: a +string(15) "1234567890.1234" +float(1234567890.1234) + +column: neg_a +string(16) "-1234567890.1234" +float(-1234567890.1234) + +column: b +string(1) "1" +int(1) +float(1) + +column: neg_b +string(2) "-1" +int(-1) +float(-1) + +column: c +string(7) ".500000" +float(0.5) + +column: neg_c +string(8) "-.550000" +float(-0.55) + +column: zero +string(1) "0" +int(0) +float(0) + +column: zerof +string(1) "0" +float(0) + +column: zerod +string(7) ".000000" +float(0) + +column: a +string(3) "0.5" +float(0.5) + +column: neg_a +string(5) "-0.55" +float(-0.55) + +column: b +string(6) "100000" +int(100000) +float(100000) + +column: neg_b +string(8) "-1234567" +int(-1234567) +float(-1234567) + +column: c +string(17) "1234567890.123400" +float(1234567890.1234) + +column: neg_c +string(18) "-1234567890.123400" +float(-1234567890.1234) + +column: zero +string(1) "0" +int(0) +float(0) + +column: zerof +string(1) "0" +float(0) + +column: zerod +string(7) ".000000" +float(0) + diff --git a/test/sqlsrv/srv_231_string_truncation_varchar_max.phpt b/test/sqlsrv/srv_231_string_truncation_varchar_max.phpt new file mode 100644 index 00000000..96d2a9b8 --- /dev/null +++ b/test/sqlsrv/srv_231_string_truncation_varchar_max.phpt @@ -0,0 +1,189 @@ +--TEST-- +GitHub issue #231 - String truncation when binding varchar(max) +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password); +$conn = sqlsrv_connect($serverName, $connectionInfo); +if( $conn === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$tableName = "#testDataTypes_GH231"; +$columnNames = array( "c1","c2" ); + +for ($k = 1; $k <= 8; $k++) +{ + $sqlType = GetSqlType($k); + $dataType = "[$columnNames[0]] int, [$columnNames[1]] $sqlType"; + + $sql = "CREATE TABLE [$tableName] ($dataType)"; + $stmt1 = sqlsrv_query($conn, $sql); + sqlsrv_free_stmt($stmt1); + + $sql = "INSERT INTO [$tableName] ($columnNames[0], $columnNames[1]) VALUES (?, ?)"; + $data = GetData($k); + $phpType = GetPhpType($k); + $driverType = GetDriverType($k, strlen($data)); + + $params = array($k, array($data, SQLSRV_PARAM_IN, $phpType, $driverType)); + $stmt2 = sqlsrv_prepare($conn, $sql, $params); + sqlsrv_execute($stmt2); + sqlsrv_free_stmt($stmt2); + + ExecProc($conn, $tableName, $columnNames, $k, $data, $sqlType); + + $stmt3 = sqlsrv_query($conn, "DROP TABLE [$tableName]"); + sqlsrv_free_stmt($stmt3); +} + +sqlsrv_close($conn); + + +function ExecProc($conn, $tableName, $columnNames, $k, $data, $sqlType) +{ + $spArgs = "@p1 int, @p2 $sqlType OUTPUT"; + $spCode = "SET @p2 = ( SELECT c2 FROM $tableName WHERE c1 = @p1 )"; + $procName = "testBindOutSp"; + + $stmt1 = sqlsrv_query($conn, "CREATE PROC [$procName] ($spArgs) AS BEGIN $spCode END"); + sqlsrv_free_stmt($stmt1); + + echo "\nData Type: ".$sqlType." binding as \n"; + + $direction = SQLSRV_PARAM_OUT; + echo "Output parameter: \t"; + CallProc($conn, $procName, $k, $direction, $data); + + $direction = SQLSRV_PARAM_INOUT; + echo "InOut parameter: \t"; + CallProc($conn, $procName, $k, $direction, $data); + + $stmt2 = sqlsrv_query($conn, "DROP PROC [$procName]"); + sqlsrv_free_stmt($stmt2); +} + +function CallProc($conn, $procName, $k, $direction, $data) +{ + $driverType = GetDriverType($k, strlen($data)); + $callArgs = "?, ?"; + + // Data to initialize $callResult variable. This variable should be shorter than inserted data in the table + $initData = "ShortString"; + $callResult = $initData; + + // Make sure not to specify the PHP type + $params = array( array( $k, SQLSRV_PARAM_IN ), + array( &$callResult, $direction, null, $driverType )); + $stmt = sqlsrv_query($conn, "{ CALL [$procName] ($callArgs)}", $params); + if($stmt === false) { + die( print_r( sqlsrv_errors(), true )); + } + + // $callResult should be updated to the value in the table + $matched = ($callResult === $data); + if ($matched) + echo "data matched!\n"; + else + echo "failed!\n"; + + sqlsrv_free_stmt($stmt); +} + +function GetData($k) +{ + $data = "LongStringForTesting"; + if ($k == 8) { + $data = "The quick brown fox jumps over the lazy dog0123456789"; + } + + return $data; +} + +function GetPhpType($k) +{ + $phpType = SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR); + if ($k == 7) { + $phpType = SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY); + } + + return $phpType; +} + +function GetSqlType($k) +{ + switch ($k) + { + case 1: return ("char(512)"); + case 2: return ("varchar(512)"); + case 3: return ("varchar(max)"); + case 4: return ("nchar(512)"); + case 5: return ("nvarchar(512)"); + case 6: return ("nvarchar(max)"); + case 7: return ("varbinary(max)"); + case 8: return ("xml"); + default: break; + } + return ("udt"); +} + +function GetDriverType($k, $dataSize) +{ + switch ($k) + { + case 1: return (SQLSRV_SQLTYPE_CHAR($dataSize)); + case 2: return (SQLSRV_SQLTYPE_VARCHAR($dataSize)); + case 3: return (SQLSRV_SQLTYPE_VARCHAR('max')); + case 4: return (SQLSRV_SQLTYPE_NCHAR($dataSize)); + case 5: return (SQLSRV_SQLTYPE_NVARCHAR($dataSize)); + case 6: return (SQLSRV_SQLTYPE_NVARCHAR('max')); + case 7: return (SQLSRV_SQLTYPE_VARBINARY('max')); + case 8: return (SQLSRV_SQLTYPE_XML); + + default: break; + } + return (SQLSRV_SQLTYPE_UDT); +} + +?> + +--EXPECT-- + +Data Type: char(512) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: varchar(512) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: varchar(max) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: nchar(512) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: nvarchar(512) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: nvarchar(max) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: varbinary(max) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: xml binding as +Output parameter: data matched! +InOut parameter: data matched! + diff --git a/test/sqlsrv/srv_308_empty_output_param.phpt b/test/sqlsrv/srv_308_empty_output_param.phpt new file mode 100644 index 00000000..638acbea --- /dev/null +++ b/test/sqlsrv/srv_308_empty_output_param.phpt @@ -0,0 +1,47 @@ +--TEST-- +GitHub issue #308 - empty string set to output parameter on stored procedure +--DESCRIPTION-- +A variation of the example in GitHub issue 308. A NULL value returned as output parameter will remain as NULL. +--SKIPIF-- +--FILE-- + +--EXPECT-- +OUT value: NULL +Done diff --git a/test/sqlsrv/tools.inc b/test/sqlsrv/tools.inc new file mode 100644 index 00000000..7336f3c7 --- /dev/null +++ b/test/sqlsrv/tools.inc @@ -0,0 +1,928 @@ +')"; + $pos = strpos($data, $str); + $tmp = substr($data, 0, $pos + 2); + } + + $data = $tmp; + + if (IsDataUnicode($colType, $data)) // this includes unicode data type and XML data that is in Unicode + { // N'data' + $data = substr($data, 2, strlen($data) - 3); + } + else if (IsLiteral($colType)) + { // 'data' + $data = substr($data, 1, strlen($data) - 2); + } + else if (IsBinary($colType)) + { // 0xdata + $data = substr($data, 2); + } + + return (trim($data)); +} + +function IsStreamable($type) +{ + switch ($type) + { + case _SQL_CHAR: // char + return true; + case _SQL_WCHAR: // nchar + return true; + case _SQL_VARCHAR: // varchar + return true; + case _SQL_WVARCHAR: // nvarchar + return true; + case _SQL_LONGVARCHAR: // text + return true; + case _SQL_WLONGVARCHAR: // ntext + return true; + case _SQL_BINARY: // binary + return true; + case _SQL_VARBINARY: // varbinary + return true; + case _SQL_LONGVARBINARY: // image + return true; + case _SQL_SS_XML: // xml + return true; + default: + break; + } + return (false); +} + +function IsNumeric($type) +{ + switch ($type) + { + case _SQL_INTEGER : // int + return true; + case _SQL_TINYINT : // tinyint + return true; + case _SQL_SMALLINT : // smallint + return true; + case _SQL_BIGINT : // bigint + return true; + case _SQL_BIT : // bit + return true; + case _SQL_FLOAT : // float + return true; + case _SQL_REAL : // real + return true; + case _SQL_DECIMAL : // decimal + return true; + case _SQL_NUMERIC : // numeric, money, smallmoney + return true; + default: break; + } + return (false); +} + +function IsChar($type) +{ + switch ($type) + { + case _SQL_WCHAR: // nchar + return true; + case _SQL_VARCHAR: // varchar + return true; + case _SQL_WVARCHAR: // nvarchar + return true; + case _SQL_LONGVARCHAR: // text + return true; + case _SQL_WLONGVARCHAR: // ntext + return true; + case _SQL_SS_XML: // xml + return true; + default: + break; + } + return (false); +} + +function IsBinary($type) +{ + switch ($type) + { + case _SQL_BINARY: // binary + return true; + case _SQL_VARBINARY: // varbinary + return true; + case _SQL_LONGVARBINARY: // image + return true; + default: + break; + } + return (false); +} + +function IsDateTime($type) +{ + switch ($type) + { + case _SQL_TYPE_TIMESTAMP: // datetime, smalldatetime + return true; + case _SQL_TYPE_DATE: // date + return true; + case _SQL_SS_TIME2: // time + return true; + case _SQL_SS_TIMESTAMPOFFSET: // datetimeoffset + return true; + default: + break; + } + return (false); +} + +function IsDataUnicode($colType, $data) +{ + if (IsUnicode($colType)) + return true; + + // This input string may be an XML string in unicode (i.e. // N'...') + $letterN = 'N'; + $index = strpos($data, $letterN); + + // Note the use of ===. Simply == would not work as expected + // because the position of letterN 'N' may be the 0th (first) character + // and strpos will return false if not found. + if ($index === 0) { + return true; + } + + return false; +} + +function IsUnicode($type) +{ + switch ($type) + { + case _SQL_WCHAR: // nchar + return true; + case _SQL_WVARCHAR: // nvarchar + return true; + case _SQL_WLONGVARCHAR: // ntext + return true; + default: + break; + } + return (false); +} + +function IsXml($type) +{ + return ($type == _SQL_SS_XML); +} + +function IsUpdatable($colName) +{ + $pos = strpos($colName, "_"); + $type = substr($colName, $pos + 1); + + return (strcasecmp($type, "timestamp") != 0); +} + +function IsLiteral($type) +{ + switch ($type) + { + case _SQL_CHAR: // char + return true; + case _SQL_WCHAR: // nchar + return true; + case _SQL_VARCHAR: // varchar + return true; + case _SQL_WVARCHAR: // nvarchar + return true; + case _SQL_LONGVARCHAR: // text + return true; + case _SQL_WLONGVARCHAR: // ntext + return true; + case _SQL_GUID: // uniqueidentifier + return true; + case _SQL_TYPE_TIMESTAMP: // datetime, smalldatetime + return true; + case _SQL_TYPE_DATE: // date + return true; + case _SQL_SS_TIME2: // time + return true; + case _SQL_SS_TIMESTAMPOFFSET: // datetimeoffset + return true; + case _SQL_SS_XML: // xml + return true; + default: + break; + } + return (false); +} + +?> \ No newline at end of file From c47bf8fa9822352abc448eef28498110234c08a6 Mon Sep 17 00:00:00 2001 From: marioperezesteso Date: Tue, 28 Mar 2017 07:49:28 +0000 Subject: [PATCH 23/43] Fix typos in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9d15705..7d994b5c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ SQL Server Team ## Take our survey -Thank you for taking time to take our Febraury survey. Let us know how we are doing and how you use PHP by taking our March pulse survey: +Thank you for taking time to take our February survey. Let us know how we are doing and how you use PHP by taking our March pulse survey: @@ -37,7 +37,7 @@ Thank you for taking time to take our Febraury survey. Let us know how we are do ## Announcements -**March 7, 2017**: We are trilled to announce that the early technical preview for SQLSRV and PDO_SQLSRV drivers is now available, both drivers have been built and tested on El Capitan (OS X 10.11). For complete list of changes please visit [4.1.7-preview release notes](https://github.com/Microsoft/msphpsql/releases/tag/v4.1.7-preview). +**March 7, 2017**: We are thrilled to announce that the early technical preview for SQLSRV and PDO_SQLSRV drivers is now available, both drivers have been built and tested on El Capitan (OS X 10.11). For complete list of changes please visit [4.1.7-preview release notes](https://github.com/Microsoft/msphpsql/releases/tag/v4.1.7-preview). Please visit the [blog][blog] for more announcements. From 288103507e3b24cba4c0e44a96fe8aadf1136e6e Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Tue, 4 Apr 2017 14:33:21 -0700 Subject: [PATCH 24/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d994b5c..99fdb607 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ SQL Server Team Thank you for taking time to take our February survey. Let us know how we are doing and how you use PHP by taking our March pulse survey: - + ### Status of Most Recent Builds | AppVeyor (Windows) |Travis CI (Linux) | Coverage Status From 0b85db5baceeffe6f64f6a61f70d2f122543609d Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Fri, 7 Apr 2017 14:43:59 -0700 Subject: [PATCH 25/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99fdb607..11bbbb0c 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,7 @@ Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apac **Mac OS X** - brew tap microsoft/msodbcsql https://github.com/Microsoft/homebrew-msodbcsql + brew tap microsoft/mssql-preview https://github.com/Microsoft/homebrew-mssql-preview brew update brew install unixodbc brew install msodbcsql From 59bbe4ae9459ef9f981f4d5a4834db3904509a07 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Thu, 13 Apr 2017 17:55:17 -0700 Subject: [PATCH 26/43] Create issue_template.md --- issue_template.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 issue_template.md diff --git a/issue_template.md b/issue_template.md new file mode 100644 index 00000000..ccd9d97c --- /dev/null +++ b/issue_template.md @@ -0,0 +1,12 @@ + + +- Driver Version: +- OS Version and Distro: +- Can you connect to SQL Server via sqlcmd: +- Environment details: e.g. PHP version, thread safe (TS) or non-thread safe (NTS), 32-bit &/or 64-bit: +- Table schema + +Steps to Reproduce: + +1. +2. From 3ca687d28fabf26ca4a536187c53e07adc2b5737 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Mon, 17 Apr 2017 13:07:01 -0700 Subject: [PATCH 27/43] Update issue_template.md --- issue_template.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/issue_template.md b/issue_template.md index ccd9d97c..0ed83a6a 100644 --- a/issue_template.md +++ b/issue_template.md @@ -5,8 +5,13 @@ - Can you connect to SQL Server via sqlcmd: - Environment details: e.g. PHP version, thread safe (TS) or non-thread safe (NTS), 32-bit &/or 64-bit: - Table schema +- Expected output + Steps to Reproduce: 1. 2. + +Repro Script + From 0c8a79546e8f0b3e30e9912fd7ba212ea4d6dc32 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Mon, 17 Apr 2017 13:08:39 -0700 Subject: [PATCH 28/43] Update issue_template.md --- issue_template.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/issue_template.md b/issue_template.md index 0ed83a6a..d5ee8d2f 100644 --- a/issue_template.md +++ b/issue_template.md @@ -14,4 +14,7 @@ Steps to Reproduce: 2. Repro Script + From a1daf297471fa0c3fe2452cab236d6be80d4d506 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Mon, 17 Apr 2017 13:08:50 -0700 Subject: [PATCH 29/43] Update issue_template.md --- issue_template.md | 1 + 1 file changed, 1 insertion(+) diff --git a/issue_template.md b/issue_template.md index d5ee8d2f..b65eebab 100644 --- a/issue_template.md +++ b/issue_template.md @@ -14,6 +14,7 @@ Steps to Reproduce: 2. Repro Script + From 16b6496882809c601029a6e66b67ded181eabaea Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Fri, 5 May 2017 14:52:27 -0700 Subject: [PATCH 30/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11bbbb0c..8668dc4b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ SQL Server Team Thank you for taking time to take our February survey. Let us know how we are doing and how you use PHP by taking our March pulse survey: - + ### Status of Most Recent Builds | AppVeyor (Windows) |Travis CI (Linux) | Coverage Status From 3080948883279c4b8b07ced962c915010f6af189 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Wed, 10 May 2017 09:40:39 -0700 Subject: [PATCH 31/43] Update CHANGELOG.md --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 093fb151..58e6f102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## Windows/Linux/MAC 4.1.9-preview - 2017-05-08 +- Updated documentation for Readme regarding instructions for Linux and MAC +- Updated PECL release packages. Here is the list of updates: +### Added +- Azure Active Directory Authentication with ActiveDirectoryPassword and SqlPassword +- [PDO::ATTR_EMULATE_PREPARES](https://github.com/Microsoft/msphpsql/wiki/PDO::ATTR_EMULATE_PREPARES) now supports binding parameters of different encodings + +### Fixed +- Fixed output parameter returning garbage when the parameter is initialized to a type that is different from the output type ([issue #378](https://github.com/Microsoft/msphpsql/issues/378)). + +#### PDO_SQLSRV only +- Fixed incorrectly binding of unicode parameter when emulate prepare is on and the encoding is set at the statement level ([issue #92](https://github.com/Microsoft/msphpsql/issues/92)). +- Fixed binary column binding when emulate prepare is on ([issue #140](https://github.com/Microsoft/msphpsql/issues/140)). + +### Known Issues +- User defined data types and SQL_VARIANT ([issue #127](https://github.com/Microsoft/msphpsql/issues/127)). +- When pooling is enabled in Linux or MAC + - unixODBC 2.3.1 (Linux) and unixODBC 2.3.4 (MAC) might not return proper diagnostics information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Connection-Pooling-on-Linux-and-Mac) + ## Windows/Linux/MAC 4.1.8-preview - 2017-04-10 Updated documentation for Readme regarding instructions for Linux and MAC Updated PECL release packages. Here is the list of updates: From 09304037792b7c6db51de4f8e76ea8ce680d72c0 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Wed, 10 May 2017 09:40:57 -0700 Subject: [PATCH 32/43] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58e6f102..f214721e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) - Updated PECL release packages. Here is the list of updates: ### Added - Azure Active Directory Authentication with ActiveDirectoryPassword and SqlPassword -- [PDO::ATTR_EMULATE_PREPARES](https://github.com/Microsoft/msphpsql/wiki/PDO::ATTR_EMULATE_PREPARES) now supports binding parameters of different encodings ### Fixed - Fixed output parameter returning garbage when the parameter is initialized to a type that is different from the output type ([issue #378](https://github.com/Microsoft/msphpsql/issues/378)). From 433f4b1122bd5a99d699a717a46ef5e4e0491df0 Mon Sep 17 00:00:00 2001 From: Andrea Lam Date: Wed, 10 May 2017 12:44:15 -0700 Subject: [PATCH 33/43] Update tutorial links in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a27c4861..c8a511e7 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ Thank you for taking time to take our February survey. Let us know how we are do ## Get Started -* [**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) -* [**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) +* [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/windows) * [**Docker**](https://hub.docker.com/r/lbosqmsft/mssql-php-msphpsql/) From a789e0864b88d9fb20cb1e445f54d58971581a6a Mon Sep 17 00:00:00 2001 From: yitam Date: Thu, 1 Jun 2017 17:51:37 -0700 Subject: [PATCH 34/43] new tests from autonomous --- ...rv_buffered_result_set_extended_ascii.phpt | 46 ++++++++++++++++++ test/sqlsrv/srv_007_login_timeout.phpt | 23 +++++++++ test/sqlsrv/srv_009_connect_app_unicode.phpt | 35 ++++++++++++++ test/sqlsrv/srv_019_char.phpt | 47 +++++++++++++++++++ test/sqlsrv/srv_020_unicode_strings.phpt | 46 ++++++++++++++++++ ...21_extended_ascii_strings_fetch_array.phpt | 47 +++++++++++++++++++ ...022_extended_ascii_strings_num_fields.phpt | 44 +++++++++++++++++ test/sqlsrv/srv_033_binary_unicode.phpt | 47 +++++++++++++++++++ 8 files changed, 335 insertions(+) create mode 100644 test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt create mode 100644 test/sqlsrv/srv_007_login_timeout.phpt create mode 100644 test/sqlsrv/srv_009_connect_app_unicode.phpt create mode 100644 test/sqlsrv/srv_019_char.phpt create mode 100644 test/sqlsrv/srv_020_unicode_strings.phpt create mode 100644 test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt create mode 100644 test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt create mode 100644 test/sqlsrv/srv_033_binary_unicode.phpt diff --git a/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt b/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt new file mode 100644 index 00000000..5a4679a1 --- /dev/null +++ b/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt @@ -0,0 +1,46 @@ +--TEST-- +Fetch array using a scrollable buffered cursor with connection CharacterSet utf-8 +--SKIPIF-- + +--FILE-- +"buffered")); +if( $stmt === false) + die( print_r(sqlsrv_errors(), true)); + +// Fetch +$row = sqlsrv_fetch_array($stmt); +var_dump($row); + +// Close connection +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +array(2) { + [0]=> + string(16) "Aå_Ð×Æ×Ø_B" + ["ID"]=> + string(16) "Aå_Ð×Æ×Ø_B" +} +Done diff --git a/test/sqlsrv/srv_007_login_timeout.phpt b/test/sqlsrv/srv_007_login_timeout.phpt new file mode 100644 index 00000000..d33e4911 --- /dev/null +++ b/test/sqlsrv/srv_007_login_timeout.phpt @@ -0,0 +1,23 @@ +--TEST-- +False connection with LoginTimeout option +--SKIPIF-- + +--FILE-- + 8)); + +$t1 = round(microtime(true)); + +echo "Connection attempt time: " . ($t1 - $t0) . " [sec]\n"; + +print "Done"; +?> + +--EXPECTREGEX-- +Connection attempt time: [7-9] \[sec\] +Done diff --git a/test/sqlsrv/srv_009_connect_app_unicode.phpt b/test/sqlsrv/srv_009_connect_app_unicode.phpt new file mode 100644 index 00000000..05c71bc1 --- /dev/null +++ b/test/sqlsrv/srv_009_connect_app_unicode.phpt @@ -0,0 +1,35 @@ +--TEST-- +Connection option APP unicode +--SKIPIF-- + +--FILE-- +$appName, "CharacterSet"=>"utf-8")); +if( !$conn ) { die( print_r( sqlsrv_errors(), true)); } + +// Query and print out +$sql = "select APP_NAME()"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Fetch the data +while( sqlsrv_fetch($stmt) ) { + echo sqlsrv_get_field($stmt, 0)."\n"; +} + +// Close connection +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +APP_PoP_银河系 +Done diff --git a/test/sqlsrv/srv_019_char.phpt b/test/sqlsrv/srv_019_char.phpt new file mode 100644 index 00000000..a5ad4d49 --- /dev/null +++ b/test/sqlsrv/srv_019_char.phpt @@ -0,0 +1,47 @@ +--TEST-- +Character data type with non-ASCII characters +--DESCRIPTION-- +For read/write non-ASCII characters on Windows and Linux the buffer +size may be different, 1 byte on Windows if 1252 code page +and 2 bytes on Linux if UTF-8 is used. +Example: the string Ð×Æ×Ø is 10 bytes on Linux, 5 bytes on Windows. +--SKIPIF-- + +--FILE-- + + +--EXPECT-- +I+PHP +Ð×Æ×Ø +Done diff --git a/test/sqlsrv/srv_020_unicode_strings.phpt b/test/sqlsrv/srv_020_unicode_strings.phpt new file mode 100644 index 00000000..944e9e78 --- /dev/null +++ b/test/sqlsrv/srv_020_unicode_strings.phpt @@ -0,0 +1,46 @@ +--TEST-- +Query non-ascii strings: sqlsrv_fetch_array +--SKIPIF-- + +--FILE-- + + +--EXPECT-- +John Doe 30 +Nhoj Eoduard -3 +Joe I❤PHP 2016 +Done diff --git a/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt b/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt new file mode 100644 index 00000000..bc2d2e03 --- /dev/null +++ b/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt @@ -0,0 +1,47 @@ +--TEST-- +Extended ASCII column names: sqlsrv_fetch_array() +--SKIPIF-- + +--FILE-- + + +--EXPECT-- +Paris 1911 +London 2012 +Berlin 1990 diff --git a/test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt b/test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt new file mode 100644 index 00000000..1d610bfb --- /dev/null +++ b/test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt @@ -0,0 +1,44 @@ +--TEST-- +Query with extended ASCII column names, sqlsrv_num_fields() +--SKIPIF-- + +--FILE-- + + +--EXPECT-- +2 +Done diff --git a/test/sqlsrv/srv_033_binary_unicode.phpt b/test/sqlsrv/srv_033_binary_unicode.phpt new file mode 100644 index 00000000..3944d6f0 --- /dev/null +++ b/test/sqlsrv/srv_033_binary_unicode.phpt @@ -0,0 +1,47 @@ +--TEST-- +Insert binary HEX data into nvarchar field then read it back using sqlsrv_get_field() +--SKIPIF-- + +--FILE-- + + +--EXPECT-- +I❤PHP +Done From ab0e50a4fb6033d50f4c80d9906a9c66629ed102 Mon Sep 17 00:00:00 2001 From: yitam Date: Fri, 2 Jun 2017 08:31:23 -0700 Subject: [PATCH 35/43] added more descriptions --- test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt | 5 ++--- test/sqlsrv/srv_007_login_timeout.phpt | 2 ++ test/sqlsrv/srv_009_connect_app_unicode.phpt | 2 ++ test/sqlsrv/srv_020_unicode_strings.phpt | 2 ++ test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt | 3 +++ test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt | 3 +++ test/sqlsrv/srv_033_binary_unicode.phpt | 4 +++- 7 files changed, 17 insertions(+), 4 deletions(-) diff --git a/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt b/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt index 5a4679a1..7ffa8aa5 100644 --- a/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt +++ b/test/sqlsrv/sqlsrv_buffered_result_set_extended_ascii.phpt @@ -1,5 +1,5 @@ --TEST-- -Fetch array using a scrollable buffered cursor with connection CharacterSet utf-8 +Fetch array of extended ASCII data using a scrollable buffered cursor --SKIPIF-- --FILE-- @@ -16,12 +16,11 @@ $query = "CREATE TABLE $tableName (ID CHAR(10))"; $stmt = sqlsrv_query($conn, $query); // Insert data -$query = "INSERT INTO $tableName VALUES ('Aå_Ð×Æ×Ø_B')"; // Ð×Æ×Ø +$query = "INSERT INTO $tableName VALUES ('Aå_Ð×Æ×Ø_B')"; $stmt = sqlsrv_query($conn, $query) ?: die(print_r( sqlsrv_errors(), true)); // Fetch data $query = "SELECT * FROM $tableName"; -// $stmt = sqlsrv_query($conn, $query) $stmt = sqlsrv_query($conn, $query, [], array("Scrollable"=>"buffered")); if( $stmt === false) die( print_r(sqlsrv_errors(), true)); diff --git a/test/sqlsrv/srv_007_login_timeout.phpt b/test/sqlsrv/srv_007_login_timeout.phpt index d33e4911..5ac33c9f 100644 --- a/test/sqlsrv/srv_007_login_timeout.phpt +++ b/test/sqlsrv/srv_007_login_timeout.phpt @@ -1,5 +1,7 @@ --TEST-- False connection with LoginTimeout option +--DESCRIPTION-- +Intentionally provide an invalid server name and set LoginTimeout. Verify the time elapsed. --SKIPIF-- --FILE-- diff --git a/test/sqlsrv/srv_009_connect_app_unicode.phpt b/test/sqlsrv/srv_009_connect_app_unicode.phpt index 05c71bc1..0e9691f8 100644 --- a/test/sqlsrv/srv_009_connect_app_unicode.phpt +++ b/test/sqlsrv/srv_009_connect_app_unicode.phpt @@ -1,5 +1,7 @@ --TEST-- Connection option APP unicode +--DESCRIPTION-- +Connect using a Unicode App name. Once connected, fetch APP_NAME. --SKIPIF-- --FILE-- diff --git a/test/sqlsrv/srv_020_unicode_strings.phpt b/test/sqlsrv/srv_020_unicode_strings.phpt index 944e9e78..505dc50f 100644 --- a/test/sqlsrv/srv_020_unicode_strings.phpt +++ b/test/sqlsrv/srv_020_unicode_strings.phpt @@ -1,5 +1,7 @@ --TEST-- Query non-ascii strings: sqlsrv_fetch_array +--DESCRIPTION-- +Test sqlsrv_fetch_array() with non-ASCII values --SKIPIF-- --FILE-- diff --git a/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt b/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt index bc2d2e03..bccb470d 100644 --- a/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt +++ b/test/sqlsrv/srv_021_extended_ascii_strings_fetch_array.phpt @@ -1,5 +1,8 @@ --TEST-- Extended ASCII column names: sqlsrv_fetch_array() +--DESCRIPTION-- +Create a temporary table with column names that contain extended ASCII characters. Fetch data afterwards +using sqlsrv_fetch_array. --SKIPIF-- --FILE-- diff --git a/test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt b/test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt index 1d610bfb..bc6a90ab 100644 --- a/test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt +++ b/test/sqlsrv/srv_022_extended_ascii_strings_num_fields.phpt @@ -1,5 +1,8 @@ --TEST-- Query with extended ASCII column names, sqlsrv_num_fields() +--DESCRIPTION-- +Create a temporary table with column names that contain extended ASCII characters. Get number of fields +using sqlsrv_num_fields. --SKIPIF-- --FILE-- diff --git a/test/sqlsrv/srv_033_binary_unicode.phpt b/test/sqlsrv/srv_033_binary_unicode.phpt index 3944d6f0..dc165bd9 100644 --- a/test/sqlsrv/srv_033_binary_unicode.phpt +++ b/test/sqlsrv/srv_033_binary_unicode.phpt @@ -1,5 +1,7 @@ --TEST-- -Insert binary HEX data into nvarchar field then read it back using sqlsrv_get_field() +Insert binary HEX data then fetch it back as string +--DESCRIPTION-- +Insert binary HEX data into an nvarchar field then read it back as UTF-8 string using sqlsrv_get_field() --SKIPIF-- --FILE-- From 9c834a7bb144e0ac0228de6566a163d7c255d834 Mon Sep 17 00:00:00 2001 From: yitam Date: Fri, 2 Jun 2017 11:03:31 -0700 Subject: [PATCH 36/43] add a numeric test --- .../sqlsrv/sqlsrv_custom_numeric_formats.phpt | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 test/sqlsrv/sqlsrv_custom_numeric_formats.phpt diff --git a/test/sqlsrv/sqlsrv_custom_numeric_formats.phpt b/test/sqlsrv/sqlsrv_custom_numeric_formats.phpt new file mode 100644 index 00000000..16f1561c --- /dev/null +++ b/test/sqlsrv/sqlsrv_custom_numeric_formats.phpt @@ -0,0 +1,70 @@ +--TEST-- +Custom numeric formats for the SQL FORMAT function +--SKIPIF-- +--FILE-- + + +--EXPECT-- +2.00 CAD +Free +199.00 CAD +2,147,483,646.00 CAD +Array +( + [0] => $56.05 + [1] => 20.00 +) +Array +( + [0] => $79.99 + [1] => 0.50 +) +Array +( + [0] => $5.00 + [1] => 0.00 +) +Array +( + [0] => $7.99 + [1] => 48.00 +) +Done From 29cadfc4eafff248e7e9e2cbdc0a24b8573cdac9 Mon Sep 17 00:00:00 2001 From: yitam Date: Fri, 2 Jun 2017 12:57:10 -0700 Subject: [PATCH 37/43] added a skipif for the numeric test --- .../sqlsrv/sqlsrv_custom_numeric_formats.phpt | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/test/sqlsrv/sqlsrv_custom_numeric_formats.phpt b/test/sqlsrv/sqlsrv_custom_numeric_formats.phpt index 16f1561c..42e7a2f4 100644 --- a/test/sqlsrv/sqlsrv_custom_numeric_formats.phpt +++ b/test/sqlsrv/sqlsrv_custom_numeric_formats.phpt @@ -1,6 +1,22 @@ --TEST-- Custom numeric formats for the SQL FORMAT function --SKIPIF-- + --FILE-- Date: Fri, 2 Jun 2017 15:34:48 -0700 Subject: [PATCH 38/43] added exclude --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 84f1bc8b..96b417e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ script: - docker exec client bash -c 'for f in ./test/pdo_sqlsrv/*.out; do ls $f 2>/dev/null; cat $f 2>/dev/null; done || true' - docker exec client python ./test/setup/cleanup_dbs.py -dbname $SQLSRV_DBNAME - docker exec client python ./test/setup/cleanup_dbs.py -dbname $PDOSQLSRV_DBNAME - - docker exec client coveralls -e ./source/shared/ --gcov-options '\-lp' + - docker exec client coveralls -e ./source/shared/ -e ./test/ --gcov-options '\-lp' - docker stop client - docker ps -a From b1356936959def9937f0586d8050e9778f1a0b57 Mon Sep 17 00:00:00 2001 From: yitam Date: Fri, 2 Jun 2017 15:45:20 -0700 Subject: [PATCH 39/43] removed exclude --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 96b417e3..84f1bc8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ script: - docker exec client bash -c 'for f in ./test/pdo_sqlsrv/*.out; do ls $f 2>/dev/null; cat $f 2>/dev/null; done || true' - docker exec client python ./test/setup/cleanup_dbs.py -dbname $SQLSRV_DBNAME - docker exec client python ./test/setup/cleanup_dbs.py -dbname $PDOSQLSRV_DBNAME - - docker exec client coveralls -e ./source/shared/ -e ./test/ --gcov-options '\-lp' + - docker exec client coveralls -e ./source/shared/ --gcov-options '\-lp' - docker stop client - docker ps -a From 8e51fff8461a1c5691463110da991def06c5c8c8 Mon Sep 17 00:00:00 2001 From: yitam Date: Fri, 2 Jun 2017 15:59:44 -0700 Subject: [PATCH 40/43] added bvt - msdn sample codes --- test/bvt/pdo_sqlsrv/break.inc | 60 +++ test/bvt/pdo_sqlsrv/break_pdo.php | 82 ++++ test/bvt/pdo_sqlsrv/connect.inc | 8 + .../msdn_pdoStatement_bindColumn.phpt | 24 ++ .../msdn_pdoStatement_bindParam.phpt | 38 ++ .../msdn_pdoStatement_bindParam_2.phpt | 22 ++ .../msdn_pdoStatement_bindParam_3.phpt | 23 ++ .../msdn_pdoStatement_bindValue.phpt | 38 ++ .../msdn_pdoStatement_closeCursor.phpt | 50 +++ .../msdn_pdoStatement_columnCount.phpt | 34 ++ .../msdn_pdoStatement_debugDumpParams.phpt | 42 +++ .../msdn_pdoStatement_errorCode.phpt | 20 + .../msdn_pdoStatement_errorInfo.phpt | 24 ++ .../pdo_sqlsrv/msdn_pdoStatement_execute.phpt | 54 +++ .../pdo_sqlsrv/msdn_pdoStatement_fetch.phpt | 152 ++++++++ .../msdn_pdoStatement_fetchAll.phpt | 156 ++++++++ .../msdn_pdoStatement_fetchColumn.phpt | 23 ++ .../msdn_pdoStatement_fetchObject.phpt | 19 + ...n_pdoStatement_fetchObject_class_name.phpt | 36 ++ .../msdn_pdoStatement_getColumnMeta.phpt | 43 +++ .../msdn_pdoStatement_nextRowset.phpt | 350 ++++++++++++++++++ .../msdn_pdoStatement_rowCount.phpt | 45 +++ .../msdn_pdoStatement_setAttribute.phpt | 25 ++ .../msdn_pdoStatement_setFetchMode.phpt | 93 +++++ .../pdo_sqlsrv/msdn_pdo_beginTransaction.phpt | 30 ++ test/bvt/pdo_sqlsrv/msdn_pdo_construct.phpt | 138 +++++++ test/bvt/pdo_sqlsrv/msdn_pdo_construct_2.phpt | 139 +++++++ .../pdo_sqlsrv/msdn_pdo_construct_MARS.phpt | 148 ++++++++ test/bvt/pdo_sqlsrv/msdn_pdo_errorCode.phpt | 18 + test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt | 26 ++ test/bvt/pdo_sqlsrv/msdn_pdo_exec.phpt | 24 ++ .../bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt | 39 ++ .../msdn_pdo_getAvailableDrivers.phpt | 13 + test/bvt/pdo_sqlsrv/msdn_pdo_prepare.phpt | 36 ++ test/bvt/pdo_sqlsrv/msdn_pdo_prepare_2.phpt | 89 +++++ .../pdo_sqlsrv/msdn_pdo_prepare_cursor.phpt | 91 +++++ test/bvt/pdo_sqlsrv/msdn_pdo_query.phpt | 135 +++++++ test/bvt/pdo_sqlsrv/msdn_pdo_quote.phpt | 42 +++ .../bvt/pdo_sqlsrv/msdn_pdo_setAttribute.phpt | 29 ++ .../msdn_pdo_setAttribute_direct_query.phpt | 42 +++ .../pdo_bindParam_inout_double.phpt | 61 +++ .../pdo_bindParam_inout_integer.phpt | 57 +++ .../pdo_bindParam_inout_string.phpt | 60 +++ test/bvt/pdo_sqlsrv/readme.txt | 4 + test/bvt/sqlsrv/break.inc | 106 ++++++ test/bvt/sqlsrv/break.php | 88 +++++ test/bvt/sqlsrv/connect.inc | 31 ++ .../sqlsrv/msdn_sqlsrv_begin_transaction.phpt | 74 ++++ .../msdn_sqlsrv_begin_transaction_2.phpt | 78 ++++ test/bvt/sqlsrv/msdn_sqlsrv_cancel.phpt | 49 +++ test/bvt/sqlsrv/msdn_sqlsrv_cancel_1.phpt | 80 ++++ test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt | 35 ++ test/bvt/sqlsrv/msdn_sqlsrv_close.phpt | 35 ++ test/bvt/sqlsrv/msdn_sqlsrv_commit.phpt | 71 ++++ test/bvt/sqlsrv/msdn_sqlsrv_configure.phpt | 69 ++++ test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt | 176 +++++++++ test/bvt/sqlsrv/msdn_sqlsrv_connect.phpt | 39 ++ test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt | 29 ++ ...dn_sqlsrv_connect_returnDateAsStrings.phpt | 23 ++ ...srv_connect_returnDatesAsStrings_utf8.phpt | 38 ++ test/bvt/sqlsrv/msdn_sqlsrv_connect_utf8.phpt | 83 +++++ test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt | 40 ++ test/bvt/sqlsrv/msdn_sqlsrv_errors.phpt | 39 ++ test/bvt/sqlsrv/msdn_sqlsrv_execute.phpt | 63 ++++ .../sqlsrv/msdn_sqlsrv_execute_datetime.phpt | 134 +++++++ .../sqlsrv/msdn_sqlsrv_execute_string.phpt | 152 ++++++++ test/bvt/sqlsrv/msdn_sqlsrv_fetch.phpt | 63 ++++ test/bvt/sqlsrv/msdn_sqlsrv_fetch_array.phpt | 39 ++ .../bvt/sqlsrv/msdn_sqlsrv_fetch_array_2.phpt | 61 +++ test/bvt/sqlsrv/msdn_sqlsrv_fetch_object.phpt | 39 ++ .../sqlsrv/msdn_sqlsrv_fetch_object_2.phpt | 108 ++++++ .../sqlsrv/msdn_sqlsrv_field_metadata.phpt | 38 ++ test/bvt/sqlsrv/msdn_sqlsrv_free_stmt.phpt | 42 +++ test/bvt/sqlsrv/msdn_sqlsrv_get_field.phpt | 63 ++++ .../sqlsrv/msdn_sqlsrv_get_field_stream.phpt | 73 ++++ .../msdn_sqlsrv_get_field_stream_binary.php | 48 +++ .../msdn_sqlsrv_get_field_stream_char.phpt | 66 ++++ .../msdn_sqlsrv_get_field_string_utf8.phpt | 40 ++ test/bvt/sqlsrv/msdn_sqlsrv_has_rows.phpt | 24 ++ test/bvt/sqlsrv/msdn_sqlsrv_next_result.phpt | 151 ++++++++ .../bvt/sqlsrv/msdn_sqlsrv_next_result_2.phpt | 111 ++++++ test/bvt/sqlsrv/msdn_sqlsrv_num_fields.phpt | 45 +++ test/bvt/sqlsrv/msdn_sqlsrv_num_rows.phpt | 27 ++ test/bvt/sqlsrv/msdn_sqlsrv_num_rows_2.phpt | 36 ++ test/bvt/sqlsrv/msdn_sqlsrv_prepare.phpt | 60 +++ test/bvt/sqlsrv/msdn_sqlsrv_prepare_2.phpt | 107 ++++++ .../msdn_sqlsrv_prepare_bind_param.phpt | 88 +++++ ...dn_sqlsrv_prepare_scrollable_buffered.phpt | 37 ++ test/bvt/sqlsrv/msdn_sqlsrv_query.phpt | 55 +++ test/bvt/sqlsrv/msdn_sqlsrv_query_2.phpt | 44 +++ .../sqlsrv/msdn_sqlsrv_query_bind_param.phpt | 63 ++++ .../sqlsrv/msdn_sqlsrv_query_param_inout.phpt | 88 +++++ .../sqlsrv/msdn_sqlsrv_query_param_out.phpt | 82 ++++ .../sqlsrv/msdn_sqlsrv_query_scrollable.phpt | 74 ++++ ...msdn_sqlsrv_query_scrollable_buffered.phpt | 66 ++++ .../bvt/sqlsrv/msdn_sqlsrv_query_sqltype.phpt | 87 +++++ test/bvt/sqlsrv/msdn_sqlsrv_query_stream.phpt | 56 +++ test/bvt/sqlsrv/msdn_sqlsrv_query_utf8.phpt | 91 +++++ test/bvt/sqlsrv/msdn_sqlsrv_rollback.phpt | 70 ++++ .../bvt/sqlsrv/msdn_sqlsrv_rows_affected.phpt | 59 +++ .../sqlsrv/msdn_sqlsrv_send_stream_data.phpt | 48 +++ ...stream_data_no_sendStreamParamsAtExec.phpt | 67 ++++ test/bvt/sqlsrv/msdn_sqlsrv_server_info.phpt | 35 ++ test/bvt/sqlsrv/readme.txt | 3 + test/bvt/sqlsrv/sqlsrv_param_inout.phpt | 67 ++++ 105 files changed, 6635 insertions(+) create mode 100644 test/bvt/pdo_sqlsrv/break.inc create mode 100644 test/bvt/pdo_sqlsrv/break_pdo.php create mode 100644 test/bvt/pdo_sqlsrv/connect.inc create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindColumn.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam_2.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam_3.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindValue.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_closeCursor.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_columnCount.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_debugDumpParams.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorCode.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_execute.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetch.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchAll.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchColumn.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchObject.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchObject_class_name.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_getColumnMeta.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_nextRowset.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_rowCount.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_setAttribute.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdoStatement_setFetchMode.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_beginTransaction.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_construct.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_construct_2.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_construct_MARS.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_errorCode.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_exec.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_getAvailableDrivers.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_prepare.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_prepare_2.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_prepare_cursor.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_query.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_quote.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute.phpt create mode 100644 test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute_direct_query.phpt create mode 100644 test/bvt/pdo_sqlsrv/pdo_bindParam_inout_double.phpt create mode 100644 test/bvt/pdo_sqlsrv/pdo_bindParam_inout_integer.phpt create mode 100644 test/bvt/pdo_sqlsrv/pdo_bindParam_inout_string.phpt create mode 100644 test/bvt/pdo_sqlsrv/readme.txt create mode 100644 test/bvt/sqlsrv/break.inc create mode 100644 test/bvt/sqlsrv/break.php create mode 100644 test/bvt/sqlsrv/connect.inc create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_begin_transaction.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_begin_transaction_2.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_cancel.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_cancel_1.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_close.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_commit.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_configure.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_connect.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDateAsStrings.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDatesAsStrings_utf8.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_connect_utf8.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_errors.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_execute.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_execute_datetime.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_execute_string.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_fetch.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_fetch_array.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_fetch_array_2.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_fetch_object.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_fetch_object_2.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_field_metadata.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_free_stmt.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_get_field.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_binary.php create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_char.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_get_field_string_utf8.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_has_rows.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_next_result.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_next_result_2.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_num_fields.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_num_rows.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_num_rows_2.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_prepare.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_prepare_2.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_prepare_bind_param.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_prepare_scrollable_buffered.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_query.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_query_2.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_query_bind_param.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_query_param_inout.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_query_param_out.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable_buffered.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_query_sqltype.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_query_stream.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_query_utf8.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_rollback.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_rows_affected.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_send_stream_data.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_send_stream_data_no_sendStreamParamsAtExec.phpt create mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_server_info.phpt create mode 100644 test/bvt/sqlsrv/readme.txt create mode 100644 test/bvt/sqlsrv/sqlsrv_param_inout.phpt diff --git a/test/bvt/pdo_sqlsrv/break.inc b/test/bvt/pdo_sqlsrv/break.inc new file mode 100644 index 00000000..0540dac8 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/break.inc @@ -0,0 +1,60 @@ + \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/break_pdo.php b/test/bvt/pdo_sqlsrv/break_pdo.php new file mode 100644 index 00000000..6082387f --- /dev/null +++ b/test/bvt/pdo_sqlsrv/break_pdo.php @@ -0,0 +1,82 @@ +query( $sql ); + + // Insert data + $sql = "INSERT INTO $tableName1 VALUES ( ?, ? )"; + for( $t = 100; $t < 116; $t++ ) + { + $stmt = $conn->prepare( $sql ); + $ts = substr( sha1( $t ),0,5 ); + $params = array( $t,$ts ); + $stmt->execute( $params ); + } + + // Create table + $sql = "CREATE TABLE $tableName2 ( c1 INT, c2 VARCHAR(40) )"; + $stmt = $conn->query( $sql ); + + // Insert data + $sql = "INSERT INTO $tableName2 VALUES ( ?, ? )"; + for( $t = 200; $t < 209; $t++ ) + { + $stmt = $conn->prepare( $sql ); + $ts = substr( sha1( $t ),0,5 ); + $params = array( $t,$ts ); + $stmt->execute( $params ); + } + + $conn = null; +} + +// Break connection by getting the session ID and killing it. +// Note that breaking a connection and testing reconnection requires a +// TCP/IP protocol connection (as opposed to a Shared Memory protocol). +function BreakConnection( $conn, $conn_break ) +{ + $stmt1 = $conn->query( "SELECT @@SPID" ); + $obj = $stmt1->fetch( PDO::FETCH_NUM ); + $spid = $obj[0]; + + $stmt2 = $conn_break->query( "KILL ".$spid ); + sleep(1); +} + +// Remove any databases previously created by GenerateDatabase +function DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ) +{ + $conn = new PDO( "sqlsrv:server = $server ; ", $uid, $pwd ); + + $query="IF OBJECT_ID('tempdb.dbo.$tableName1', 'U') IS NOT NULL DROP TABLE tempdb.dbo.$tableName1"; + $stmt=$conn->query( $query ); + + $query="IF OBJECT_ID('tempdb.dbo.$tableName2', 'U') IS NOT NULL DROP TABLE tempdb.dbo.$tableName2"; + $stmt=$conn->query( $query ); +} + +DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ); +GenerateTables( $server, $uid, $pwd, $dbName, $tableName1, $tableName2 ); + +?> \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/connect.inc b/test/bvt/pdo_sqlsrv/connect.inc new file mode 100644 index 00000000..a6bd479f --- /dev/null +++ b/test/bvt/pdo_sqlsrv/connect.inc @@ -0,0 +1,8 @@ + + + diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindColumn.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindColumn.phpt new file mode 100644 index 00000000..3904d76e --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindColumn.phpt @@ -0,0 +1,24 @@ +--TEST-- +a variable bound to a column in a result set +--SKIPIF-- + +--FILE-- +prepare($query); +$stmt->execute(); + +$stmt->bindColumn('EmailPromotion', $emailpromo); +while ( $row = $stmt->fetch( PDO::FETCH_BOUND ) ){ + echo "EmailPromotion: $emailpromo\n"; +} + +//free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +EmailPromotion: 2 \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam.phpt new file mode 100644 index 00000000..11f22687 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam.phpt @@ -0,0 +1,38 @@ +--TEST-- +after a variable is bound, changing the value changes the value passed in the query +--SKIPIF-- + +--FILE-- +prepare("select * from Person.ContactType where name = ?"); +$stmt->bindParam(1, $contact); +$contact = "Owner"; +$stmt->execute(); + +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print "Result: "."$row[Name]\n\n"; +} + +$stmt = null; +$contact = "Sales Agent"; +$stmt = $conn->prepare("select * from Person.ContactType where name = :contact"); +$stmt->bindParam(':contact', $contact); +$contact = "Owner"; +$stmt->execute(); + +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print "Result: "."$row[Name]\n\n"; +} + +//free the statement and connection +$stmt = null; +$conn = null; +?> +--EXPECT-- +Result: Owner + +Result: Owner \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam_2.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam_2.phpt new file mode 100644 index 00000000..f514821e --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam_2.phpt @@ -0,0 +1,22 @@ +--TEST-- +accesses an output parameter +--SKIPIF-- + +--FILE-- +prepare("select ? = count(* ) from Person.Person"); +$stmt->bindParam( 1, $input1, PDO::PARAM_STR, 10); +$stmt->execute(); +echo "Result: ".$input1; + +//free the statement and connection +$conn = null; +$stmt = null; +?> +--EXPECT-- +Result: 19972 \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam_3.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam_3.phpt new file mode 100644 index 00000000..d211c29b --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindParam_3.phpt @@ -0,0 +1,23 @@ +--TEST-- +uses an input/output parameter +--SKIPIF-- + +--FILE-- +query("IF OBJECT_ID('dbo.sp_ReverseString', 'P') IS NOT NULL DROP PROCEDURE dbo.sp_ReverseString"); + $dbh->query("CREATE PROCEDURE dbo.sp_ReverseString @String as VARCHAR(2048) OUTPUT as SELECT @String = REVERSE(@String)"); + $stmt = $dbh->prepare("EXEC dbo.sp_ReverseString ?"); + $string = "123456789"; + $stmt->bindParam(1, $string, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 2048); + $stmt->execute(); + print "Result: ".$string; // Expect 987654321 + + //free the statement and connection + $stmt = null; + $dbh = null; +?> +--EXPECT-- +Result: 987654321 \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindValue.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindValue.phpt new file mode 100644 index 00000000..07e70b0e --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindValue.phpt @@ -0,0 +1,38 @@ +--TEST-- +after a value $contact is bound, changing the value does not change the value passed in the query +--SKIPIF-- + +--FILE-- +prepare("select * from Person.ContactType where name = ?"); +$stmt->bindValue(1, $contact); +$contact = "Owner"; +$stmt->execute(); + +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print "Name: $row[Name]\n\n"; +} + +$stmt = null; +$contact = "Sales Agent"; +$stmt = $conn->prepare("select * from Person.ContactType where name = :contact"); +$stmt->bindValue(':contact', $contact); +$contact = "Owner"; +$stmt->execute(); + +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print "Name: $row[Name]\n\n"; +} + +//free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +Name: Sales Agent + +Name: Sales Agent diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_closeCursor.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_closeCursor.phpt new file mode 100644 index 00000000..5cd8cd82 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_closeCursor.phpt @@ -0,0 +1,50 @@ +--TEST-- +closes the cursor +--SKIPIF-- + +--FILE-- + false ) ); + +$stmt = $conn->prepare('SELECT * FROM Person.ContactType'); + +$stmt2 = $conn->prepare('SELECT * FROM HumanResources.Department'); + +$stmt->execute(); + +$result = $stmt->fetch(); +print_r($result); + +$stmt->closeCursor(); + +$stmt2->execute(); +$result = $stmt2->fetch(); +print_r($result); + +//free the statements and connection +$stmt=null; +$stmt2=null; +$conn=null; +?> +--EXPECT-- +Array +( + [ContactTypeID] => 1 + [0] => 1 + [Name] => Accounting Manager + [1] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 + [2] => 2008-04-30 00:00:00.000 +) +Array +( + [DepartmentID] => 1 + [0] => 1 + [Name] => Engineering + [1] => Engineering + [GroupName] => Research and Development + [2] => Research and Development + [ModifiedDate] => 2008-04-30 00:00:00.000 + [3] => 2008-04-30 00:00:00.000 +) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_columnCount.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_columnCount.phpt new file mode 100644 index 00000000..ba3ca910 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_columnCount.phpt @@ -0,0 +1,34 @@ +--TEST-- +returns the number of columns in a result set for 3 queries +--SKIPIF-- + +--FILE-- +prepare( $query ); +print $stmt->columnCount(); // 0 +echo " columns in the result set\n"; + +echo "\n"; +$stmt->execute(); +print $stmt->columnCount(); +echo " columns in the result set\n"; + +echo "\n"; +$stmt = $conn->query("select * from HumanResources.Department"); +print $stmt->columnCount(); +echo " columns in the result set\n"; + +//free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +0 columns in the result set + +3 columns in the result set + +4 columns in the result set diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_debugDumpParams.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_debugDumpParams.phpt new file mode 100644 index 00000000..55c0b8fa --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_debugDumpParams.phpt @@ -0,0 +1,42 @@ +--TEST-- +displays a prepared statement +--SKIPIF-- + +--FILE-- +prepare("select * from Person.ContactType where name = :param"); +$stmt->execute(array($param)); +$stmt->debugDumpParams(); + +echo "\n\n"; + +$stmt = $conn->prepare("select * from Person.ContactType where name = ?"); +$stmt->execute(array($param)); +$stmt->debugDumpParams(); + +//free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +SQL: [52] select * from Person.ContactType where name = :param +Params: 1 +Key: Name: [6] :param +paramno=0 +name=[6] ":param" +is_param=1 +param_type=2 + + +SQL: [47] select * from Person.ContactType where name = ? +Params: 1 +Key: Position #0: +paramno=0 +name=[0] "" +is_param=1 +param_type=2 \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorCode.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorCode.phpt new file mode 100644 index 00000000..2a3e7501 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorCode.phpt @@ -0,0 +1,20 @@ +--TEST-- +shows the error code of a SQL query with a mispelled table +--SKIPIF-- + +--FILE-- +prepare('SELECT * FROM Person.Addressx'); + +$stmt->execute(); +echo "Error Code: "; +print $stmt->errorCode(); + +// free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +Error Code: 42S02 \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt new file mode 100644 index 00000000..6f8e4bc6 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt @@ -0,0 +1,24 @@ +--TEST-- +reports the error info of a SQL statement with a mispelled table name +--SKIPIF-- + +--FILE-- +prepare('SELECT * FROM Person.Addressx'); + +$stmt->execute(); +print_r ($stmt->errorInfo()); + +// free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECTREGEX-- +Array +\( + \[0\] => 42S02 + \[1\] => 208 + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid object name 'Person.Addressx'. +\) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_execute.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_execute.phpt new file mode 100644 index 00000000..b43bd1c7 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_execute.phpt @@ -0,0 +1,54 @@ +--TEST-- +Executes a statement +--SKIPIF-- + +--FILE-- +prepare( $query ); +$stmt->execute(); + +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print "$row[Name]\n"; +} + +echo "\n"; +$param = "Owner"; +$query = "select * from Person.ContactType where name = ?"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param)); + +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print "$row[Name]\n"; +} + +// free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +Accounting Manager +Assistant Sales Agent +Assistant Sales Representative +Coordinator Foreign Markets +Export Administrator +International Marketing Manager +Marketing Assistant +Marketing Manager +Marketing Representative +Order Administrator +Owner +Owner/Marketing Assistant +Product Manager +Purchasing Agent +Purchasing Manager +Regional Account Representative +Sales Agent +Sales Associate +Sales Manager +Sales Representative + +Owner \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetch.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetch.phpt new file mode 100644 index 00000000..f8cc9190 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetch.phpt @@ -0,0 +1,152 @@ +--TEST-- +fetch with all fetch styles +--SKIPIF-- + +--FILE-- +query( "select * from HumanResources.Department order by GroupName" ); + + class cc { + function __construct( $arg ) { + echo "$arg"; + } + + function __toString() { + return $this->DepartmentID . "; " . $this->Name . "; " . $this->GroupName; + } + } + + $stmt->setFetchMode(PDO::FETCH_CLASS, 'cc', array( "arg1 " )); + while ( $row = $stmt->fetch(PDO::FETCH_CLASS)) { + print($row . "\n"); + } + + print( "\n---------- PDO::FETCH_INTO -------------\n" ); + $stmt = $conn->query( "select * from HumanResources.Department order by GroupName" ); + $c_obj = new cc( '' ); + + $stmt->setFetchMode(PDO::FETCH_INTO, $c_obj); + while ( $row = $stmt->fetch(PDO::FETCH_INTO)) { + echo "$c_obj\n"; + } + + print( "\n---------- PDO::FETCH_ASSOC -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $result = $stmt->fetch( PDO::FETCH_ASSOC ); + print_r( $result ); + + print( "\n---------- PDO::FETCH_NUM -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $result = $stmt->fetch( PDO::FETCH_NUM ); + print_r ($result ); + + print( "\n---------- PDO::FETCH_BOTH -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $result = $stmt->fetch( PDO::FETCH_BOTH ); + print_r( $result ); + + print( "\n---------- PDO::FETCH_LAZY -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $result = $stmt->fetch( PDO::FETCH_LAZY ); + print_r( $result ); + + print( "\n---------- PDO::FETCH_OBJ -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $result = $stmt->fetch( PDO::FETCH_OBJ ); + print $result->Name; + print( "\n \n" ); + + print( "\n---------- PDO::FETCH_BOUND -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $stmt->bindColumn('Name', $name); + $result = $stmt->fetch( PDO::FETCH_BOUND ); + print $name; + print( "\n \n" ); + + //free the statement and connection + $stmt=null; + $conn=null; +?> +--EXPECT-- +---------- PDO::FETCH_CLASS ------------- +arg1 9; Human Resources; Executive General and Administration +arg1 10; Finance; Executive General and Administration +arg1 11; Information Services; Executive General and Administration +arg1 14; Facilities and Maintenance; Executive General and Administration +arg1 16; Executive; Executive General and Administration +arg1 15; Shipping and Receiving; Inventory Management +arg1 5; Purchasing; Inventory Management +arg1 7; Production; Manufacturing +arg1 8; Production Control; Manufacturing +arg1 12; Document Control; Quality Assurance +arg1 13; Quality Assurance; Quality Assurance +arg1 6; Research and Development; Research and Development +arg1 1; Engineering; Research and Development +arg1 2; Tool Design; Research and Development +arg1 3; Sales; Sales and Marketing +arg1 4; Marketing; Sales and Marketing + +---------- PDO::FETCH_INTO ------------- +9; Human Resources; Executive General and Administration +10; Finance; Executive General and Administration +11; Information Services; Executive General and Administration +14; Facilities and Maintenance; Executive General and Administration +16; Executive; Executive General and Administration +15; Shipping and Receiving; Inventory Management +5; Purchasing; Inventory Management +7; Production; Manufacturing +8; Production Control; Manufacturing +12; Document Control; Quality Assurance +13; Quality Assurance; Quality Assurance +6; Research and Development; Research and Development +1; Engineering; Research and Development +2; Tool Design; Research and Development +3; Sales; Sales and Marketing +4; Marketing; Sales and Marketing + +---------- PDO::FETCH_ASSOC ------------- +Array +( + [ContactTypeID] => 1 + [Name] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) + +---------- PDO::FETCH_NUM ------------- +Array +( + [0] => 1 + [1] => Accounting Manager + [2] => 2008-04-30 00:00:00.000 +) + +---------- PDO::FETCH_BOTH ------------- +Array +( + [ContactTypeID] => 1 + [0] => 1 + [Name] => Accounting Manager + [1] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 + [2] => 2008-04-30 00:00:00.000 +) + +---------- PDO::FETCH_LAZY ------------- +PDORow Object +( + [queryString] => select * from Person.ContactType where ContactTypeID < 5 + [ContactTypeID] => 1 + [Name] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) + +---------- PDO::FETCH_OBJ ------------- +Accounting Manager + + +---------- PDO::FETCH_BOUND ------------- +Accounting Manager \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchAll.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchAll.phpt new file mode 100644 index 00000000..1611942c --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchAll.phpt @@ -0,0 +1,156 @@ +--TEST-- +fetches the rows in a result set in an array +--SKIPIF-- + +--FILE-- +query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $result = $stmt->fetchAll(PDO::FETCH_BOTH); + print_r( $result ); + print "\n-----------\n"; + + print "-----------\n"; + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $result = $stmt->fetchAll(PDO::FETCH_NUM); + print_r( $result ); + print "\n-----------\n"; + + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $result = $stmt->fetchAll(PDO::FETCH_COLUMN, 1); + print_r( $result ); + print "\n-----------\n"; + + class cc { + function __construct( $arg ) { + echo "$arg\n"; + } + + function __toString() { + echo "To string\n"; + } + }; + + $stmt = $conn->query( 'SELECT TOP(2) * FROM Person.ContactType' ); + $all = $stmt->fetchAll( PDO::FETCH_CLASS, 'cc', array( 'Hi!' )); + var_dump( $all ); + + //free the statement and connection + $stmt=null; + $conn=null; +?> +--EXPECT-- +----------- +Array +( + [0] => Array + ( + [ContactTypeID] => 1 + [0] => 1 + [Name] => Accounting Manager + [1] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 + [2] => 2008-04-30 00:00:00.000 + ) + + [1] => Array + ( + [ContactTypeID] => 2 + [0] => 2 + [Name] => Assistant Sales Agent + [1] => Assistant Sales Agent + [ModifiedDate] => 2008-04-30 00:00:00.000 + [2] => 2008-04-30 00:00:00.000 + ) + + [2] => Array + ( + [ContactTypeID] => 3 + [0] => 3 + [Name] => Assistant Sales Representative + [1] => Assistant Sales Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 + [2] => 2008-04-30 00:00:00.000 + ) + + [3] => Array + ( + [ContactTypeID] => 4 + [0] => 4 + [Name] => Coordinator Foreign Markets + [1] => Coordinator Foreign Markets + [ModifiedDate] => 2008-04-30 00:00:00.000 + [2] => 2008-04-30 00:00:00.000 + ) + +) + +----------- +----------- +Array +( + [0] => Array + ( + [0] => 1 + [1] => Accounting Manager + [2] => 2008-04-30 00:00:00.000 + ) + + [1] => Array + ( + [0] => 2 + [1] => Assistant Sales Agent + [2] => 2008-04-30 00:00:00.000 + ) + + [2] => Array + ( + [0] => 3 + [1] => Assistant Sales Representative + [2] => 2008-04-30 00:00:00.000 + ) + + [3] => Array + ( + [0] => 4 + [1] => Coordinator Foreign Markets + [2] => 2008-04-30 00:00:00.000 + ) + +) + +----------- +Array +( + [0] => Accounting Manager + [1] => Assistant Sales Agent + [2] => Assistant Sales Representative + [3] => Coordinator Foreign Markets +) + +----------- +Hi! +Hi! +array(2) { + [0]=> + object(cc)#2 (3) { + ["ContactTypeID"]=> + string(1) "1" + ["Name"]=> + string(18) "Accounting Manager" + ["ModifiedDate"]=> + string(23) "2008-04-30 00:00:00.000" + } + [1]=> + object(cc)#4 (3) { + ["ContactTypeID"]=> + string(1) "2" + ["Name"]=> + string(21) "Assistant Sales Agent" + ["ModifiedDate"]=> + string(23) "2008-04-30 00:00:00.000" + } +} \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchColumn.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchColumn.phpt new file mode 100644 index 00000000..f2137200 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchColumn.phpt @@ -0,0 +1,23 @@ +--TEST-- +fetches a column in a row +--SKIPIF-- + +--FILE-- +query( "select * from Person.ContactType where ContactTypeID < 5 " ); + while ( $result = $stmt->fetchColumn(1)) { + print($result . "\n"); + } + + //free the statement and connection + $stmt=null; + $conn=null; +?> +--EXPECT-- +Accounting Manager +Assistant Sales Agent +Assistant Sales Representative +Coordinator Foreign Markets \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchObject.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchObject.phpt new file mode 100644 index 00000000..93158bb4 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchObject.phpt @@ -0,0 +1,19 @@ +--TEST-- +fetches the next row as an object +--SKIPIF-- + +--FILE-- +query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $result = $stmt->fetchObject(); + print $result->Name; + + //free the statement and connection + $stmt=null; + $conn=null; +?> +--EXPECT-- +Accounting Manager \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchObject_class_name.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchObject_class_name.phpt new file mode 100644 index 00000000..a1cd3820 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_fetchObject_class_name.phpt @@ -0,0 +1,36 @@ +--TEST-- +fetches the next row as an object of a user defined class +--SKIPIF-- + +--FILE-- +Name); + } + }// end of class + require('connect.inc'); + $conn = new PDO( "sqlsrv:server=$server ; Database = $databaseName", "$uid", "$pwd"); + + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID = 5 " ); + $contactTypes = $stmt->fetchObject('contactTypes'); + + //print the class properties + print $contactTypes->ContactTypeID."\n"; + print $contactTypes->upperCaseName()."\n"; + print $contactTypes->ModifiedDate; + + // close the database connection + $stmt=null; + $conn=null; +?> +--EXPECT-- +5 +EXPORT ADMINISTRATOR +2008-04-30 00:00:00.000 \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_getColumnMeta.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_getColumnMeta.phpt new file mode 100644 index 00000000..80ad5cdd --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_getColumnMeta.phpt @@ -0,0 +1,43 @@ +--TEST-- +retrieves metadata for a column +--SKIPIF-- + +--FILE-- +query("select * from Person.ContactType"); +$metadata = $stmt->getColumnMeta(2); +var_dump($metadata); + +print $metadata['sqlsrv:decl_type'] . "\n"; +print $metadata['native_type'] . "\n"; +print $metadata['name']; + +// free the statement and connection +$stmt = null; +$conn = null; +?> +--EXPECT-- +array(8) { + ["flags"]=> + int(0) + ["sqlsrv:decl_type"]=> + string(8) "datetime" + ["native_type"]=> + string(6) "string" + ["table"]=> + string(0) "" + ["pdo_type"]=> + int(2) + ["name"]=> + string(12) "ModifiedDate" + ["len"]=> + int(23) + ["precision"]=> + int(3) +} +datetime +string +ModifiedDate \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_nextRowset.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_nextRowset.phpt new file mode 100644 index 00000000..475083c7 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_nextRowset.phpt @@ -0,0 +1,350 @@ +--TEST-- +moves the cursor to the next result set and fetches results +--SKIPIF-- + +--FILE-- +query( $query1 . $query2); +$rowset1 = $stmt->fetchAll(); +$stmt->nextRowset(); +$rowset2 = $stmt->fetchAll(); +var_dump( $rowset1 ); +var_dump( $rowset2 ); + +// free the statement and connection +$stmt = null; +$conn = null; +?> +--EXPECT-- +array(26) { + [0]=> + array(2) { + ["AddressID"]=> + string(1) "5" + [0]=> + string(1) "5" + } + [1]=> + array(2) { + ["AddressID"]=> + string(2) "11" + [0]=> + string(2) "11" + } + [2]=> + array(2) { + ["AddressID"]=> + string(1) "6" + [0]=> + string(1) "6" + } + [3]=> + array(2) { + ["AddressID"]=> + string(2) "18" + [0]=> + string(2) "18" + } + [4]=> + array(2) { + ["AddressID"]=> + string(2) "40" + [0]=> + string(2) "40" + } + [5]=> + array(2) { + ["AddressID"]=> + string(1) "1" + [0]=> + string(1) "1" + } + [6]=> + array(2) { + ["AddressID"]=> + string(2) "10" + [0]=> + string(2) "10" + } + [7]=> + array(2) { + ["AddressID"]=> + string(3) "868" + [0]=> + string(3) "868" + } + [8]=> + array(2) { + ["AddressID"]=> + string(2) "19" + [0]=> + string(2) "19" + } + [9]=> + array(2) { + ["AddressID"]=> + string(2) "16" + [0]=> + string(2) "16" + } + [10]=> + array(2) { + ["AddressID"]=> + string(2) "15" + [0]=> + string(2) "15" + } + [11]=> + array(2) { + ["AddressID"]=> + string(2) "12" + [0]=> + string(2) "12" + } + [12]=> + array(2) { + ["AddressID"]=> + string(5) "18249" + [0]=> + string(5) "18249" + } + [13]=> + array(2) { + ["AddressID"]=> + string(1) "7" + [0]=> + string(1) "7" + } + [14]=> + array(2) { + ["AddressID"]=> + string(2) "21" + [0]=> + string(2) "21" + } + [15]=> + array(2) { + ["AddressID"]=> + string(1) "8" + [0]=> + string(1) "8" + } + [16]=> + array(2) { + ["AddressID"]=> + string(2) "17" + [0]=> + string(2) "17" + } + [17]=> + array(2) { + ["AddressID"]=> + string(2) "20" + [0]=> + string(2) "20" + } + [18]=> + array(2) { + ["AddressID"]=> + string(5) "26486" + [0]=> + string(5) "26486" + } + [19]=> + array(2) { + ["AddressID"]=> + string(1) "3" + [0]=> + string(1) "3" + } + [20]=> + array(2) { + ["AddressID"]=> + string(2) "14" + [0]=> + string(2) "14" + } + [21]=> + array(2) { + ["AddressID"]=> + string(1) "9" + [0]=> + string(1) "9" + } + [22]=> + array(2) { + ["AddressID"]=> + string(2) "13" + [0]=> + string(2) "13" + } + [23]=> + array(2) { + ["AddressID"]=> + string(1) "4" + [0]=> + string(1) "4" + } + [24]=> + array(2) { + ["AddressID"]=> + string(1) "2" + [0]=> + string(1) "2" + } + [25]=> + array(2) { + ["AddressID"]=> + string(3) "834" + [0]=> + string(3) "834" + } +} +array(20) { + [0]=> + array(2) { + ["Name"]=> + string(18) "Accounting Manager" + [0]=> + string(18) "Accounting Manager" + } + [1]=> + array(2) { + ["Name"]=> + string(21) "Assistant Sales Agent" + [0]=> + string(21) "Assistant Sales Agent" + } + [2]=> + array(2) { + ["Name"]=> + string(30) "Assistant Sales Representative" + [0]=> + string(30) "Assistant Sales Representative" + } + [3]=> + array(2) { + ["Name"]=> + string(27) "Coordinator Foreign Markets" + [0]=> + string(27) "Coordinator Foreign Markets" + } + [4]=> + array(2) { + ["Name"]=> + string(20) "Export Administrator" + [0]=> + string(20) "Export Administrator" + } + [5]=> + array(2) { + ["Name"]=> + string(31) "International Marketing Manager" + [0]=> + string(31) "International Marketing Manager" + } + [6]=> + array(2) { + ["Name"]=> + string(19) "Marketing Assistant" + [0]=> + string(19) "Marketing Assistant" + } + [7]=> + array(2) { + ["Name"]=> + string(17) "Marketing Manager" + [0]=> + string(17) "Marketing Manager" + } + [8]=> + array(2) { + ["Name"]=> + string(24) "Marketing Representative" + [0]=> + string(24) "Marketing Representative" + } + [9]=> + array(2) { + ["Name"]=> + string(19) "Order Administrator" + [0]=> + string(19) "Order Administrator" + } + [10]=> + array(2) { + ["Name"]=> + string(5) "Owner" + [0]=> + string(5) "Owner" + } + [11]=> + array(2) { + ["Name"]=> + string(25) "Owner/Marketing Assistant" + [0]=> + string(25) "Owner/Marketing Assistant" + } + [12]=> + array(2) { + ["Name"]=> + string(15) "Product Manager" + [0]=> + string(15) "Product Manager" + } + [13]=> + array(2) { + ["Name"]=> + string(16) "Purchasing Agent" + [0]=> + string(16) "Purchasing Agent" + } + [14]=> + array(2) { + ["Name"]=> + string(18) "Purchasing Manager" + [0]=> + string(18) "Purchasing Manager" + } + [15]=> + array(2) { + ["Name"]=> + string(31) "Regional Account Representative" + [0]=> + string(31) "Regional Account Representative" + } + [16]=> + array(2) { + ["Name"]=> + string(11) "Sales Agent" + [0]=> + string(11) "Sales Agent" + } + [17]=> + array(2) { + ["Name"]=> + string(15) "Sales Associate" + [0]=> + string(15) "Sales Associate" + } + [18]=> + array(2) { + ["Name"]=> + string(13) "Sales Manager" + [0]=> + string(13) "Sales Manager" + } + [19]=> + array(2) { + ["Name"]=> + string(20) "Sales Representative" + [0]=> + string(20) "Sales Representative" + } +} \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_rowCount.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_rowCount.phpt new file mode 100644 index 00000000..58c7d563 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_rowCount.phpt @@ -0,0 +1,45 @@ +--TEST-- +returns the number of rows added to a table; returns the number of rows in a result set when you specify a scrollable cursor +--SKIPIF-- + +--FILE-- +exec("CREAtE TABLE Table1(col1 VARCHAR(15), col2 VARCHAR(15)) "); + +$col1 = 'a'; +$col2 = 'b'; + +$query = "insert into Table1(col1, col2) values(?, ?)"; +$stmt = $conn->prepare( $query ); +$stmt->execute( array( $col1, $col2 ) ); +print $stmt->rowCount(); +print " rows affects."; + +echo "\n\n"; + +//revert the insert +$conn->exec("delete from Table1 where col1 = 'a' AND col2 = 'b'"); + +$conn->exec("DROP TABLE Table1 "); + +$conn = null; + +$conn = new PDO( "sqlsrv:server=$server ; Database = $databaseName", "$uid", "$pwd"); + +$query = "select * from Person.ContactType"; +$stmt = $conn->prepare( $query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL)); +$stmt->execute(); +print $stmt->rowCount(); +print " rows in result set."; + + +//free the statement and connection +$stmt = null; +$conn = null; +?> +--EXPECT-- +1 rows affects. + +20 rows in result set. \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_setAttribute.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_setAttribute.phpt new file mode 100644 index 00000000..e579d851 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_setAttribute.phpt @@ -0,0 +1,25 @@ +--TEST-- +sets the query timeout attribute +--SKIPIF-- + +--FILE-- +false ) ); + +$stmt = $conn->prepare('SELECT * FROM Person.ContactType'); + +echo "Attribute number for ATTR_CURSOR: ".$stmt->getAttribute( constant( "PDO::ATTR_CURSOR" ) ); + +echo "\n"; + +$stmt->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, 2); +echo "Attribute number for SQLSRV_ATTR_QUERY_TIMEOUT: ".$stmt->getAttribute( constant( "PDO::SQLSRV_ATTR_QUERY_TIMEOUT" ) ); + +//free the statement and connection +$stmt = null; +$conn = null; +?> +--EXPECT-- +Attribute number for ATTR_CURSOR: 0 +Attribute number for SQLSRV_ATTR_QUERY_TIMEOUT: 2 \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_setFetchMode.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_setFetchMode.phpt new file mode 100644 index 00000000..85419b5d --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_setFetchMode.phpt @@ -0,0 +1,93 @@ +--TEST-- +specifies the fetch mode before fetching +--SKIPIF-- + +--FILE-- +query( "select * from Person.ContactType where ContactTypeID < 5 " ); + while ( $row = $stmt1->fetch()) { + print($row['Name'] . "\n"); + } + print( "\n---------- PDO::FETCH_ASSOC -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $stmt->setFetchMode(PDO::FETCH_ASSOC); + $result = $stmt->fetch(); + print_r( $result ); + + print( "\n---------- PDO::FETCH_NUM -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $stmt->setFetchMode(PDO::FETCH_NUM); + $result = $stmt->fetch(); + print_r ($result ); + + print( "\n---------- PDO::FETCH_BOTH -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $stmt->setFetchMode(PDO::FETCH_BOTH); + $result = $stmt->fetch(); + print_r( $result ); + + print( "\n---------- PDO::FETCH_LAZY -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $stmt->setFetchMode(PDO::FETCH_LAZY); + $result = $stmt->fetch(); + print_r( $result ); + + print( "\n---------- PDO::FETCH_OBJ -------------\n" ); + $stmt = $conn->query( "select * from Person.ContactType where ContactTypeID < 5 " ); + $stmt->setFetchMode(PDO::FETCH_OBJ); + $result = $stmt->fetch(); + print $result->Name; + print( "\n \n" ); + + //free the statements and connection + $stmt1 = null; + $stmt = null; + $conn = null; +?> +--EXPECT-- +Accounting Manager +Assistant Sales Agent +Assistant Sales Representative +Coordinator Foreign Markets + +---------- PDO::FETCH_ASSOC ------------- +Array +( + [ContactTypeID] => 1 + [Name] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) + +---------- PDO::FETCH_NUM ------------- +Array +( + [0] => 1 + [1] => Accounting Manager + [2] => 2008-04-30 00:00:00.000 +) + +---------- PDO::FETCH_BOTH ------------- +Array +( + [ContactTypeID] => 1 + [0] => 1 + [Name] => Accounting Manager + [1] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 + [2] => 2008-04-30 00:00:00.000 +) + +---------- PDO::FETCH_LAZY ------------- +PDORow Object +( + [queryString] => select * from Person.ContactType where ContactTypeID < 5 + [ContactTypeID] => 1 + [Name] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) + +---------- PDO::FETCH_OBJ ------------- +Accounting Manager \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_beginTransaction.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_beginTransaction.phpt new file mode 100644 index 00000000..b28b09ce --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_beginTransaction.phpt @@ -0,0 +1,30 @@ +--TEST-- +starts a transaction, insert 2 rows and commit the transaction +--SKIPIF-- + +--FILE-- +exec("CREAtE TABLE Table1(col1 CHARACTER(1), col2 CHARACTER(1)) "); + + $conn->beginTransaction(); + $ret = $conn->exec("insert into Table1(col1, col2) values('a', 'b') "); + $ret = $conn->exec("insert into Table1(col1, col2) values('a', 'c') "); + + //revert the inserts + $ret = $conn->exec("delete from Table1 where col1 = 'a'"); + $conn->commit(); + // $conn->rollback(); + echo $ret." rows affected"; + + //drop the created temp table + $conn->exec("DROP TABLE Table1 "); + + //free statement and connection + $ret=NULL; + $conn=NULL; +?> +--EXPECT-- +2 rows affected \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_construct.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_construct.phpt new file mode 100644 index 00000000..ea1875ed --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_construct.phpt @@ -0,0 +1,138 @@ +--TEST-- +connect to a server and specify a database +--SKIPIF-- + +--FILE-- + true)); + + $query = 'SELECT * FROM Person.ContactType'; + $stmt = $c->query( $query ); + while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ) { + print_r( $row ); + } + $stmt=null; + $c = null; +?> +--EXPECT-- +Array +( + [ContactTypeID] => 1 + [Name] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 2 + [Name] => Assistant Sales Agent + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 3 + [Name] => Assistant Sales Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 4 + [Name] => Coordinator Foreign Markets + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 5 + [Name] => Export Administrator + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 6 + [Name] => International Marketing Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 7 + [Name] => Marketing Assistant + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 8 + [Name] => Marketing Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 9 + [Name] => Marketing Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 10 + [Name] => Order Administrator + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 11 + [Name] => Owner + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 12 + [Name] => Owner/Marketing Assistant + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 13 + [Name] => Product Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 14 + [Name] => Purchasing Agent + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 15 + [Name] => Purchasing Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 16 + [Name] => Regional Account Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 17 + [Name] => Sales Agent + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 18 + [Name] => Sales Associate + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 19 + [Name] => Sales Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 20 + [Name] => Sales Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_construct_2.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_construct_2.phpt new file mode 100644 index 00000000..c2032000 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_construct_2.phpt @@ -0,0 +1,139 @@ +--TEST-- +connect to a server, specifying the database later +--SKIPIF-- + +--FILE-- +exec( "USE $databaseName"); + $query = 'SELECT * FROM Person.ContactType'; + $stmt = $c->query( $query ); + while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print_r( $row ); + } + $stmt=null; + $c = null; +?> +--EXPECT-- +Array +( + [ContactTypeID] => 1 + [Name] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 2 + [Name] => Assistant Sales Agent + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 3 + [Name] => Assistant Sales Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 4 + [Name] => Coordinator Foreign Markets + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 5 + [Name] => Export Administrator + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 6 + [Name] => International Marketing Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 7 + [Name] => Marketing Assistant + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 8 + [Name] => Marketing Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 9 + [Name] => Marketing Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 10 + [Name] => Order Administrator + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 11 + [Name] => Owner + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 12 + [Name] => Owner/Marketing Assistant + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 13 + [Name] => Product Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 14 + [Name] => Purchasing Agent + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 15 + [Name] => Purchasing Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 16 + [Name] => Regional Account Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 17 + [Name] => Sales Agent + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 18 + [Name] => Sales Associate + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 19 + [Name] => Sales Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 20 + [Name] => Sales Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_construct_MARS.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_construct_MARS.phpt new file mode 100644 index 00000000..8dfd6762 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_construct_MARS.phpt @@ -0,0 +1,148 @@ +--TEST-- +connect to a server, setting MARS to false +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +} + +catch( PDOException $e ) { + die( "Error connecting to SQL Server" ); +} + +$query = 'SELECT * FROM Person.ContactType'; +$stmt = $conn->query( $query ); +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print_r( $row ); +} + +$stmt=null; +$conn = null; +?> +--EXPECT-- +Array +( + [ContactTypeID] => 1 + [Name] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 2 + [Name] => Assistant Sales Agent + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 3 + [Name] => Assistant Sales Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 4 + [Name] => Coordinator Foreign Markets + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 5 + [Name] => Export Administrator + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 6 + [Name] => International Marketing Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 7 + [Name] => Marketing Assistant + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 8 + [Name] => Marketing Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 9 + [Name] => Marketing Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 10 + [Name] => Order Administrator + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 11 + [Name] => Owner + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 12 + [Name] => Owner/Marketing Assistant + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 13 + [Name] => Product Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 14 + [Name] => Purchasing Agent + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 15 + [Name] => Purchasing Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 16 + [Name] => Regional Account Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 17 + [Name] => Sales Agent + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 18 + [Name] => Sales Associate + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 19 + [Name] => Sales Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 +) +Array +( + [ContactTypeID] => 20 + [Name] => Sales Representative + [ModifiedDate] => 2008-04-30 00:00:00.000 +) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_errorCode.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_errorCode.phpt new file mode 100644 index 00000000..59cfc751 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_errorCode.phpt @@ -0,0 +1,18 @@ +--TEST-- +reports the error code of querying a misspelled column +--SKIPIF-- + +--FILE-- +query($query); +print $conn->errorCode(); + +//free the connection +$conn=null; +?> +--EXPECT-- +42S22 \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt new file mode 100644 index 00000000..9252ad32 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt @@ -0,0 +1,26 @@ +--TEST-- +reports the error info of querying a misspelled column +--SKIPIF-- + +--FILE-- +query($query); +print $conn->errorCode(); +echo "\n"; +print_r ($conn->errorInfo()); + +//free the connection +$conn=null; +?> +--EXPECTREGEX-- +42S22 +Array +\( + \[0\] => 42S22 + \[1\] => 207 + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid column name 'Cityx'. +\) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_exec.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_exec.phpt new file mode 100644 index 00000000..76ede8e8 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_exec.phpt @@ -0,0 +1,24 @@ +--TEST-- +execute a delete and reports how many rows were deleted +--SKIPIF-- + +--FILE-- +exec("use tempdb"); + $c->exec("CREAtE TABLE Table1(col1 VARCHAR(100), col2 VARCHAR(100)) "); + + $ret = $c->exec("insert into Table1 values('xxxyy', 'yyxx')"); + $ret = $c->exec("delete from Table1 where col1 = 'xxxyy'"); + echo $ret," rows affected"; + + $c->exec("DROP TABLE Table1 "); + + //free the statement and connection + $ret=null; + $c=null; +?> +--EXPECT-- +1 rows affected \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt new file mode 100644 index 00000000..a134c797 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt @@ -0,0 +1,39 @@ +--TEST-- +shows the PDO::ATR_ERRMODE attribute, before and after changing its value +--SKIPIF-- + +--FILE-- +getAttribute( constant( "PDO::ATTR_$val" ) )); +} + +$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + +$attributes1 = array( "ERRMODE" ); +foreach ( $attributes1 as $val ) { + echo "PDO::ATTR_$val: "; + var_dump ($conn->getAttribute( constant( "PDO::ATTR_$val" ) )); +} + +// An example using PDO::ATTR_CLIENT_VERSION +print_r($conn->getAttribute( PDO::ATTR_CLIENT_VERSION )); + +//free the connection +$conn=null; +?> +--EXPECTREGEX-- +PDO::ATTR_ERRMODE: int\(0\) +PDO::ATTR_ERRMODE: int\(2\) +Array +\( + \[DriverDllName\] => msodbcsql[0-9]{2}\.dll|libmsodbcsql-[0-9]{2}\.[0-9]\.so\.[0-9]\.[0-9] + \[DriverODBCVer\] => [0-9]{1,2}\.[0-9]{1,2} + \[DriverVer\] => [0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4} + \[ExtensionVer\] => [0-9]\.[0-9]\.[0-9](\-((rc)|(preview))(\.[0-9]+)?)?(\+[0-9]+)? +\) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_getAvailableDrivers.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_getAvailableDrivers.phpt new file mode 100644 index 00000000..fbaaede6 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_getAvailableDrivers.phpt @@ -0,0 +1,13 @@ +--TEST-- +returns an array of PDO drivers +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Array +( + [0] => sqlsrv +) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_prepare.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_prepare.phpt new file mode 100644 index 00000000..486ef016 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_prepare.phpt @@ -0,0 +1,36 @@ +--TEST-- +prepares a statement with parameter markers and forward-only (server-side) cursor +--SKIPIF-- + +--FILE-- +exec("CREAtE TABLE Table1(col1 VARCHAR(100), col2 VARCHAR(100))"); + +$col1 = 'a'; +$col2 = 'b'; + +$query = "insert into Table1(col1, col2) values(?, ?)"; +$stmt = $conn->prepare( $query, array( PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY, PDO::SQLSRV_ATTR_QUERY_TIMEOUT => 1 ) ); +$stmt->execute( array( $col1, $col2 ) ); +print $stmt->rowCount(); +echo " row affected\n"; + +$query = "insert into Table1(col1, col2) values(:col1, :col2)"; +$stmt = $conn->prepare( $query, array( PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY, PDO::SQLSRV_ATTR_QUERY_TIMEOUT => 1 ) ); +$stmt->execute( array( ':col1' => $col1, ':col2' => $col2 ) ); +print $stmt->rowCount(); +echo " row affected\n"; + +// revert the inserts +$conn->exec("delete from Table1 where col1 = 'a' AND col2 = 'b'"); + +$conn->exec("DROP TABLE Table1 "); +$stmt = null; +$conn = null; +?> +--EXPECT-- +1 row affected +1 row affected \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_prepare_2.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_prepare_2.phpt new file mode 100644 index 00000000..639b5b7c --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_prepare_2.phpt @@ -0,0 +1,89 @@ +--TEST-- +prepares a statement with a client-side cursor +--SKIPIF-- + +--FILE-- +prepare( $query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL)); +$stmt->execute(); + +echo "\n"; + +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print "$row[Name]\n"; +} +echo "\n..\n"; + +$row = $stmt->fetch( PDO::FETCH_BOTH, PDO::FETCH_ORI_FIRST ); +print_r($row); + +$row = $stmt->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_REL, 1 ); +print "$row[Name]\n"; + +$row = $stmt->fetch( PDO::FETCH_NUM, PDO::FETCH_ORI_NEXT ); +print "$row[1]\n"; + +$row = $stmt->fetch( PDO::FETCH_NUM, PDO::FETCH_ORI_PRIOR ); +print "$row[1]..\n"; + +$row = $stmt->fetch( PDO::FETCH_NUM, PDO::FETCH_ORI_ABS, 0 ); +print_r($row); + +$row = $stmt->fetch( PDO::FETCH_NUM, PDO::FETCH_ORI_LAST ); +print_r($row); + +//free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +Accounting Manager +Assistant Sales Agent +Assistant Sales Representative +Coordinator Foreign Markets +Export Administrator +International Marketing Manager +Marketing Assistant +Marketing Manager +Marketing Representative +Order Administrator +Owner +Owner/Marketing Assistant +Product Manager +Purchasing Agent +Purchasing Manager +Regional Account Representative +Sales Agent +Sales Associate +Sales Manager +Sales Representative + +.. +Array +( + [ContactTypeID] => 1 + [0] => 1 + [Name] => Accounting Manager + [1] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 + [2] => 2008-04-30 00:00:00.000 +) +Assistant Sales Agent +Assistant Sales Representative +Assistant Sales Agent.. +Array +( + [0] => 1 + [1] => Accounting Manager + [2] => 2008-04-30 00:00:00.000 +) +Array +( + [0] => 20 + [1] => Sales Representative + [2] => 2008-04-30 00:00:00.000 +) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_prepare_cursor.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_prepare_cursor.phpt new file mode 100644 index 00000000..2bb2d4eb --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_prepare_cursor.phpt @@ -0,0 +1,91 @@ +--TEST-- +prepares a statement with a client-side cursor and specifies scroll type to buffered +--SKIPIF-- + +--FILE-- +prepare( $query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +print $stmt->rowCount(); + +echo "\n"; + +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print "$row[Name]\n"; +} +echo "\n..\n"; + +$row = $stmt->fetch( PDO::FETCH_BOTH, PDO::FETCH_ORI_FIRST ); +print_r($row); + +$row = $stmt->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_REL, 1 ); +print "$row[Name]\n"; + +$row = $stmt->fetch( PDO::FETCH_NUM, PDO::FETCH_ORI_NEXT ); +print "$row[1]\n"; + +$row = $stmt->fetch( PDO::FETCH_NUM, PDO::FETCH_ORI_PRIOR ); +print "$row[1]..\n"; + +$row = $stmt->fetch( PDO::FETCH_NUM, PDO::FETCH_ORI_ABS, 0 ); +print_r($row); + +$row = $stmt->fetch( PDO::FETCH_NUM, PDO::FETCH_ORI_LAST ); +print_r($row); + +//free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +20 +Accounting Manager +Assistant Sales Agent +Assistant Sales Representative +Coordinator Foreign Markets +Export Administrator +International Marketing Manager +Marketing Assistant +Marketing Manager +Marketing Representative +Order Administrator +Owner +Owner/Marketing Assistant +Product Manager +Purchasing Agent +Purchasing Manager +Regional Account Representative +Sales Agent +Sales Associate +Sales Manager +Sales Representative + +.. +Array +( + [ContactTypeID] => 1 + [0] => 1 + [Name] => Accounting Manager + [1] => Accounting Manager + [ModifiedDate] => 2008-04-30 00:00:00.000 + [2] => 2008-04-30 00:00:00.000 +) +Assistant Sales Agent +Assistant Sales Representative +Assistant Sales Agent.. +Array +( + [0] => 1 + [1] => Accounting Manager + [2] => 2008-04-30 00:00:00.000 +) +Array +( + [0] => 20 + [1] => Sales Representative + [2] => 2008-04-30 00:00:00.000 +) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_query.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_query.phpt new file mode 100644 index 00000000..77029053 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_query.phpt @@ -0,0 +1,135 @@ +--TEST-- +default query; query for a column; query with a new class; query into an existing class +--SKIPIF-- + +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$conn->setAttribute( PDO::SQLSRV_ATTR_QUERY_TIMEOUT, 1 ); + +$query = 'select * from Person.ContactType'; + +// simple query +$stmt = $conn->query( $query ); +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print_r( $row['Name'] ."\n" ); +} + +echo "\n........ query for a column ............\n"; + +// query for one column +$stmt = $conn->query( $query, PDO::FETCH_COLUMN, 1 ); +while ( $row = $stmt->fetch() ){ + echo "$row\n"; +} + +echo "\n........ query with a new class ............\n"; +$query = 'select * from HumanResources.Department order by GroupName'; +// query with a class +class cc { + function __construct( $arg ) { + echo "$arg"; + } + + function __toString() { + return $this->DepartmentID . "; " . $this->Name . "; " . $this->GroupName; + } +} + +$stmt = $conn->query( $query, PDO::FETCH_CLASS, 'cc', array( "arg1 " )); + +while ( $row = $stmt->fetch() ){ + echo "$row\n"; +} + +echo "\n........ query into an existing class ............\n"; +$c_obj = new cc( '' ); +$stmt = $conn->query( $query, PDO::FETCH_INTO, $c_obj ); +while ( $stmt->fetch() ){ + echo "$c_obj\n"; +} + +$stmt = null; +$conn=null; +?> +--EXPECT-- +Accounting Manager +Assistant Sales Agent +Assistant Sales Representative +Coordinator Foreign Markets +Export Administrator +International Marketing Manager +Marketing Assistant +Marketing Manager +Marketing Representative +Order Administrator +Owner +Owner/Marketing Assistant +Product Manager +Purchasing Agent +Purchasing Manager +Regional Account Representative +Sales Agent +Sales Associate +Sales Manager +Sales Representative + +........ query for a column ............ +Accounting Manager +Assistant Sales Agent +Assistant Sales Representative +Coordinator Foreign Markets +Export Administrator +International Marketing Manager +Marketing Assistant +Marketing Manager +Marketing Representative +Order Administrator +Owner +Owner/Marketing Assistant +Product Manager +Purchasing Agent +Purchasing Manager +Regional Account Representative +Sales Agent +Sales Associate +Sales Manager +Sales Representative + +........ query with a new class ............ +arg1 9; Human Resources; Executive General and Administration +arg1 10; Finance; Executive General and Administration +arg1 11; Information Services; Executive General and Administration +arg1 14; Facilities and Maintenance; Executive General and Administration +arg1 16; Executive; Executive General and Administration +arg1 15; Shipping and Receiving; Inventory Management +arg1 5; Purchasing; Inventory Management +arg1 7; Production; Manufacturing +arg1 8; Production Control; Manufacturing +arg1 12; Document Control; Quality Assurance +arg1 13; Quality Assurance; Quality Assurance +arg1 6; Research and Development; Research and Development +arg1 1; Engineering; Research and Development +arg1 2; Tool Design; Research and Development +arg1 3; Sales; Sales and Marketing +arg1 4; Marketing; Sales and Marketing + +........ query into an existing class ............ +9; Human Resources; Executive General and Administration +10; Finance; Executive General and Administration +11; Information Services; Executive General and Administration +14; Facilities and Maintenance; Executive General and Administration +16; Executive; Executive General and Administration +15; Shipping and Receiving; Inventory Management +5; Purchasing; Inventory Management +7; Production; Manufacturing +8; Production Control; Manufacturing +12; Document Control; Quality Assurance +13; Quality Assurance; Quality Assurance +6; Research and Development; Research and Development +1; Engineering; Research and Development +2; Tool Design; Research and Development +3; Sales; Sales and Marketing +4; Marketing; Sales and Marketing \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_quote.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_quote.phpt new file mode 100644 index 00000000..73ffc429 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_quote.phpt @@ -0,0 +1,42 @@ +--TEST-- +insert with quoted parameters +--SKIPIF-- + +--FILE-- +exec("CREAtE TABLE Table1(col1 VARCHAR(15), col2 VARCHAR(15)) "); + +$param = 'a \' g'; +$param2 = $conn->quote( $param ); + +$query = "INSERT INTO Table1 VALUES( ?, '1' )"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param)); + +$query = "INSERT INTO Table1 VALUES( ?, ? )"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param, $param2)); + +$query = "SELECT * FROM Table1"; +$stmt = $conn->query($query); +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print_r( $row['col1'] ." was inserted\n" ); +} + +// revert the inserts +$query = "delete from Table1 where col1 = ?"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param)); + +$conn->exec("DROP TABLE Table1 "); + +//free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +a ' g was inserted +a ' g was inserted \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute.phpt new file mode 100644 index 00000000..8991ffa9 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute.phpt @@ -0,0 +1,29 @@ +--TEST-- +sets to PDO::ATTR_ERRMODE +--SKIPIF-- + +--FILE-- +getAttribute( constant( "PDO::ATTR_$val" ) )); + } + + $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + + $attributes1 = array( "ERRMODE" ); + foreach ( $attributes1 as $val ) { + echo "PDO::ATTR_$val: "; + var_dump ($conn->getAttribute( constant( "PDO::ATTR_$val" ) )); + } + + //free the connection + $conn=null; +?> +--EXPECT-- +PDO::ATTR_ERRMODE: int(0) +PDO::ATTR_ERRMODE: int(2) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute_direct_query.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute_direct_query.phpt new file mode 100644 index 00000000..ae2ca1b1 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute_direct_query.phpt @@ -0,0 +1,42 @@ +--TEST-- +sets to PDO::SQLSRV_ATTR_DIRECT_QUERY +--SKIPIF-- + +--FILE-- +setAttribute(constant('PDO::SQLSRV_ATTR_DIRECT_QUERY'), true); + + $stmt1 = $conn->query("DROP TABLE #php_test_table"); + + $stmt2 = $conn->query("CREATE TABLE #php_test_table ([c1_int] int, [c2_int] int)"); + + $v1 = 1; + $v2 = 2; + + $stmt3 = $conn->prepare("INSERT INTO #php_test_table (c1_int, c2_int) VALUES (:var1, :var2)"); + + if ($stmt3) { + $stmt3->bindValue(1, $v1); + $stmt3->bindValue(2, $v2); + + if ($stmt3->execute()) + echo "Execution succeeded\n"; + else + echo "Execution failed\n"; + } + else + var_dump($conn->errorInfo()); + + $stmt4 = $conn->query("DROP TABLE #php_test_table"); + + // free the statements and connection + $stmt1=null; + $stmt2=null; + $stmt3=null; + $stmt4=null; + $conn=null; + ?> +--EXPECT-- +Execution succeeded \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_double.phpt b/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_double.phpt new file mode 100644 index 00000000..abcbdac2 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_double.phpt @@ -0,0 +1,61 @@ +--TEST-- +call a stored procedure and retrieve the errorNumber that is returned +--SKIPIF-- + +--FILE-- +query($tsql_dropSP); + + // Create the stored procedure + $tsql_createSP = "CREATE PROCEDURE sp_Test_Double + @ErrorNumber as float(53) = 0.0 OUTPUT + AS + BEGIN + SET @ErrorNumber = -1.111 + SELECT 1, 2, 3 + END"; + $stmt = $conn->query($tsql_createSP); + + // Call the stored procedure + $stmt = $conn->prepare("{CALL sp_Test_Double (?)}"); + + $errorNumber = 0.0; + $stmt->bindParam(1, $errorNumber, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 20); + $stmt->execute(); + + $result = $stmt->fetchAll(PDO::FETCH_NUM); + + $stmt->closeCursor(); + + print("Error Number: $errorNumber\n\n"); + $value = $errorNumber - 2; + print("Error Number minus 2: $value\n\n"); + + print_r($result); + + //free the statement and connection + $stmt = null; + $conn = null; +?> +--EXPECT-- +Error Number: -1.111 + +Error Number minus 2: -3.111 + +Array +( + [0] => Array + ( + [0] => 1 + [1] => 2 + [2] => 3 + ) + +) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_integer.phpt b/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_integer.phpt new file mode 100644 index 00000000..d1e6839c --- /dev/null +++ b/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_integer.phpt @@ -0,0 +1,57 @@ +--TEST-- +call a stored procedure and retrieve the errorNumber that is returned +--SKIPIF-- + +--FILE-- +query($tsql_dropSP); + + // Create the stored procedure + $tsql_createSP = "CREATE PROCEDURE sp_Test_Integer + @ErrorNumber AS INT = 0 OUTPUT + AS + BEGIN + SET @ErrorNumber = -1 + SELECT 1,2,3 + END"; + + $stmt = $conn->query($tsql_createSP); + + // Call the stored procedure + $stmt = $conn->prepare("{CALL sp_Test_Integer (:errornumber)}"); + + $errorNumber = 0; + $stmt->bindParam('errornumber', $errorNumber, PDO::PARAM_INT|PDO::PARAM_INPUT_OUTPUT, 4); + + $stmt->execute(); + $result = $stmt->fetchAll(PDO::FETCH_NUM); + + $stmt->closeCursor(); + + print("Error Number: $errorNumber\n\n"); + print_r($result); + + //free the statement and connection + $stmt = null; + $conn = null; +?> +--EXPECT-- +Error Number: -1 + +Array +( + [0] => Array + ( + [0] => 1 + [1] => 2 + [2] => 3 + ) + +) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_string.phpt b/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_string.phpt new file mode 100644 index 00000000..b8d60af2 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_string.phpt @@ -0,0 +1,60 @@ +--TEST-- +call a stored procedure and retrieve the errorString that is returned +--SKIPIF-- + +--FILE-- +query($tsql_dropSP); + + // Create the stored procedure + $tsql_createSP = "CREATE PROCEDURE sp_Test_String + @ErrorString as varchar(20) OUTPUT + AS + BEGIN + SET @ErrorString = REVERSE(@ErrorString) + SELECT 1,2,3 + END"; + $stmt = $conn->query($tsql_createSP); + + // Call the stored procedure + $stmt = $conn->prepare("{CALL sp_Test_String (?)}"); + + $errorString = "12345"; + $stmt->bindParam(1, $errorString, PDO::PARAM_STR|PDO::PARAM_INPUT_OUTPUT, 20); + print("Error String: $errorString\n\n"); + + $stmt->execute(); + + $result = $stmt->fetchAll(PDO::FETCH_NUM); + + $stmt->closeCursor(); + + print("Error String: $errorString\n\n"); + print_r($result); + + //free the statement and connection + $stmt = null; + $conn = null; +?> +--EXPECT-- +Error String: 12345 + +Error String: 54321 + +Array +( + [0] => Array + ( + [0] => 1 + [1] => 2 + [2] => 3 + ) + +) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/readme.txt b/test/bvt/pdo_sqlsrv/readme.txt new file mode 100644 index 00000000..191a4092 --- /dev/null +++ b/test/bvt/pdo_sqlsrv/readme.txt @@ -0,0 +1,4 @@ +This folder mainly contains tests that are derived from the code examples on + +https://docs.microsoft.com/en-us/sql/connect/php/pdo-class +https://docs.microsoft.com/en-us/sql/connect/php/pdostatement-class \ No newline at end of file diff --git a/test/bvt/sqlsrv/break.inc b/test/bvt/sqlsrv/break.inc new file mode 100644 index 00000000..6a2e190e --- /dev/null +++ b/test/bvt/sqlsrv/break.inc @@ -0,0 +1,106 @@ +"$databaseName", "username"=>"$username", "password"=>"$password" ); +$conn = sqlsrv_connect( $serverName, $connectionInfo ); + +// CREATE database +$stmt0 = sqlsrv_query($conn, "CREATE DATABASE $databaseName"); + +// Create table +$sql = "CREATE TABLE $tableName1 (c1 INT, c2 VARCHAR(40))"; +$stmt = sqlsrv_query($conn, $sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName1 VALUES (?,?)"; +for($t=100; $t<115; $t++) { + $stmt = sqlsrv_prepare($conn, $sql); + $ts = substr(sha1($t),0,5); + $params = array($t,$ts); + sqlsrv_execute($stmt, $params); +} + +// Create table +$sql = "CREATE TABLE $tableName2 (c1 INT, c2 VARCHAR(40))"; +$stmt = sqlsrv_query($conn, $sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName2 VALUES (?,?)"; +for($t=200; $t<208; $t++) { + $stmt = sqlsrv_prepare($conn, $sql); + $ts = substr(sha1($t),0,5); + $params = array($t,$ts); + sqlsrv_execute($stmt, $params); +} + +sqlsrv_close( $conn ); + +function RestartConn($serverName) +{ + $powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"; + $restart_string = "$powershell (get-service -ComputerName $serverName -Name mssqlserver).Stop()"; + exec( $restart_string ); + $servstring = shell_exec("$powershell get-service -ComputerName $serverName -Name mssqlserver"); + + // Wait until the service is fully stopped + while (substr_count($servstring, "Stopped") != 1) + { + sleep(1); + $servstring = shell_exec("$powershell get-service -ComputerName $serverName -Name mssqlserver"); + } + $restart_string = "$powershell (get-service -ComputerName $serverName -Name mssqlserver).Start()"; + exec( $restart_string ); + $servstring = shell_exec("$powershell get-service -ComputerName $serverName -Name mssqlserver"); + + // Wait until the service is fully started + while (substr_count($servstring, "Running") != 1) + { + sleep(1); + $servstring = shell_exec("$powershell get-service -ComputerName $serverName -Name mssqlserver"); + } +} + +function StopConn($serverName) +{ + $powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"; + $restart_string = "$powershell (get-service -ComputerName $serverName -Name mssqlserver).Stop()"; + exec( $restart_string ); + $servstring = shell_exec("$powershell get-service -ComputerName $serverName -Name mssqlserver"); + + // Wait until the service is fully stopped + while (substr_count($servstring, "Stopped") != 1) + { + sleep(1); + $servstring = shell_exec("$powershell get-service -ComputerName $serverName -Name mssqlserver"); + } +} + +function StartConn($serverName) +{ + $powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"; + $servstring = shell_exec("$powershell get-service -ComputerName $serverName -Name mssqlserver"); + if (substr_count($servstring, "Running") != 1) + { + $restart_string = "$powershell (get-service -ComputerName $serverName -Name mssqlserver).Start()"; + exec( $restart_string ); + } + $servstring = shell_exec("$powershell get-service -ComputerName $serverName -Name mssqlserver"); + + // Wait until the service is fully started + while (substr_count($servstring, "Running") != 1) + { + sleep(1); + $servstring = shell_exec("$powershell get-service -ComputerName $serverName -Name mssqlserver"); + } +} +?> \ No newline at end of file diff --git a/test/bvt/sqlsrv/break.php b/test/bvt/sqlsrv/break.php new file mode 100644 index 00000000..da5826c5 --- /dev/null +++ b/test/bvt/sqlsrv/break.php @@ -0,0 +1,88 @@ +$dbName, "uid"=>$uid, "pwd"=>$pwd ); + + $conn = sqlsrv_connect( $server, $connectionInfo ); + if ( $conn === false ) + { + die ( print_r( sqlsrv_errors() ) ); + } + + // Create table + $sql = "CREATE TABLE $tableName1 ( c1 INT, c2 VARCHAR(40) )"; + $stmt = sqlsrv_query( $conn, $sql ); + + // Insert data + $sql = "INSERT INTO $tableName1 VALUES ( ?, ? )"; + for( $t = 100; $t < 116; $t++ ) + { + $ts = substr( sha1( $t ),0,5 ); + $params = array( $t,$ts ); + $stmt = sqlsrv_prepare( $conn, $sql, $params ); + sqlsrv_execute( $stmt ); + } + + // Create table + $sql = "CREATE TABLE $tableName2 ( c1 INT, c2 VARCHAR(40) )"; + $stmt = sqlsrv_query( $conn, $sql ); + + // Insert data + $sql = "INSERT INTO $tableName2 VALUES ( ?, ? )"; + for( $t = 200; $t < 209; $t++ ) + { + $ts = substr( sha1( $t ),0,5 ); + $params = array( $t,$ts ); + $stmt = sqlsrv_prepare( $conn, $sql, $params ); + sqlsrv_execute( $stmt ); + } + + sqlsrv_close( $conn ); +} + +// Break connection by getting the session ID and killing it. +// Note that breaking a connection and testing reconnection requires a +// TCP/IP protocol connection (as opposed to a Shared Memory protocol). +function BreakConnection( $conn, $conn_break ) +{ + $stmt1 = sqlsrv_query( $conn, "SELECT @@SPID" ); + if ( sqlsrv_fetch( $stmt1 ) ) + { + $spid=sqlsrv_get_field( $stmt1, 0 ); + } + + $stmt2 = sqlsrv_prepare( $conn_break, "KILL ".$spid ); + sqlsrv_execute( $stmt2 ); + sleep(1); +} + +// Remove the tables generated by GenerateTables +function DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ) +{ + $connectionInfo = array( "UID"=>$uid, "PWD"=>$pwd ); + $conn = sqlsrv_connect( $server, $connectionInfo ); + + $query="IF OBJECT_ID('tempdb.dbo.$tableName1, 'U') IS NOT NULL DROP TABLE tempdb.dbo.$tableName1"; + $stmt=sqlsrv_query( $conn, $query ); + + $query="IF OBJECT_ID('tempdb.dbo.$tableName2, 'U') IS NOT NULL DROP TABLE tempdb.dbo.$tableName2"; + $stmt=sqlsrv_query( $conn, $query ); +} + +DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ); +GenerateTables( $server, $uid, $pwd, $dbName, $tableName1, $tableName2 ); + +?> \ No newline at end of file diff --git a/test/bvt/sqlsrv/connect.inc b/test/bvt/sqlsrv/connect.inc new file mode 100644 index 00000000..07f9289a --- /dev/null +++ b/test/bvt/sqlsrv/connect.inc @@ -0,0 +1,31 @@ +$databaseName, "UID"=>$uid, "PWD"=>$pwd ); + $conn = sqlsrv_connect( $server, $connectionInfo); + if( $conn === false ) + { + echo "Could not connect.
    "; + die( print_r( sqlsrv_errors(), true)); + } + + $stmt0 = sqlsrv_query( $conn, "UPDATE Sales.SalesOrderHeader SET RevisionNumber = 2"); + if ( !$stmt0 ) + { + echo "Resetting the RevisionNumber failed.\n"; + die( print_r( sqlsrv_errors(), true)); + } + + sqlsrv_close( $conn ); +} + +?> diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_begin_transaction.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_begin_transaction.phpt new file mode 100644 index 00000000..c4b5c9b9 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_begin_transaction.phpt @@ -0,0 +1,74 @@ +--TEST-- +executes two queries as part of a transaction +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>$uid, "PWD"=>$pwd); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true )); +} + +/* Initiate transaction. */ +/* Exit script if transaction cannot be initiated. */ +if ( sqlsrv_begin_transaction( $conn ) === false ) +{ + echo "Could not begin transaction.\n"; + die( print_r( sqlsrv_errors(), true )); +} + +/* Initialize parameter values. */ +$orderId = 43659; $qty = 5; $productId = 709; +$offerId = 1; $price = 5.70; + +/* Set up and execute the first query. */ +$tsql1 = "INSERT INTO Sales.SalesOrderDetail + (SalesOrderID, + OrderQty, + ProductID, + SpecialOfferID, + UnitPrice) + VALUES (?, ?, ?, ?, ?)"; +$params1 = array( $orderId, $qty, $productId, $offerId, $price); +$stmt1 = sqlsrv_query( $conn, $tsql1, $params1 ); + +/* Set up and execute the second query. */ +$tsql2 = "UPDATE Production.ProductInventory + SET Quantity = (Quantity - ?) + WHERE ProductID = ?"; +$params2 = array($qty, $productId); +$stmt2 = sqlsrv_query( $conn, $tsql2, $params2 ); + +/* If both queries were successful, commit the transaction. */ +/* Otherwise, rollback the transaction. */ +if( $stmt1 && $stmt2 ) +{ + sqlsrv_commit( $conn ); + echo "Transaction was committed.\n"; +} +else +{ + sqlsrv_rollback( $conn ); + echo "Transaction was rolled back.\n"; +} + +/* Revert the changes */ +$d_sql = "DELETE FROM Sales.SalesOrderDetail WHERE SalesOrderID=43659 AND OrderQty=5 AND ProductID=709 AND SpecialOfferID=1 AND Unitprice=5.70"; +$stmt3 = sqlsrv_query($conn, $d_sql); + +/* Free statement and connection resources. */ +sqlsrv_free_stmt( $stmt1); +sqlsrv_free_stmt( $stmt2); +sqlsrv_free_stmt($stmt3); +sqlsrv_close( $conn); +?> +--EXPECT-- +Transaction was committed. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_begin_transaction_2.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_begin_transaction_2.phpt new file mode 100644 index 00000000..e7b4abcf --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_begin_transaction_2.phpt @@ -0,0 +1,78 @@ +--TEST-- +delete in a transaction +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Begin transaction. */ +if( sqlsrv_begin_transaction($conn) === false ) +{ + echo "Could not begin transaction.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Set the Order ID. */ +$orderId = 43667; + +/* Execute operations that are part of the transaction. Commit on +success, roll back on failure. */ +if (perform_trans_ops($conn, $orderId)) +{ + //If commit fails, roll back the transaction. + if(sqlsrv_commit($conn)) + { + echo "Transaction committed.\n"; + } + else + { + echo "Commit failed - rolling back.\n"; + sqlsrv_rollback($conn); + } +} +else +{ + "Error in transaction operation - rolling back.\n"; + sqlsrv_rollback($conn); +} + +/*Free connection resources*/ +sqlsrv_close( $conn); +/*---------------- FUNCTION: perform_trans_ops -----------------*/ +function perform_trans_ops($conn, $orderId) +{ + /* Define query to update inventory based on sales order info. */ + $tsql1 = "UPDATE Production.ProductInventory + SET Quantity = Quantity + s.OrderQty + FROM Production.ProductInventory p + JOIN Sales.SalesOrderDetail s + ON s.ProductID = p.ProductID + WHERE s.SalesOrderID = ?"; + + /* Define the parameters array. */ + $params = array($orderId); + + /* Execute the UPDATE statement. Return false on failure. */ + if( sqlsrv_query( $conn, $tsql1, $params) === false ) return false; + + /* Delete the sales order. Return false on failure */ + $tsql2 = "DELETE FROM Sales.SalesOrderDetail + WHERE SalesOrderID = ?"; + if(sqlsrv_query( $conn, $tsql2, $params) === false ) return false; + + /* Return true because all operations were successful. */ + return true; +} +?> +--EXPECT-- +Transaction committed. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_cancel.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_cancel.phpt new file mode 100644 index 00000000..27364fee --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_cancel.phpt @@ -0,0 +1,49 @@ +--TEST-- +executes a query, then comsumes and counts results until reaches a specified amount. The remaining query results are then discarded. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Prepare and execute the query. */ +$tsql = "SELECT OrderQty, UnitPrice FROM Sales.SalesOrderDetail"; +$stmt = sqlsrv_prepare( $conn, $tsql); +if( $stmt === false ) +{ + echo "Error in statement preparation.\n"; + die( print_r( sqlsrv_errors(), true)); +} +if( sqlsrv_execute( $stmt ) === false) +{ + echo "Error in statement execution.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Initialize tracking variables. */ +$salesTotal = 0; +$count = 0; + +/* Count and display the number of sales that produce revenue +of $100,000. */ +while( ($row = sqlsrv_fetch_array( $stmt)) && $salesTotal <=100000) +{ + $qty = $row[0]; + $price = $row[1]; + $salesTotal += ( $price * $qty); + $count++; +} +echo "$count sales accounted for the first $$salesTotal in revenue.\n"; + +/* Cancel the pending results. The statement can be reused. */ +sqlsrv_cancel( $stmt); +?> +--EXPECT-- +57 sales accounted for the first $104171.7607 in revenue. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_cancel_1.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_cancel_1.phpt new file mode 100644 index 00000000..6bf0bcc4 --- /dev/null +++ b/test/bvt/sqlsrv/msdn_sqlsrv_cancel_1.phpt @@ -0,0 +1,80 @@ +--TEST-- +cancels a statement then reuse. +--SKIPIF-- + +--FILE-- +"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$conn = sqlsrv_connect( $server, $connectionInfo); +if( $conn === false ) +{ + echo "Could not connect.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +/* Prepare and execute the query. */ +echo "