Merge pull request #1296 from microsoft/dev

Dev
This commit is contained in:
Jenny Tam 2021-09-08 11:41:43 -07:00 committed by GitHub
commit f2c340892a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
171 changed files with 7115 additions and 2881 deletions

View file

@ -3,6 +3,48 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
## 5.10.0-beta1 - 2021-09-08
Updated PECL release packages. Here is the list of updates:
### Added
- Support for PHP 8.1 RC 1
- Support for Ubuntu 21.04 and Alpine 3.13
- Support for Apple M1 ARM64 hardware (requires [MS ODBC Driver 17.8+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/install-microsoft-odbc-driver-sql-server-macos?view=sql-server-ver15)
- Feature Request [#795](https://github.com/microsoft/msphpsql/issues/795) - adding support for [Table-valued parameters](https://github.com/Microsoft/msphpsql/wiki/Features#tvp)
### Removed
- Support for Ubuntu 16.04
### Fixed
- Issue [#1244](https://github.com/microsoft/msphpsql/issues/1244) - use lower case for object names for PDO::lastInsertId() - pull request [#1245](https://github.com/microsoft/msphpsql/pull/1245) by morozov
- Pull request [#1251](https://github.com/microsoft/msphpsql/pull/1251) - simplified implementations of last insert id and quote
- Issue [#1258](https://github.com/microsoft/msphpsql/issues/1258) - updated pdo_sqlsrv_get_driver_methods as per documentation - pull request [#1259](https://github.com/microsoft/msphpsql/pull/1259)
- Pull request [#1260](https://github.com/microsoft/msphpsql/pull/1260) - cleaned up redundant code
- Issue [#1261](https://github.com/microsoft/msphpsql/issues/1261) - simplified get_field_as_string and made it more robust - pull request [#1265](https://github.com/microsoft/msphpsql/pull/1265)
- Pull request [#1262](https://github.com/microsoft/msphpsql/pull/1262) - simplified parse_param_array in sqlsrv
- Pull request [#1267](https://github.com/microsoft/msphpsql/pull/1267) - replaced the obsolete MACRO ZVAL_NEW_ARR with array_init
- Pull request [#1275](https://github.com/microsoft/msphpsql/pull/1275) - fixed warning compiling core_stmt.cpp by mlocati
- Pull request [#1288](https://github.com/microsoft/msphpsql/pull/1288) - applied mask to pdo quote for binary inputs
- Pull request [#1290](https://github.com/microsoft/msphpsql/pull/1290) - updated list of supported processor architecture
### Limitations
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
### Known Issues
- This release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
## 5.9.0 - 2021-01-29
Updated PECL release packages. Here is the list of updates:

View file

@ -1,5 +1,5 @@
# Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server
The following instructions assume a clean environment and show how to install PHP 8.0, the Microsoft ODBC driver, the Apache web server, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 20.04, RedHat 7 and 8, Debian 9 and 10, Suse 12 and 15, Alpine 3.11 and 3.12, and macOS 10.14, 10.15, and 11.0. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup).
The following instructions assume a clean environment and show how to install PHP 8.0, the Microsoft ODBC driver, the Apache web server, and the Microsoft Drivers for PHP for SQL Server on Ubuntu, RedHat, Debian, Suse, Alpine, and macOS. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup).
The following instructions install PHP 8.0 by default using `pecl install`, if the PHP 8.0 packages are available. You may need to run `pecl channel-update pecl.php.net` first. Note that some supported Linux distros default to PHP 7.1 or earlier, which is not supported for the latest version of the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.4 or 7.3 instead.
@ -9,15 +9,15 @@ While these instructions contain commands to install both SQLSRV and PDO_SQLSRV
## Contents of this page
- [Installing the drivers on Ubuntu 16.04, 18.04, and 20.04](#installing-the-drivers-on-ubuntu-1604-1804-and-2004)
- [Installing the drivers on Ubuntu](#installing-the-drivers-on-ubuntu)
- [Installing the drivers with PHP-FPM on Ubuntu](#installing-the-drivers-with-php-fpm-on-ubuntu)
- [Installing the drivers on Red Hat 7 and 8](#installing-the-drivers-on-red-hat-7-and-8)
- [Installing the drivers on Debian 9 and 10](#installing-the-drivers-on-debian-9-and-10)
- [Installing the drivers on Suse 12 and 15](#installing-the-drivers-on-suse-12-and-15)
- [Installing the drivers on Alpine 3.11 and 3.12](#installing-the-drivers-on-alpine-311-and-312)
- [Installing the drivers on macOS Mojave, Catalina and Big Sur](#installing-the-drivers-on-macos-mojave-catalina-and-big-sur)
- [Installing the drivers on Red Hat](#installing-the-drivers-on-red-hat)
- [Installing the drivers on Debian](#installing-the-drivers-on-debian)
- [Installing the drivers on Suse](#installing-the-drivers-on-suse)
- [Installing the drivers on Alpine](#installing-the-drivers-on-alpine)
- [Installing the drivers on macOS](#installing-the-drivers-on-macos)
## Installing the drivers on Ubuntu 16.04, 18.04, and 20.04
## Installing the drivers on Ubuntu
> [!NOTE]
> To install PHP 7.4 or 7.3, replace 8.0 with 7.4 or 7.3 in the following commands.
@ -127,7 +127,7 @@ sudo systemctl restart nginx.service
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on Red Hat 7 and 8
## Installing the drivers on Red Hat
### Step 1. Install PHP
@ -193,7 +193,7 @@ sudo apachectl restart
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on Debian 9 and 10
## Installing the drivers on Debian
> [!NOTE]
> To install PHP 7.4 or 7.3, replace 8.0 in the following commands with 7.4 or 7.3.
@ -245,7 +245,7 @@ sudo service apache2 restart
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on Suse 12 and 15
## Installing the drivers on Suse
> [!NOTE]
> In the following instructions, replace `<SuseVersion>` with your version of Suse - if you are using Suse Enterprise Linux 15, it will be SLE_15_SP1 or SLE_15_SP2. For Suse 12, use SLE_12_SP4 (or above if applicable). Not all versions of PHP are available for all versions of Suse Linux - please refer to `http://download.opensuse.org/repositories/devel:/languages:/php` to see which versions of Suse have the default version PHP available, or check `http://download.opensuse.org/repositories/devel:/languages:/php:/` to see which other versions of PHP are available for which versions of Suse.
@ -292,7 +292,7 @@ sudo systemctl restart apache2
```
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on Alpine 3.11 and 3.12
## Installing the drivers on Alpine
> [!NOTE]
> The default version of PHP is 7.3. PHP 7.4 or above may be available from testing or edge repositories for Alpine. You can instead compile PHP from source.
@ -332,13 +332,16 @@ sudo rc-service apache2 restart
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
## Installing the drivers on macOS Mojave, Catalina and Big Sur
## Installing the drivers on macOS
If you do not already have it, install brew as follows:
If you do not already have it, install Homebrew as follows:
```bash
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```
> [!NOTE]
> If using Apple M1 ARM64 hardware, please install Homebrew and PHP directly without using the emulator Rosetta 2.
> [!NOTE]
> To install PHP 7.4 or 7.3, replace php@8.0 with php@7.4 or php@7.3 respectively in the following commands.
@ -358,6 +361,9 @@ brew link --force --overwrite php@8.0
Install the ODBC driver for macOS by following the instructions on the [Install the Microsoft ODBC driver for SQL Server (macOS)](
https://docs.microsoft.com/sql/connect/odbc/linux-mac/install-microsoft-odbc-driver-sql-server-macos?view=sql-server-ver15).
> [!NOTE]
> If using Apple M1 ARM64 hardware, please install Microsoft ODBC driver 17.8+ directly without using the emulator Rosetta 2.
In addition, you may need to install the GNU make tools:
```bash
brew install autoconf automake libtool
@ -368,7 +374,18 @@ brew install autoconf automake libtool
sudo pecl install sqlsrv
sudo pecl install pdo_sqlsrv
```
If using Apple M1 ARM64, do the following instead:
```bash
sudo CXXFLAGS="-I/opt/homebrew/opt/unixodbc/include/" LDFLAGS="-L/opt/homebrew/lib/" pecl install sqlsrv
sudo CXXFLAGS="-I/opt/homebrew/opt/unixodbc/include/" LDFLAGS="-L/opt/homebrew/lib/" pecl install pdo_sqlsrv
```
### Step 4. Install Apache and configure driver loading
> [!NOTE]
> The latest macOS 11.0 Big Sur comes with Apache 2.4 pre-installed, but Apple has also removed some required scripts. The solution is to install Apache 2.4 via Homebrew and then configure it, but this is out of scope for this installation guide, so please check Apache or Homebrew for detailed instructions.
```bash
brew install apache2
```

View file

@ -2,7 +2,7 @@
**Welcome to the Microsoft Drivers for PHP for Microsoft SQL Server**
The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2012 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server][odbcdoc] to handle the low-level communication with SQL Server.
The [Microsoft Drivers for PHP for Microsoft SQL Server][phpdoc] are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2012 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server][odbcdoc] to handle the low-level communication with SQL Server.
This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.3+ with improvements on both drivers and some limitations. Upcoming [releases][releases] will contain additional functionalities, bug fixes, and more.
@ -23,7 +23,7 @@ Azure Pipelines | AppVeyor (Windows) | Travis CI (Linux) | Co
[tv-site]: https://travis-ci.org/microsoft/msphpsql/
[az-site]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_build/latest?definitionId=6&branchName=dev
[az-image]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_apis/build/status/Microsoft.msphpsql?branchName=dev
[Coverage Coveralls]: https://coveralls.io/repos/github/microsoft/msphpsql/badge.svg?branch=dev
[Coverage Coveralls]: https://coveralls.io/repos/github/microsoft/msphpsql/badge.svg?branch=dev&&service=github
[coveralls-site]: https://coveralls.io/github/microsoft/msphpsql?branch=dev
[Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/dev/graph/badge.svg
[codecov-site]: https://codecov.io/gh/microsoft/msphpsql
@ -66,7 +66,7 @@ Finally, if running PHP in a Web server, restart the Web server.
For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on Microsoft Docs][unixinstructions].
## Sample Code
For PHP code samples, please see the [sample](https://github.com/Microsoft/msphpsql/tree/master/sample) folder or the [code samples on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/code-samples-for-php-sql-driver).
For PHP code samples, please see the [sample](https://github.com/Microsoft/msphpsql/tree/master/sample) folder or the [code samples on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/code-samples-for-php-sql-driver). For information on how to use the driver, see [Microsoft Drivers for PHP for Microsoft SQL Server][phpdoc].
## Limitations and Known Issues
Please refer to [Releases][releases] for the latest limitations and known issues.

View file

@ -30,7 +30,7 @@ environment:
SQL_INSTANCE: SQL2019
PHP_VC: vs16
PHP_MAJOR_VER: 8.0
PHP_MINOR_VER: 0RC2
PHP_MINOR_VER: latest
PHP_EXE_PATH: Release
THREAD: nts
platform: x86
@ -79,7 +79,7 @@ install:
}
- echo Downloading MSODBCSQL 17
# AppVeyor build works are x64 VMs and 32-bit ODBC driver cannot be installed on it
- ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/6/b/3/6b3dd05c-678c-4e6b-b503-1d66e16ef23d/en-US/17.6.1.1/x64/msodbcsql.msi', 'c:\projects\msodbcsql.msi')
- ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/a/e/b/aeb7d4ff-ca20-45db-86b8-8a8f774ce97b/en-US/17.8.1.1/x64/msodbcsql.msi', 'c:\projects\msodbcsql.msi')
- cmd /c start /wait msiexec /i "c:\projects\msodbcsql.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL
- echo Checking the version of MSODBCSQL
- reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server"
@ -116,10 +116,13 @@ test_script:
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\setup_dbs.py -dbname %SQLSRV_DBNAME%
- Echo setup test database for PDO_SQLSRV tests - %PDOSQLSRV_DBNAME%
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\setup_dbs.py -dbname %PDOSQLSRV_DBNAME%
# for code coverage - exclude the following files:
# core_init.cpp, which primarily consists module initialization and shutdown
# core_stream.cpp for pdo_sqlsrv because it is only used by sqlsrv driver
- ps: >-
If ($env:BUILD_PLATFORM -Match "x86") {
Write-Host "Running phpt tests via OpenCppCoverage..."
OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --excluded_sources ${env:PHP_SRC_DIR}\pdo_sqlsrv\shared\core_stream.cpp --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P --no-color ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8;
OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --excluded_sources core_init.cpp --excluded_sources ${env:PHP_SRC_DIR}\pdo_sqlsrv\shared\core_stream.cpp --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P --no-color ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8;
Write-Host "Showing the last 25 lines of the log file..."
Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -Tail 25;
ls *.xml

View file

@ -99,7 +99,7 @@ class BuildUtil(object):
if self.vc == '':
VC = 'vc15'
version = self.version_label()
if version == '80': # Compiler version for PHP 8.0 or above
if version[0] == '8': # Compiler version for PHP 8.0 or above
VC = 'vs16'
self.vc = VC
print('Compiler: ' + self.vc)

View file

@ -4,7 +4,7 @@ dnl
dnl Contents: the code that will go into the configure script, indicating options,
dnl external libraries and includes, and what source files are to be compiled.
dnl
dnl Microsoft Drivers 5.9 for PHP for SQL Server
dnl Microsoft Drivers 5.10 for PHP for SQL Server
dnl Copyright(c) Microsoft Corporation
dnl All rights reserved.
dnl MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: JScript build configuration used by buildconf.bat
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Implements the PDO object for PDO_SQLSRV
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -34,7 +34,7 @@ namespace {
const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;";
const size_t LAST_INSERT_ID_BUFF_LEN = 50; // size of the buffer to hold the string value of the last inserted id, which may be an int, bigint, decimal(p,0) or numeric(p,0)
const char SEQUENCE_CURRENT_VALUE_QUERY[] = "SELECT CURRENT_VALUE FROM SYS.SEQUENCES WHERE NAME=%s";
const char SEQUENCE_CURRENT_VALUE_QUERY[] = "SELECT current_value FROM sys.sequences WHERE name=N'%s'";
const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( SEQUENCE_CURRENT_VALUE_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes
// List of PDO supported connection options.
@ -436,36 +436,66 @@ const connection_option PDO_CONN_OPTS[] = {
};
#if PHP_VERSION_ID < 80100
// close the connection
int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh );
int pdo_sqlsrv_dbh_close(_Inout_ pdo_dbh_t *dbh);
// execute queries
int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql,
_Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options );
zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len );
int pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql,
_Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options);
zend_long pdo_sqlsrv_dbh_do(_Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len);
// quote a string, meaning put quotes around it and escape any quotes within it
int pdo_sqlsrv_dbh_quote(_Inout_ pdo_dbh_t* dbh, _In_reads_(unquotedlen) const char* unquoted, _In_ size_t unquotedlen, _Outptr_result_buffer_(*quotedlen) char **quoted, _Out_ size_t* quotedlen,
enum pdo_param_type paramtype);
// transaction support functions
int pdo_sqlsrv_dbh_commit( _Inout_ pdo_dbh_t *dbh );
int pdo_sqlsrv_dbh_begin( _Inout_ pdo_dbh_t *dbh );
int pdo_sqlsrv_dbh_rollback( _Inout_ pdo_dbh_t *dbh );
int pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh);
int pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh);
int pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh);
// attribute functions
int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val );
int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value );
// return more information
int pdo_sqlsrv_dbh_return_error( _In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt,
_Out_ zval *info);
int pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val);
int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value);
// return the last id generated by an executed SQL statement
char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len );
char * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len);
// return more information
int pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info);
#else
// close the connection
void pdo_sqlsrv_dbh_close(_Inout_ pdo_dbh_t *dbh);
// execute queries
bool pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_ zend_string *sql,
_Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options);
zend_long pdo_sqlsrv_dbh_do(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *sql);
// quote a string, meaning put quotes around it and escape any quotes within it
zend_string* pdo_sqlsrv_dbh_quote(_Inout_ pdo_dbh_t* dbh, _In_ const zend_string *unquoted, _In_ enum pdo_param_type paramtype);
// transaction support functions
bool pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh);
bool pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh);
bool pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh);
// attribute functions
bool pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val);
int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value);
// return the last id generated by an executed SQL statement
zend_string * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *name);
// return more information
void pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info);
#endif
// additional methods are supported in this function
pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( _Inout_ pdo_dbh_t *dbh, int kind );
// quote a string, meaning put quotes around it and escape any quotes within it
int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquotedlen) const char* unquoted, _In_ size_t unquotedlen, _Outptr_result_buffer_(*quotedlen) char **quoted, _Out_ size_t* quotedlen,
enum pdo_param_type paramtype );
struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = {
@ -480,12 +510,17 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = {
pdo_sqlsrv_dbh_last_id,
pdo_sqlsrv_dbh_return_error,
pdo_sqlsrv_dbh_get_attr,
NULL, // check liveness not implemented
NULL, // check liveness not implemented
pdo_sqlsrv_get_driver_methods,
NULL, // request shutdown not implemented
NULL // in transaction not implemented
NULL, // request shutdown not implemented
#if PHP_VERSION_ID < 80100
NULL // in transaction not implemented
};
#else
NULL, // in transaction not implemented
NULL // get_gc not implemented
};
#endif
// log a function entry point
#define PDO_LOG_DBH_ENTRY \
@ -632,15 +667,22 @@ int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// Always returns 1 for success.
// Always returns 1 for success. (for PHP_VERSION_ID < 80100)
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh )
#else
void pdo_sqlsrv_dbh_close(_Inout_ pdo_dbh_t *dbh)
#endif
{
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 ) {
#if PHP_VERSION_ID < 80100
return 1;
#else
return;
#endif
}
PDO_RESET_DBH_ERROR;
@ -649,8 +691,10 @@ int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh )
core_sqlsrv_close( reinterpret_cast<sqlsrv_conn*>( dbh->driver_data ) );
dbh->driver_data = NULL;
#if PHP_VERSION_ID < 80100
// always return success that the connection is closed
return 1;
#endif
}
// pdo_sqlsrv_dbh_prepare
@ -664,8 +708,12 @@ int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh )
// driver_options - User provided list of statement options.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql,
_Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options )
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql,
_Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options)
#else
bool pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_ zend_string *sql_zstr, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
@ -707,27 +755,44 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch
driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size;
}
#if PHP_VERSION_ID >= 80100
zend_string* sql_rewrite_zstr = NULL;
const char* sql = ZSTR_VAL(sql_zstr);
size_t sql_len = ZSTR_LEN(sql_zstr);
#endif
// 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.
#if PHP_VERSION_ID < 80100
int zr = pdo_parse_params( stmt, const_cast<char*>( sql ), sql_len, &sql_rewrite, &sql_rewrite_len );
CHECK_ZEND_ERROR( zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE) {
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) {
if (sql_rewrite != 0) {
sql = sql_rewrite;
sql_len = sql_rewrite_len;
}
#else
int zr = pdo_parse_params(stmt, sql_zstr, &sql_rewrite_zstr);
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_zstr != NULL) {
sql = ZSTR_VAL(sql_rewrite_zstr);
sql_len = ZSTR_LEN(sql_rewrite_zstr);
}
#endif
}
if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) {
core_sqlsrv_prepare( driver_stmt, sql, sql_len );
core_sqlsrv_prepare(driver_stmt, sql, sql_len);
}
else if( driver_stmt->direct_query ) {
@ -739,14 +804,26 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch
driver_stmt->direct_query_subst_string = estrdup( sql );
driver_stmt->direct_query_subst_string_len = sql_len;
}
#if PHP_VERSION_ID >= 80100
if (sql_rewrite_zstr != NULL) {
zend_string_release(sql_rewrite_zstr);
}
#endif
// else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be
// set to the substituted query
if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) {
// parse placeholders in the sql query into the placeholders ht
// parse placeholders in the sql query into the placeholders ht
ALLOC_HASHTABLE( placeholders );
core::sqlsrv_zend_hash_init( *driver_dbh, placeholders, 5, ZVAL_PTR_DTOR /* dtor */, 0 /* persistent */ );
sql_parser = new ( sqlsrv_malloc( sizeof( sql_string_parser ))) sql_string_parser( *driver_dbh, stmt->query_string,
static_cast<int>(stmt->query_stringlen), placeholders );
core::sqlsrv_zend_hash_init(*driver_dbh, placeholders, 5, ZVAL_PTR_DTOR /* dtor */, 0 /* persistent */);
#if PHP_VERSION_ID < 80100
sql_parser = new (sqlsrv_malloc(sizeof(sql_string_parser))) sql_string_parser(*driver_dbh, stmt->query_string,
static_cast<int>(stmt->query_stringlen), placeholders);
#else
sql_parser = new (sqlsrv_malloc(sizeof(sql_string_parser))) sql_string_parser(*driver_dbh, ZSTR_VAL(stmt->query_string),
ZSTR_LEN(stmt->query_string), placeholders);
#endif
sql_parser->parse_sql_string();
driver_stmt->placeholders = placeholders;
placeholders.transferred();
@ -772,7 +849,11 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch
reinterpret_cast<const char*>( driver_dbh->last_error()->sqlstate ));
}
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// catch any errant exception and die
@ -781,7 +862,11 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch
DIE( "pdo_sqlsrv_dbh_prepare: Unknown exception caught." );
}
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
@ -795,7 +880,11 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch
// sql_len - length of sql query
// Return
// # of rows affected, -1 for an error.
#if PHP_VERSION_ID < 80100
zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len )
#else
zend_long pdo_sqlsrv_dbh_do(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *sql)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
@ -823,8 +912,11 @@ zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) c
NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt );
driver_stmt->set_func( __FUNCTION__ );
SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt, sql, static_cast<int>( sql_len ) );
#if PHP_VERSION_ID < 80100
SQLRETURN execReturn = core_sqlsrv_execute(driver_stmt, sql, static_cast<int>(sql_len));
#else
SQLRETURN execReturn = core_sqlsrv_execute(driver_stmt, ZSTR_VAL(sql), ZSTR_LEN(sql));
#endif
// 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.
@ -883,8 +975,13 @@ zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) c
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// 0 for failure and 1 for success.
int pdo_sqlsrv_dbh_begin( _Inout_ pdo_dbh_t *dbh )
// 0 for failure and 1 for success. (if PHP_VERSION_ID < 80100)
// Return true if currently inside a transaction, false otherwise
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh)
#else
bool pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
@ -902,18 +999,29 @@ int pdo_sqlsrv_dbh_begin( _Inout_ pdo_dbh_t *dbh )
core_sqlsrv_begin_transaction( driver_conn );
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_begin: Uncaught exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
@ -927,8 +1035,13 @@ int pdo_sqlsrv_dbh_begin( _Inout_ pdo_dbh_t *dbh )
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// 0 for failure and 1 for success.
int pdo_sqlsrv_dbh_commit( _Inout_ pdo_dbh_t *dbh )
// 0 for failure and 1 for success. (if PHP_VERSION_ID < 80100)
// Return true for success and false otherwise
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh)
#else
bool pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
@ -946,18 +1059,30 @@ int pdo_sqlsrv_dbh_commit( _Inout_ pdo_dbh_t *dbh )
core_sqlsrv_commit( driver_conn );
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_commit: Uncaught exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// pdo_sqlsrv_dbh_rollback
@ -969,8 +1094,13 @@ int pdo_sqlsrv_dbh_commit( _Inout_ pdo_dbh_t *dbh )
// Parameters:
// dbh - The PDO managed connection object.
// Return:
// 0 for failure and 1 for success.
int pdo_sqlsrv_dbh_rollback( _Inout_ pdo_dbh_t *dbh )
// 0 for failure and 1 for success. (if PHP_VERSION_ID < 80100)
// Return true for success and false otherwise
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh)
#else
bool pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
@ -987,18 +1117,30 @@ int pdo_sqlsrv_dbh_rollback( _Inout_ pdo_dbh_t *dbh )
core_sqlsrv_rollback( driver_conn );
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_rollback: Uncaught exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// pdo_sqlsrv_dbh_set_attr
@ -1010,8 +1152,13 @@ int pdo_sqlsrv_dbh_rollback( _Inout_ pdo_dbh_t *dbh )
// 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( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val )
// 0 for failure, 1 for success. (if PHP_VERSION_ID < 80100)
// Return true on success and false in case of failure
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val)
#else
bool pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
@ -1049,7 +1196,7 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
break;
case SQLSRV_ATTR_DIRECT_QUERY:
driver_dbh->direct_query = ( zend_is_true( val ) ) ? true : false;
driver_dbh->direct_query = zend_is_true(val);
break;
case SQLSRV_ATTR_QUERY_TIMEOUT:
@ -1069,15 +1216,15 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
break;
case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE:
driver_dbh->fetch_numeric = (zend_is_true(val)) ? true : false;
driver_dbh->fetch_numeric = zend_is_true(val);
break;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
driver_dbh->fetch_datetime = (zend_is_true(val)) ? true : false;
driver_dbh->fetch_datetime = zend_is_true(val);
break;
case SQLSRV_ATTR_FORMAT_DECIMALS:
driver_dbh->format_decimals = (zend_is_true(val)) ? true : false;
driver_dbh->format_decimals = zend_is_true(val);
break;
case SQLSRV_ATTR_DECIMAL_PLACES:
@ -1158,11 +1305,18 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
}
}
catch( pdo::PDOException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
@ -1173,8 +1327,12 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
// 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( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value )
// 0 for failure, 1 for success. (if PHP_VERSION_ID < 80100)
// There are 3 return states:
// -1 for errors while retrieving a valid attribute
// 0 for attempting to retrieve an attribute which is not supported by the driver
// any other value for success, *return_value must be set to the attribute value
int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value)
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
@ -1196,8 +1354,12 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
case PDO_ATTR_AUTOCOMMIT:
case PDO_ATTR_TIMEOUT:
{
#if PHP_VERSION_ID < 80100
// PDO does not throw "not supported" error message for these attributes.
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR );
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR);
#else
return 0;
#endif
}
// Statement level only
@ -1303,10 +1465,18 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
}
}
#if PHP_VERSION_ID < 80100
return 1;
#else
return 1;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return -1;
#endif
}
}
@ -1318,8 +1488,11 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
// info - zval in which to return the error info.
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_dbh_return_error( _In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt,
_Out_ zval *info)
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info)
#else
void pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info)
#endif
{
SQLSRV_ASSERT( dbh != NULL || stmt != NULL, "Either dbh or stmt must not be NULL to dereference the error." );
@ -1333,7 +1506,9 @@ int pdo_sqlsrv_dbh_return_error( _In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt,
pdo_sqlsrv_retrieve_context_error( ctx_error, info );
#if PHP_VERSION_ID < 80100
return 1;
#endif
}
// pdo_sqlsrv_dbh_last_id
@ -1344,8 +1519,13 @@ int pdo_sqlsrv_dbh_return_error( _In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt,
// name - Table name.
// len - Length of the name.
// Return:
// Returns the last insert id as a string.
char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len )
// Returns the last insert id as a string. (if PHP_VERSION_ID < 80100)
// Returning NULL indicates an error condition. The input "name" MIGHT be NULL
#if PHP_VERSION_ID < 80100
char * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len)
#else
zend_string * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *name)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
@ -1360,22 +1540,28 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name,
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ASSERT( driver_dbh != NULL, "pdo_sqlsrv_dbh_last_id: driver_data object was NULL." );
sqlsrv_malloc_auto_ptr<char> id_str;
id_str = reinterpret_cast<char*>( sqlsrv_malloc( LAST_INSERT_ID_BUFF_LEN ));
char idSTR[LAST_INSERT_ID_BUFF_LEN] = { '\0' };
char* str = NULL;
SQLLEN cbID = 0;
try {
sqlsrv_malloc_auto_ptr<SQLWCHAR> wsql_string;
unsigned int wsql_len;
char last_insert_id_query[LAST_INSERT_ID_QUERY_MAX_LEN] = {'\0'};
if( name == NULL ) {
strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY );
if (name == NULL) {
wsql_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_CHAR, LAST_INSERT_ID_QUERY, sizeof(LAST_INSERT_ID_QUERY), &wsql_len);
} else {
char buffer[LAST_INSERT_ID_QUERY_MAX_LEN] = { '\0' };
#if PHP_VERSION_ID < 80100
snprintf(buffer, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, name);
#else
const char *name_str = ZSTR_VAL(name);
snprintf(buffer, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, ZSTR_VAL(name));
#endif
wsql_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_CHAR, buffer, sizeof(buffer), &wsql_len);
}
else {
char* quoted_table = NULL;
size_t quoted_len = 0;
int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strnlen_s( name ), &quoted_table, &quoted_len, PDO_PARAM_NULL );
SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name.");
snprintf( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, quoted_table );
sqlsrv_free( quoted_table );
CHECK_CUSTOM_ERROR(wsql_string == 0, driver_stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
// temp PDO statement used for error handling if something happens
@ -1386,31 +1572,18 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name,
driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt<pdo_sqlsrv_stmt>, NULL /*options_ht*/, NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt );
driver_stmt->set_func( __FUNCTION__ );
sqlsrv_malloc_auto_ptr<SQLWCHAR> wsql_string;
unsigned int wsql_len;
wsql_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_CHAR, reinterpret_cast<const char*>( last_insert_id_query ), sizeof(last_insert_id_query), &wsql_len );
CHECK_CUSTOM_ERROR( wsql_string == 0, driver_stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) {
throw core::CoreException();
}
// execute the last insert id query
// execute the last insert id query
core::SQLExecDirectW( driver_stmt, wsql_string );
core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 );
SQLRETURN r = core::SQLGetData( driver_stmt, 1, SQL_C_CHAR, id_str, LAST_INSERT_ID_BUFF_LEN,
reinterpret_cast<SQLLEN*>( len ), false );
CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt,
PDO_SQLSRV_ERROR_LAST_INSERT_ID ) {
SQLRETURN r = core::SQLGetData(driver_stmt, 1, SQL_C_CHAR, idSTR, LAST_INSERT_ID_BUFF_LEN, &cbID, false);
CHECK_CUSTOM_ERROR((!SQL_SUCCEEDED(r) || cbID == SQL_NULL_DATA || cbID == SQL_NO_TOTAL), driver_stmt,
PDO_SQLSRV_ERROR_LAST_INSERT_ID) {
throw core::CoreException();
}
driver_stmt->~sqlsrv_stmt();
}
catch( core::CoreException& ) {
} 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 ),
@ -1420,18 +1593,31 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name,
if( driver_stmt ) {
driver_stmt->~sqlsrv_stmt();
}
strcpy_s( id_str.get(), 1, "" );
#if PHP_VERSION_ID < 80100
*len = 0;
str = reinterpret_cast<char*>(sqlsrv_malloc(0, sizeof(char), 1)); // return an empty string with a null terminator
str[0] = '\0';
return str;
#else
return NULL;
#endif
}
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;
// copy the last ID string and return it
str = reinterpret_cast<char*>(sqlsrv_malloc(cbID, sizeof(char), 1)); // include space for null terminator
strcpy_s(str, cbID + 1, idSTR);
#if PHP_VERSION_ID < 80100
*len = static_cast<size_t>(cbID);
return str;
#else
zend_string *zstr = zend_string_init(str, cbID, 0);
sqlsrv_free(str);
return zstr;
#endif
}
// pdo_sqlsrv_dbh_quote
@ -1445,8 +1631,12 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name,
// quoted_len - Length of the output string.
// Return:
// 0 for failure, 1 for success.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const char* unquoted, _In_ size_t unquoted_len, _Outptr_result_buffer_(*quoted_len) char **quoted, _Out_ size_t* quoted_len,
enum pdo_param_type paramtype )
#else
zend_string* pdo_sqlsrv_dbh_quote(_Inout_ pdo_dbh_t* dbh, _In_ const zend_string *unquoted, _In_ enum pdo_param_type paramtype)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
@ -1523,85 +1713,113 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
}
#endif
if ( encoding == SQLSRV_ENCODING_BINARY ) {
// convert from char* to hex digits using os
std::basic_ostringstream<char> os;
for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) {
// if unquoted is < 0 or > 255, that means this is a non-ascii character. Translation from non-ascii to binary is not supported.
// return an empty terminated string for now
if (( int )unquoted[index] < 0 || ( int )unquoted[index] > 255) {
*quoted_len = 0;
*quoted = reinterpret_cast<char*>( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 ));
( *quoted )[0] = '\0';
return 1;
if (encoding == SQLSRV_ENCODING_BINARY) {
#if PHP_VERSION_ID < 80100
*quoted_len = (unquoted_len * 2) + 2; // each character will be converted to 2 hex digits and prepend '0x' to the result
*quoted = reinterpret_cast<char*>(sqlsrv_malloc(*quoted_len, sizeof(char), 1)); // include space for null terminator
memset(*quoted, '\0', *quoted_len + 1);
unsigned int pos = 0;
(*quoted)[pos++] = '0';
(*quoted)[pos++] = 'x';
for (size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index) {
// On success, snprintf returns the total number of characters written
// On failure, a negative number is returned
// The generated string has a length of at most len - 1, so
// len is 3 (2 hex digits + 1)
// Requires "& 0x000000FF", or snprintf will translate "0x90" to "0xFFFFFF90"
int n = snprintf((char*)(*quoted + pos), 3, "%02X", unquoted[index] & 0x000000FF);
if (n < 0) {
// Something went wrong, simply return 0 (failure)
return 0;
}
// when an int is < 16 and is appended to os, its hex representation which starts
// with '0' does not get appended properly (the starting '0' does not get appended)
// thus append '0' first
if (( int )unquoted[index] < 16 ) {
os << '0';
}
os << std::hex << ( int )unquoted[index];
pos += 2;
}
std::basic_string<char> 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<char*>( 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<char*>( 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
size_t unquoted_len = ZSTR_LEN(unquoted);
const char *unquoted_str = ZSTR_VAL(unquoted);
sqlsrv_malloc_auto_ptr<char> quoted;
size_t quoted_len = (unquoted_len * 2) + 2; // each character will be converted to 2 hex digits and prepend '0x' to the result
quoted = reinterpret_cast<char*>(sqlsrv_malloc(quoted_len, sizeof(char), 1)); // include space for null terminator
memset(quoted, '\0', quoted_len + 1);
unsigned int pos = 0;
quoted[pos++] = '0';
quoted[pos++] = 'x';
char *p = quoted;
for (size_t index = 0; index < unquoted_len && unquoted_str[index] != '\0'; ++index) {
// On success, snprintf returns the total number of characters written
// On failure, a negative number is returned
// The generated string has a length of at most len - 1, so
// len is 3 (2 hex digits + 1)
// Requires "& 0x000000FF", or snprintf will translate "0x90" to "0xFFFFFF90"
int n = snprintf((char*)(p + pos), 3, "%02X", unquoted_str[index] & 0x000000FF);
if (n < 0) {
// Something went wrong, simply return NULL (failure)
return NULL;
}
pos += 2;
}
zend_string* zstr = zend_string_init(quoted, quoted_len, 0);
return zstr;
#endif
}
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 (use_national_char_set) {
quotes_needed = 3;
// The minimum number of single quotes needed is 2 -- the initial start and end quotes
// Add the letter N before the initial quote if the encoding is UTF8
int quotes_needed = (use_national_char_set) ? 3 : 2;
char c = '\'';
#if PHP_VERSION_ID < 80100
std::string tmp_str(unquoted, unquoted_len); // Copy all unquoted_len characters from unquoted
#else
size_t unquoted_len = ZSTR_LEN(unquoted);
const char *unquoted_str = ZSTR_VAL(unquoted);
std::string tmp_str(unquoted_str, unquoted_len); // Copy all unquoted_len characters from unquoted
#endif
std::size_t found = tmp_str.find(c); // Find the first single quote
while (found != std::string::npos) {
tmp_str.insert(found + 1, 1, c); // Insert an additional single quote
found = tmp_str.find(c, found + 2); // Find the next single quote
}
for ( size_t index = 0; index < unquoted_len; ++index ) {
if ( unquoted[index] == '\'' ) {
++quotes_needed;
}
size_t len = tmp_str.length();
#if PHP_VERSION_ID < 80100
*quoted_len = quotes_needed + len; // The new length should be number of quotes plus the length of tmp_str
*quoted = reinterpret_cast<char*>(sqlsrv_malloc(*quoted_len, sizeof(char), 1)); // include space for null terminator
memset(*quoted, '\0', *quoted_len + 1);
char *p = *quoted;
#else
sqlsrv_malloc_auto_ptr<char> quoted;
size_t quoted_len = quotes_needed + len; // length returned to the caller should not account for null terminator
quoted = reinterpret_cast<char*>(sqlsrv_malloc(quoted_len, sizeof(char), 1)); // include space for null terminator
memset(quoted, '\0', quoted_len + 1);
char *p = quoted;
#endif
size_t pos = 0;
if (use_national_char_set) { // Insert the letter N if the encoding is UTF8
*(p + (pos++)) = 'N';
}
*(p + (pos++)) = c; // Add the initial quote
tmp_str.copy(p + pos, len, 0); // Copy tmp_str to *quoted
pos += len;
*(p + pos) = c; // Add the end quote
*quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator.
*quoted = reinterpret_cast<char*>( 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 (use_national_char_set) {
( *quoted )[out_current++] = 'N';
}
// insert initial quote
( *quoted )[out_current++] = '\'';
for ( size_t index = 0; index < unquoted_len; ++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';
#if PHP_VERSION_ID < 80100
return 1;
#else
zend_string* zstr = zend_string_init(quoted, quoted_len, 0);
return zstr;
#endif
}
}
@ -1613,12 +1831,13 @@ pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( _Inout_ pdo_dbh_t *dbh
PDO_LOG_DBH_ENTRY;
sqlsrv_conn* driver_conn = reinterpret_cast<sqlsrv_conn*>( dbh->driver_data );
SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_get_driver_methods: driver_data object was NULL." );
CHECK_CUSTOM_ERROR( true, driver_conn, PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED ) {
return NULL;
}
return NULL; // to avoid a compiler warning
// As per documentation, simply return false if the method does not exist
// https://www.php.net/manual/en/function.is-callable.php
// But user can call PDO::errorInfo() to check the error message if necessary
CHECK_CUSTOM_WARNING_AS_ERROR(true, driver_conn, PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED);
return NULL; // return NULL for PDO to take care of the rest
}
namespace {

View file

@ -3,7 +3,7 @@
//
// Contents: initialization routines for PDO_SQLSRV
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -5,7 +5,7 @@
//
// Copyright Microsoft Corporation
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Implements the PDOStatement object for the PDO_SQLSRV
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -211,15 +211,6 @@ void set_stmt_encoding( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
}
}
// internal helper function to free meta data structures allocated
void meta_data_free( _Inout_ field_meta_data* meta )
{
if( meta->field_name ) {
meta->field_name.reset();
}
sqlsrv_free( meta );
}
zval convert_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _Inout_ void** in_val, _In_opt_ SQLLEN field_len )
{
zval out_zval;
@ -287,8 +278,13 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta
int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
_Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type );
int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno );
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
_Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees );
#else
int pdo_sqlsrv_stmt_get_col_data(_Inout_ pdo_stmt_t *stmt, _In_ int colno, _Inout_ zval *result, _Inout_ enum pdo_param_type *type);
#endif
int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val );
int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value );
int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value );
@ -324,7 +320,7 @@ void stmt_option_encoding:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option c
void stmt_option_direct_query:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->direct_query = ( zend_is_true( value_z )) ? true : false;
pdo_stmt->direct_query = zend_is_true(value_z);
}
void stmt_option_cursor_scroll_type:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
@ -341,13 +337,13 @@ void stmt_option_emulate_prepares:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_
void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false;
pdo_stmt->fetch_numeric = zend_is_true(value_z);
}
void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->fetch_datetime = ( zend_is_true( value_z )) ? true : false;
pdo_stmt->fetch_datetime = zend_is_true(value_z);
}
// log a function entry point
@ -362,9 +358,6 @@ void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_op
// 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;
@ -465,9 +458,10 @@ int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno)
// Set the precision
column_data->precision = core_meta_data->field_scale;
#if PHP_VERSION_ID < 80100
// Set the param_type
column_data->param_type = PDO_PARAM_ZVAL;
#endif
// store the field data for use by pdo_sqlsrv_stmt_get_col_data
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "Invalid driver statement in pdo_sqlsrv_stmt_describe_col" );
@ -563,8 +557,13 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt )
zend_hash_internal_pointer_reset(driver_stmt->placeholders);
#if PHP_VERSION_ID < 80100
query = stmt->active_query_string;
query_len = static_cast<unsigned int>(stmt->active_query_stringlen);
#else
query = ZSTR_VAL(stmt->active_query_string);
query_len = ZSTR_LEN(stmt->active_query_string);
#endif
}
// The query timeout setting is inherited from the corresponding connection attribute, but
@ -664,14 +663,23 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta
pdo_bound_param_data* bind_data = NULL;
if( !driver_stmt->bound_column_param_types ) {
driver_stmt->bound_column_param_types =
#if PHP_VERSION_ID < 80100
driver_stmt->bound_column_param_types =
reinterpret_cast<pdo_param_type*>( 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 );
#else
// TODO: possibly no longer need bound_column_param_types?? default to PDO_PARAM_STR???
driver_stmt->bound_column_param_types =
reinterpret_cast<pdo_param_type*>(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_STR);
#endif
}
for( long i = 0; i < stmt->column_count; ++i ) {
#if PHP_VERSION_ID < 80100
if (NULL== (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, i))) &&
(NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) {
@ -684,6 +692,15 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta
driver_stmt->bound_column_param_types[i] = bind_data->param_type;
bind_data->param_type = PDO_PARAM_ZVAL;
}
#else
if (NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, i))) &&
(NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) {
continue;
}
// TODO: possibly no longer need bound_column_param_types??
driver_stmt->bound_column_param_types[i] = bind_data->param_type;
#endif
}
}
@ -742,8 +759,13 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta
// freeing the memory.
// Return:
// 0 for failure, 1 for success.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
_Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees)
#else
int pdo_sqlsrv_stmt_get_col_data(_Inout_ pdo_stmt_t *stmt, _In_ int colno, _Inout_ zval *result_z, _Inout_ enum pdo_param_type *type)
#endif
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
@ -751,39 +773,44 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
try {
SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: pdo_stmt object was null" );
SQLSRV_ASSERT(stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: pdo_stmt object was null");
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: driver_data object was null" );
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
CHECK_CUSTOM_ERROR((colno < 0), driver_stmt, PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX ) {
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;
}
#if PHP_VERSION_ID < 80100
// Let PDO free the memory after use.
*caller_frees = 1;
* caller_frees = 1;
#endif
// translate the pdo type to a type the core layer understands
sqlsrv_phptype sqlsrv_php_type;
SQLSRV_ASSERT( colno >= 0 && colno < static_cast<int>( driver_stmt->current_meta_data.size()),
"Invalid column number in pdo_sqlsrv_stmt_get_col_data" );
SQLSRV_ASSERT(colno >= 0 && colno < static_cast<int>(driver_stmt->current_meta_data.size()),
"Invalid column number in pdo_sqlsrv_stmt_get_col_data");
// set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding
// save the php type for next use
sqlsrv_php_type = driver_stmt->sql_type_to_php_type(
static_cast<SQLINTEGER>(driver_stmt->current_meta_data[colno]->field_type),
static_cast<SQLUINTEGER>(driver_stmt->current_meta_data[colno]->field_size),
true);
static_cast<SQLINTEGER>(driver_stmt->current_meta_data[colno]->field_type),
static_cast<SQLUINTEGER>(driver_stmt->current_meta_data[colno]->field_size),
true);
driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
// 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]
);
#if PHP_VERSION_ID < 80100
if (stmt->bound_columns && driver_stmt->bound_column_param_types[colno] != PDO_PARAM_ZVAL) {
#else
if (stmt->bound_columns) {
#endif
sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type(driver_stmt,
driver_stmt->bound_column_param_types[colno]
);
pdo_bound_param_data* bind_data = NULL;
bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, colno));
@ -792,40 +819,41 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[colno].name));
}
if( bind_data != NULL && !Z_ISUNDEF(bind_data->driver_params) ) {
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 ) {
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 ) {
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();
throw pdo::PDOException();
}
sqlsrv_php_type.typeinfo.encoding = Z_LVAL( 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:
case SQLSRV_ENCODING_BINARY:
case SQLSRV_ENCODING_UTF8:
break;
default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, colno );
break;
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;
}
}
// save the php type for the bound column
driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
}
SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID;
core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast<void**>(ptr)),
reinterpret_cast<SQLLEN*>( len ), true, &sqlsrv_phptype_out );
#if PHP_VERSION_ID < 80100
core_sqlsrv_get_field(driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast<void**>(ptr)),
reinterpret_cast<SQLLEN*>(len), true, &sqlsrv_phptype_out);
if (ptr) {
zval* zval_ptr = reinterpret_cast<zval*>(sqlsrv_malloc(sizeof(zval)));
@ -833,7 +861,14 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
*ptr = reinterpret_cast<char*>(zval_ptr);
*len = sizeof(zval);
}
#else
SQLLEN len = 0;
void *ptr = NULL;
core_sqlsrv_get_field(driver_stmt, colno, sqlsrv_php_type, false, ptr, &len, true, &sqlsrv_phptype_out);
if (ptr) {
*result_z = convert_to_zval(driver_stmt, sqlsrv_phptype_out, &ptr, len);
}
#endif
return 1;
}
catch ( core::CoreException& ) {
@ -892,15 +927,15 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In
break;
case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE:
driver_stmt->fetch_numeric = ( zend_is_true( val )) ? true : false;
driver_stmt->fetch_numeric = zend_is_true(val);
break;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
driver_stmt->fetch_datetime = ( zend_is_true( val )) ? true : false;
driver_stmt->fetch_datetime = zend_is_true(val);
break;
case SQLSRV_ATTR_FORMAT_DECIMALS:
driver_stmt->format_decimals = ( zend_is_true( val )) ? true : false;
driver_stmt->format_decimals = zend_is_true(val);
break;
case SQLSRV_ATTR_DECIMAL_PLACES:
@ -908,7 +943,7 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In
break;
case SQLSRV_ATTR_DATA_CLASSIFICATION:
driver_stmt->data_classification = (zend_is_true(val)) ? true : false;
driver_stmt->data_classification = zend_is_true(val);
break;
default:
@ -1126,9 +1161,15 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno
&out_buff_len, &field_type_num );
add_assoc_string( return_value, "table", table_name );
#if PHP_VERSION_ID < 80100
if( stmt->columns && stmt->columns[colno].param_type == PDO_PARAM_ZVAL ) {
add_assoc_long( return_value, "pdo_type", pdo_type );
}
#else
if (stmt->columns) {
add_assoc_long(return_value, "pdo_type", pdo_type);
}
#endif
}
catch( core::CoreException& ) {
zval_ptr_dtor(return_value);
@ -1170,8 +1211,7 @@ int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt )
core_sqlsrv_next_result( static_cast<sqlsrv_stmt*>( stmt->driver_data ) );
// 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();
driver_stmt->clean_up_results_metadata();
// if there are no more result sets, return that it failed.
if( driver_stmt->past_next_result_end == true ) {
@ -1338,8 +1378,19 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
switch( php_out_type ) {
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_STREAM:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE );
{
zval *zv = &param->parameter;
if (Z_ISREF_P(zv)) {
ZVAL_DEREF(zv);
}
// Table-valued parameters are input-only
CHECK_CUSTOM_ERROR(Z_TYPE_P(zv) == IS_ARRAY, driver_stmt, SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY) {
throw pdo::PDOException();
}
// For other types, simply throw the following error
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
break;
}
case SQLSRV_PHPTYPE_INT:
column_size = SQLSRV_UNKNOWN_SIZE;
break;
@ -1404,7 +1455,7 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
// and bind the parameter
core_sqlsrv_bind_param( driver_stmt, static_cast<SQLUSMALLINT>( param->paramno ), direction, &(param->parameter) , php_out_type, encoding,
sql_type, column_size, decimal_digits );
sql_type, column_size, decimal_digits);
}
break;
// undo any work done by the core layer after the statement is executed
@ -1412,15 +1463,8 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
{
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
// skip column bindings
if( !param->is_param ) {
break;
}
core_sqlsrv_post_param( reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data ), param->paramno,
&(param->parameter) );
}
break;
case PDO_PARAM_EVT_FETCH_PRE:
break;

View file

@ -3,7 +3,7 @@
//
// Contents: Utility functions used by both connection or statement functions
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -383,7 +383,7 @@ pdo_error PDO_ERRORS[] = {
},
{
SQLSRV_ERROR_CE_DRIVER_REQUIRED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.", -78, false }
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server (or above) for %1!s!.", -78, true }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
@ -461,6 +461,42 @@ pdo_error PDO_ERRORS[] = {
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID,
{ IMSSP, (SQLCHAR*) "Invalid extended string type specified. PDO_ATTR_DEFAULT_STR_PARAM can be either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL.", -97, false}
},
{
SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating a string for Table-Valued Param %1!d! Column %2!d! to UTF-16: %3!s!", -98, true }
},
{
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
{ IMSSP, (SQLCHAR*) "An invalid type for Table-Valued Param %1!d! Column %2!d! was specified", -99, true }
},
{
SQLSRV_ERROR_TVP_FETCH_METADATA,
{ IMSSP, (SQLCHAR*) "Failed to get metadata for Table-Valued Param %1!d!", -100, true }
},
{
SQLSRV_ERROR_TVP_INVALID_INPUTS,
{ IMSSP, (SQLCHAR*) "Invalid inputs for Table-Valued Param %1!d!", -101, true }
},
{
SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME,
{ IMSSP, (SQLCHAR*) "Expect a non-empty string for a Type Name for Table-Valued Param %1!d!", -102, true }
},
{
SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE,
{ IMSSP, (SQLCHAR*) "For Table-Valued Param %1!d! the number of values in a row is expected to be %2!d!", -103, true }
},
{
SQLSRV_ERROR_TVP_STRING_KEYS,
{ IMSSP, (SQLCHAR*) "Associative arrays not allowed for Table-Valued Param %1!d!", -104, true }
},
{
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
{ IMSSP, (SQLCHAR*) "Expect an array for each row for Table-Valued Param %1!d!", -105, true }
},
{
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
{ IMSSP, (SQLCHAR*) "You cannot return data in a table-valued parameter. Table-valued parameters are input-only.", -106, false }
},
{ UINT_MAX, {} }
};

View file

@ -6,7 +6,7 @@
//
// Contents: Declarations for the extension
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -6,7 +6,7 @@
//
// Contents: Internal declarations for the extension
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Version resource
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -6,7 +6,7 @@
// Contents: Contains functions for handling Windows format strings
// and UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// Contents: Contains functions for handling Windows format strings
// and UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Contains functions for handling UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Contains functions for handling UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -48,9 +48,6 @@ const int INFO_BUFFER_LEN = 256;
// length for name of keystore used in CEKeyStoreData
const int MAX_CE_NAME_LEN = 260;
// processor architectures
const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" };
// ODBC driver names.
// the order of this list should match the order of DRIVER_VERSION enum
std::vector<std::string> CONNECTION_STRING_DRIVER_NAME{ "Driver={ODBC Driver 17 for SQL Server};", "Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};" };
@ -561,18 +558,16 @@ void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) c
// prepare our wide char query string
core::SQLPrepareW( stmt, reinterpret_cast<SQLWCHAR*>( wsql_string.get() ), wsql_len );
stmt->param_descriptions.clear();
// if AE is enabled, get meta data for all parameters before binding them
if( stmt->conn->ce_option.enabled ) {
SQLSMALLINT num_params;
core::SQLNumParams( stmt, &num_params);
for( int i = 0; i < num_params; i++ ) {
param_meta_data param;
core::SQLDescribeParam(stmt, i + 1, &(param.sql_type), &(param.column_size), &(param.decimal_digits), &(param.nullable));
core::SQLDescribeParam( stmt, i + 1, &( param.sql_type ), &( param.column_size ), &( param.decimal_digits ), &( param.nullable ) );
stmt->param_descriptions.push_back( param );
stmt->params_container.params_meta_ae.push_back(param);
}
}
}
@ -919,45 +914,30 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
// 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
// processor architectures
const char* PROCESSOR_ARCH[] = {"x86", "x64", "arm64"};
#ifdef _WIN32
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;
GetSystemInfo(&sys_info);
switch (sys_info.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_INTEL:
return PROCESSOR_ARCH[0];
case PROCESSOR_ARCHITECTURE_AMD64:
return PROCESSOR_ARCH[1];
default:
DIE("Unsupported Windows processor architecture.");
return NULL;
}
#elif defined(__arm64__)
return PROCESSOR_ARCH[2];
#elif defined(__x86_64__)
return PROCESSOR_ARCH[1];
#else
DIE("Unsupported processor architecture.");
return NULL;
#endif // !_WIN32
#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.
@ -1010,8 +990,8 @@ void load_azure_key_vault(_Inout_ sqlsrv_conn* conn)
char *akv_id = conn->ce_option.akv_id.get();
char *akv_secret = conn->ce_option.akv_secret.get();
unsigned int id_len = strnlen_s(akv_id);
unsigned int key_size = strnlen_s(akv_secret);
size_t id_len = strnlen_s(akv_id);
size_t key_size = strnlen_s(akv_secret);
configure_azure_key_vault(conn, AKV_CONFIG_FLAGS, conn->ce_option.akv_mode, 0);
configure_azure_key_vault(conn, AKV_CONFIG_PRINCIPALID, akv_id, id_len);

View file

@ -3,7 +3,7 @@
//
// Contents: common initialization routines shared by PDO and sqlsrv
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Result sets
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -6,7 +6,7 @@
//
// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -207,6 +207,7 @@ enum SQLSRV_PHPTYPE {
SQLSRV_PHPTYPE_STRING,
SQLSRV_PHPTYPE_DATETIME,
SQLSRV_PHPTYPE_STREAM,
SQLSRV_PHPTYPE_TABLE,
MAX_SQLSRV_PHPTYPE, // highest value for a php type
SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid
};
@ -1394,51 +1395,185 @@ struct param_meta_data
SQLULEN get_column_size() { return column_size; }
};
// holds the output parameter information. Strings also need the encoding and other information for
// after processing. Only integer, float, and strings are allowable output parameters.
struct sqlsrv_output_param {
zval* param_z;
// *** parameter struct used for SQLBindParameter ***
struct sqlsrv_param
{
SQLUSMALLINT param_pos; // 0-based - the position in the parameters of the statement
SQLSMALLINT direction;
SQLSMALLINT c_data_type;
SQLSMALLINT sql_data_type;
SQLULEN column_size;
SQLSMALLINT decimal_digits;
SQLPOINTER buffer;
SQLLEN buffer_length;
SQLLEN strlen_or_indptr;
int param_php_type;
SQLSRV_ENCODING encoding;
SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement
SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer
SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary
bool is_bool;
param_meta_data meta_data; // parameter meta data
bool was_null; // false by default - the original parameter was a NULL zval
zval placeholder_z; // A temp zval for binding any input parameter value, including simple data types, wide input string (UTF-16 buffer), the datetime strings, etc.
zval* param_ptr_z; // NULL by default - points to the original parameter or its reference
std::size_t num_bytes_read; // 0 by default - number of bytes processed so far (for an empty PHP stream, an empty string is sent to the server)
php_stream* param_stream; // NULL by default - used to send stream data from an input parameter to the server
sqlsrv_param(_In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT dir, _In_ SQLSRV_ENCODING enc, _In_ SQLSMALLINT sql_type, _In_ SQLULEN col_size, _In_ SQLSMALLINT dec_digits) :
c_data_type(0), buffer(NULL), buffer_length(0), strlen_or_indptr(0), param_pos(param_num), direction(dir), encoding(enc), sql_data_type(sql_type),
column_size(col_size), decimal_digits(dec_digits), param_php_type(0), was_null(false), param_ptr_z(NULL), num_bytes_read(0), param_stream(NULL)
{
ZVAL_UNDEF(&placeholder_z);
}
// string output param constructor
sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len ) :
param_z(p_z), encoding(enc), param_num(num), original_buffer_len(buffer_len), is_bool(false), php_out_type(SQLSRV_PHPTYPE_INVALID)
void copy_param_meta_ae(_Inout_ zval* param_z, _In_ param_meta_data& meta); // Only used when Always Encrypted is enabled
virtual ~sqlsrv_param(){ release_data(); }
virtual void release_data();
bool derive_string_types_sizes(_In_ zval* param_z);
bool preprocess_datetime_object(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
// The following methods change the member placeholder_z, and both will return false if conversions fail
bool convert_input_str_to_utf16(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
bool convert_datetime_to_string(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
virtual bool prepare_param(_In_ zval* param_ref, _Inout_ zval* param_z);
virtual void process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
virtual void process_null_param(_Inout_ zval* param_z);
virtual void process_bool_param(_Inout_ zval* param_z);
virtual void process_long_param(_Inout_ zval* param_z);
virtual void process_double_param(_Inout_ zval* param_z);
virtual void process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
virtual void process_resource_param(_Inout_ zval* param_z);
virtual void process_object_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
virtual void bind_param(_Inout_ sqlsrv_stmt* stmt);
// The following methods are used to supply data to the server via SQLPutData
virtual void init_data_from_zval(_Inout_ sqlsrv_stmt* stmt);
virtual bool send_data_packet(_Inout_ sqlsrv_stmt* stmt);
};
// *** output / inout parameter struct used for SQLBindParameter, inheriting sqlsrv_param ***
struct sqlsrv_param_inout : public sqlsrv_param
{
SQLSRV_PHPTYPE php_out_type; // Used to convert output param when necessary
bool was_bool; // false by default - the original parameter was a boolean zval
sqlsrv_stmt* stmt; // NULL by default - points to the statement object mainly for error processing
sqlsrv_param_inout(_In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT dir, _In_ SQLSRV_ENCODING enc, _In_ SQLSMALLINT sql_type,
_In_ SQLULEN col_size, _In_ SQLSMALLINT dec_digits, SQLSRV_PHPTYPE php_out_type) :
sqlsrv_param(param_num, dir, enc, sql_type, col_size, dec_digits),
php_out_type(php_out_type), was_bool(false), stmt(NULL)
{
}
// every other type output parameter constructor
sqlsrv_output_param( _In_ zval* p_z, _In_ int num, _In_ bool is_bool, _In_ SQLSRV_PHPTYPE php_out_type) :
param_z( p_z ),
encoding( SQLSRV_ENCODING_INVALID ),
param_num( num ),
original_buffer_len( -1 ),
is_bool( is_bool ),
php_out_type(php_out_type)
virtual ~sqlsrv_param_inout() { param_ptr_z = NULL; }
virtual void release_data() { param_ptr_z = NULL; }
virtual bool prepare_param(_In_ zval* param_ref, _Inout_ zval* param_z);
virtual void process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
virtual void process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
// Called when the output parameter is ready to be finalized, using the value stored in param_ptr_z
void finalize_output_value();
// Resize the output string buffer based on its properties and whether it is a numeric type
void resize_output_string_buffer(_Inout_ zval* param_z, _In_ bool is_numeric_type);
// A helper method called by finalize_output_value() to finalize output string parameters
void finalize_output_string();
};
// *** Table-valued parameter struct used for SQLBindParameter, inheriting sqlsrv_param
// *** A sqlsrv_param_tvp can be representing a table-valued parameter itself or one of
// *** its constituent columns. When it is a table-valued parameter, tvp_columns cannot
// *** be empty. When it is a TVP column, parent_tvp points to its table-valued parameter
// *** and tvp_columns must be empty. The member param_pos refers to the ordinal position
// *** of this column in the corresponding table type.
struct sqlsrv_param_tvp : public sqlsrv_param
{
std::map<SQLUSMALLINT, sqlsrv_param_tvp*> tvp_columns; // The constituent columns of the table-valued parameter
sqlsrv_param_tvp* parent_tvp; // For a TVP column to reference to the table-valued parameter. NULL if this is the TVP itself.
int num_rows; // The total number of rows
int current_row; // A counter to keep track of which row is to be processed
sqlsrv_param_tvp(_In_ SQLUSMALLINT param_num, _In_ SQLSRV_ENCODING enc, _In_ SQLSMALLINT sql_type, _In_ SQLULEN col_size, _In_ SQLSMALLINT dec_digits, _In_ sqlsrv_param_tvp* tvp) :
sqlsrv_param(param_num, SQL_PARAM_INPUT, enc, sql_type, col_size, dec_digits), num_rows(0), current_row(0), parent_tvp(tvp)
{
ZVAL_UNDEF(&placeholder_z);
}
virtual ~sqlsrv_param_tvp() { release_data(); }
virtual void release_data();
virtual void bind_param(_Inout_ sqlsrv_stmt* stmt);
virtual void process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
// The following methods are used to supply data to the server post execution
virtual void init_data_from_zval(_Inout_ sqlsrv_stmt* stmt) {}
virtual bool send_data_packet(_Inout_ sqlsrv_stmt* stmt);
// Change the column encoding based on the sql data type
static void sql_type_to_encoding(_In_ SQLSMALLINT sql_type, _Inout_ SQLSRV_ENCODING* encoding);
// The following methods are only applicable to a table-valued parameter or its individual columns
int parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
void get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ zend_string* table_type_name, _In_ zend_string* schema_name);
void process_param_column_value(_Inout_ sqlsrv_stmt* stmt);
void process_null_param_value(_Inout_ sqlsrv_stmt* stmt);
void populate_cell_placeholder(_Inout_ sqlsrv_stmt* stmt, _In_ int ordinal);
void send_string_data_in_batches(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z);
};
// *** a container of all parameters used for SQLBindParameter ***
struct sqlsrv_params_container
{
std::vector<param_meta_data> params_meta_ae; // Empty by default - only used when Always Encrypted is enabled
std::map<SQLUSMALLINT, sqlsrv_param*> input_params; // map of pointers to the input params with their ordinal positions as keys
std::map<SQLUSMALLINT, sqlsrv_param*> output_params; // map of pointers to the output / inout params with their ordinal positions as keys
sqlsrv_param* current_param; // Null by default - points to a sqlsrv_param object used for sending stream data
sqlsrv_params_container() { current_param = NULL; }
~sqlsrv_params_container() { params_meta_ae.clear(); clean_up_param_data(); }
sqlsrv_param* find_param(_In_ SQLUSMALLINT param_num, _In_ bool is_input);
void insert_param(_In_ SQLUSMALLINT param_num, _In_ sqlsrv_param* new_param)
{
if (new_param->direction == SQL_PARAM_INPUT) {
input_params[param_num] = new_param;
} else {
output_params[param_num] = new_param;
}
}
void saveMetaData(SQLSMALLINT sql_type, SQLSMALLINT column_size, SQLSMALLINT decimal_digits, SQLSMALLINT nullable = SQL_NULLABLE)
void remove_params(std::map<SQLUSMALLINT, sqlsrv_param*>& params_map)
{
meta_data.sql_type = sql_type;
meta_data.column_size = column_size;
meta_data.decimal_digits = decimal_digits;
meta_data.nullable = nullable;
std::map<SQLUSMALLINT, sqlsrv_param*>::iterator it1;
for (it1 = params_map.begin(); it1 != params_map.end(); ++it1) {
sqlsrv_param* ptr = it1->second;
if (ptr) {
ptr->release_data();
sqlsrv_free(ptr);
}
}
params_map.clear();
}
param_meta_data& getMetaData()
void clean_up_param_data(_In_opt_ bool only_input = false);
void finalize_output_parameters();
// The following functions are used to supply data to the server post execution
bool get_next_parameter(_Inout_ sqlsrv_stmt* stmt);
bool send_next_packet(_Inout_ sqlsrv_stmt* stmt);
void send_all_packets(_Inout_ sqlsrv_stmt* stmt)
{
return meta_data;
while (get_next_parameter(stmt)) {
while (current_param->send_data_packet(stmt)) {}
}
}
};
namespace data_classification {
const int VERSION_RANK_AVAILABLE = 2; // Rank info is available when data classification version is 2+
const size_t VERSION_RANK_AVAILABLE = 2; // Rank info is available when data classification version is 2+
const int RANK_NOT_DEFINED = -1;
// *** data classficiation metadata structures and helper methods -- to store and/or process the sensitivity classification data ***
struct name_id_pair;
@ -1522,6 +1657,10 @@ struct sqlsrv_stmt : public sqlsrv_context {
// free sensitivity classification metadata
void clean_up_sensitivity_metadata();
// free resultset metadata
void clean_up_results_metadata();
// set query timeout
void set_query_timeout();
@ -1544,23 +1683,12 @@ struct sqlsrv_stmt : public sqlsrv_context {
short decimal_places; // indicates number of decimals shown in fetched results (-1 by default, which means no change to number of decimal digits)
bool data_classification; // false by default but the user can set this to true to retrieve data classification sensitivity metadata
// 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<SQLLEN> 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 col_cache; // Used by get_field_as_string not to call SQLColAttribute() after every fetch.
zval active_stream; // the currently active stream reading data from the database
std::vector<param_meta_data> param_descriptions;
sqlsrv_params_container params_container; // holds all parameters and references used for SQLBindParameter
// meta data for current result set
std::vector<field_meta_data*, sqlsrv_allocator<field_meta_data*>> current_meta_data;
@ -1630,7 +1758,7 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm
_In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver );
void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z,
_In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size,
_Inout_ SQLSMALLINT decimal_digits );
_Inout_ SQLSMALLINT decimal_digits);
SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql = NULL, _In_ int sql_len = 0 );
field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno );
bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLULEN fetch_offset );
@ -1639,11 +1767,9 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i
_Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out);
bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt );
void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt, _In_ bool finalize_output_params = true, _In_ bool throw_on_errors = true );
void core_sqlsrv_post_param( _Inout_ sqlsrv_stmt* stmt, _In_ zend_ulong paramno, zval* param_z );
void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type );
void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z );
void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z );
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt );
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt, _In_opt_ bool get_all = false);
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z );
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit );
void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z);
@ -1889,6 +2015,15 @@ enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION,
SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE,
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE,
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
SQLSRV_ERROR_TVP_FETCH_METADATA,
SQLSRV_ERROR_TVP_INVALID_INPUTS,
SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME,
SQLSRV_ERROR_TVP_STRING_KEYS,
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE,
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
// Driver specific error codes starts from here.
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,

View file

@ -3,7 +3,7 @@
//
// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -94,42 +94,22 @@ 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.
// Only declarations are put here. Functions contain more explanations they need in their definitions
void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size );
size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) const char* buffer, _In_ size_t buffer_end );
bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt );
bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* convert_param_z );
void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype
sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len);
// returns the ODBC C type constant that matches the PHP type and encoding given
SQLSMALLINT default_c_type(_Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSMALLINT sql_type, _In_ SQLSRV_ENCODING encoding);
void default_sql_size_and_scale( _Inout_ sqlsrv_stmt* stmt, _In_opt_ unsigned int paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding,
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits );
// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate)
void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding,
_Out_ SQLSMALLINT& sql_type );
void col_cache_dtor( _Inout_ zval* data_z );
void field_cache_dtor( _Inout_ zval* data_z );
int round_up_decimal_numbers(_Inout_ char* buffer, _In_ int decimal_pos, _In_ int decimals_places, _In_ int offset, _In_ int lastpos);
void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len);
void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt, _In_opt_ bool exception_thrown = false );
void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type,
_Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len );
stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] );
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
// assure there is enough space for the output parameter string
void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z, _In_ SQLULEN paramno, SQLSRV_ENCODING encoding,
_In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _In_ SQLSMALLINT decimal_digits,
_Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len );
void adjustDecimalPrecision(_Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits);
void save_output_param_for_later( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_output_param& param );
// send all the stream data
void send_param_streams( _Inout_ sqlsrv_stmt* stmt );
// called when a bound output string parameter is to be destroyed
void sqlsrv_output_param_dtor( _Inout_ zval* data );
// called when a bound stream parameter is to be destroyed.
void sqlsrv_stream_dtor( _Inout_ zval* data );
bool is_a_numeric_type(_In_ SQLSMALLINT sql_type);
bool is_a_string_type(_In_ SQLSMALLINT sql_type);
}
// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier.
@ -152,32 +132,16 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error
decimal_places(NO_CHANGE_DECIMAL_PLACES), // the default is no formatting to resultset required
data_classification(false),
buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ),
param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte
send_streams_at_exec( true ),
current_stream( NULL, SQLSRV_ENCODING_DEFAULT ),
current_stream_read( 0 )
send_streams_at_exec( true )
{
ZVAL_UNDEF( &active_stream );
// initialize the input string parameters array (which holds zvals)
array_init(&param_input_strings);
// initialize the (input only) stream parameters (which holds sqlsrv_stream structures)
ZVAL_NEW_ARR( &param_streams );
core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/);
// initialize the (input only) datetime parameters of converted date time objects to strings
array_init( &param_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*/);
// initialize the col cache
ZVAL_NEW_ARR( &col_cache );
array_init(&col_cache);
core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL(col_cache), 5 /* # of buckets */, col_cache_dtor, 0 /*persistent*/ );
// initialize the field cache
ZVAL_NEW_ARR( &field_cache );
array_init(&field_cache);
core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/);
}
@ -198,11 +162,10 @@ sqlsrv_stmt::~sqlsrv_stmt( void )
// delete sensivity data
clean_up_sensitivity_metadata();
// clean up metadata
clean_up_results_metadata();
invalidate();
zval_ptr_dtor( &param_input_strings );
zval_ptr_dtor( &output_params );
zval_ptr_dtor( &param_streams );
zval_ptr_dtor( &param_datetime_buffers );
zval_ptr_dtor( &col_cache );
zval_ptr_dtor( &field_cache );
}
@ -213,12 +176,8 @@ sqlsrv_stmt::~sqlsrv_stmt( void )
// execution phase.
void sqlsrv_stmt::free_param_data( void )
{
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 ));
params_container.clean_up_param_data();
zend_hash_clean( Z_ARRVAL( col_cache ));
zend_hash_clean( Z_ARRVAL( field_cache ));
}
@ -275,6 +234,22 @@ void sqlsrv_stmt::clean_up_sensitivity_metadata()
}
}
// internal helper function to free meta data structures allocated
void meta_data_free(_Inout_ field_meta_data* meta)
{
meta->field_name.reset();
sqlsrv_free(meta);
}
void sqlsrv_stmt::clean_up_results_metadata()
{
std::for_each(current_meta_data.begin(), current_meta_data.end(), meta_data_free);
current_meta_data.clear();
column_count = ACTIVE_NUM_COLS_INVALID;
row_count = ACTIVE_NUM_ROWS_INVALID;
}
void sqlsrv_stmt::set_query_timeout()
{
if (query_timeout == QUERY_TIMEOUT_INVALID || query_timeout < 0) {
@ -371,7 +346,6 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm
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.
@ -389,380 +363,74 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm
void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z,
_In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size,
_Inout_ SQLSMALLINT decimal_digits )
_Inout_ SQLSMALLINT decimal_digits)
{
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 ){
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<size_t>( param_num + 1 )){
stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA );
}
SQLLEN& ind_ptr = stmt->param_ind_ptrs[param_num];
// Dereference the parameter if necessary
zval* param_ref = param_z;
if( Z_ISREF_P( param_z )){
ZVAL_DEREF( 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 );
sqlsrv_param* param_ptr = stmt->params_container.find_param(param_num, (direction == SQL_PARAM_INPUT));
try {
if (param_ptr == NULL) {
sqlsrv_malloc_auto_ptr<sqlsrv_param> new_param;
if (direction == SQL_PARAM_INPUT) {
// Check if it's a Table-Valued Parameter first
if (Z_TYPE_P(param_z) == IS_ARRAY) {
new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param_tvp))) sqlsrv_param_tvp(param_num, encoding, SQL_SS_TABLE, 0, 0, NULL);
} else {
new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param))) sqlsrv_param(param_num, direction, encoding, sql_type, column_size, decimal_digits);
}
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 the user specifies a certain type for an output parameter, we have to convert the zval
// to that type so that when the buffer is filled, the type is correct. But first,
// should check if a LOB type is specified.
CHECK_CUSTOM_ERROR( direction != SQL_PARAM_INPUT && ( sql_type == SQL_LONGVARCHAR
|| sql_type == SQL_WLONGVARCHAR || sql_type == SQL_LONGVARBINARY ),
stmt, SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED ){
throw core::CoreException();
}
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( stmt->conn->ce_option.enabled && ( sql_type == SQL_UNKNOWN_TYPE || column_size == SQLSRV_UNKNOWN_SIZE )){
// use the meta data only if the user has not specified the sql type or column size
SQLSRV_ASSERT( param_num < stmt->param_descriptions.size(), "Invalid param_num passed in core_sqlsrv_bind_param!" );
sql_type = stmt->param_descriptions[param_num].get_sql_type();
column_size = stmt->param_descriptions[param_num].get_column_size();
decimal_digits = stmt->param_descriptions[param_num].get_decimal_digits();
// change long to double if the sql type is decimal
if(( sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC ) && Z_TYPE_P(param_z) == IS_LONG )
convert_to_double( param_z );
}
else{
// 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 );
} else if (direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT) {
new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param_inout))) sqlsrv_param_inout(param_num, direction, encoding, sql_type, column_size, decimal_digits, php_out_type);
} else {
SQLSRV_ASSERT(false, "sqlsrv_params_container::insert_param - Invalid parameter direction.");
}
stmt->params_container.insert_param(param_num, new_param);
param_ptr = new_param;
new_param.transferred();
}
// 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<unsigned int>(param_num), param_z, encoding, column_size, decimal_digits );
}
}
// determine the ODBC C type
c_type = default_c_type(stmt, param_num, param_z, sql_type, encoding);
SQLSRV_ASSERT(param_ptr != NULL, "core_sqlsrv_bind_param: param_ptr is null. Something went wrong.");
// 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 = &param_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<int>( param_num ), zval_was_bool, php_out_type);
save_output_param_for_later( stmt, output_param );
}
}
break;
case IS_DOUBLE:
{
buffer = &param_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<int>( param_num ), zval_was_bool, php_out_type);
save_output_param_for_later( stmt, output_param );
}
}
break;
case IS_STRING:
{
// With AE, the precision of the decimal or numeric inputs have to match exactly as defined in the columns.
// Without AE, the derived default sql types will not be this specific. Thus, if sql_type is SQL_DECIMAL
// or SQL_NUMERIC, the user must have clearly specified it (using the SQLSRV driver) as SQL_DECIMAL or SQL_NUMERIC.
// In either case, the input passed into SQLBindParam requires matching scale (i.e., number of decimal digits).
if (sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC) {
adjustDecimalPrecision(param_z, decimal_digits);
}
buffer = Z_STRVAL_P( param_z );
buffer_len = Z_STRLEN_P( param_z );
bool is_numeric = is_a_numeric_type(sql_type);
// 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 && !is_numeric){
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 );
add_index_zval(&(stmt->param_input_strings), param_num, &wbuffer_z);
}
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<const char*>(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, decimal_digits,
buffer, buffer_len );
// save the parameter to be adjusted and/or converted after the results are processed
// no need to use wide chars for numeric types
SQLSRV_ENCODING enc = (is_numeric) ? SQLSRV_ENCODING_CHAR : encoding;
sqlsrv_output_param output_param(param_ref, enc, param_num, static_cast<SQLUINTEGER>(buffer_len));
output_param.saveMetaData(sql_type, column_size, decimal_digits);
save_output_param_for_later( stmt, output_param );
// 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 column encryption is enabled, at this point the correct column size has been set by SQLDescribeParam
if( direction == SQL_PARAM_OUTPUT && !stmt->conn->ce_option.enabled ){
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) );
buffer = reinterpret_cast<SQLPOINTER>( 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 );
while( class_entry != NULL ){
SQLSRV_ASSERT( class_entry->name != NULL, "core_sqlsrv_bind_param: class_entry->name is 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 ){
bool result = param_ptr->prepare_param(param_ref, param_z);
if (!result && direction == SQL_PARAM_INPUT_OUTPUT) {
CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, 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<char*>( DateTime::DATETIMEOFFSET_FORMAT ),
DateTime::DATETIMEOFFSET_FORMAT_LEN );
}
else if( sql_type == SQL_TYPE_DATE ){
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN );
}
else{
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( 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 );
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 );
// When calling SQLDescribeParam() on a parameter targeting a Datetime column, the return values for ParameterType, ColumnSize and DecimalDigits are SQL_TYPE_TIMESTAMP, 23, and 3 respectively.
// For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error.
// This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns.
// To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column.
// Note this must be called after SQLBindParameter() or SQLSetDescField() may fail.
// TODO: how to correctly distinguish datetime from datetime2(3)? Both have the same decimal_digits and column_size
if (stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP) {
if (decimal_digits == 3) {
core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER);
} else if (decimal_digits == 0 && column_size == 16) {
core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER);
// If Always Encrypted is enabled, transfer the known param meta data if applicable, which might alter param_z for decimal types
if (stmt->conn->ce_option.enabled) {
if (param_ptr->sql_data_type == SQL_UNKNOWN_TYPE || param_ptr->column_size == SQLSRV_UNKNOWN_SIZE) {
// meta data parameters are always sorted based on parameter number
param_ptr->copy_param_meta_ae(param_z, stmt->params_container.params_meta_ae[param_num]);
}
}
// Get all necessary values to prepare for SQLBindParameter
param_ptr->process_param(stmt, param_z);
param_ptr->bind_param(stmt);
// When calling SQLDescribeParam() on a parameter targeting a Datetime column, the return values for ParameterType, ColumnSize and DecimalDigits are SQL_TYPE_TIMESTAMP, 23, and 3 respectively.
// For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error.
// This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns.
// To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column.
// Note this must be called after SQLBindParameter() or SQLSetDescField() may fail.
// VSO BUG 2693: how to correctly distinguish datetime from datetime2(3)? Both have the same decimal_digits and column_size
if (stmt->conn->ce_option.enabled && param_ptr->sql_data_type == SQL_TYPE_TIMESTAMP) {
if (param_ptr->decimal_digits == 3) {
core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER);
} else if (param_ptr->decimal_digits == 0 && param_ptr->column_size == 16) {
core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER);
}
}
}
}
catch( core::CoreException& e ){
stmt->free_param_data();
@ -771,7 +439,6 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
}
}
// core_sqlsrv_execute
// Executes the statement previously prepared
// Parameters:
@ -814,8 +481,7 @@ SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_l
// 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 );
core_sqlsrv_send_stream_packet(stmt, true);
}
stmt->new_result_set();
@ -823,23 +489,16 @@ SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_l
// 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 ))) {
stmt->params_container.finalize_output_parameters();
}
finalize_output_parameters( stmt );
}
// stream parameters are sent, clean the Hashtable
if ( stmt->send_streams_at_exec ) {
zend_hash_clean( Z_ARRVAL( stmt->param_streams ));
}
return r;
}
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 ) {
finalize_output_parameters( stmt, true );
zend_hash_clean( Z_ARRVAL( stmt->param_streams ));
}
// we need to remove all the parameters and cancel the statement
stmt->params_container.clean_up_param_data();
if( stmt->executed ) {
SQLCancel( stmt->handle() );
// stmt->executed = false; should this be reset if something fails?
@ -867,6 +526,10 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient
"core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." );
try {
// first check if the end of all results has been reached
CHECK_CUSTOM_ERROR(stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END) {
throw core::CoreException();
}
// clear the field cache of the previous fetch
zend_hash_clean( Z_ARRVAL( stmt->field_cache ));
@ -1307,7 +970,7 @@ void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt, _In_ bool finalize_outp
if( finalize_output_params ) {
// if we're finished processing result sets, handle the output parameters
finalize_output_parameters( stmt );
stmt->params_container.finalize_output_parameters();
}
// mark we are past the end of all results
@ -1324,34 +987,6 @@ void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt, _In_ bool finalize_outp
}
}
// 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( _Inout_ sqlsrv_stmt* stmt, _In_ zend_ulong param_num, zval* param_z )
{
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 );
}
// 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 );
}
}
//Calls SQLSetStmtAttr to set a cursor.
void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type )
{
@ -1461,136 +1096,42 @@ void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_
}
}
void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
// 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.
// given. Any errors that occur will be thrown.
// Parameters:
// stmt - query to send the next packet for
// get_all - send stream data all at once (false by default)
// Returns:
// true if more data remains to be sent, false if all data processed
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt )
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt, _In_opt_ bool get_all /*= false*/)
{
// 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 ) == false ) {
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR );
stmt->current_stream_read = 0;
return false;
}
}
bool bMore = 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 );
// if we're at the end, then reset both current_stream and current_stream_read
if (php_stream_eof(param_stream)) {
// yet return to the very beginning of param_stream since SQLParamData() may ask for the same data again
int ret = php_stream_seek(param_stream, 0, SEEK_SET);
if (ret != 0) {
LOG(SEV_ERROR, "PHP stream: stream seek failed.");
throw core::CoreException();
}
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] = {'\0'};
std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character
std::size_t read = php_stream_read( param_stream, buffer, buffer_size );
if (read > UINT_MAX)
{
LOG(SEV_ERROR, "PHP stream: buffer length exceeded.");
throw core::CoreException();
if (get_all) {
// send stream data all at once (so no more after this)
stmt->params_container.send_all_packets(stmt);
} else {
bMore = stmt->params_container.send_next_packet(stmt);
}
stmt->current_stream_read += static_cast<unsigned int>( read );
if (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);
if (!bMore) {
// All resources parameters are sent, so it's time to clean up
stmt->params_container.clean_up_param_data(true);
}
else 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] = {L'\0'};
int wbuffer_size = static_cast<int>( 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<int>(read), wbuffer, wbuffer_size, &last_error_code );
#else
int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, buffer, static_cast<int>( 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 );
// read the missing bytes
size_t new_read = php_stream_read( param_stream, static_cast<char*>( 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<int>(read + new_read), wbuffer, static_cast<int>(sizeof( wbuffer ) / sizeof( SQLWCHAR )));
#else
wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, buffer, static_cast<int>( read + new_read ), wbuffer, static_cast<int>( 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 ) );
}
else {
core::SQLPutData( stmt, buffer, read );
}
}
}
}
catch( core::CoreException& e ) {
} catch (core::CoreException& e) {
stmt->free_param_data();
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
SQLCancel( stmt->handle() );
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT );
stmt->current_stream_read = 0;
SQLFreeStmt(stmt->handle(), SQL_RESET_PARAMS);
SQLCancel(stmt->handle());
throw e;
}
return true;
return bMore;
}
void stmt_option_functor::operator()( _Inout_ sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, _In_ zval* /*value_z*/ )
@ -1606,7 +1147,8 @@ void stmt_option_query_timeout:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt
void stmt_option_send_at_exec:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
core_sqlsrv_set_send_at_exec( stmt, value_z );
// zend_is_true does not fail. It either returns true or false.
stmt->send_streams_at_exec = (zend_is_true(value_z));
}
void stmt_option_buffered_query_limit:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
@ -1616,22 +1158,12 @@ void stmt_option_buffered_query_limit:: operator()( _Inout_ sqlsrv_stmt* stmt, s
void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z )
{
if (zend_is_true(value_z)) {
stmt->date_as_string = true;
}
else {
stmt->date_as_string = false;
}
stmt->date_as_string = zend_is_true(value_z);
}
void stmt_option_format_decimals:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z )
{
if (zend_is_true(value_z)) {
stmt->format_decimals = true;
}
else {
stmt->format_decimals = false;
}
stmt->format_decimals = zend_is_true(value_z);
}
void stmt_option_decimal_places:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z )
@ -1641,12 +1173,7 @@ void stmt_option_decimal_places:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_op
void stmt_option_data_classification:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z )
{
if (zend_is_true(value_z)) {
stmt->data_classification = true;
}
else {
stmt->data_classification = false;
}
stmt->data_classification = zend_is_true(value_z);
}
// internal function to release the active stream. Called by each main API function
@ -1713,6 +1240,28 @@ bool is_a_numeric_type(_In_ SQLSMALLINT sql_type)
return false;
}
bool is_a_string_type(_In_ SQLSMALLINT sql_type)
{
switch (sql_type) {
case SQL_BIGINT:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_SS_VARIANT:
case SQL_SS_UDT:
case SQL_GUID:
case SQL_SS_XML:
case SQL_CHAR:
case SQL_WCHAR:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
return true;
}
return false;
}
void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size )
{
try {
@ -1770,10 +1319,8 @@ void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index,
}
}
// 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( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) const char* buffer, _In_ size_t buffer_end )
{
const char* last_char = buffer + buffer_end - 1;
@ -1807,7 +1354,6 @@ size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) cons
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
@ -1986,323 +1532,6 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i
}
}
// 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 )
{
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<SQLPOINTER*>( &stream_index ) );
// 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<sqlsrv_stream*>(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( _In_ zval* input_param_z, _Inout_ 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;
}
#ifndef _WIN32
// Declare wchar_size to be the largest possible number of UTF-16 characters after
// conversion, to avoid the performance penalty of calling ToUtf16
wchar_size = buffer_len;
#else
// Calculate the size of the necessary buffer from the length of the string -
// no performance penalty because MultiByteToWidechar is highly optimised
wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ), static_cast<int>( 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<SQLWCHAR> wbuffer;
wbuffer = reinterpret_cast<SQLWCHAR*>( sqlsrv_malloc( (wchar_size + 1) * sizeof( SQLWCHAR ) ));
// convert the utf-8 string to a wchar string in the new buffer
#ifndef _WIN32
int rc = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast<LPCSTR>( buffer ), static_cast<int>( buffer_len ), wbuffer, wchar_size );
#else
int rc = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ), static_cast<int>( buffer_len ), wbuffer, wchar_size );
#endif // !_WIN32
// if there was a problem converting the string, then free the memory and return false
if( rc == 0 ) {
return false;
}
wchar_size = rc;
// 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<char*>( 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( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSMALLINT sql_type, _In_ SQLSRV_ENCODING encoding )
{
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:
// When binding any integer, the zend_long value and its length are used as the buffer
// and buffer length. When the buffer is 8 bytes use the corresponding C type for
// 8-byte integers
#ifdef ZEND_ENABLE_ZVAL_LONG64
sql_c_type = SQL_C_SBIGINT;
#else
sql_c_type = SQL_C_SLONG;
#endif
break;
case IS_DOUBLE:
sql_c_type = SQL_C_DOUBLE;
break;
case IS_STRING:
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 = (is_a_numeric_type(sql_type)) ? SQL_C_CHAR : SQL_C_WCHAR;
break;
default:
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno);
break;
}
break;
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( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding,
_Out_ SQLSMALLINT& sql_type )
{
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( _Inout_ sqlsrv_stmt* stmt, _In_opt_ unsigned int paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding,
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits )
{
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 col_cache_dtor( _Inout_ zval* data_z )
{
col_cache* cache = static_cast<col_cache*>( Z_PTR_P( data_z ));
@ -2392,172 +1621,8 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f
*field_len = len;
}
// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output
// parameters will be present until all results are processed (since output parameters can depend on results
// 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( _Inout_ sqlsrv_stmt* stmt, _In_opt_ bool exception_thrown /*= false*/ )
{
if (Z_ISUNDEF(stmt->output_params))
return;
// If an error occurs or an exception is thrown during an execution, the values of any output
// parameters or columns are undefined. Therefore, do not depend on them having any specific
// values, because the ODBC driver may or may not have modified them.
if (exception_thrown) {
zend_hash_clean(Z_ARRVAL(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;
try {
ZEND_HASH_FOREACH_KEY_PTR(params_ht, index, key, output_param_temp)
{
sqlsrv_output_param* output_param = static_cast<sqlsrv_output_param*>(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 == 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;
}
// 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();
}
// 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 (output_param->encoding == SQLSRV_ENCODING_BINARY) {
// 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.
if (str_len < output_param->original_buffer_len) {
str[str_len] = '\0';
}
core::sqlsrv_zval_stringl(value_z, str, str_len);
}
else {
param_meta_data metaData = output_param->getMetaData();
if (output_param->encoding != SQLSRV_ENCODING_CHAR) {
char* outString = NULL;
SQLLEN outLen = 0;
bool result = convert_string_from_utf16(output_param->encoding, reinterpret_cast<const SQLWCHAR*>(str), int(str_len / sizeof(SQLWCHAR)), &outString, outLen);
CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message())
{
throw core::CoreException();
}
if (stmt->format_decimals && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) {
format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, outString, &outLen);
}
core::sqlsrv_zval_stringl(value_z, outString, outLen);
sqlsrv_free(outString);
}
else {
if (stmt->format_decimals && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) {
format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, str, &str_len);
}
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<int>(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);
}
else if (output_param->php_out_type == SQLSRV_PHPTYPE_INT) {
// first check if its value is out of range
double dval = Z_DVAL_P(value_z);
if (dval > INT_MAX || dval < INT_MIN) {
CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED)
{
throw core::CoreException();
}
}
// if the output param is a boolean, still convert to
// a long integer first to take care of rounding
convert_to_long(value_z);
if (output_param->is_bool) {
convert_to_boolean(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();
}
catch (core::CoreException&) {
// empty the hash table due to exception caught
zend_hash_clean(Z_ARRVAL(stmt->output_params));
throw;
}
// empty the hash table since it's been processed
zend_hash_clean(Z_ARRVAL(stmt->output_params));
return;
}
void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type,
_Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len )
void get_field_as_string(_Inout_ sqlsrv_stmt *stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type,
_Inout_updates_bytes_(*field_len) void *&field_value, _Inout_ SQLLEN *field_len)
{
SQLRETURN r;
SQLSMALLINT c_type;
@ -2566,7 +1631,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
SQLLEN field_len_temp = 0;
SQLLEN sql_display_size = 0;
char* field_value_temp = NULL;
unsigned int intial_field_len = INITIAL_FIELD_STRING_LEN;
unsigned int initial_field_len = INITIAL_FIELD_STRING_LEN;
try {
@ -2605,222 +1670,144 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
if (sqlsrv_php_type.typeinfo.encoding == CP_UTF8 && !is_a_numeric_type(sql_field_type)) {
c_type = SQL_C_WCHAR;
extra = sizeof(SQLWCHAR);
sql_display_size = (sql_display_size * sizeof(SQLWCHAR));
}
}
// If this is a large type, then read the first few bytes to get the actual length from SQLGetData
// If this is a large type, then read the first chunk to get the actual length from SQLGetData
// The user may use "SET TEXTSIZE" to specify the size of varchar(max), nvarchar(max),
// varbinary(max), text, ntext, and image data returned by a SELECT statement.
// For varchar(max) and nvarchar(max), sql_display_size will be 0, regardless
if (sql_display_size == 0 || sql_display_size == INT_MAX ||
sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ||
(sql_display_size > SQL_SERVER_MAX_FIELD_SIZE &&
(sql_field_type == SQL_WLONGVARCHAR || sql_field_type == SQL_LONGVARCHAR || sql_field_type == SQL_LONGVARBINARY))) {
// For varbinary(max), varchar(max) and nvarchar(max), sql_display_size will be 0, regardless
if (sql_display_size == 0 ||
(sql_field_type == SQL_WLONGVARCHAR || sql_field_type == SQL_LONGVARCHAR || sql_field_type == SQL_LONGVARBINARY)) {
field_len_temp = intial_field_len;
SQLLEN initiallen = field_len_temp + extra;
field_value_temp = static_cast<char*>( 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*/ );
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] = {L'\0'};
SQLSMALLINT len = 0;
stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
// 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 = 0;
// 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 = intial_field_len;
do {
SQLLEN initial_field_len = field_len_temp;
// Double the size.
field_len_temp *= 2;
field_value_temp = static_cast<char*>( 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*/ );
// 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
);
}
} while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state ));
}
else {
// the real field length is returned here, thus no need to double the allocation size here, just have to
// allocate field_len_temp (which is the field length retrieved from the first SQLGetData
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
// We have already received intial_field_len size data.
field_len_temp -= intial_field_len;
// Get the rest of the data.
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + intial_field_len,
field_len_temp + extra, &dummy_field_len, true /*handle_warning*/ );
field_len_temp += intial_field_len;
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();
}
}
} // if( is_truncation_warning ( state ) )
else {
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
}
} // if( r == SQL_SUCCESS_WITH_INFO )
if (c_type == SQL_C_WCHAR) {
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( 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<char*>( sqlsrv_malloc( sql_display_size + extra + 1 ));
field_len_temp = initial_field_len;
field_value_temp = static_cast<char*>(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*/);
} else {
field_len_temp = sql_display_size;
field_value_temp = static_cast<char*>(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*/ );
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 (c_type == SQL_C_WCHAR) {
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( 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 (stmt->format_decimals && (sql_field_type == SQL_DECIMAL || sql_field_type == SQL_NUMERIC)) {
// number of decimal places only affect money / smallmoney fields
SQLSMALLINT decimal_places = (stmt->current_meta_data[field_index]->field_is_money_type) ? stmt->decimal_places : NO_CHANGE_DECIMAL_PLACES;
format_decimal_numbers(decimal_places, stmt->current_meta_data[field_index]->field_scale, field_value_temp, &field_len_temp);
}
} // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE )
else {
DIE( "Invalid sql_display_size" );
return; // to eliminate a warning
r = stmt->current_results->get_data(field_index + 1, c_type, field_value_temp, sql_display_size + extra, &field_len_temp, false /*handle_warning*/);
}
field_value = field_value_temp;
*field_len = field_len_temp;
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] = { L'\0' };
SQLSMALLINT len = 0;
stmt->current_results->get_diag_field(1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len);
if (is_truncated_warning(state)) {
SQLLEN chunk_field_len = 0;
// 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_len;
do {
SQLLEN buffer_len = field_len_temp;
// Double the size.
field_len_temp *= 2;
field_value_temp = static_cast<char*>(sqlsrv_realloc(field_value_temp, field_len_temp + extra + 1));
field_len_temp -= buffer_len;
// Get the rest of the data
r = stmt->current_results->get_data(field_index + 1, c_type, field_value_temp + buffer_len,
field_len_temp + extra, &chunk_field_len, false /*handle_warning*/);
// 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 (chunk_field_len != SQL_NO_TOTAL)
field_len_temp += chunk_field_len;
else
field_len_temp += buffer_len;
if (r == SQL_SUCCESS_WITH_INFO) {
core::SQLGetDiagField(stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len);
}
} while (r == SQL_SUCCESS_WITH_INFO && is_truncated_warning(state));
} // if (field_len_temp == SQL_NO_TOTAL)
else {
// The field length (or its estimate) is returned, thus no need to double the allocation size.
// Allocate field_len_temp (which is the field length retrieved from the first SQLGetData) but with some padding
// because there is a chance that the estimated field_len_temp is not accurate enough
SQLLEN buffer_len = 50;
field_value_temp = static_cast<char*>(sqlsrv_realloc(field_value_temp, field_len_temp + buffer_len + 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 + buffer_len, &chunk_field_len, false /*handle_warning*/);
field_len_temp = initial_field_len + chunk_field_len;
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
throw core::CoreException();
}
// Reallocate field_value_temp next
field_value_temp = static_cast<char*>(sqlsrv_realloc(field_value_temp, field_len_temp + extra + 1));
}
} // if (is_truncated_warning(state))
} // if (r == SQL_SUCCESS_WITH_INFO)
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
throw core::CoreException();
}
if (c_type == SQL_C_WCHAR) {
bool converted = convert_string_from_utf16_inplace(static_cast<SQLSRV_ENCODING>(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 (stmt->format_decimals && (sql_field_type == SQL_DECIMAL || sql_field_type == SQL_NUMERIC)) {
// number of decimal places only affect money / smallmoney fields
SQLSMALLINT decimal_places = (stmt->current_meta_data[field_index]->field_is_money_type) ? stmt->decimal_places : NO_CHANGE_DECIMAL_PLACES;
format_decimal_numbers(decimal_places, stmt->current_meta_data[field_index]->field_scale, field_value_temp, &field_len_temp);
}
// finalized the returned values and set field_len to 0 if field_len_temp is negative (which may happen with unixODBC connection pooling)
field_value = field_value_temp;
*field_len = (field_len_temp > 0) ? field_len_temp : 0;
// 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 )
{
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and add 1 to fill the null terminator
if (field_len_temp > 0) {
field_value_temp[field_len_temp] = '\0';
}
else
{
*field_len = 0;
}
}
catch( core::CoreException& ) {
catch (core::CoreException&) {
field_value = NULL;
*field_len = 0;
sqlsrv_free( field_value_temp );
sqlsrv_free(field_value_temp);
throw;
}
catch ( ... ) {
} catch (...) {
field_value = NULL;
*field_len = 0;
sqlsrv_free( field_value_temp );
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.
@ -2869,6 +1856,7 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type )
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_TABLE:
return true;
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM:
@ -2884,127 +1872,6 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type )
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( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z, _In_ SQLULEN paramno, SQLSRV_ENCODING encoding,
_In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _In_ SQLSMALLINT decimal_digits,
_Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len )
{
SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." );
buffer_len = Z_STRLEN_P( param_z );
SQLLEN original_len = buffer_len;
SQLLEN expected_len;
SQLLEN buffer_null_extra;
SQLLEN elem_size;
// 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 || 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
SQLULEN field_size = column_size;
// with AE on, when column_size is retrieved from SQLDescribeParam, column_size
// does not include the negative sign or decimal place for numeric values
// VSO Bug 2913: without AE, the same can happen as well, in particular to decimals
// and numerics with precision/scale specified
if (sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC || sql_type == SQL_BIGINT || sql_type == SQL_INTEGER || sql_type == SQL_SMALLINT) {
// include the possible negative sign
field_size += elem_size;
// include the decimal for output params by adding elem_size
if (decimal_digits > 0) {
field_size += elem_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;
// 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.
// initialize the newly allocated space
char *p = ZSTR_VAL(param_z_string);
p = p + original_len;
memset(p, '\0', expected_len - original_len);
ZVAL_NEW_STR(param_z, param_z_string);
// buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the
// 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( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_output_param& param )
{
HashTable* param_ht = Z_ARRVAL( stmt->output_params );
zend_ulong paramno = static_cast<zend_ulong>( param.param_num );
core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, &param, 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( _Inout_ sqlsrv_stmt* stmt )
{
while( core_sqlsrv_send_stream_packet( stmt )) { }
}
// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed
void sqlsrv_output_param_dtor( _Inout_ zval* data )
{
sqlsrv_output_param *output_param = static_cast<sqlsrv_output_param*>( 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( _Inout_ zval* data )
{
sqlsrv_stream* stream_encoding = static_cast<sqlsrv_stream*>( 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 );
}
void adjustDecimalPrecision(_Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits)
{
char* value = Z_STRVAL_P(param_z);
@ -3198,6 +2065,1576 @@ int round_up_decimal_numbers(_Inout_ char* buffer, _In_ int decimal_pos, _In_ in
// Do nothing and just return
return lastpos;
}
} // end of anonymous namespace
////////////////////////////////////////////////////////////////////////////////////////////////
//
// *** implementations of structures used for SQLBindParameter ***
//
void sqlsrv_param::release_data()
{
if (Z_TYPE(placeholder_z) == IS_STRING) {
zend_string_release(Z_STR(placeholder_z));
}
ZVAL_UNDEF(&placeholder_z);
buffer = NULL;
param_stream = NULL;
num_bytes_read = 0;
param_ptr_z = NULL;
}
void sqlsrv_param::copy_param_meta_ae(_Inout_ zval* param_z, _In_ param_meta_data& meta)
{
// Always Encrypted (AE) enabled - copy the meta data from SQLDescribeParam()
sql_data_type = meta.sql_type;
column_size = meta.column_size;
decimal_digits = meta.decimal_digits;
// Due to strict rules of AE, convert long to double if the sql type is decimal (numeric)
if (Z_TYPE_P(param_z) == IS_LONG && (sql_data_type == SQL_DECIMAL || sql_data_type == SQL_NUMERIC)) {
convert_to_double(param_z);
}
}
bool sqlsrv_param::prepare_param(_In_ zval* param_ref, _Inout_ zval* param_z)
{
// For input parameters, check if the original parameter was null
was_null = (Z_TYPE_P(param_z) == IS_NULL);
return true;
}
// Derives the ODBC C type constant that matches the PHP type and/or the encoding given
// If SQL type or column size is unknown, derives the appropriate values as well using the provided param zval and encoding
void sqlsrv_param::process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
// Get param php type
param_php_type = Z_TYPE_P(param_z);
switch (param_php_type) {
case IS_NULL:
process_null_param(param_z);
break;
case IS_TRUE:
case IS_FALSE:
process_bool_param(param_z);
break;
case IS_LONG:
process_long_param(param_z);
break;
case IS_DOUBLE:
process_double_param(param_z);
break;
case IS_STRING:
process_string_param(stmt, param_z);
break;
case IS_RESOURCE:
process_resource_param(param_z);
break;
case IS_OBJECT:
process_object_param(stmt, param_z);
break;
case IS_ARRAY:
default:
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_pos + 1);
break;
}
}
void sqlsrv_param::process_null_param(_Inout_ zval* param_z)
{
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
// 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.
sql_data_type = (encoding == SQLSRV_ENCODING_BINARY) ? SQL_BINARY : SQL_CHAR;
}
c_data_type = (encoding == SQLSRV_ENCODING_BINARY) ? SQL_C_BINARY : SQL_C_CHAR;
if (column_size == SQLSRV_UNKNOWN_SIZE) {
column_size = 1;
decimal_digits = 0;
}
buffer = NULL;
buffer_length = 0;
strlen_or_indptr = SQL_NULL_DATA;
}
void sqlsrv_param::process_bool_param(_Inout_ zval* param_z)
{
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
sql_data_type = SQL_INTEGER;
}
c_data_type = SQL_C_SLONG;
// The column size and decimal digits are by default 0
// Ignore column_size and decimal_digits because they will be inferred by ODBC
// Convert the lval to 0 or 1
convert_to_long(param_z);
buffer = &param_z->value;
buffer_length = sizeof(Z_LVAL_P(param_z));
strlen_or_indptr = buffer_length;
}
void sqlsrv_param::process_long_param(_Inout_ zval* param_z)
{
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
//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_data_type = SQL_BIGINT;
} else {
sql_data_type = SQL_INTEGER;
}
}
// When binding any integer, the zend_long value and its length are used as the buffer
// and buffer length. When the buffer is 8 bytes use the corresponding C type for
// 8-byte integers
#ifdef ZEND_ENABLE_ZVAL_LONG64
c_data_type = SQL_C_SBIGINT;
#else
c_data_type = SQL_C_SLONG;
#endif
// The column size and decimal digits are by default 0
// Ignore column_size and decimal_digits because they will be inferred by ODBC
buffer = &param_z->value;
buffer_length = sizeof(Z_LVAL_P(param_z));
strlen_or_indptr = buffer_length;
}
void sqlsrv_param::process_double_param(_Inout_ zval* param_z)
{
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
sql_data_type = SQL_FLOAT;
}
// The column size and decimal digits are by default 0
// Ignore column_size and decimal_digits because they will be inferred by ODBC
c_data_type = SQL_C_DOUBLE;
buffer = &param_z->value;
buffer_length = sizeof(Z_DVAL_P(param_z));
strlen_or_indptr = buffer_length;
}
bool sqlsrv_param::derive_string_types_sizes(_In_ zval* param_z)
{
SQLSRV_ASSERT(encoding == SQLSRV_ENCODING_CHAR || encoding == SQLSRV_ENCODING_UTF8 || encoding == SQLSRV_ENCODING_BINARY, "Invalid encoding in sqlsrv_param::derive_string_types_sizes");
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
switch (encoding) {
case SQLSRV_ENCODING_CHAR:
sql_data_type = SQL_VARCHAR;
break;
case SQLSRV_ENCODING_BINARY:
sql_data_type = SQL_VARBINARY;
break;
case SQLSRV_ENCODING_UTF8:
sql_data_type = SQL_WVARCHAR;
break;
default:
break;
}
}
bool is_numeric = is_a_numeric_type(sql_data_type);
// Derive the C Data type next
switch (encoding) {
case SQLSRV_ENCODING_CHAR:
c_data_type = SQL_C_CHAR;
break;
case SQLSRV_ENCODING_BINARY:
c_data_type = SQL_C_BINARY;
break;
case SQLSRV_ENCODING_UTF8:
c_data_type = is_numeric ? SQL_C_CHAR : SQL_C_WCHAR;
break;
default:
break;
}
// Derive the column size also only if it is unknown
if (column_size == SQLSRV_UNKNOWN_SIZE) {
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;
}
}
return is_numeric;
}
bool sqlsrv_param::convert_input_str_to_utf16(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
{
// This converts the string in param_z and stores the wide string in the member placeholder_z
char* str = Z_STRVAL_P(param_z);
SQLLEN str_length = Z_STRLEN_P(param_z);
if (str_length > 0) {
sqlsrv_malloc_auto_ptr<SQLWCHAR> wide_buffer;
unsigned int wchar_size = 0;
wide_buffer = utf16_string_from_mbcs_string(encoding, reinterpret_cast<const char*>(str), static_cast<int>(str_length), &wchar_size, true);
if (wide_buffer == 0) {
return false;
}
wide_buffer[wchar_size] = L'\0';
core::sqlsrv_zval_stringl(&placeholder_z, reinterpret_cast<char*>(wide_buffer.get()), wchar_size * sizeof(SQLWCHAR));
} else {
// If the string is empty, then nothing needs to be done
core::sqlsrv_zval_stringl(&placeholder_z, "", 0);
}
return true;
}
void sqlsrv_param::process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
bool is_numeric = derive_string_types_sizes(param_z);
// With AE, the precision of the decimal or numeric inputs have to match exactly as defined in the columns.
// Without AE, the derived default sql types will not be this specific. Thus, if sql_type is SQL_DECIMAL
// or SQL_NUMERIC, the user must have clearly specified it (using the SQLSRV driver) as SQL_DECIMAL or SQL_NUMERIC.
// In either case, the input passed into SQLBindParam requires matching scale (i.e., number of decimal digits).
if (sql_data_type == SQL_DECIMAL || sql_data_type == SQL_NUMERIC) {
adjustDecimalPrecision(param_z, decimal_digits);
}
if (!is_numeric && encoding == CP_UTF8) {
// Convert the input param value to wide string and save it for later
if (Z_STRLEN_P(param_z) > INT_MAX) {
LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded.");
throw core::CoreException();
}
// This changes the member placeholder_z to hold the wide string
bool converted = convert_input_str_to_utf16(stmt, param_z);
CHECK_CUSTOM_ERROR(!converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, param_pos + 1, get_last_error_message()) {
throw core::CoreException();
}
// Bind the wide string in placeholder_z
buffer = Z_STRVAL(placeholder_z);
buffer_length = Z_STRLEN(placeholder_z);
} else {
buffer = Z_STRVAL_P(param_z);
buffer_length = Z_STRLEN_P(param_z);
}
strlen_or_indptr = buffer_length;
}
void sqlsrv_param::process_resource_param(_Inout_ zval* param_z)
{
SQLSRV_ASSERT(encoding == SQLSRV_ENCODING_CHAR || encoding == SQLSRV_ENCODING_UTF8 || encoding == SQLSRV_ENCODING_BINARY, "Invalid encoding in sqlsrv_param::get_resource_param_info");
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
switch (encoding) {
case SQLSRV_ENCODING_CHAR:
sql_data_type = SQL_VARCHAR;
break;
case SQLSRV_ENCODING_BINARY:
sql_data_type = SQL_VARBINARY;
break;
case SQLSRV_ENCODING_UTF8:
sql_data_type = SQL_WVARCHAR;
break;
default:
break;
}
}
// The column_size will be inferred by ODBC unless it is SQLSRV_UNKNOWN_SIZE
if (column_size == SQLSRV_UNKNOWN_SIZE) {
column_size = 0;
}
switch (encoding) {
case SQLSRV_ENCODING_CHAR:
c_data_type = SQL_C_CHAR;
break;
case SQLSRV_ENCODING_BINARY:
c_data_type = SQL_C_BINARY;
break;
case SQLSRV_ENCODING_UTF8:
c_data_type = SQL_C_WCHAR;
break;
default:
break;
}
param_ptr_z = param_z;
buffer = reinterpret_cast<SQLPOINTER>(this);
buffer_length = 0;
strlen_or_indptr = SQL_DATA_AT_EXEC;
}
bool sqlsrv_param::convert_datetime_to_string(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
{
// This changes the member placeholder_z to hold the converted string of the datetime object
zval function_z;
zval format_z;
zval params[1];
ZVAL_UNDEF(&function_z);
ZVAL_UNDEF(&format_z);
ZVAL_UNDEF(params);
// 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_data_type == SQL_SS_TIMESTAMPOFFSET) {
ZVAL_STRINGL(&format_z, DateTime::DATETIMEOFFSET_FORMAT, DateTime::DATETIMEOFFSET_FORMAT_LEN);
} else if (sql_data_type == SQL_TYPE_DATE) {
ZVAL_STRINGL(&format_z, DateTime::DATE_FORMAT, DateTime::DATE_FORMAT_LEN);
} else {
ZVAL_STRINGL(&format_z, 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);
//core::sqlsrv_zval_stringl(&function_z, "format", sizeof("format") - 1);
params[0] = format_z;
// If placeholder_z is a string, release it first before assigning a new string value
if (Z_TYPE(placeholder_z) == IS_STRING && Z_STR(placeholder_z) != NULL) {
zend_string_release(Z_STR(placeholder_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, &placeholder_z, 1, params);
zend_string_release(Z_STR(format_z));
zend_string_release(Z_STR(function_z));
return (zr != FAILURE);
}
bool sqlsrv_param::preprocess_datetime_object(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
{
bool valid_class_name_found = false;
zend_class_entry *class_entry = Z_OBJCE_P(param_z);
while (class_entry != NULL) {
SQLSRV_ASSERT(class_entry->name != NULL, "sqlsrv_param::get_object_param_info -- class_entry->name is 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;
}
}
if (!valid_class_name_found) {
return false;
}
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
// For SQL Server 2005 or earlier, make it a SQLSRV_SQLTYPE_DATETIME.
// Otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET because these
// are the date types of the highest precision for the server
if (stmt->conn->server_version <= SERVER_VERSION_2005) {
sql_data_type = SQL_TYPE_TIMESTAMP;
} else {
sql_data_type = SQL_SS_TIMESTAMPOFFSET;
}
}
c_data_type = SQL_C_CHAR;
// Derive the column size also only if it is unknown
if (column_size == SQLSRV_UNKNOWN_SIZE) {
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;
}
}
return true;
}
void sqlsrv_param::process_object_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
// Assume the param refers to a DateTime object since it's the only type the drivers support.
// Verification occurs in the calling function as the drivers convert the DateTime object
// to a string before sending it to the server.
bool succeeded = preprocess_datetime_object(stmt, param_z);
if (succeeded) {
succeeded = convert_datetime_to_string(stmt, param_z);
}
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_pos + 1) {
throw core::CoreException();
}
buffer = Z_STRVAL(placeholder_z);
buffer_length = Z_STRLEN(placeholder_z) - 1;
strlen_or_indptr = buffer_length;
}
void sqlsrv_param::bind_param(_Inout_ sqlsrv_stmt* stmt)
{
if (was_null) {
strlen_or_indptr = SQL_NULL_DATA;
}
core::SQLBindParameter(stmt, param_pos + 1, direction, c_data_type, sql_data_type, column_size, decimal_digits, buffer, buffer_length, &strlen_or_indptr);
}
void sqlsrv_param::init_data_from_zval(_Inout_ sqlsrv_stmt* stmt)
{
// Get the stream from the param zval value
num_bytes_read = 0;
param_stream = NULL;
core::sqlsrv_php_stream_from_zval_no_verify(*stmt, param_stream, param_ptr_z);
}
bool sqlsrv_param::send_data_packet(_Inout_ sqlsrv_stmt* stmt)
{
// Check EOF first
if (php_stream_eof(param_stream)) {
// But return to the very beginning of param_stream since SQLParamData() may ask for the same data again
int ret = php_stream_seek(param_stream, 0, SEEK_SET);
if (ret != 0) {
LOG(SEV_ERROR, "PHP stream: stream seek failed.");
throw core::CoreException();
}
// Reset num_bytes_read
num_bytes_read = 0;
return false;
} else {
// Read the data from the stream, send it via SQLPutData and track how much is already sent.
char buffer[PHP_STREAM_BUFFER_SIZE + 1] = { '\0' };
std::size_t buffer_size = sizeof(buffer) - 3; // -3 to preserve enough space for a cut off UTF-8 character
std::size_t read = php_stream_read(param_stream, buffer, buffer_size);
if (read > UINT_MAX) {
LOG(SEV_ERROR, "PHP stream: buffer length exceeded.");
throw core::CoreException();
}
num_bytes_read += read;
if (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);
} else 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 (encoding == CP_UTF8) {
// The size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is an
// expansion of 2x the UTF-8 size.
SQLWCHAR wbuffer[PHP_STREAM_BUFFER_SIZE + 1] = { L'\0' };
int wbuffer_size = static_cast<int>(sizeof(wbuffer) / sizeof(SQLWCHAR));
DWORD last_error_code = ERROR_SUCCESS;
// The buffer_size is the # of wchars. Set to buffer_size / 2
#ifndef _WIN32
int wsize = SystemLocale::ToUtf16Strict(encoding, buffer, static_cast<int>(read), wbuffer, wbuffer_size, &last_error_code);
#else
int wsize = MultiByteToWideChar(encoding, MB_ERR_INVALID_CHARS, buffer, static_cast<int>(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);
// read the missing bytes
size_t new_read = php_stream_read(param_stream, static_cast<char*>(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(encoding, buffer, static_cast<int>(read + new_read), wbuffer, static_cast<int>(sizeof(wbuffer) / sizeof(SQLWCHAR)));
#else
wsize = MultiByteToWideChar(encoding, MB_ERR_INVALID_CHARS, buffer, static_cast<int>(read + new_read), wbuffer, static_cast<int>(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));
}
else {
core::SQLPutData(stmt, buffer, read);
} // NOT UTF8
} // read > 0
return true;
} // NOT EOF
}
bool sqlsrv_param_inout::prepare_param(_In_ zval* param_ref, _Inout_ zval* param_z)
{
// Save the output param reference now
param_ptr_z = param_ref;
int type = Z_TYPE_P(param_z);
was_null = (type == IS_NULL);
was_bool = (type == IS_TRUE || type == IS_FALSE);
if (direction == SQL_PARAM_INPUT_OUTPUT) {
// 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.
bool matched = false;
switch (php_out_type) {
case SQLSRV_PHPTYPE_INT:
if (was_null || was_bool) {
convert_to_long(param_z);
}
matched = (Z_TYPE_P(param_z) == IS_LONG);
break;
case SQLSRV_PHPTYPE_FLOAT:
if (was_null) {
convert_to_double(param_z);
}
matched = (Z_TYPE_P(param_z) == IS_DOUBLE);
break;
case SQLSRV_PHPTYPE_STRING:
// anything can be converted to a string
convert_to_string(param_z);
matched = true;
break;
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_STREAM:
default:
SQLSRV_ASSERT(false, "sqlsrv_param_inout::prepare_param -- invalid type for an output parameter.");
break;
}
return matched;
} else if (direction == SQL_PARAM_OUTPUT) {
// If the user specifies a certain type for an output parameter, we have to convert the zval
// to that type so that when the buffer is filled, the type is correct. But first,
// should check if a LOB type is specified.
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:
default:
SQLSRV_ASSERT(false, "sqlsrv_param_inout::prepare_param -- invalid type for an output parameter");
break;
}
return true;
} else {
SQLSRV_ASSERT(false, "sqlsrv_param_inout::prepare_param -- wrong param direction.");
}
return false;
}
// Derives the ODBC C type constant that matches the PHP type and/or the encoding given
// If SQL type or column size is unknown, derives the appropriate values as well using the provided param zval and encoding
void sqlsrv_param_inout::process_param(_Inout_ sqlsrv_stmt* stmt, zval* param_z)
{
// Get param php type NOW because the original parameter might have been converted beforehand
param_php_type = Z_TYPE_P(param_z);
switch (param_php_type) {
case IS_LONG:
process_long_param(param_z);
break;
case IS_DOUBLE:
process_double_param(param_z);
break;
case IS_STRING:
process_string_param(stmt, param_z);
break;
default:
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_pos + 1);
break;
}
// Save the pointer to the statement object
this->stmt = stmt;
}
void sqlsrv_param_inout::process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
bool is_numeric_type = derive_string_types_sizes(param_z);
buffer = Z_STRVAL_P(param_z);
buffer_length = Z_STRLEN_P(param_z);
if (ZSTR_IS_INTERNED(Z_STR_P(param_z))) {
// PHP 5.4 added interned strings, and since we obviously want to change that string here in some fashion,
// we reallocate the string if it's interned
core::sqlsrv_zval_stringl(param_z, static_cast<const char*>(buffer), buffer_length);
// reset buffer and its length
buffer = Z_STRVAL_P(param_z);
buffer_length = 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/NTEXT SQL type,
// convert it to wchar first
if (direction == SQL_PARAM_INPUT_OUTPUT &&
(c_data_type == SQL_C_WCHAR ||
(c_data_type == SQL_C_BINARY &&
(sql_data_type == SQL_WCHAR || sql_data_type == SQL_WVARCHAR || sql_data_type == SQL_WLONGVARCHAR)))) {
if (buffer_length > 0) {
sqlsrv_malloc_auto_ptr<SQLWCHAR> wide_buffer;
unsigned int wchar_size = 0;
wide_buffer = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, reinterpret_cast<const char*>(buffer), static_cast<int>(buffer_length), &wchar_size);
CHECK_CUSTOM_ERROR(wide_buffer == 0, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, param_pos + 1, get_last_error_message()) {
throw core::CoreException();
}
wide_buffer[wchar_size] = L'\0';
core::sqlsrv_zval_stringl(param_z, reinterpret_cast<char*>(wide_buffer.get()), wchar_size * sizeof(SQLWCHAR));
buffer = Z_STRVAL_P(param_z);
buffer_length = Z_STRLEN_P(param_z);
}
}
strlen_or_indptr = buffer_length;
// Since this is an output string, assure there is enough space to hold the requested size and
// update all the variables accordingly (param_z, buffer, buffer_length, and strlen_or_indptr)
resize_output_string_buffer(param_z, is_numeric_type);
if (is_numeric_type) {
encoding = SQLSRV_ENCODING_CHAR;
}
// 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 column encryption is enabled, at this point the correct column size has been set by SQLDescribeParam
if (direction == SQL_PARAM_OUTPUT && !stmt->conn->ce_option.enabled) {
switch (sql_data_type) {
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_WVARCHAR:
column_size = SQL_SS_LENGTH_UNLIMITED;
break;
default:
break;
}
}
}
// Called when the output parameter is ready to be finalized, using the value stored in param_ptr_z
void sqlsrv_param_inout::finalize_output_value()
{
if (param_ptr_z == NULL) {
return;
}
zval* value_z = Z_REFVAL_P(param_ptr_z);
switch (Z_TYPE_P(value_z)) {
case IS_STRING:
finalize_output_string();
break;
case IS_LONG:
// For a long or a float, simply check if NULL was returned and if so, set the parameter to a PHP null
if (strlen_or_indptr == SQL_NULL_DATA) {
ZVAL_NULL(value_z);
} else if (was_bool) {
convert_to_boolean(value_z);
} else {
ZVAL_LONG(value_z, static_cast<int>(Z_LVAL_P(value_z)));
}
break;
case IS_DOUBLE:
// For a long or a float, simply check if NULL was returned and if so, set the parameter to a PHP null
if (strlen_or_indptr == SQL_NULL_DATA) {
ZVAL_NULL(value_z);
} else if (php_out_type == SQLSRV_PHPTYPE_INT) {
// First check if its value is out of range
double dval = Z_DVAL_P(value_z);
if (dval > INT_MAX || dval < INT_MIN) {
CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED) {
throw core::CoreException();
}
}
// Even if the output param is a boolean, still convert to a long
// integer first to take care of rounding
convert_to_long(value_z);
if (was_bool) {
convert_to_boolean(value_z);
}
}
break;
default:
SQLSRV_ASSERT(false, "Should not have reached here - invalid output parameter type in sqlsrv_param_inout::finalize_output_value.");
break;
}
value_z = NULL;
param_ptr_z = NULL; // Do not keep the reference now that the output param has been processed
}
// A helper method called by finalize_output_value() to finalize output string parameters
void sqlsrv_param_inout::finalize_output_string()
{
zval* value_z = Z_REFVAL_P(param_ptr_z);
// Adjust the length of the string to the value returned by SQLBindParameter in the strlen_or_indptr argument
if (strlen_or_indptr == 0) {
core::sqlsrv_zval_stringl(value_z, "", 0);
return;
}
if (strlen_or_indptr == SQL_NULL_DATA) {
zend_string_release(Z_STR_P(value_z));
ZVAL_NULL(value_z);
return;
}
// If there was more to output than buffer size to hold it, then throw a truncation error
SQLLEN str_len = strlen_or_indptr;
char* str = Z_STRVAL_P(value_z);
int null_size = 0;
switch (encoding) {
case SQLSRV_ENCODING_UTF8:
null_size = sizeof(SQLWCHAR); // The string isn't yet converted to UTF-8, still UTF-16
break;
case SQLSRV_ENCODING_SYSTEM:
null_size = sizeof(SQLCHAR);
break;
case SQLSRV_ENCODING_BINARY:
null_size = 0;
break;
default:
SQLSRV_ASSERT(false, "Should not have reached here - invalid encoding in sqlsrv_param_inout::process_output_string.");
break;
}
CHECK_CUSTOM_ERROR(str_len > (buffer_length - null_size), stmt, SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, param_pos + 1) {
throw core::CoreException();
}
// For ODBC 11+ see https://docs.microsoft.com/sql/relational-databases/native-client/features/odbc-driver-behavior-change-when-handling-character-conversions
// A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains data up to the
// original buffer_length 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 = buffer_length - null_size;
}
if (encoding == SQLSRV_ENCODING_BINARY) {
// 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.
if (str_len < buffer_length) {
str[str_len] = '\0';
}
core::sqlsrv_zval_stringl(value_z, str, str_len);
}
else {
if (encoding != SQLSRV_ENCODING_CHAR) {
char* outString = NULL;
SQLLEN outLen = 0;
bool result = convert_string_from_utf16(encoding, reinterpret_cast<const SQLWCHAR*>(str), int(str_len / sizeof(SQLWCHAR)), &outString, outLen);
CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
if (stmt->format_decimals && (sql_data_type == SQL_DECIMAL || sql_data_type == SQL_NUMERIC)) {
format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, decimal_digits, outString, &outLen);
}
core::sqlsrv_zval_stringl(value_z, outString, outLen);
sqlsrv_free(outString);
}
else {
if (stmt->format_decimals && (sql_data_type == SQL_DECIMAL || sql_data_type == SQL_NUMERIC)) {
format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, decimal_digits, str, &str_len);
}
core::sqlsrv_zval_stringl(value_z, str, str_len);
}
}
value_z = NULL;
}
void sqlsrv_param_inout::resize_output_string_buffer(_Inout_ zval* param_z, _In_ bool is_numeric_type)
{
// Prerequisites: buffer, buffer_length, column_size, and strlen_or_indptr have been set to a known value
// Purpose:
// Verify there is enough space to hold the output string parameter, and allocate if necessary. The param_z
// is updated to contain the new buffer with the correct size and its reference is incremented, and all required
// values for SQLBindParameter will also be updated.
SQLLEN original_len = buffer_length;
SQLLEN expected_len;
SQLLEN buffer_null_extra;
SQLLEN elem_size;
// Calculate the size of each 'element' represented by column_size. WCHAR is the size of a wide char (2), and so is
// a N(VAR)CHAR/NTEXT field being returned as a binary field.
elem_size = (c_data_type == SQL_C_WCHAR ||
(c_data_type == SQL_C_BINARY &&
(sql_data_type == SQL_WCHAR || sql_data_type == SQL_WVARCHAR || sql_data_type == SQL_WLONGVARCHAR))) ? sizeof(SQLWCHAR) : sizeof(SQLCHAR);
// account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning
SQLULEN field_size = column_size;
// With AE enabled, column_size is already retrieved from SQLDescribeParam, but column_size
// does not include the negative sign or decimal place for numeric values
// VSO Bug 2913: without AE, the same can happen as well, in particular to decimals
// and numerics with precision/scale specified
if (is_numeric_type) {
// Include the possible negative sign
field_size += elem_size;
// Include the decimal dot for output params by adding elem_size
if (decimal_digits > 0) {
field_size += elem_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_data_type == SQL_C_BINARY) ? elem_size : 0;
// Increment to include the null terminator since the Zend length doesn't include the null terminator
buffer_length += 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_length < 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.
// initialize the newly allocated space
char *p = ZSTR_VAL(param_z_string);
p = p + original_len;
memset(p, '\0', expected_len - original_len);
ZVAL_NEW_STR(param_z, param_z_string);
// buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the
// buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY
buffer_length = 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,
// then the error 22001 is returned by ODBC.
if (strlen_or_indptr > buffer_length - (elem_size - buffer_null_extra)) {
strlen_or_indptr = buffer_length - (elem_size - buffer_null_extra);
}
}
// Change the column encoding based on the sql data type
/*static*/ void sqlsrv_param_tvp::sql_type_to_encoding(_In_ SQLSMALLINT sql_type, _Inout_ SQLSRV_ENCODING* encoding)
{
switch (sql_type) {
case SQL_BIGINT:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_FLOAT:
case SQL_REAL:
*encoding = SQLSRV_ENCODING_CHAR;
break;
case SQL_BINARY:
case SQL_LONGVARBINARY:
case SQL_VARBINARY:
case SQL_SS_UDT:
*encoding = SQLSRV_ENCODING_BINARY;
break;
default:
// Do nothing
break;
}
}
void sqlsrv_param_tvp::get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ zend_string* table_type_name, _In_ zend_string* schema_name)
{
SQLHANDLE chstmt = SQL_NULL_HANDLE;
SQLRETURN rc;
SQLSMALLINT data_type, dec_digits;
SQLINTEGER col_size;
SQLLEN cb_data_type, cb_col_size, cb_dec_digits;
char* table_type = ZSTR_VAL(table_type_name);
core::SQLAllocHandle(SQL_HANDLE_STMT, *(stmt->conn), &chstmt);
rc = SQLSetStmtAttr(chstmt, SQL_SOPT_SS_NAME_SCOPE, (SQLPOINTER)SQL_SS_NAME_SCOPE_TABLE_TYPE, SQL_IS_UINTEGER);
CHECK_CUSTOM_ERROR(!SQL_SUCCEEDED(rc), stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) {
throw core::CoreException();
}
// Check table type name and see if the schema is specified. Otherwise, assume DBO
if (schema_name != NULL) {
char* schema = ZSTR_VAL(schema_name);
rc = SQLColumns(chstmt, NULL, 0, reinterpret_cast<SQLCHAR*>(schema), SQL_NTS, reinterpret_cast<SQLCHAR*>(table_type), SQL_NTS, NULL, 0);
} else {
rc = SQLColumns(chstmt, NULL, 0, NULL, SQL_NTS, reinterpret_cast<SQLCHAR*>(table_type), SQL_NTS, NULL, 0);
}
CHECK_CUSTOM_ERROR(!SQL_SUCCEEDED(rc), stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) {
throw core::CoreException();
}
SQLSRV_ENCODING stmt_encoding = (stmt->encoding() == SQLSRV_ENCODING_DEFAULT) ? stmt->conn->encoding() : stmt->encoding();
if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
SQLBindCol(chstmt, 5, SQL_C_SSHORT, &data_type, 0, &cb_data_type);
SQLBindCol(chstmt, 7, SQL_C_SLONG, &col_size, 0, &cb_col_size);
SQLBindCol(chstmt, 9, SQL_C_SSHORT, &dec_digits, 0, &cb_dec_digits);
SQLUSMALLINT pos = 0;
while (SQL_SUCCESS == rc) {
rc = SQLFetch(chstmt);
if (rc == SQL_NO_DATA) {
CHECK_CUSTOM_ERROR(tvp_columns.size() == 0, stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) {
throw core::CoreException();
}
break;
}
sqlsrv_malloc_auto_ptr<sqlsrv_param_tvp> param_ptr;
// The SQL data type is used to derive the column encoding
SQLSRV_ENCODING column_encoding = stmt_encoding;
sql_type_to_encoding(data_type, &column_encoding);
param_ptr = new (sqlsrv_malloc(sizeof(sqlsrv_param_tvp))) sqlsrv_param_tvp(pos, column_encoding, data_type, col_size, dec_digits, this);
param_ptr->num_rows = this->num_rows; // Each column inherits the number of rows from the TVP
tvp_columns[pos] = param_ptr.get();
param_ptr.transferred();
pos++;
}
} else {
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1);
}
SQLCloseCursor(chstmt);
SQLFreeHandle(SQL_HANDLE_STMT, chstmt);
}
void sqlsrv_param_tvp::release_data()
{
// Clean up tvp_columns
std::map<SQLUSMALLINT, sqlsrv_param_tvp*>::iterator it;
for (it = tvp_columns.begin(); it != tvp_columns.end(); ++it) {
sqlsrv_param_tvp* ptr = it->second;
if (ptr) {
ptr->release_data();
sqlsrv_free(ptr);
}
}
tvp_columns.clear();
sqlsrv_param::release_data();
}
void sqlsrv_param_tvp::process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
if (sql_data_type == SQL_SS_TABLE) {
// This is a table-valued parameter
param_php_type = IS_ARRAY;
c_data_type = SQL_C_DEFAULT;
// The decimal_digits must be 0 for TVP
decimal_digits = 0;
// The column_size for a TVP is the row array size
// The following method will verify the input array and also derive num_rows
this->num_rows = 0;
int num_columns = parse_tv_param_arrays(stmt, param_z);
column_size = num_rows;
buffer = NULL;
buffer_length = 0;
strlen_or_indptr = (num_columns == 0)? SQL_DEFAULT_PARAM : SQL_DATA_AT_EXEC;
} else {
// This is one of the constituent columns of the table-valued parameter
// The column value of the first row is already saved in member variable param_ptr_z
process_param_column_value(stmt);
}
}
int sqlsrv_param_tvp::parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
// If this is not a table-valued parameter, simply return
if (sql_data_type != SQL_SS_TABLE) {
return 0;
}
// This method verifies if the table-valued parameter (i.e. param_z) provided by the user is valid.
// The number of columns in the given table-valued parameter is returned, which may be zero.
HashTable* inputs_ht = Z_ARRVAL_P(param_z);
zend_string *tvp_name = NULL;
zend_string *schema_name = NULL;
zval *tvp_data_z = NULL;
HashPosition pos;
zend_hash_internal_pointer_reset_ex(inputs_ht, &pos);
if (zend_hash_has_more_elements_ex(inputs_ht, &pos) == SUCCESS) {
zend_ulong num_index = -1;
size_t key_len = 0;
int key_type = zend_hash_get_current_key(inputs_ht, &tvp_name, &num_index);
if (key_type == HASH_KEY_IS_STRING) {
key_len = ZSTR_LEN(tvp_name);
tvp_data_z = zend_hash_get_current_data_ex(inputs_ht, &pos);
}
CHECK_CUSTOM_ERROR((key_type == HASH_KEY_IS_LONG || key_len == 0), stmt, SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME, param_pos + 1) {
throw core::CoreException();
}
}
// TODO: Find the docs page somewhere that says a TVP can not be null but it may have null columns??
CHECK_CUSTOM_ERROR(tvp_data_z == NULL || Z_TYPE_P(tvp_data_z) == IS_NULL || Z_TYPE_P(tvp_data_z) != IS_ARRAY, stmt, SQLSRV_ERROR_TVP_INVALID_INPUTS, param_pos + 1) {
throw core::CoreException();
}
// Check if schema is provided by the user
if (zend_hash_move_forward_ex(inputs_ht, &pos) == SUCCESS) {
zval *schema_z = zend_hash_get_current_data_ex(inputs_ht, &pos);
if (schema_z != NULL && Z_TYPE_P(schema_z) == IS_STRING) {
schema_name = Z_STR_P(schema_z);
}
}
// Save the TVP multi-dim array data, which should be something like this
// [
// [r1c1, r1c2, r1c3],
// [r2c1, r2c2, r2c3],
// [r3c1, r3c2, r3c3]
// ]
param_ptr_z = tvp_data_z;
HashTable* rows_ht = Z_ARRVAL_P(tvp_data_z);
this->num_rows = zend_hash_num_elements(rows_ht);
if (this->num_rows == 0) {
// TVP has no data
return 0;
}
// Given the table type name, get its column meta data next
size_t total_num_columns = 0;
get_tvp_metadata(stmt, tvp_name, schema_name);
total_num_columns = tvp_columns.size();
// (1) Is the array empty?
// (2) Check individual rows and see if their sizes are consistent?
zend_ulong id = -1;
zend_string *key = NULL;
zval* row_z = NULL;
int num_columns = 0;
int type = HASH_KEY_NON_EXISTENT;
// Loop through the rows to check the number of columns
ZEND_HASH_FOREACH_KEY_VAL(rows_ht, id, key, row_z) {
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR(type == HASH_KEY_IS_STRING, stmt, SQLSRV_ERROR_TVP_STRING_KEYS, param_pos + 1) {
throw core::CoreException();
}
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
// Individual row must be an array
CHECK_CUSTOM_ERROR(Z_TYPE_P(row_z) != IS_ARRAY, stmt, SQLSRV_ERROR_TVP_ROW_NOT_ARRAY, param_pos + 1) {
throw core::CoreException();
}
// Are all the TVP's rows the same size
num_columns = zend_hash_num_elements(Z_ARRVAL_P(row_z));
CHECK_CUSTOM_ERROR(num_columns != total_num_columns, stmt, SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE, param_pos + 1, total_num_columns) {
throw core::CoreException();
}
} ZEND_HASH_FOREACH_END();
// Return the number of columns
return num_columns;
}
void sqlsrv_param_tvp::process_param_column_value(_Inout_ sqlsrv_stmt* stmt)
{
// This is one of the constituent columns of the table-valued parameter
// The corresponding column value of the TVP's first row is already saved in
// the member variable param_ptr_z, which may be a NULL value
zval *data_z = param_ptr_z;
param_php_type = is_a_string_type(sql_data_type) ? IS_STRING : Z_TYPE_P(data_z);
switch (param_php_type) {
case IS_TRUE:
case IS_FALSE:
case IS_LONG:
case IS_DOUBLE:
sqlsrv_param::process_param(stmt, data_z);
buffer = &placeholder_z.value; // use placeholder zval for binding later
break;
case IS_RESOURCE:
sqlsrv_param::process_resource_param(data_z);
break;
case IS_STRING:
case IS_OBJECT:
if (param_php_type == IS_STRING) {
derive_string_types_sizes(data_z);
} else {
// If preprocessing a datetime object fails, throw an error of invalid php type
bool succeeded = preprocess_datetime_object(stmt, data_z);
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE, parent_tvp->param_pos + 1, param_pos + 1) {
throw core::CoreException();
}
}
buffer = reinterpret_cast<SQLPOINTER>(this);
buffer_length = 0;
strlen_or_indptr = SQL_DATA_AT_EXEC;
break;
case IS_NULL:
process_null_param_value(stmt);
break;
default:
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE, parent_tvp->param_pos + 1, param_pos + 1);
break;
}
// Release the reference
param_ptr_z = NULL;
}
void sqlsrv_param_tvp::process_null_param_value(_Inout_ sqlsrv_stmt* stmt)
{
// This is one of the constituent columns of the table-valued parameter
// This method is called when the corresponding column value of the TVP's first row is NULL
// So keep looking in the subsequent rows and find the first non-NULL value in the same column
HashTable* rows_ht = Z_ARRVAL_P(parent_tvp->param_ptr_z);
zval* row_z = NULL;
zval* value_z = NULL;
int php_type = IS_NULL;
int row_id = 1; // Start from the second row
while ((row_z = zend_hash_index_find(rows_ht, row_id++)) != NULL) {
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
value_z = zend_hash_index_find(Z_ARRVAL_P(row_z), param_pos);
php_type = Z_TYPE_P(value_z);
if (php_type != IS_NULL) {
// Save this non-NULL value before calling process_param_column_value()
param_ptr_z = value_z;
process_param_column_value(stmt);
break;
}
}
if (php_type == IS_NULL) {
// This means that the entire column contains nothing but NULLs
sqlsrv_param::process_null_param(param_ptr_z);
}
}
void sqlsrv_param_tvp::bind_param(_Inout_ sqlsrv_stmt* stmt)
{
core::SQLBindParameter(stmt, param_pos + 1, direction, c_data_type, sql_data_type, column_size, decimal_digits, buffer, buffer_length, &strlen_or_indptr);
// No need to continue if this is one of the constituent columns of the table-valued parameter
if (sql_data_type != SQL_SS_TABLE) {
return;
}
if (num_rows == 0) {
// TVP has no data
return;
}
// Bind the TVP columns one by one
// Register this object first using SQLSetDescField() for sending TVP data post execution
SQLHDESC desc;
core::SQLGetStmtAttr(stmt, SQL_ATTR_APP_PARAM_DESC, &desc, 0, 0);
SQLRETURN r = ::SQLSetDescField(desc, param_pos + 1, SQL_DESC_DATA_PTR, reinterpret_cast<SQLPOINTER>(this), 0);
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
throw core::CoreException();
}
// First set focus on this parameter
size_t ordinal = param_pos + 1;
core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(ordinal), SQL_IS_INTEGER);
// Bind the TVP columns
HashTable* rows_ht = Z_ARRVAL_P(param_ptr_z);
zval* row_z = zend_hash_index_find(rows_ht, 0);
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
HashTable* cols_ht = Z_ARRVAL_P(row_z);
zend_ulong id = -1;
zend_string *key = NULL;
zval* data_z = NULL;
int num_columns = 0;
// In case there are null values in the first row, have to loop
// through the entire first row of column values using the Zend macros.
ZEND_HASH_FOREACH_KEY_VAL(cols_ht, id, key, data_z) {
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR(type == HASH_KEY_IS_STRING, stmt, SQLSRV_ERROR_TVP_STRING_KEYS, param_pos + 1) {
throw core::CoreException();
}
// Assume the user has supplied data for all columns in the right order
SQLUSMALLINT pos = static_cast<SQLUSMALLINT>(id);
sqlsrv_param* column_param = tvp_columns[pos];
SQLSRV_ASSERT(column_param != NULL, "sqlsrv_param_tvp::bind_param -- column param should not be null");
// If data_z is NULL, will need to keep looking in the subsequent rows of
// the same column until a non-null value is found. Since Zend macros must be
// used to traverse the array items, nesting Zend macros in different directions
// does not work.
// Therefore, save data_z for later processing and binding.
column_param->param_ptr_z = data_z;
num_columns++;
} ZEND_HASH_FOREACH_END();
// Process the columns and bind each of them using the saved data
for (int i = 0; i < num_columns; i++) {
sqlsrv_param* column_param = tvp_columns[i];
column_param->process_param(stmt, NULL);
column_param->bind_param(stmt);
}
// Reset focus
core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(0), SQL_IS_INTEGER);
}
// For each of the constituent columns of the table-valued parameter, check its PHP type
// For pure scalar types, map the cell value (based on current_row and ordinal) to the
// member placeholder_z
void sqlsrv_param_tvp::populate_cell_placeholder(_Inout_ sqlsrv_stmt* stmt, _In_ int ordinal)
{
if (sql_data_type == SQL_SS_TABLE || ordinal >= num_rows) {
return;
}
zval* row_z = NULL;
HashTable* values_ht = NULL;
zval* value_z = NULL;
int type = IS_NULL;
switch (param_php_type) {
case IS_TRUE:
case IS_FALSE:
case IS_LONG:
case IS_DOUBLE:
// Find the row from the TVP data based on ordinal
row_z = zend_hash_index_find(Z_ARRVAL_P(parent_tvp->param_ptr_z), ordinal);
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
// Now find the column value based on param_pos
value_z = zend_hash_index_find(Z_ARRVAL_P(row_z), param_pos);
type = Z_TYPE_P(value_z);
// First check if value_z is NULL
if (type == IS_NULL) {
ZVAL_NULL(&placeholder_z);
strlen_or_indptr = SQL_NULL_DATA;
} else {
// Once the placeholder is bound with the correct value from the array, update current_row
if (param_php_type == IS_DOUBLE) {
if (type != IS_DOUBLE) {
// If value_z type is different from param_php_type convert first
convert_to_double(value_z);
}
strlen_or_indptr = sizeof(Z_DVAL_P(value_z));
ZVAL_DOUBLE(&placeholder_z, Z_DVAL_P(value_z));
} else {
if (type != IS_LONG) {
// If value_z type is different from param_php_type convert first
// Even for boolean values
convert_to_long(value_z);
}
strlen_or_indptr = sizeof(Z_LVAL_P(value_z));
ZVAL_LONG(&placeholder_z, Z_LVAL_P(value_z));
}
}
current_row++;
break;
default:
// Do nothing for non-scalar types
break;
}
}
// If this is the table-valued parameter, loop through each parameter column
// and populate the cell's placeholder_z.
// If this is one of the constituent columns of the table-valued parameter,
// call SQLPutData() to send the cell value to the server (based on current_row
// and param_pos)
bool sqlsrv_param_tvp::send_data_packet(_Inout_ sqlsrv_stmt* stmt)
{
if (sql_data_type != SQL_SS_TABLE) {
// This is one of the constituent columns of the table-valued parameter
// Check current_row first
if (current_row >= num_rows) {
return false;
}
// Find the row from the TVP data based on current_row
zval* row_z = zend_hash_index_find(Z_ARRVAL_P(parent_tvp->param_ptr_z), current_row);
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
// Now find the column value based on param_pos
zval* value_z = zend_hash_index_find(Z_ARRVAL_P(row_z), param_pos);
// First check if value_z is NULL
if (Z_TYPE_P(value_z) == IS_NULL) {
core::SQLPutData(stmt, NULL, SQL_NULL_DATA);
current_row++;
} else {
switch (param_php_type) {
case IS_RESOURCE:
{
num_bytes_read = 0;
param_stream = NULL;
// Get the stream from the zval value
core::sqlsrv_php_stream_from_zval_no_verify(*stmt, param_stream, value_z);
// Keep sending the packets until EOF is reached
while (sqlsrv_param::send_data_packet(stmt)) {
}
current_row++;
}
break;
case IS_OBJECT:
{
// This method updates placeholder_z as a string
bool succeeded = convert_datetime_to_string(stmt, value_z);
// Conversion failed so assume the input was an invalid PHP type
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE, parent_tvp->param_pos + 1, param_pos + 1) {
throw core::CoreException();
}
core::SQLPutData(stmt, Z_STRVAL(placeholder_z), SQL_NTS);
current_row++;
}
break;
case IS_STRING:
{
int type = Z_TYPE_P(value_z);
if (type != IS_STRING) {
convert_to_string(value_z);
}
SQLLEN value_len = Z_STRLEN_P(value_z);
if (value_len == 0) {
// If it's an empty string
core::SQLPutData(stmt, Z_STRVAL_P(value_z), 0);
} else {
if (encoding == CP_UTF8 && !is_a_numeric_type(sql_data_type)) {
if (value_len > INT_MAX) {
LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded.");
throw core::CoreException();
}
// This method would change the member placeholder_z
bool succeeded = convert_input_str_to_utf16(stmt, value_z);
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE, parent_tvp->param_pos + 1, param_pos + 1, get_last_error_message()) {
throw core::CoreException();
}
send_string_data_in_batches(stmt, &placeholder_z);
} else {
send_string_data_in_batches(stmt, value_z);
}
}
current_row++;
}
break;
default:
// Do nothing for basic types as they should be processed elsewhere
break;
}
} // else not IS_NULL
} else {
// This is the table-valued parameter
if (current_row < num_rows) {
// Loop through the table parameter columns and populate each cell's placeholder whenever applicable
for (size_t i = 0; i < tvp_columns.size(); i++) {
tvp_columns[i]->populate_cell_placeholder(stmt, current_row);
}
// This indicates a TVP row is available
core::SQLPutData(stmt, reinterpret_cast<SQLPOINTER>(1), 1);
current_row++;
} else {
// This indicates there is no more TVP row
core::SQLPutData(stmt, reinterpret_cast<SQLPOINTER>(0), 0);
}
}
// Return false to indicate that the current row has been sent
return false;
}
// A helper method for sending large string data in batches
void sqlsrv_param_tvp::send_string_data_in_batches(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z)
{
SQLLEN len = Z_STRLEN_P(value_z);
SQLLEN batch = (encoding == CP_UTF8) ? PHP_STREAM_BUFFER_SIZE / sizeof(SQLWCHAR) : PHP_STREAM_BUFFER_SIZE;
char* p = Z_STRVAL_P(value_z);
while (len > batch) {
core::SQLPutData(stmt, p, batch);
len -= batch;
p += batch;
}
// Put final batch
core::SQLPutData(stmt, p, len);
}
void sqlsrv_params_container::clean_up_param_data(_In_opt_ bool only_input/* = false*/)
{
current_param = NULL;
remove_params(input_params);
if (!only_input) {
remove_params(output_params);
}
}
// 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 strlen_or_indptr
// argument passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary.
// If a NULL was returned by SQL Server to any output parameter, set the parameter to NULL as well
void sqlsrv_params_container::finalize_output_parameters()
{
std::map<SQLUSMALLINT, sqlsrv_param*>::iterator it;
for (it = output_params.begin(); it != output_params.end(); ++it) {
sqlsrv_param_inout* ptr = dynamic_cast<sqlsrv_param_inout*>(it->second);
if (ptr) {
ptr->finalize_output_value();
}
}
}
sqlsrv_param* sqlsrv_params_container::find_param(_In_ SQLUSMALLINT param_num, _In_ bool is_input)
{
try {
if (is_input) {
return input_params.at(param_num);
} else {
return output_params.at(param_num);
}
} catch (std::out_of_range&) {
// not found
return NULL;
}
}
bool sqlsrv_params_container::get_next_parameter(_Inout_ sqlsrv_stmt* stmt)
{
// Get the param ptr when binding the resource parameter
SQLPOINTER param = NULL;
SQLRETURN r = core::SQLParamData(stmt, &param);
// If no more data, all the bound parameters have been exhausted, so return false (done)
if (SQL_SUCCEEDED(r) || r == SQL_NO_DATA) {
// Done now, reset current_param
current_param = NULL;
return false;
}
current_param = reinterpret_cast<sqlsrv_param*>(param);
SQLSRV_ASSERT(current_param != NULL, "sqlsrv_params_container::get_next_parameter - The parameter requested is missing!");
current_param->init_data_from_zval(stmt);
return true;
}
// The following helper method sends one stream packet at a time, if available
bool sqlsrv_params_container::send_next_packet(_Inout_ sqlsrv_stmt* stmt)
{
if (current_param == NULL) {
// If current_stream is NULL, either this is the first time checking or the previous parameter
// is done. In either case, MUST call get_next_parameter() to see if there is any more
// parameter requested by ODBC. Otherwise, "Function sequence error" will result, meaning the
// ODBC functions are called out of the order required by the ODBC Specification
if (get_next_parameter(stmt) == false) {
return false;
}
}
// The helper method send_stream_packet() returns false when EOF is reached
if (current_param->send_data_packet(stmt) == false) {
// Now that EOF has been reached, reset current_param for next round
// Bear in mind that SQLParamData might request the same stream resource again
current_param = NULL;
}
// Returns true regardless such that either get_next_parameter() will be called or next packet will be sent
return true;
}

View file

@ -3,7 +3,7 @@
//
// Contents: Implementation of PHP streams for reading SQL Server data
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -5,7 +5,7 @@
//
// Comments: Mostly error handling and some type handling
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -237,8 +237,8 @@ void convert_datetime_string_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_opt_ char* i
ZVAL_UNDEF(params);
// Convert the datetime string to a PHP DateTime object
core::sqlsrv_zval_stringl(&value_temp_z, input, length);
core::sqlsrv_zval_stringl(&function_z, "date_create", sizeof("date_create") - 1);
ZVAL_STRINGL(&value_temp_z, input, length);
ZVAL_STRINGL(&function_z, "date_create", sizeof("date_create") - 1);
params[0] = value_temp_z;
if (call_user_function(EG(function_table), NULL, &function_z, &out_zval, 1,

View file

@ -4,7 +4,7 @@
// Contents: Contains functions for handling Windows format strings
// and UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// Contents: Contains a portable abstraction for interlocked, atomic
// operations on int32_t and pointer types.
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// Contents: Contains a portable abstraction for interlocked, atomic
// operations on int32_t and pointer types.
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// Contents: Contains a portable abstraction for interlocked, singly
// linked list.
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Contains portable classes for localization
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -5,7 +5,7 @@
// Must be included in one c/cpp file per binary
// A build error will occur if this inclusion policy is not followed
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -20,7 +20,7 @@
// pecuniary loss) arising out of the use of or inability to use
// this SDK, even if Microsoft has been advised of the possibility
// of such damages.
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -77,6 +77,8 @@
#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_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_COLUMN_ENCRYPTION (SQL_SOPT_SS_BASE+13)// Sets the column encryption mode
// Define old names
#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING
@ -180,6 +182,10 @@
#define SQL_COLUMN_ENCRYPTION_DEFAULT SQL_COLUMN_ENCRYPTION_DISABLE
// Defines for use with SQL_COPT_SS_CEKCACHETTL
#define SQL_CEKCACHETTL_DEFAULT 7200L // TTL value in seconds (2 hours)
//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_DEFAULT SQL_SS_NAME_SCOPE_TABLE
// SQL_COPT_SS_ENCRYPT
#define SQL_EN_OFF 0L
#define SQL_EN_ON 1L

View file

@ -3,7 +3,7 @@
//
// Contents: Contains the minimal definitions to build on non-Windows platforms
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -1,7 +1,7 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: typedefs_for_linux.h
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// File: version.h
// Contents: Version number constants
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -26,12 +26,12 @@
// Increase Minor with backward compatible new functionalities and API changes.
// Increase Patch for backward compatible fixes.
#define SQLVERSION_MAJOR 5
#define SQLVERSION_MINOR 9
#define SQLVERSION_MINOR 10
#define SQLVERSION_PATCH 0
#define SQLVERSION_BUILD 0
// For previews, set this constant to 1, 2 and so on. Otherwise, set it to 0
#define PREVIEW 0
#define PREVIEW 1
#define SEMVER_PRERELEASE
// Semantic versioning build metadata, build meta data is not counted in precedence order.
@ -59,7 +59,7 @@
#define _FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_PATCH,SQLVERSION_BUILD
// PECL package version ('-' or '+' is not allowed) - to support Pickle do not use macros below
#define PHP_SQLSRV_VERSION "5.9.0"
#define PHP_PDO_SQLSRV_VERSION "5.9.0"
#define PHP_SQLSRV_VERSION "5.10.0beta1"
#define PHP_PDO_SQLSRV_VERSION "5.10.0beta1"
#endif // VERSION_H

View file

@ -3,7 +3,7 @@
//
// Contents: include for definition of Windows types for non-Windows platforms
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// Contents: This module defines helper functions to prevent
// integer overflow bugs.
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Contains the minimal definitions to build on non-Windows platforms
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Contains the minimal definitions to build on non-Windows platforms
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@ dnl
dnl Contents: the code that will go into the configure script, indicating options,
dnl external libraries and includes, and what source files are to be compiled.
dnl
dnl Microsoft Drivers 5.9 for PHP for SQL Server
dnl Microsoft Drivers 5.10 for PHP for SQL Server
dnl Copyright(c) Microsoft Corporation
dnl All rights reserved.
dnl MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: JScript build configuration used by buildconf.bat
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Routines that use connection handles
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -30,6 +30,8 @@ extern "C" {
namespace {
const int MAX_CONN_VALSTRING_LEN = 256;
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_CONN;
@ -38,13 +40,7 @@ struct date_as_string_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>( conn );
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);
}
};
@ -53,13 +49,7 @@ struct format_decimals_func
static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/)
{
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
if (zend_is_true(value)) {
ss_conn->format_decimals = true;
}
else {
ss_conn->format_decimals = false;
}
ss_conn->format_decimals = zend_is_true(value);
}
};
@ -117,17 +107,10 @@ struct bool_conn_str_func {
static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str )
{
char 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 += "};";
char temp_str[MAX_CONN_VALSTRING_LEN];
snprintf(temp_str, MAX_CONN_VALSTRING_LEN, "%s={%s};", option->odbc_name, (zend_is_true(value) ? "yes" : "no"));
conn_str += temp_str;
}
};
@ -137,12 +120,10 @@ struct int_conn_str_func {
{
SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_LONG, "An integer is expected for this keyword" )
std::string val_str = std::to_string( Z_LVAL_P( value ));
conn_str += option->odbc_name;
conn_str += "={";
conn_str += val_str;
conn_str += "};";
char temp_str[MAX_CONN_VALSTRING_LEN];
snprintf(temp_str, MAX_CONN_VALSTRING_LEN, "%s={%ld};", option->odbc_name, Z_LVAL_P(value));
conn_str += temp_str;
}
};

View file

@ -2,7 +2,7 @@
// File: init.cpp
// Contents: initialization routines for the extension
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -335,6 +335,7 @@ PHP_MINIT_FUNCTION(sqlsrv)
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_INT", SQLSRV_PHPTYPE_INT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_FLOAT", SQLSRV_PHPTYPE_FLOAT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_DATETIME", SQLSRV_PHPTYPE_DATETIME, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_TABLE", SQLSRV_PHPTYPE_TABLE, CONST_PERSISTENT | CONST_CS);
std::string bin = "binary";
std::string chr = "char";
@ -377,6 +378,7 @@ PHP_MINIT_FUNCTION(sqlsrv)
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIMESTAMP", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TINYINT", SQL_TINYINT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UDT", SQL_SS_UDT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TABLE", SQL_SS_TABLE, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UNIQUEIDENTIFIER", SQL_GUID, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_XML", SQL_SS_XML, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_DATE;

View file

@ -8,7 +8,7 @@
//
// Comments: Also contains "internal" declarations shared across source files.
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -8,7 +8,7 @@
//
// Comments: Also contains "internal" declarations shared across source files.
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Routines that use statement handles
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -64,7 +64,7 @@ enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = {
SQLSRV_PHPTYPE_INT,
SQLSRV_PHPTYPE_FLOAT,
SQLSRV_PHPTYPE_STRING,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_TABLE,
SQLSRV_PHPTYPE_DATETIME,
SQLSRV_PHPTYPE_STREAM,
SQLSRV_PHPTYPE_INVALID,
@ -96,25 +96,17 @@ sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sq
void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt );
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype type );
void parse_param_array( _Inout_ 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 );
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding );
zval* parse_param_array(_Inout_ ss_sqlsrv_stmt* stmt, _Inout_ HashTable* param_ht, zend_ulong index,
_Out_ SQLSMALLINT& direction, _Out_ SQLSRV_PHPTYPE& php_out_type,
_Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits);
}
// internal helper function to free meta data structures allocated
void meta_data_free( _Inout_ field_meta_data* meta )
{
if( meta->field_name ) {
meta->field_name.reset();
}
sqlsrv_free( meta );
}
// query options for cursor types
namespace SSCursorTypes {
@ -144,9 +136,6 @@ ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_
ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void )
{
std::for_each(current_meta_data.begin(), current_meta_data.end(), meta_data_free);
current_meta_data.clear();
if( fetch_field_names != NULL ) {
for( int i=0; i < fetch_fields_count; ++i ) {
@ -239,7 +228,9 @@ sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _
case SQL_REAL:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
break;
case SQL_SS_TABLE:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_TABLE;
break;
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
@ -578,8 +569,7 @@ PHP_FUNCTION( sqlsrv_next_result )
core_sqlsrv_next_result( stmt, true );
// clear the current meta data since the new result will generate new meta data
std::for_each(stmt->current_meta_data.begin(), stmt->current_meta_data.end(), meta_data_free);
stmt->current_meta_data.clear();
stmt->clean_up_results_metadata();
if( stmt->past_next_result_end ) {
@ -1205,50 +1195,70 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt )
HashTable* params_ht = Z_ARRVAL_P( params_z );
zend_ulong index = -1;
zend_string *key = NULL;
zval* param_z = NULL;
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;
ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) {
// make sure it's an integer index
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR(type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX) {
throw ss::SSException();
}
// make sure it's an integer index
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) {
throw ss::SSException();
}
zval* value_z = NULL;
SQLSMALLINT direction = SQL_PARAM_INPUT;
SQLSRV_ENCODING encoding = stmt->encoding();
if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) {
encoding = stmt->conn->encoding();
}
SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
SQLSMALLINT decimal_digits = 0;
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
// if it's a parameter array
if( Z_TYPE_P( param_z ) == IS_ARRAY ) {
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();
if (Z_TYPE_P(param_z) == IS_ARRAY) {
try {
HashTable* param_ht = Z_ARRVAL_P(param_z);
// Check the number of elements in the array
int num_elems = zend_hash_num_elements(param_ht);
if (num_elems > 1) {
value_z = parse_param_array(stmt, param_ht, index, direction, php_out_type, encoding, sql_type, column_size, decimal_digits);
} else {
// Simply get the first variable and use the defaults
value_z = zend_hash_index_find(param_ht, 0);
if (value_z == NULL) {
THROW_SS_ERROR(stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1);
}
}
} catch (core::CoreException&) {
SQLFreeStmt(stmt->handle(), SQL_RESET_PARAMS);
throw;
}
// 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 );
value_z = var;
}
else {
CHECK_CUSTOM_ERROR( !stmt->prepared && stmt->conn->ce_option.enabled, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED ) {
CHECK_CUSTOM_ERROR(!stmt->prepared && stmt->conn->ce_option.enabled, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED) {
throw ss::SSException();
}
value_z = param_z;
}
// If the user specifies a certain type for an output parameter, we have to convert the zval
// to that type so that when the buffer is filled, the type is correct. But first,
// should check if a LOB type is specified.
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && (sql_type == SQL_LONGVARCHAR
|| sql_type == SQL_WLONGVARCHAR || sql_type == SQL_LONGVARBINARY),
stmt, SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED) {
throw core::CoreException();
}
// Table-valued parameters are input-only
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && (sql_type == SQL_SS_TABLE || php_out_type == SQLSRV_PHPTYPE_TABLE), stmt, SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY) {
throw ss::SSException();
}
// bind the parameter
SQLSRV_ASSERT( value_z != NULL, "bind_params: value_z is null." );
core_sqlsrv_bind_param( stmt, static_cast<SQLUSMALLINT>( index ), direction, value_z, php_out_type, encoding, sql_type, column_size,
decimal_digits );
@ -1564,6 +1574,7 @@ bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sq
*column_size = INT_MAX >> 1;
break;
case SQL_SS_XML:
case SQL_SS_TABLE:
*column_size = SQL_SS_LENGTH_UNLIMITED;
break;
case SQL_BINARY:
@ -1713,8 +1724,13 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_
}
break;
}
case SQL_SS_TABLE:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_TABLE;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
break;
default:
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
SQLSRV_ASSERT(false, "An invalid php type was returned with (supposedly) validated sql type and column_size");
break;
}
@ -1896,179 +1912,106 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ
}
void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction,
zval* parse_param_array(_Inout_ ss_sqlsrv_stmt* stmt, _Inout_ HashTable* param_ht, zend_ulong index, _Out_ SQLSMALLINT& direction,
_Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits )
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits)
{
zval* var_or_val = NULL;
zval* temp = NULL;
HashTable* param_ht = Z_ARRVAL_P( param_array );
sqlsrv_sqltype sqlsrv_sql_type;
HashPosition pos;
zval* var_or_val = zend_hash_index_find(param_ht, 0);
bool php_type_param_is_null = true;
bool sql_type_param_is_null = true;
try {
// Assumption: there are more than only the variable, parse the rest of the array
zval* dir = zend_hash_index_find(param_ht, 1);
if (Z_TYPE_P(dir) != IS_NULL) {
// if param direction is specified, make sure it's valid
CHECK_CUSTOM_ERROR(Z_TYPE_P(dir) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1) {
throw ss::SSException();
}
direction = static_cast<SQLSMALLINT>(Z_LVAL_P(dir));
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_ex( param_ht, &pos );
if( zend_hash_has_more_elements_ex( param_ht, &pos ) == FAILURE ||
(var_or_val = zend_hash_get_current_data_ex(param_ht, &pos)) == NULL) {
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 );
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT,
stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1) {
throw ss::SSException();
}
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && !Z_ISREF_P(var_or_val), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1) {
throw ss::SSException();
}
}
// if the direction is included, then use what they gave, otherwise INPUT is assumed
if ( zend_hash_move_forward_ex( param_ht, &pos ) == SUCCESS && ( temp = zend_hash_get_current_data_ex( param_ht, &pos )) != NULL &&
Z_TYPE_P( temp ) != IS_NULL ) {
// Check if the user provides php type or sql type or both
zval* phptype_z = zend_hash_index_find(param_ht, 2);
zval* sqltype_z = zend_hash_index_find(param_ht, 3);
CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) {
php_type_param_is_null = (phptype_z == NULL || Z_TYPE_P(phptype_z) == IS_NULL);
sql_type_param_is_null = (sqltype_z == NULL || Z_TYPE_P(sqltype_z) == IS_NULL);
throw ss::SSException();
if (php_type_param_is_null) {
// so set default for php type based on the variable
if (Z_ISREF_P(var_or_val)) {
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(Z_REFVAL_P(var_or_val))];
} else {
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(var_or_val)];
}
direction = static_cast<SQLSMALLINT>( 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 ) {
} else {
CHECK_CUSTOM_ERROR(Z_TYPE_P(phptype_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, 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 ) {
sqlsrv_phptype srv_phptype;
srv_phptype.value = Z_LVAL_P(phptype_z);
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_phptype(srv_phptype), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1) {
throw ss::SSException();
}
}
else {
direction = SQL_PARAM_INPUT;
}
// extract the php type and encoding from the 3rd parameter
if ( zend_hash_move_forward_ex( param_ht, &pos ) == SUCCESS && ( temp = zend_hash_get_current_data_ex( param_ht, &pos )) != 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>( sqlsrv_phptype.typeinfo.type );
encoding = ( SQLSRV_ENCODING ) sqlsrv_phptype.typeinfo.encoding;
php_out_type = static_cast<SQLSRV_PHPTYPE>(srv_phptype.typeinfo.type);
encoding = (SQLSRV_ENCODING)srv_phptype.typeinfo.encoding;
// if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established
// by the connection
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
encoding = stmt->conn->encoding();
}
}
// 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 ) {
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_ex( param_ht, &pos ) == SUCCESS && ( temp = zend_hash_get_current_data_ex( param_ht, &pos )) != 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 ) {
if (sql_type_param_is_null) {
// the sql type is not specified, which is required for always encrypted for non-prepared statements
CHECK_CUSTOM_ERROR(stmt->conn->ce_option.enabled && !stmt->prepared, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED) {
throw ss::SSException();
}
} else {
CHECK_CUSTOM_ERROR(Z_TYPE_P(sqltype_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1) {
throw ss::SSException();
}
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 ) {
sqlsrv_sqltype sqlsrv_sql_type;
sqlsrv_sql_type.value = Z_LVAL_P(sqltype_z);
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_sqltype(sqlsrv_sql_type), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1) {
throw ss::SSException();
}
bool size_okay = determine_column_size_or_precision( stmt, sqlsrv_sql_type, &column_size, &decimal_digits );
CHECK_CUSTOM_ERROR( !size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1 ) {
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 unknown, so tell the core layer to use its defaults
else {
CHECK_CUSTOM_ERROR( !stmt->prepared && stmt->conn->ce_option.enabled, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED ) {
throw ss::SSException();
if (direction != SQL_PARAM_INPUT && php_type_param_is_null) {
sqlsrv_phptype srv_phptype;
srv_phptype = determine_sqlsrv_php_type(stmt, sql_type, (SQLUINTEGER)column_size, true);
php_out_type = static_cast<SQLSRV_PHPTYPE>(srv_phptype.typeinfo.type);
encoding = static_cast<SQLSRV_ENCODING>(srv_phptype.typeinfo.encoding);
}
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 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_INPUT && php_type_param_was_null && !sql_type_param_was_null ) {
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>( sqlsrv_phptype.typeinfo.type );
encoding = static_cast<SQLSRV_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;
if (direction == SQL_PARAM_OUTPUT) {
if (php_out_type == SQLSRV_PHPTYPE_NULL || php_out_type == SQLSRV_PHPTYPE_DATETIME || php_out_type == SQLSRV_PHPTYPE_STREAM) {
THROW_CORE_ERROR(stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
}
}
}
catch( core::CoreException& ) {
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
throw;
}
return var_or_val;
}
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type )
@ -2079,6 +2022,7 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type )
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_TABLE:
return true;
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM:
@ -2123,6 +2067,7 @@ bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype sql_type )
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TABLE:
break;
default:
return false;

View file

@ -3,7 +3,7 @@
//
// Contents: Version resource
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -5,7 +5,7 @@
//
// Comments: Mostly error handling and some type handling
//
// Microsoft Drivers 5.9 for PHP for SQL Server
// Microsoft Drivers 5.10 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -379,7 +379,7 @@ ss_error SS_ERRORS[] = {
},
{
SQLSRV_ERROR_CE_DRIVER_REQUIRED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.", -105, false }
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server (or above) for %1!s!.", -105, true }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
@ -445,6 +445,42 @@ ss_error SS_ERRORS[] = {
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -121, true}
},
{
SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating a string for Table-Valued Param %1!d! Column %2!d! to UTF-16: %3!s!", -122, true }
},
{
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
{ IMSSP, (SQLCHAR*) "An invalid type for Table-Valued Param %1!d! Column %2!d! was specified", -123, true }
},
{
SQLSRV_ERROR_TVP_FETCH_METADATA,
{ IMSSP, (SQLCHAR*) "Failed to get metadata for Table-Valued Param %1!d!", -124, true }
},
{
SQLSRV_ERROR_TVP_INVALID_INPUTS,
{ IMSSP, (SQLCHAR*) "Invalid inputs for Table-Valued Param %1!d!", -125, true }
},
{
SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME,
{ IMSSP, (SQLCHAR*) "Expect a non-empty string for a Type Name for Table-Valued Param %1!d!", -126, true }
},
{
SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE,
{ IMSSP, (SQLCHAR*) "For Table-Valued Param %1!d! the number of values in a row is expected to be %2!d!", -127, true }
},
{
SQLSRV_ERROR_TVP_STRING_KEYS,
{ IMSSP, (SQLCHAR*) "Associative arrays not allowed for Table-Valued Param %1!d!", -128, true }
},
{
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
{ IMSSP, (SQLCHAR*) "Expect an array for each row for Table-Valued Param %1!d!", -129, true }
},
{
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
{ IMSSP, (SQLCHAR*) "You cannot return data in a table-valued parameter. Table-valued parameters are input-only.", -130, false }
},
// terminate the list of errors/warnings
{ UINT_MAX, {} }

View file

@ -1,60 +0,0 @@
<?php
function RestartConn($server)
{
$powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
$restart_string = "$powershell (get-service -ComputerName $server -Name mssqlserver).Stop()";
exec( $restart_string );
$servstring = shell_exec("$powershell get-service -ComputerName $server -Name mssqlserver");
// Wait until the service is fully stopped
while (substr_count($servstring, "Stopped") != 1)
{
sleep(1);
$servstring = shell_exec("$powershell get-service -ComputerName $server -Name mssqlserver");
}
$restart_string = "$powershell (get-service -ComputerName $server -Name mssqlserver).Start()";
exec( $restart_string );
$servstring = shell_exec("$powershell get-service -ComputerName $server -Name mssqlserver");
// Wait until the service is fully started
while (substr_count($servstring, "Running") != 1)
{
sleep(1);
$servstring = shell_exec("$powershell get-service -ComputerName $server -Name mssqlserver");
}
}
function StopConn($server)
{
$powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
$restart_string = "$powershell (get-service -ComputerName $server -Name mssqlserver).Stop()";
exec( $restart_string );
$servstring = shell_exec("$powershell get-service -ComputerName $server -Name mssqlserver");
// Wait until the service is fully stopped
while (substr_count($servstring, "Stopped") != 1)
{
sleep(1);
$servstring = shell_exec("$powershell get-service -ComputerName $server -Name mssqlserver");
}
}
function StartConn($server)
{
$powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
$servstring = shell_exec("$powershell get-service -ComputerName $server -Name mssqlserver");
if (substr_count($servstring, "Running") != 1)
{
$restart_string = "$powershell (get-service -ComputerName $server -Name mssqlserver).Start()";
exec( $restart_string );
}
$servstring = shell_exec("$powershell get-service -ComputerName $server -Name mssqlserver");
// Wait until the service is fully started
while (substr_count($servstring, "Running") != 1)
{
sleep(1);
$servstring = shell_exec("$powershell get-service -ComputerName $server -Name mssqlserver");
}
}
?>

View file

@ -1,82 +0,0 @@
<?php
require_once("ConInfo.inc");
// Using the tempdb database for two tables specifically constructed
// for the connection resiliency tests
$dbName = "tempdb";
$tableName1 = "test_connres1";
$tableName2 = "test_connres2";
// Generate tables for use with the connection resiliency tests.
// Using generated tables will eventually allow us to put the
// connection resiliency tests on Github, since the integrated testing
// from AppVeyor does not have AdventureWorks.
function GenerateTables( $server, $uid, $pwd, $dbName, $tableName1, $tableName2 )
{
$conn = new PDO( "sqlsrv:server = $server ; Database = $dbName ;", $uid, $pwd );
if ( $conn === false )
{
die ( print_r( sqlsrv_errors() ) );
}
// Create table
$sql = "CREATE TABLE $tableName1 ( c1 INT, c2 VARCHAR(40) )";
$stmt = $conn->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 );
?>

View file

@ -3,6 +3,19 @@ $server = 'TARGET_SERVER';
$databaseName = 'TARGET_DATABASE';
$uid = 'TARGET_USERNAME';
$pwd = 'TARGET_PASSWORD';
function dropTable($conn, $tableName)
{
$tsql = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" . $tableName . "') AND type in (N'U')) DROP TABLE [$tableName]";
$conn->exec($tsql);
}
function dropProc($conn, $procName)
{
$tsql = "IF OBJECT_ID('". $procName ."', 'P') IS NOT NULL DROP PROCEDURE [$procName]";
$conn->exec($tsql);
}
?>

View file

@ -7,17 +7,19 @@ uses an input/output parameter
require('connect.inc');
$dbh = new PDO( "sqlsrv:server=$server ; Database = $databaseName", "$uid", "$pwd");
$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 ?");
dropProc($dbh, 'sp_ReverseString');
$dbh->query("CREATE PROCEDURE sp_ReverseString @String as VARCHAR(2048) OUTPUT as SELECT @String = REVERSE(@String)");
$stmt = $dbh->prepare("EXEC sp_ReverseString ?");
$string = "123456789";
$stmt->bindParam(1, $string, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 2048);
$stmt->execute();
print "Result: ".$string; // Expect 987654321
dropProc($dbh, 'sp_ReverseString', false);
//free the statement and connection
$stmt = null;
$dbh = null;
unset($stmt);
unset($dbh);
?>
--EXPECT--
Result: 987654321

View file

@ -23,20 +23,20 @@ $stmt->debugDumpParams();
$stmt=null;
$conn=null;
?>
--EXPECT--
SQL: [52] select * from Person.ContactType where name = :param
--EXPECTREGEX--
SQL: \[52\] select \* from Person.ContactType where name = \:param
Params: 1
Key: Name: [6] :param
Key: Name: \[6\] :param
paramno=0
name=[6] ":param"
name=\[6\] ":param"
is_param=1
param_type=2
param_type=(2|3)
SQL: [47] select * from Person.ContactType where name = ?
SQL: \[47\] select \* from Person.ContactType where name = \?
Params: 1
Key: Position #0:
paramno=0
name=[0] ""
name=\[0\] ""
is_param=1
param_type=2
param_type=(2|3)

View file

@ -19,24 +19,24 @@ print $metadata['name'];
$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)
--EXPECTREGEX--
array\(8\) {
\["flags"\]=>
int\(0\)
\["sqlsrv:decl_type"\]=>
string\(8\) "datetime"
\["native_type"\]=>
string\(6\) "string"
\["table"\]=>
string\(0\) ""
\["pdo_type"\]=>
int\((2|3)\)
\["name"\]=>
string\(12\) "ModifiedDate"
\["len"\]=>
int\(23\)
\["precision"\]=>
int\(3\)
}
datetime
string

View file

@ -5,30 +5,33 @@ returns the number of rows added to a table; returns the number of rows in a res
--FILE--
<?php
require('connect.inc');
$conn = new PDO( "sqlsrv:server=$server ; Database = tempdb", "$uid", "$pwd");
$conn->exec("CREAtE TABLE Table1(col1 VARCHAR(15), col2 VARCHAR(15)) ");
$conn = new PDO("sqlsrv:Server=$server; Database = $databaseName", $uid, $pwd);
$tableName = "pdoRowCount";
dropTable($conn, $tableName);
$conn->exec("CREATE TABLE $tableName(col1 VARCHAR(15), col2 VARCHAR(15)) ");
$col1 = 'a';
$col2 = 'b';
$query = "insert into Table1(col1, col2) values(?, ?)";
$query = "INSERT INTO $tableName(col1, col2) values(?, ?)";
$stmt = $conn->prepare( $query );
$stmt->execute( array( $col1, $col2 ) );
print $stmt->rowCount();
print " rows affects.";
print " rows affected.";
echo "\n\n";
//revert the insert
$conn->exec("delete from Table1 where col1 = 'a' AND col2 = 'b'");
$conn->exec("DELETE FROM $tableName where col1 = 'a' AND col2 = 'b'");
$conn->exec("DROP TABLE Table1 ");
dropTable($conn, $tableName, false);
$conn = null;
$conn = new PDO( "sqlsrv:server=$server ; Database = $databaseName", "$uid", "$pwd");
$conn = new PDO("sqlsrv:Server=$server; Database = $databaseName", $uid, $pwd);
$query = "select * from Person.ContactType";
$query = "SELECT * FROM Person.ContactType";
$stmt = $conn->prepare( $query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL));
$stmt->execute();
print $stmt->rowCount();
@ -40,6 +43,6 @@ $stmt = null;
$conn = null;
?>
--EXPECT--
1 rows affects.
1 rows affected.
20 rows in result set.

View file

@ -4,27 +4,30 @@ starts a transaction, insert 2 rows and commit the transaction
<?php require('skipif.inc'); ?>
--FILE--
<?php
require('connect.inc');
//make connection and create a temporaty table in tempdb
$conn = new PDO( "sqlsrv:Server=$server; Database = tempdb ", "$uid", "$pwd");
$conn->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;
require('connect.inc');
//make connection and create a temporaty table
$conn = new PDO( "sqlsrv:Server=$server; Database = $databaseName ", "$uid", "$pwd");
$tableName = "pdoBeginTransaction";
dropTable($conn, $tableName);
$conn->exec("CREATE TABLE $tableName(col1 CHARACTER(1), col2 CHARACTER(1)) ");
$conn->beginTransaction();
$ret = $conn->exec("insert into $tableName(col1, col2) values('a', 'b') ");
$ret = $conn->exec("insert into $tableName(col1, col2) values('a', 'c') ");
//revert the inserts
$ret = $conn->exec("delete from $tableName where col1 = 'a'");
$conn->commit();
// $conn->rollback();
echo $ret." rows affected";
//drop the created temp table
dropTable($conn, $tableName, false);
unset($conn);
?>
--EXPECT--
2 rows affected

View file

@ -5,9 +5,8 @@ connect to a server, specifying the database later
--FILE--
<?php
require('connect.inc');
$c = new PDO( "sqlsrv:Server=$server", "$uid", "$pwd");
$c = new PDO("sqlsrv:Server=$server; Database = $databaseName", $uid, $pwd);
$c->exec( "USE $databaseName");
$query = 'SELECT * FROM Person.ContactType';
$stmt = $c->query( $query );
while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){

View file

@ -4,21 +4,22 @@ execute a delete and reports how many rows were deleted
<?php require('skipif.inc'); ?>
--FILE--
<?php
require('connect.inc');
$c = new PDO( "sqlsrv:Server=$server", "$uid", "$pwd");
require('connect.inc');
$c = new PDO("sqlsrv:server=$server; Database = $databaseName", $uid, $pwd);
$c->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;
$tableName = "pdoExec";
dropTable($c, $tableName);
$c->exec("CREATE TABLE $tableName(col1 VARCHAR(100), col2 VARCHAR(100)) ");
$ret = $c->exec("INSERT INTO $tableName VALUES('xxxyy', 'yyxx')");
$ret = $c->exec("DELETE FROM $tableName WHERE col1 = 'xxxyy'");
echo $ret," rows affected";
dropTable($c, $tableName, false);
//free the statement and connection
unset($c);
?>
--EXPECT--
1 rows affected

View file

@ -5,31 +5,34 @@ prepares a statement with parameter markers and forward-only (server-side) curso
--FILE--
<?php
require('connect.inc');
$conn = new PDO( "sqlsrv:server=$server ; Database = tempdb", "$uid", "$pwd");
$conn = new PDO("sqlsrv:Server=$server; Database = $databaseName", $uid, $pwd);
$conn->exec("CREAtE TABLE Table1(col1 VARCHAR(100), col2 VARCHAR(100))");
$tableName = "pdoPrepare";
dropTable($conn, $tableName);
$conn->exec("CREATE TABLE $tableName(col1 VARCHAR(100), col2 VARCHAR(100))");
$col1 = 'a';
$col2 = 'b';
$query = "insert into Table1(col1, col2) values(?, ?)";
$query = "INSERT INTO $tableName(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)";
$query = "INSERT INTO $tableName(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("DELETE FROM $tableName WHERE col1 = 'a' AND col2 = 'b'");
dropTable($conn, $tableName, false);
$conn->exec("DROP TABLE Table1 ");
$stmt = null;
$conn = null;
unset($stmt);
unset($conn);
?>
--EXPECT--
1 row affected

View file

@ -6,36 +6,40 @@ insert with quoted parameters
<?php
require('connect.inc');
$conn = new PDO( "sqlsrv:server=$server ; Database = tempdb", "$uid", "$pwd");
$conn->exec("CREAtE TABLE Table1(col1 VARCHAR(15), col2 VARCHAR(15)) ");
$conn = new PDO("sqlsrv:Server=$server; Database = $databaseName", $uid, $pwd);
$tableName = "pdoQuote";
dropTable($conn, $tableName);
$conn->exec("CREATE TABLE $tableName(col1 VARCHAR(15), col2 VARCHAR(15)) ");
$param = 'a \' g';
$param2 = $conn->quote( $param );
$query = "INSERT INTO Table1 VALUES( ?, '1' )";
$query = "INSERT INTO $tableName VALUES( ?, '1' )";
$stmt = $conn->prepare( $query );
$stmt->execute(array($param));
$query = "INSERT INTO Table1 VALUES( ?, ? )";
$query = "INSERT INTO $tableName VALUES( ?, ? )";
$stmt = $conn->prepare( $query );
$stmt->execute(array($param, $param2));
$query = "SELECT * FROM Table1";
$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 Table1 where col1 = ?";
$query = "DELETE FROM $tableName WHERE col1 = ?";
$stmt = $conn->prepare( $query );
$stmt->execute(array($param));
$conn->exec("DROP TABLE Table1 ");
dropTable($conn, $tableName, false);
//free the statement and connection
$stmt=null;
$conn=null;
unset($stmt);
unset($conn);
?>
--EXPECT--
a ' g was inserted

View file

@ -4,27 +4,24 @@ sets to PDO::SQLSRV_ATTR_DIRECT_QUERY
<?php require('skipif.inc'); ?>
--FILE--
<?php
require('connect.inc');
$conn = new PDO("sqlsrv:Server=$server", "$uid", "$pwd");
$conn->setAttribute(constant('PDO::SQLSRV_ATTR_DIRECT_QUERY'), true);
require('connect.inc');
$conn = new PDO("sqlsrv:Server=$server; Database = $databaseName", $uid, $pwd);
$conn->setAttribute(PDO::SQLSRV_ATTR_DIRECT_QUERY, true);
$tableName = 'pdo_direct_query';
$tsql = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" . $tableName . "') AND type in (N'U')) DROP TABLE $tableName";
$stmt1 = $conn->query($tsql);
$stmt2 = $conn->query("CREATE TABLE $tableName ([c1_int] int, [c2_int] int)");
$conn->query("CREATE TABLE $tableName ([c1_int] int, [c2_int] int)");
$v1 = 1;
$v2 = 2;
$stmt3 = $conn->prepare("INSERT INTO $tableName (c1_int, c2_int) VALUES (:var1, :var2)");
$stmt = $conn->prepare("INSERT INTO $tableName (c1_int, c2_int) VALUES (:var1, :var2)");
if ($stmt3) {
$stmt3->bindValue(1, $v1);
$stmt3->bindValue(2, $v2);
if ($stmt) {
$stmt->bindValue(1, $v1);
$stmt->bindValue(2, $v2);
if ($stmt3->execute()) {
echo "Execution succeeded\n";
if ($stmt->execute()) {
echo "Execution succeeded\n";
} else {
echo "Execution failed\n";
}
@ -32,13 +29,10 @@ sets to PDO::SQLSRV_ATTR_DIRECT_QUERY
var_dump($conn->errorInfo());
}
$stmt4 = $conn->query("DROP TABLE $tableName");
$stmt = $conn->query("DROP TABLE $tableName");
// free the statements and connection
unset($stmt1);
unset($stmt2);
unset($stmt3);
unset($stmt4);
unset($stmt);
unset($conn);
?>
--EXPECT--

View file

@ -7,11 +7,7 @@ call a stored procedure and retrieve the errorNumber that is returned
require('connect.inc');
$conn = new PDO( "sqlsrv:server=$server ; Database = $databaseName", "$uid", "$pwd");
// Drop the stored procedure if it already exists
$tsql_dropSP = "IF OBJECT_ID('sp_Test_Double', 'P') IS NOT NULL
DROP PROCEDURE sp_Test_Double";
$stmt = $conn->query($tsql_dropSP);
dropProc($conn, 'sp_Test_Double');
// Create the stored procedure
$tsql_createSP = "CREATE PROCEDURE sp_Test_Double
@ -39,10 +35,11 @@ call a stored procedure and retrieve the errorNumber that is returned
print("Error Number minus 2: $value\n\n");
print_r($result);
dropProc($conn, 'sp_Test_Double', false);
//free the statement and connection
$stmt = null;
$conn = null;
unset($stmt);
unset($conn);
?>
--EXPECT--
Error Number: -1.111

View file

@ -4,43 +4,43 @@ call a stored procedure and retrieve the errorNumber that is returned
<?php require('skipif.inc'); ?>
--FILE--
<?php
require('connect.inc');
$conn = new PDO( "sqlsrv:server=$server ; Database = tempdb", "$uid", "$pwd");
require('connect.inc');
$conn = new PDO( "sqlsrv:server=$server ; Database = $databaseName", $uid, $pwd);
// Drop the stored procedure if it already exists
$tsql_dropSP = "IF OBJECT_ID('sp_Test_Integer', 'P') IS NOT NULL
DROP PROCEDURE sp_Test_Integer";
$stmt = $conn->query($tsql_dropSP);
// Drop the stored procedure if it already exists
dropProc($conn, 'sp_Test_Integer');
// 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";
// 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);
$stmt = $conn->query($tsql_createSP);
// Call the stored procedure
$stmt = $conn->prepare("{CALL sp_Test_Integer (:errornumber)}");
// 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);
$errorNumber = 0;
$stmt->bindParam('errornumber', $errorNumber, PDO::PARAM_INT|PDO::PARAM_INPUT_OUTPUT, 4);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_NUM);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_NUM);
$stmt->closeCursor();
$stmt->closeCursor();
print("Error Number: $errorNumber\n\n");
print_r($result);
//free the statement and connection
$stmt = null;
$conn = null;
print("Error Number: $errorNumber\n\n");
print_r($result);
// Drop the stored procedure
dropProc($conn, 'sp_Test_Integer', false);
//free the statement and connection
unset($stmt);
unset($conn);
?>
--EXPECT--
Error Number: -1

View file

@ -4,44 +4,43 @@ call a stored procedure and retrieve the errorString that is returned
<?php require('skipif.inc'); ?>
--FILE--
<?php
require('connect.inc');
$conn = new PDO( "sqlsrv:server=$server ; Database = tempdb", "$uid", "$pwd");
require('connect.inc');
$conn = new PDO("sqlsrv:Server=$server; Database = $databaseName", $uid, $pwd);
// Drop the stored procedure if it already exists
dropProc($conn, 'sp_Test_String');
// Drop the stored procedure if it already exists
$tsql_dropSP = "IF OBJECT_ID('sp_Test_String', 'P') IS NOT NULL
DROP PROCEDURE sp_Test_String";
$stmt = $conn->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);
// 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 (?)}");
// 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");
$errorString = "12345";
$stmt->bindParam(1, $errorString, PDO::PARAM_STR|PDO::PARAM_INPUT_OUTPUT, 20);
print("Error String: $errorString\n\n");
$stmt->execute();
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_NUM);
$result = $stmt->fetchAll(PDO::FETCH_NUM);
$stmt->closeCursor();
print("Error String: $errorString\n\n");
print_r($result);
dropProc($conn, 'sp_Test_String', false);
$stmt->closeCursor();
print("Error String: $errorString\n\n");
print_r($result);
//free the statement and connection
$stmt = null;
$conn = null;
//free the statement and connection
unset($stmt);
unset($conn);
?>
--EXPECT--
Error String: 12345

View file

@ -1,109 +0,0 @@
<?php
require_once('connect.inc');
// Set SQL server + user + password
$serverName = getenv('MSSQL_SERVERNAME') ?: $server2;
$username = getenv('MSSQL_USERNAME') ?: $uid;
$password = getenv('MSSQL_PASSWORD') ?: $pwd;
// Generate unique DB name, example: php_20160817_1471475608267
$databaseName = "php_" . date("Ymd") . "_" . round(microtime(true)*1000);
// Generic table name example: php_20160817_1471475608267.dbo.php_table
$tableName1 = $databaseName.".dbo.php_table1";
$tableName2 = $databaseName.".dbo.php_table2";
$connectionInfo = array( "Database"=>"$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");
}
}
?>

View file

@ -1,88 +0,0 @@
<?php
require_once("connect.inc");
// Using the tempdb database for two tables specifically constructed
// for the connection resiliency tests
$dbName = "tempdb";
$tableName1 = "test_connres1";
$tableName2 = "test_connres2";
// Generate tables for use with the connection resiliency tests.
// Using generated tables will eventually allow us to put the
// connection resiliency tests on Github, since the integrated testing
// from AppVeyor does not have AdventureWorks.
function GenerateTables( $server, $uid, $pwd, $dbName, $tableName1, $tableName2 )
{
$connectionInfo = array( "Database"=>$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 );
?>

View file

@ -4,30 +4,23 @@ $databaseName = 'TARGET_DATABASE';
$uid = 'TARGET_USERNAME';
$pwd = 'TARGET_PASSWORD';
$server2 = 'ANOTHER_SERVER';
function dropTable($conn, $tableName)
{
$tsql = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" . $tableName . "') AND type in (N'U')) DROP TABLE [$tableName]";
sqlsrv_query($conn, $tsql);
}
function dropProc($conn, $procName)
{
$tsql = "IF OBJECT_ID('". $procName ."', 'P') IS NOT NULL DROP PROCEDURE [$procName]";
sqlsrv_query($conn, $tsql);
}
// RevisionNumber in SalesOrderHeader is subject to a trigger incrementing it whenever
// changes are made to SalesOrderDetail. Since RevisionNumber is a tinyint, it can
// overflow quickly if the BVT tests often run. So we change it directly here first
// before it can overflow.
function ResetRevisionNumber( $server, $databaseName, $uid, $pwd )
{
$connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd );
$conn = sqlsrv_connect( $server, $connectionInfo);
if( $conn === false )
{
echo "Could not connect.<br>";
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 );
// overflow quickly if the BVT tests often run. Disable the trigger.
function disableTrigger($conn)
{
sqlsrv_query($conn, 'DISABLE TRIGGER uSalesOrderHeader ON Sales.SalesOrderHeader');
}
?>

View file

@ -6,9 +6,6 @@ executes two queries as part of a transaction
<?php
require('connect.inc');
// First, reset the RevisionNumber to make sure it won't overflow
ResetRevisionNumber( $server, $databaseName, $uid, $pwd );
$connectionInfo = array( "Database"=>"$databaseName", "UID"=>$uid, "PWD"=>$pwd);
$conn = sqlsrv_connect( $server, $connectionInfo);
if( $conn === false )
@ -17,6 +14,8 @@ if( $conn === false )
die( print_r( sqlsrv_errors(), true ));
}
disableTrigger($conn);
/* Initiate transaction. */
/* Exit script if transaction cannot be initiated. */
if ( sqlsrv_begin_transaction( $conn ) === false )

View file

@ -34,26 +34,15 @@ $r_sql="UPDATE HumanResources.Employee SET VacationHours=61 WHERE BusinessEntity
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 );
$stmt=sqlsrv_query($conn, $r_sql);
sqlsrv_free_stmt( $stmt );
/* 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);
dropProc($conn, "SubtractVacationHours");
/* 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,
@ -69,10 +58,10 @@ $tsql2 = "CREATE PROCEDURE SubtractVacationHours
BEGIN
PRINT 'WARNING: Vacation hours are now less than zero.'
END;";
$stmt2 = sqlsrv_query( $conn, $tsql2 );
$stmt = sqlsrv_query( $conn, $tsql2 );
/* If the query fails, display errors and exit the script. */
if( $stmt2 === false)
if( $stmt === false)
{
DisplayErrors();
die;
@ -81,7 +70,7 @@ if( $stmt2 === false)
DisplayWarnings();
/* Free the statement resources. */
sqlsrv_free_stmt( $stmt2 );
sqlsrv_free_stmt( $stmt );
/* Set up the array that maps employee ID to used vacation hours. */
$emp_hrs = array (7=>4, 8=>5, 9=>8, 11=>50);
@ -98,10 +87,10 @@ $params = array(
/* Define and prepare the query to substract used vacation hours. */
$tsql3 = "{call SubtractVacationHours(?, ?)}";
$stmt3 = sqlsrv_prepare($conn, $tsql3, $params);
$stmt = sqlsrv_prepare($conn, $tsql3, $params);
/* If the statement preparation fails, display errors and exit the script. */
if( $stmt3 === false)
if( $stmt === false)
{
DisplayErrors();
die;
@ -115,7 +104,7 @@ 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)
if( sqlsrv_execute($stmt) === false)
{
DisplayErrors();
die;
@ -124,7 +113,7 @@ foreach(array_keys($emp_hrs) as $businessEntityId)
DisplayWarnings();
/*Move to the next result returned by the stored procedure. */
if( sqlsrv_next_result($stmt3) === false)
if( sqlsrv_next_result($stmt) === false)
{
DisplayErrors();
die;
@ -138,7 +127,9 @@ foreach(array_keys($emp_hrs) as $businessEntityId)
}
/* Free the statement*/
sqlsrv_free_stmt( $stmt3 );
sqlsrv_free_stmt( $stmt );
dropProc($conn, "SubtractVacationHours", false);
/* close connection resources. */
sqlsrv_close( $conn );
@ -169,10 +160,10 @@ function DisplayWarnings()
}
?>
--EXPECTREGEX--
Warning: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Changed database context to 'AdventureWorks201[4|7]'.
Warning: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Changed database context to 'AdventureWorks201.'.
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 "AdventureWorks201[4|7]", table "HumanResources.Employee", column 'VacationHours'.
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 "AdventureWorks201.", table "HumanResources.Employee", column 'VacationHours'.
Error: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]The statement has been terminated.

View file

@ -5,17 +5,9 @@ disables MARS support.
--FILE--
<?php
require('connect.inc');
$connectionInfo = array( "Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd");
$connectionInfo = array("Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, 'MultipleActiveResultSets'=> false);
$conn = sqlsrv_connect( $server, $connectionInfo);
/* Connect to the local server using Windows Authentication and
specify the AdventureWorks database as the database in use. */
$serverName = $server2;
$connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, 'MultipleActiveResultSets'=> false);
$conn = sqlsrv_connect( $serverName, $connectionInfo);
if( $conn === false )
{
if( $conn === false ) {
echo "Could not connect.\n";
die( print_r( sqlsrv_errors(), true));
}

View file

@ -57,17 +57,13 @@ echo "<br>";
sqlsrv_free_stmt( $stmt);
disableTrigger($conn);
/* 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);
@ -140,11 +136,9 @@ else
die( print_r( sqlsrv_errors(), true));
}
echo sqlsrv_rows_affected( $stmt)." rows affected.<br>";
sqlsrv_free_stmt( $stmt);
/* Free the statement and connection resources. */
//sqlsrv_free_stmt( $stmt);
sqlsrv_free_stmt( $stmt);
sqlsrv_close( $conn);
?>
--EXPECT--

View file

@ -17,17 +17,10 @@ if( $conn === false )
/*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);
$stmt = 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));
}
dropProc($conn, 'InsertProductReview');
/* Create the stored procedure. */
$tsql_createSP = " CREATE PROCEDURE InsertProductReview
@ -56,9 +49,9 @@ $tsql_createSP = " CREATE PROCEDURE InsertProductReview
SELECT * FROM Production.ProductReview
WHERE ProductID = @ProductID;
END";
$stmt2 = sqlsrv_query( $conn, $tsql_createSP);
$stmt = sqlsrv_query( $conn, $tsql_createSP);
if( $stmt2 === false)
if( $stmt === false)
{
echo "Error in executing statement 2.\n";
die( print_r( sqlsrv_errors(), true));
@ -86,8 +79,8 @@ $params = array(
);
/* Execute the query. */
$stmt3 = sqlsrv_query( $conn, $tsql_callSP, $params);
if( $stmt3 === false)
$stmt = sqlsrv_query( $conn, $tsql_callSP, $params);
if( $stmt === false)
{
echo "Error in executing statement 3.\n";
die( print_r( sqlsrv_errors(), true));
@ -97,17 +90,17 @@ echo "<p>";
/* 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 "Rows affectd: ".sqlsrv_rows_affected($stmt)."-----\n";
echo "<p>";
/* Move to the next result and display results. */
$next_result = sqlsrv_next_result($stmt3);
$next_result = sqlsrv_next_result($stmt);
if( $next_result )
{
echo "<p>";
echo "\nReview information for product ID ".$productID.".---\n";
while( $row = sqlsrv_fetch_array( $stmt3, SQLSRV_FETCH_ASSOC))
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC))
{
echo "<br>ReviewerName: ".$row['ReviewerName']."\n";
echo "<br>ReviewDate: ".date_format($row['ReviewDate'],
@ -127,13 +120,10 @@ else
die(print_r(sqlsrv_errors(), true));
}
dropProc($conn, 'InsertProductReview', false);
/* Free statement and connection resources. */
sqlsrv_free_stmt( $stmt1 );
sqlsrv_free_stmt( $stmt2 );
sqlsrv_free_stmt( $stmt3 );
sqlsrv_free_stmt( $stmt4 );
sqlsrv_free_stmt( $stmt );
sqlsrv_close( $conn );
?>
--EXPECT--

View file

@ -15,6 +15,8 @@ if( $conn === false )
die( print_r( sqlsrv_errors(), true));
}
disableTrigger($conn);
/* Set up the parameterized query. */
$tsql = "INSERT INTO Sales.SalesOrderDetail
(SalesOrderID,
@ -29,12 +31,6 @@ $tsql = "INSERT INTO Sales.SalesOrderDetail
/* 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 )

View file

@ -13,14 +13,7 @@ if( $conn === false )
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));
}
dropProc($conn, 'SubtractVacationHours');
/* Create the stored procedure. */
$tsql_createSP = "CREATE PROCEDURE SubtractVacationHours
@ -34,8 +27,8 @@ $tsql_createSP = "CREATE PROCEDURE SubtractVacationHours
FROM HumanResources.Employee
WHERE BusinessEntityID = @BusinessEntityID)";
$stmt2 = sqlsrv_query( $conn, $tsql_createSP);
if( $stmt2 === false )
$stmt = sqlsrv_query( $conn, $tsql_createSP);
if( $stmt === false )
{
echo "Error in executing statement 2.\n";
die( print_r( sqlsrv_errors(), true));
@ -62,26 +55,25 @@ $params = array(
);
/* Execute the query. */
$stmt3 = sqlsrv_query( $conn, $tsql_callSP, $params);
if( $stmt3 === false )
$stmt = sqlsrv_query( $conn, $tsql_callSP, $params);
if( $stmt === 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);
sqlsrv_next_result($stmt);
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);
$stmt = sqlsrv_query($conn, $r_sql);
dropProc($conn, 'SubtractVacationHours', false);
/*Free the statement and connection resources. */
sqlsrv_free_stmt( $stmt1);
sqlsrv_free_stmt( $stmt2);
sqlsrv_free_stmt( $stmt3);
sqlsrv_free_stmt( $stmt4);
sqlsrv_free_stmt( $stmt);
sqlsrv_close( $conn);
?>
--EXPECT--

View file

@ -16,14 +16,7 @@ if( $conn === false )
}
/* 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));
}
dropProc($conn, 'GetEmployeeSalesYTD');
/* Create the stored procedure. */
$tsql_createSP = " CREATE PROCEDURE GetEmployeeSalesYTD
@ -35,8 +28,8 @@ $tsql_createSP = " CREATE PROCEDURE GetEmployeeSalesYTD
JOIN HumanResources.vEmployee AS e
ON e.BusinessEntityID = sp.BusinessEntityID
WHERE LastName = @SalesPerson";
$stmt2 = sqlsrv_query( $conn, $tsql_createSP);
if( $stmt2 === false )
$stmt = sqlsrv_query( $conn, $tsql_createSP);
if( $stmt === false )
{
echo "Error in executing statement 2.\n";
die( print_r( sqlsrv_errors(), true));
@ -62,8 +55,8 @@ $params = array(
);
/* Execute the query. */
$stmt3 = sqlsrv_query( $conn, $tsql_callSP, $params);
if( $stmt3 === false )
$stmt = sqlsrv_query( $conn, $tsql_callSP, $params);
if( $stmt === false )
{
echo "Error in executing statement 3.\n";
die( print_r( sqlsrv_errors(), true));
@ -72,10 +65,10 @@ if( $stmt3 === false )
/* Display the value of the output parameter $salesYTD. */
echo "YTD sales for ".$lastName." are ". $salesYTD. ".";
dropProc($conn, 'GetEmployeeSalesYTD', false);
/*Free the statement and connection resources. */
sqlsrv_free_stmt( $stmt1);
sqlsrv_free_stmt( $stmt2);
sqlsrv_free_stmt( $stmt3);
sqlsrv_free_stmt( $stmt);
sqlsrv_close( $conn);
?>
--EXPECT--

View file

@ -11,34 +11,32 @@ if ( $conn === false ) {
die( print_r( sqlsrv_errors(), true ));
}
$stmt = sqlsrv_query( $conn, "DROP TABLE dbo.ScrollTest" );
if ( $stmt !== false ) {
sqlsrv_free_stmt( $stmt );
}
$tableName = 'ScrollTest';
dropTable($conn, $tableName);
$stmt = sqlsrv_query( $conn, "CREATE TABLE ScrollTest (id int, value char(10))" );
$stmt = sqlsrv_query( $conn, "CREATE TABLE $tableName (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" ));
$stmt = sqlsrv_query( $conn, "INSERT INTO $tableName (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" ));
$stmt = sqlsrv_query( $conn, "INSERT INTO $tableName (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" ));
$stmt = sqlsrv_query( $conn, "INSERT INTO $tableName (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' ));
$stmt = sqlsrv_query( $conn, "SELECT * FROM $tableName", array(), array( "Scrollable" => 'keyset' ));
// $stmt = sqlsrv_query( $conn, "SELECT * FROM $tableName", array(), array( "Scrollable" => 'dynamic' ));
// $stmt = sqlsrv_query( $conn, "SELECT * FROM $tableName", array(), array( "Scrollable" => 'static' ));
$rows = sqlsrv_has_rows( $stmt );
if ( $rows != true ) {
@ -50,9 +48,9 @@ $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" );
//$stmt2 = sqlsrv_query( $conn, "delete from $tableName where id = 3" );
// or
$stmt2 = sqlsrv_query( $conn, "UPDATE ScrollTest SET id = 4 WHERE id = 3" );
$stmt2 = sqlsrv_query( $conn, "UPDATE $tableName SET id = 4 WHERE id = 3" );
if ( $stmt2 !== false ) {
sqlsrv_free_stmt( $stmt2 );
}
@ -62,6 +60,8 @@ $field1 = sqlsrv_get_field( $stmt, 0 );
$field2 = sqlsrv_get_field( $stmt, 1 );
echo "\n$field1 $field2\n";
dropTable($conn, $tableName, false);
sqlsrv_free_stmt( $stmt );
sqlsrv_close( $conn );
?>

View file

@ -32,4 +32,4 @@ else
sqlsrv_close( $conn);
?>
--EXPECTREGEX--
CurrentDatabase: AdventureWorks.*<br>SQLServerVersion: 1[2-9].00.[0-9]{4}<br>SQLServerName: SQL.+<br>
CurrentDatabase: AdventureWorks201.<br>SQLServerVersion: 1[2-9].00.[0-9]{4}<br>SQLServerName: .+<br>

View file

@ -15,14 +15,8 @@ call a stored procedure (SQLSRV Driver) and retrieve the errorNumber that is ret
}
// 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));
}
dropProc($conn, "sp_Test");
// Create the stored procedure
$tsql_createSP = "CREATE PROCEDURE sp_Test
@ErrorNumber INT = 0 OUTPUT
@ -31,8 +25,8 @@ call a stored procedure (SQLSRV Driver) and retrieve the errorNumber that is ret
SET @ErrorNumber = -1
SELECT 1,2,3
END";
$stmt2 = sqlsrv_query($conn, $tsql_createSP);
if ($stmt2 === false) {
$stmt = sqlsrv_query($conn, $tsql_createSP);
if ($stmt === false) {
echo "Error in executing statement 2.\n";
die (print_r (sqlsrv_errors(), true));
}
@ -47,20 +41,20 @@ call a stored procedure (SQLSRV Driver) and retrieve the errorNumber that is ret
);
// Execute the query
$stmt3 = sqlsrv_query($conn, $tsql_callSP, $params);
if ($stmt3 === false) {
$stmt = sqlsrv_query($conn, $tsql_callSP, $params);
if ($stmt === 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);
sqlsrv_next_result($stmt);
print("Error Number: $errorNumber\n\n");
dropProc($conn, "sp_Test", false);
// Free the statement and connection resources. */
sqlsrv_free_stmt( $stmt1);
sqlsrv_free_stmt( $stmt2);
sqlsrv_free_stmt( $stmt3);
sqlsrv_free_stmt( $stmt);
sqlsrv_close( $conn);
?>
--EXPECT--

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -0,0 +1,209 @@
<?php
$createTVPOrd = <<<ORD
CREATE TABLE TVPOrd(
OrdNo INTEGER IDENTITY(1,1),
OrdDate DATETIME,
CustID VARCHAR(10))
ORD;
$createTVPItem = <<<ITEM
CREATE TABLE TVPItem(
OrdNo INTEGER,
ItemNo INTEGER IDENTITY(1,1),
ProductCode CHAR(10),
OrderQty INTEGER,
PackedOn DATE,
Label NVARCHAR(30),
Price DECIMAL(5,2),
Photo VARBINARY(MAX))
ITEM;
$createTVPParam = <<<TYPE
CREATE TYPE TVPParam AS TABLE(
ProductCode CHAR(10),
OrderQty INTEGER,
PackedOn DATE,
Label NVARCHAR(30),
Price DECIMAL(5,2),
Photo VARBINARY(MAX))
TYPE;
$createTVPOrderEntry = <<<PROC
CREATE PROCEDURE TVPOrderEntry(
@CustID VARCHAR(10),
@Items TVPParam READONLY,
@OrdNo INTEGER OUTPUT,
@OrdDate DATETIME OUTPUT)
AS
BEGIN
SET @OrdDate = GETDATE(); SET NOCOUNT ON;
INSERT INTO TVPOrd (OrdDate, CustID) VALUES (@OrdDate, @CustID);
SELECT @OrdNo = SCOPE_IDENTITY();
INSERT INTO TVPItem (OrdNo, ProductCode, OrderQty, PackedOn, Label, Price, Photo)
SELECT @OrdNo, ProductCode, OrderQty, PackedOn, Label, Price, Photo
FROM @Items
END;
PROC;
$callTVPOrderEntry = "{call TVPOrderEntry(?, ?, ?, ?)}";
$callTVPOrderEntryNamed = "{call TVPOrderEntry(:id, :tvp, :ordNo, :ordDate)}";
// The following gif files are some random product pictures
// retrieved from the AdventureWorks sample database (their
// sizes ranging from 12 KB to 26 KB)
$gif1 = 'awc_tee_male_large.gif';
$gif2 = 'superlight_black_f_large.gif';
$gif3 = 'silver_chain_large.gif';
$items = [
['0062836700', 367, "2009-03-12", 'AWC Tee Male Shirt', '20.75'],
['1250153272', 256, "2017-11-07", 'Superlight Black Bicycle', '998.45'],
['1328781505', 260, "2010-03-03", 'Silver Chain for Bikes', '88.98'],
];
$selectTVPItemQuery = 'SELECT OrdNo, ItemNo, ProductCode, OrderQty, PackedOn, Label, Price FROM TVPItem ORDER BY ItemNo';
///////////////////////////////////////////////////////
$createTestTVP = <<<TYPE1
CREATE TYPE TestTVP AS TABLE(
C01 VARCHAR(255),
C02 VARCHAR(MAX),
C03 BIT,
C04 SMALLDATETIME,
C05 DATETIME2(5),
C06 UNIQUEIDENTIFIER,
C07 BIGINT,
C08 FLOAT,
C09 NUMERIC(38, 24))
TYPE1;
$createSelectTVP = <<<PROC1
CREATE PROCEDURE SelectTVP (
@TVP TestTVP READONLY)
AS
SELECT * FROM @TVP
PROC1;
$callSelectTVP = "{call SelectTVP(?)}";
///////////////////////////////////////////////////////
$createTestTVP2 = <<<TYPE2
CREATE TYPE TestTVP2 AS TABLE(
C01 NVARCHAR(50),
C02 NVARCHAR(MAX),
C03 INT,
C04 REAL,
C05 VARBINARY(10),
C06 VARBINARY(MAX),
C07 MONEY,
C08 XML,
C09 SQL_VARIANT)
TYPE2;
$createSelectTVP2 = <<<PROC2
CREATE PROCEDURE SelectTVP2 (
@TVP TestTVP2 READONLY)
AS
SELECT * FROM @TVP
PROC2;
$callSelectTVP2 = "{call SelectTVP2(?)}";
///////////////////////////////////////////////////////
// Use schema other than DBO
///////////////////////////////////////////////////////
$createSchema = 'CREATE SCHEMA [Sales DB]';
$dropSchema = 'DROP SCHEMA IF EXISTS [Sales DB]';
$createTestTVP3 = <<<TYPE3
CREATE TYPE [Sales DB].[TestTVP3] AS TABLE(
Review VARCHAR(100) NOT NULL,
SupplierId INT,
SalesDate DATETIME2 NULL
)
TYPE3;
$createSelectTVP3 = <<<PROC3
CREATE PROCEDURE [Sales DB].[SelectTVP3] (
@TVP TestTVP3 READONLY )
AS
SELECT * FROM @TVP
PROC3;
$callSelectTVP3 = "{call [Sales DB].[SelectTVP3](?)}";
$createSupplierType = <<<SUPP_TYPE
CREATE TYPE [Sales DB].[SupplierType] AS TABLE(
SupplierId INT,
SupplierName NVARCHAR(50)
)
SUPP_TYPE;
$createAddReview = <<<SUPP_PROC
CREATE PROCEDURE [Sales DB].[AddReview] (
@suppType SupplierType READONLY,
@reviewType TestTVP3 READONLY,
@image VARBINARY(MAX))
AS
SELECT * FROM @suppType;
SELECT SupplierId, SalesDate, Review FROM @reviewType;
SELECT @image
SUPP_PROC;
$callAddReview = "{call [Sales DB].[AddReview](?, ?, ?)}";
///////////////////////////////////////////////////////
// Common functions
///////////////////////////////////////////////////////
function dropProcSQL($conn, $procName)
{
return "IF OBJECT_ID('$procName', 'P') IS NOT NULL DROP PROCEDURE $procName";
}
function dropTableTypeSQL($conn, $typeName, $schema = 'dbo')
{
return "IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '$typeName') DROP TYPE [$schema].[$typeName]";
}
function verifyBinaryData($fp, $data)
{
$size = 8192;
$pos = 0;
$matched = true;
while (!feof($fp)) {
$original = fread($fp, $size);
$str = substr($data, $pos, $size);
if ($original !== $str) {
$matched = false;
break;
}
$pos += $size;
}
return $matched;
}
function verifyBinaryStream($fp, $stream)
{
$size = 8192;
$matched = true;
while (!feof($fp) && !feof($stream)) {
$original = fread($fp, $size);
$data = fread($stream, $size);
if ($original !== $data) {
$matched = false;
break;
}
}
return $matched;
}
?>

View file

@ -8,6 +8,10 @@
*/
$tvpIncPath = dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'inc'.DIRECTORY_SEPARATOR;
require_once($tvpIncPath. 'test_tvp_data.php');
//
// looks like an additional file (in addition to pdo_test_base.inc) may be needed for these PHPTs
// to be runnable from the MSSQL teams' internal proprietary test running system
@ -302,7 +306,7 @@ class BindParamOp
}
if ($length >= 0 || is_null($length)) {
$this->length = $length;
$this->length = is_null($length) ? 0 : $length;
} else {
printf("BindParamOp construct: The length provided must be greater or equal to 0.\n");
exit;
@ -527,14 +531,11 @@ function fetchAll($conn, $tbname)
* @param string $fetchStyle : fetch_style argument passed to PDOStatement::fetchAll
* @return array rows in a result set
*/
function selectAll($conn, $tbname, $fetchStyle = null)
function selectAll($conn, $tbname, $fetchStyle = PDO::FETCH_BOTH)
{
try {
$sql = "SELECT * FROM $tbname";
$stmt = $conn->query($sql);
if ($fetchStyle) {
$fetchStyle = constant($fetchStyle);
}
$data = $stmt->fetchAll($fetchStyle);
return $data;
} catch (PDOException $e) {
@ -1794,3 +1795,30 @@ function PhpVersionComponents(&$major, &$minor, &$sub)
$minor = strtok(".");
$sub = strtok(".");
}
function getTodayDateAsString($conn)
{
$tsql = 'SELECT CONVERT (VARCHAR(20), GETDATE())';
$stmt = $conn->query($tsql);
$row = $stmt->fetch(PDO::FETCH_NUM);
return $row[0];
}
function compareResourceToInput($actual, $expected)
{
$size = 8192;
$pos = 0;
$matched = true;
while (!feof($actual)) {
$original = fread($actual, $size);
$str = substr($expected, $pos, $size);
if ($original !== $str) {
$matched = false;
break;
}
$pos += $size;
}
return $matched;
}

View file

@ -65,7 +65,7 @@ function insertNullsTest($bindType)
$stmt2->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_BINARY);
$stmt2->bindValue(":p1", null, $bindType);
} elseif ($bindType == PDO::PARAM_STR) {
$stmt2->bindParam(":p1", $outvar, $bindType, null, PDO::SQLSRV_ENCODING_BINARY);
$stmt2->bindParam(":p1", $outvar, $bindType, 0, PDO::SQLSRV_ENCODING_BINARY);
}
} else {
$stmt2->bindParam(":p1", $outvar);

View file

@ -10,7 +10,7 @@ PHPT_EXEC=true
<?php
include 'MsCommon.inc';
function ReadOnly()
function testReadOnly()
{
include 'MsSetup.inc';
@ -62,7 +62,7 @@ function Repro()
try
{
ReadOnly();
testReadOnly();
}
catch (Exception $e)
{

View file

@ -81,7 +81,7 @@ function Repro()
Repro();
?>
--EXPECT--
--EXPECTF--
array(8) {
["flags"]=>
int(0)
@ -92,7 +92,7 @@ array(8) {
["table"]=>
string(0) ""
["pdo_type"]=>
int(2)
int(%d)
["name"]=>
string(2) "id"
["len"]=>
@ -110,7 +110,7 @@ array(8) {
["table"]=>
string(0) ""
["pdo_type"]=>
int(2)
int(%d)
["name"]=>
string(3) "val"
["len"]=>
@ -128,7 +128,7 @@ array(8) {
["table"]=>
string(0) ""
["pdo_type"]=>
int(2)
int(%d)
["name"]=>
string(4) "val2"
["len"]=>
@ -146,7 +146,7 @@ array(8) {
["table"]=>
string(0) ""
["pdo_type"]=>
int(2)
int(%d)
["name"]=>
string(0) ""
["len"]=>

View file

@ -61,8 +61,14 @@ function fetchLob($offset, $conn, $table, $sqlType, $data1, $data2)
if ($id != $data1) {
logInfo($offset, "ID data corruption: [$id] instead of [$data1]");
}
if ($label != $data2) {
logInfo($offset, "Label data corruption: [$label] instead of [$data2]");
if (PHP_VERSION_ID < 80100) {
if ($label != $data2) {
logInfo($offset, "Label data corruption: [$label] instead of [$data2]");
}
} else {
if (!compareResourceToInput($label, $data2)) {
logInfo($offset, "Label data corruption");
}
}
unset($stmt);
unset($label);

View file

@ -5,7 +5,14 @@ Verification of capabilities for extending PDO.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif.inc'); ?>
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID < 80000) {
die("skip Test designed for PHP 8.*");
}
?>
--FILE--
<?php
include 'MsCommon.inc';
@ -48,7 +55,7 @@ class ExPDO extends PDO
function __destruct()
{
echo __METHOD__ . "()\n";
}
}
function test()
{
@ -56,9 +63,9 @@ class ExPDO extends PDO
var_dump($this->test1);
var_dump($this->test2);
$this->test2 = 22;
}
}
function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args)
function query(string $sql, ?int $fetchMode = null, mixed ...$fetchModeArgs): PDOStatement|false
{
echo __METHOD__ . "()\n";
$stmt = parent::prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('ExPDOStatement')));

View file

@ -0,0 +1,140 @@
--TEST--
Extending PDO Test #1
--DESCRIPTION--
Verification of capabilities for extending PDO.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID >= 80000) {
die("skip Test designed for PHP 7.*");
}
?>
--FILE--
<?php
include 'MsCommon.inc';
function Extend()
{
include 'MsSetup.inc';
$testName = "PDO - Extension";
StartTest($testName);
$conn1 = PDOConnect('ExPDO', $server, $uid, $pwd, true);
$conn1->test();
var_dump($conn1);
// Prepare test table
DropTable($conn1, $tableName);
$conn1->query("CREATE TABLE [$tableName] (id int NOT NULL PRIMARY KEY, val VARCHAR(10))");
$conn1->query("INSERT INTO [$tableName] VALUES(0, 'A')");
$conn1->query("INSERT INTO [$tableName] VALUES(1, 'B')");
// Retrieve test data via a direct query
$stmt1 = ExecuteQuery($conn1, "SELECT val, id FROM [$tableName]");
$result = $stmt1->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE);
var_dump($result);
// Cleanup
DropTable($conn1, $tableName);
$stmt1 = null;
$conn1 = null;
EndTest($testName);
}
class ExPDO extends PDO
{
public $test1 = 1;
function __destruct()
{
echo __METHOD__ . "()\n";
}
function test()
{
$this->test2 = 2;
var_dump($this->test1);
var_dump($this->test2);
$this->test2 = 22;
}
function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args)
{
echo __METHOD__ . "()\n";
$stmt = parent::prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('ExPDOStatement')));
$stmt->execute();
return ($stmt);
}
}
class ExPDOStatement extends PDOStatement
{
protected function __construct()
{
echo __METHOD__ . "()\n";
}
function __destruct()
{
echo __METHOD__ . "()\n";
}
}
//--------------------------------------------------------------------
// Repro
//
//--------------------------------------------------------------------
function Repro()
{
try
{
Extend();
}
catch (Exception $e)
{
echo $e->getMessage();
}
}
Repro();
?>
--EXPECTF--
int(1)
int(2)
object(ExPDO)#%d (2) {
["test1"]=>
int(1)
["test2"]=>
int(22)
}
ExPDO::query()
ExPDOStatement::__construct()
ExPDOStatement::__destruct()
ExPDO::query()
ExPDOStatement::__construct()
ExPDOStatement::__destruct()
ExPDO::query()
ExPDOStatement::__construct()
ExPDOStatement::__destruct()
ExPDO::query()
ExPDOStatement::__construct()
array(2) {
["A"]=>
string(1) "0"
["B"]=>
string(1) "1"
}
ExPDOStatement::__destruct()
ExPDO::__destruct()
Test "PDO - Extension" completed successfully.

View file

@ -5,7 +5,14 @@ Verification of capabilities for extending PDO.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif.inc'); ?>
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID < 80000) {
die("skip Test designed for PHP 8.*");
}
?>
--FILE--
<?php
include 'MsCommon.inc';
@ -48,14 +55,14 @@ class ExPDO extends PDO
return (call_user_func_array(array($this, 'parent::__construct'), $args));
}
public function exec($args1)
public function exec(string $args1) : int|false
{
$this->protocol();
$args = func_get_args();
return (call_user_func_array(array($this, 'parent::exec'), $args));
}
public function query($statement, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args)
function query(string $sql, ?int $fetchMode = null, mixed ...$fetchModeArgs): PDOStatement|false
{
$this->protocol();
$args = func_get_args();

View file

@ -0,0 +1,141 @@
--TEST--
Extending PDO Test #1
--DESCRIPTION--
Verification of capabilities for extending PDO.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID >= 80000) {
die("skip Test designed for PHP 7.*");
}
?>
--FILE--
<?php
include 'MsCommon.inc';
function Extend()
{
include 'MsSetup.inc';
$testName = "PDO - Extension";
StartTest($testName);
// simply use $databaseName from MsSetup.inc to facilitate testing in Azure,
// which does not support switching databases
$conn2 = new ExPDO("sqlsrv:Server=$server;Database=$databaseName", $uid, $pwd);
// With PHP 8.0 the default is PDO::ERRMODE_EXCEPTION rather than PDO::ERRMODE_SILENT
$conn2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
DropTable($conn2, "tmp_table");
$conn2->exec("CREATE TABLE tmp_table (id INT)");
$conn2->exec("INSERT INTO tmp_table (id) VALUES (1), (2)");
$stmt1 = $conn2->query("SELECT * FROM tmp_table ORDER BY id ASC");
var_dump($stmt1->fetchAll(PDO::FETCH_ASSOC));
var_dump($stmt1->fetch());
$conn2->intercept_call();
// Cleanup
DropTable($conn2, "tmp_table");
$stmt1 = null;
$conn2 = null;
EndTest($testName);
}
class ExPDO extends PDO
{
public function __construct()
{
$this->protocol();
$args = func_get_args();
return (call_user_func_array(array($this, 'parent::__construct'), $args));
}
public function exec($args1)
{
$this->protocol();
$args = func_get_args();
return (call_user_func_array(array($this, 'parent::exec'), $args));
}
public function query($statement, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args)
{
$this->protocol();
$args = func_get_args();
return (call_user_func_array(array($this, 'parent::query'), $args));
}
public function __call($method, $args)
{
print "__call(".var_export($method, true).", ".var_export($args, true).")\n";
}
private function protocol()
{
$stack = debug_backtrace();
if (!isset($stack[1]))
{
return;
}
printf("%s(", $stack[1]['function']);
$args = '';
foreach ($stack[1]['args'] as $k => $v)
{
$args .= sprintf("%s, ", var_export($v, true));
}
if ($args != '')
{
printf("%s", substr($args, 0, -2));
}
printf(")\n");
}
}
//--------------------------------------------------------------------
// Repro
//
//--------------------------------------------------------------------
function Repro()
{
try
{
Extend();
}
catch (Exception $e)
{
echo $e->getMessage();
}
}
Repro();
?>
--EXPECTF--
__construct('%s', '%s', '%s')
exec('IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(%s) AND type in (N\'U\')) DROP TABLE %s')
exec('CREATE TABLE %s (id INT)')
exec('INSERT INTO %s (id) VALUES (1), (2)')
query('SELECT * FROM %s ORDER BY id ASC')
array(2) {
[0]=>
array(1) {
["id"]=>
string(1) "1"
}
[1]=>
array(1) {
["id"]=>
string(1) "2"
}
}
bool(false)
__call('intercept_call', array (
))
exec('IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(%s) AND type in (N\'U\')) DROP TABLE %s')
Test "PDO - Extension" completed successfully.

View file

@ -5,7 +5,14 @@ Verification of capabilities for extending PDO.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif.inc'); ?>
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID < 80000) {
die("skip Test designed for PHP 8.*");
}
?>
--FILE--
<?php
include 'MsCommon.inc';
@ -61,7 +68,7 @@ class ExPDO extends PDO
echo __METHOD__ . "()\n";
}
function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args)
function query(string $sql, ?int $fetchMode = null, mixed ...$fetchModeArgs): PDOStatement|false
{
echo __METHOD__ . "()\n";
$stmt = $this->prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('ExPDOStatement', array($this))));

View file

@ -0,0 +1,152 @@
--TEST--
Extending PDO Test #2
--DESCRIPTION--
Verification of capabilities for extending PDO.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID >= 80000) {
die("skip Test designed for PHP 7.*");
}
?>
--FILE--
<?php
include 'MsCommon.inc';
function Extend()
{
include 'MsSetup.inc';
$testName = "PDO - Extension";
StartTest($testName);
$data = array( array('10', 'Abc', 'zxy'),
array('20', 'Def', 'wvu'),
array('30', 'Ghi', 'tsr'));
$conn1 = PDOConnect('ExPDO', $server, $uid, $pwd, true);
var_dump(get_class($conn1));
// Prepare test table
DropTable($conn1, $tableName);
$conn1->exec("CREATE TABLE [$tableName] (id int NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))");
$stmt1 = $conn1->prepare("INSERT INTO [$tableName] VALUES(?, ?, ?)");
var_dump(get_class($stmt1));
foreach ($data as $row)
{
$stmt1->execute($row);
}
unset($stmt1);
// Retrieve test data via a direct query
$stmt1 = $conn1->query("SELECT * FROM [$tableName]");
var_dump(get_class($stmt1));
var_dump(get_class($stmt1->dbh));
foreach($stmt1 as $obj)
{
var_dump($obj);
}
echo "===DONE===\n";
// Cleanup
DropTable($conn1, $tableName);
$stmt1 = null;
$conn1 = null;
EndTest($testName);
}
class ExPDO extends PDO
{
function __destruct()
{
echo __METHOD__ . "()\n";
}
function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args)
{
echo __METHOD__ . "()\n";
$stmt = $this->prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('ExPDOStatement', array($this))));
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute();
return ($stmt);
}
}
class ExPDOStatement extends PDOStatement
{
public $dbh;
protected function __construct($dbh)
{
$this->dbh = $dbh;
echo __METHOD__ . "()\n";
}
function __destruct()
{
echo __METHOD__ . "()\n";
}
}
//--------------------------------------------------------------------
// Repro
//
//--------------------------------------------------------------------
function Repro()
{
try
{
Extend();
}
catch (Exception $e)
{
echo $e->getMessage();
}
}
Repro();
?>
--EXPECT--
string(5) "ExPDO"
string(12) "PDOStatement"
ExPDO::query()
ExPDOStatement::__construct()
string(14) "ExPDOStatement"
string(5) "ExPDO"
array(3) {
["id"]=>
string(2) "10"
["val"]=>
string(3) "Abc"
["val2"]=>
string(3) "zxy"
}
array(3) {
["id"]=>
string(2) "20"
["val"]=>
string(3) "Def"
["val2"]=>
string(3) "wvu"
}
array(3) {
["id"]=>
string(2) "30"
["val"]=>
string(3) "Ghi"
["val2"]=>
string(3) "tsr"
}
===DONE===
ExPDOStatement::__destruct()
ExPDO::__destruct()
Test "PDO - Extension" completed successfully.

View file

@ -5,7 +5,14 @@ Verification of capabilities for extending PDO.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif.inc'); ?>
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID < 80000) {
die("skip Test designed for PHP 8.*");
}
?>
--FILE--
<?php
include 'MsCommon.inc';
@ -66,7 +73,7 @@ class ExPDO extends PDO
echo __METHOD__ . "()\n";
}
function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args)
function query(string $sql, ?int $fetchMode = null, mixed ...$fetchModeArgs): PDOStatement|false
{
echo __METHOD__ . "()\n";
$stmt = $this->prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('ExPDOStatement', array($this))));
@ -91,10 +98,10 @@ class ExPDOStatement extends PDOStatement
echo __METHOD__ . "()\n";
}
function execute($params = array())
function execute(?array $params = null) : bool
{
echo __METHOD__ . "()\n";
parent::execute();
return parent::execute();
}
}

View file

@ -0,0 +1,166 @@
--TEST--
Extending PDO Test #3
--DESCRIPTION--
Verification of capabilities for extending PDO.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID >= 80000) {
die("skip Test designed for PHP 7.*");
}
?>
--FILE--
<?php
include 'MsCommon.inc';
function Extend()
{
include 'MsSetup.inc';
$testName = "PDO - Extension";
StartTest($testName);
$data = array( array('10', 'Abc', 'zxy'),
array('20', 'Def', 'wvu'),
array('30', 'Ghi', 'tsr'));
$conn1 = PDOConnect('ExPDO', $server, $uid, $pwd, true);
var_dump(get_class($conn1));
// Prepare test table
DropTable($conn1, $tableName);
$conn1->exec("CREATE TABLE [$tableName] (id int NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))");
$stmt1 = $conn1->prepare("INSERT INTO [$tableName] VALUES(?, ?, ?)");
var_dump(get_class($stmt1));
foreach ($data as $row)
{
$stmt1->execute($row);
}
unset($stmt1);
echo "===QUERY===\n";
// Retrieve test data via a direct query
$stmt1 = $conn1->query("SELECT * FROM [$tableName]");
var_dump(get_class($stmt1));
var_dump(get_class($stmt1->dbh));
echo "===FOREACH===\n";
foreach($stmt1 as $obj)
{
var_dump($obj);
}
echo "===DONE===\n";
// Cleanup
DropTable($conn1, $tableName);
$stmt1 = null;
$conn1 = null;
EndTest($testName);
}
class ExPDO extends PDO
{
function __destruct()
{
echo __METHOD__ . "()\n";
}
function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args)
{
echo __METHOD__ . "()\n";
$stmt = $this->prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('ExPDOStatement', array($this))));
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute();
return ($stmt);
}
}
class ExPDOStatement extends PDOStatement
{
public $dbh;
protected function __construct($dbh)
{
$this->dbh = $dbh;
echo __METHOD__ . "()\n";
}
function __destruct()
{
echo __METHOD__ . "()\n";
}
function execute($params = array())
{
echo __METHOD__ . "()\n";
parent::execute();
}
}
//--------------------------------------------------------------------
// Repro
//
//--------------------------------------------------------------------
function Repro()
{
try
{
Extend();
}
catch (Exception $e)
{
echo $e->getMessage();
}
}
Repro();
?>
--EXPECT--
string(5) "ExPDO"
string(12) "PDOStatement"
===QUERY===
ExPDO::query()
ExPDOStatement::__construct()
ExPDOStatement::execute()
string(14) "ExPDOStatement"
string(5) "ExPDO"
===FOREACH===
array(3) {
["id"]=>
string(2) "10"
["val"]=>
string(3) "Abc"
["val2"]=>
string(3) "zxy"
}
array(3) {
["id"]=>
string(2) "20"
["val"]=>
string(3) "Def"
["val2"]=>
string(3) "wvu"
}
array(3) {
["id"]=>
string(2) "30"
["val"]=>
string(3) "Ghi"
["val2"]=>
string(3) "tsr"
}
===DONE===
ExPDOStatement::__destruct()
ExPDO::__destruct()
Test "PDO - Extension" completed successfully.

View file

@ -5,7 +5,14 @@ Verification of capabilities for extending PDO.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif.inc'); ?>
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID < 80000) {
die("skip Test designed for PHP 8.*");
}
?>
--FILE--
<?php
include 'MsCommon.inc';
@ -69,7 +76,7 @@ class ExPDO extends PDO
echo __METHOD__ . "()\n";
}
function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args)
function query(string $sql, ?int $fetchMode = null, mixed ...$fetchModeArgs): PDOStatement|false
{
echo __METHOD__ . "()\n";
$stmt = parent::query($sql);
@ -93,10 +100,10 @@ class ExPDOStatement extends PDOStatement
echo __METHOD__ . "()\n";
}
function execute($params = array())
function execute(?array $params = null) : bool
{
echo __METHOD__ . "()\n";
parent::execute();
return parent::execute();
}
}

View file

@ -0,0 +1,181 @@
--TEST--
Extending PDO Test #4
--DESCRIPTION--
Verification of capabilities for extending PDO.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID >= 80000) {
die("skip Test designed for PHP 7.*");
}
?>
--FILE--
<?php
include 'MsCommon.inc';
function Extend()
{
include 'MsSetup.inc';
$testName = "PDO - Extension";
StartTest($testName);
$data = array( array('10', 'Abc', 'zxy'),
array('20', 'Def', 'wvu'),
array('30', 'Ghi', 'tsr'));
$conn1 = PDOConnect('ExPDO', $server, $uid, $pwd, true);
var_dump(get_class($conn1));
// Prepare test table
DropTable($conn1, $tableName);
$conn1->exec("CREATE TABLE [$tableName] (id int NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))");
$stmt1 = $conn1->prepare("INSERT INTO [$tableName] VALUES(?, ?, ?)");
var_dump(get_class($stmt1));
foreach ($data as $row)
{
$stmt1->execute($row);
}
unset($stmt1);
echo "===QUERY===\n";
// Retrieve test data via a direct query
var_dump($conn1->getAttribute(PDO::ATTR_STATEMENT_CLASS));
$conn1->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('ExPDOStatement', array($conn1)));
var_dump($conn1->getAttribute(PDO::ATTR_STATEMENT_CLASS));
$stmt1 = $conn1->query("SELECT * FROM [$tableName]");
var_dump(get_class($stmt1));
var_dump(get_class($stmt1->dbh));
echo "===FOREACH===\n";
foreach($stmt1 as $obj)
{
var_dump($obj);
}
echo "===DONE===\n";
// Cleanup
DropTable($conn1, $tableName);
$stmt1 = null;
$conn1 = null;
EndTest($testName);
}
class ExPDO extends PDO
{
function __destruct()
{
echo __METHOD__ . "()\n";
}
function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args)
{
echo __METHOD__ . "()\n";
$stmt = parent::query($sql);
return ($stmt);
}
}
class ExPDOStatement extends PDOStatement
{
public $dbh;
protected function __construct($dbh)
{
$this->dbh = $dbh;
$this->setFetchMode(PDO::FETCH_ASSOC);
echo __METHOD__ . "()\n";
}
function __destruct()
{
echo __METHOD__ . "()\n";
}
function execute($params = array())
{
echo __METHOD__ . "()\n";
parent::execute();
}
}
//--------------------------------------------------------------------
// Repro
//
//--------------------------------------------------------------------
function Repro()
{
try
{
Extend();
}
catch (Exception $e)
{
echo $e->getMessage();
}
}
Repro();
?>
--EXPECTF--
string(5) "ExPDO"
string(12) "PDOStatement"
===QUERY===
array(1) {
[0]=>
string(12) "PDOStatement"
}
array(2) {
[0]=>
string(14) "ExPDOStatement"
[1]=>
array(1) {
[0]=>
object(ExPDO)#%d (0) {
}
}
}
ExPDO::query()
ExPDOStatement::__construct()
string(14) "ExPDOStatement"
string(5) "ExPDO"
===FOREACH===
array(3) {
["id"]=>
string(2) "10"
["val"]=>
string(3) "Abc"
["val2"]=>
string(3) "zxy"
}
array(3) {
["id"]=>
string(2) "20"
["val"]=>
string(3) "Def"
["val2"]=>
string(3) "wvu"
}
array(3) {
["id"]=>
string(2) "30"
["val"]=>
string(3) "Ghi"
["val2"]=>
string(3) "tsr"
}
===DONE===
ExPDOStatement::__destruct()
Test "PDO - Extension" completed successfully.
ExPDO::__destruct()

View file

@ -42,7 +42,7 @@ try {
}
// Get data
$row = selectAll($conn, $tableName, "PDO::FETCH_ASSOC");
$row = selectAll($conn, $tableName, PDO::FETCH_ASSOC);
var_dump($row);
// Close connection

View file

@ -28,9 +28,18 @@ try {
$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->fetch(PDO::FETCH_BOUND);
if (PHP_VERSION_ID < 80100) {
var_dump($val1 === $value);
} else {
// $val1 is a stream object
if (!feof($val1)) {
$str = fread($val1, 8192);
var_dump($str === $value);
}
}
// Close connection
dropTable($conn, $tableName);
unset($stmt);

Some files were not shown because too many files have changed in this diff Show more