commit
f2c340892a
42
CHANGELOG.md
42
CHANGELOG.md
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ), "ed_table, "ed_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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = ¶m->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;
|
||||
|
|
|
@ -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, {} }
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(¶m_input_strings);
|
||||
|
||||
// initialize the (input only) stream parameters (which holds sqlsrv_stream structures)
|
||||
ZVAL_NEW_ARR( ¶m_streams );
|
||||
core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/);
|
||||
|
||||
// initialize the (input only) datetime parameters of converted date time objects to strings
|
||||
array_init( ¶m_datetime_buffers );
|
||||
|
||||
// initialize the output string parameters (which holds sqlsrv_output_param structures)
|
||||
ZVAL_NEW_ARR( &output_params );
|
||||
core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/);
|
||||
|
||||
// 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( ¶m_input_strings );
|
||||
zval_ptr_dtor( &output_params );
|
||||
zval_ptr_dtor( ¶m_streams );
|
||||
zval_ptr_dtor( ¶m_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 = ¶m_z->value;
|
||||
buffer_len = sizeof( Z_LVAL_P( param_z ));
|
||||
ind_ptr = buffer_len;
|
||||
if( direction != SQL_PARAM_INPUT ){
|
||||
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
|
||||
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), zval_was_bool, php_out_type);
|
||||
save_output_param_for_later( stmt, output_param );
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IS_DOUBLE:
|
||||
{
|
||||
buffer = ¶m_z->value;
|
||||
buffer_len = sizeof( Z_DVAL_P( param_z ));
|
||||
ind_ptr = buffer_len;
|
||||
if( direction != SQL_PARAM_INPUT ){
|
||||
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
|
||||
sqlsrv_output_param output_param( param_ref, static_cast<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, ¶m, sizeof( sqlsrv_output_param ));
|
||||
Z_TRY_ADDREF_P( param.param_z ); // we have a reference to the param
|
||||
}
|
||||
|
||||
|
||||
// send all the stream data
|
||||
|
||||
void send_param_streams( _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 = ¶m_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 = ¶m_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 = ¶m_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, ¶m);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, {} }
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -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 );
|
||||
|
||||
?>
|
|
@ -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);
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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
|
|
@ -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 ) ){
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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--
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -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 );
|
||||
|
||||
?>
|
|
@ -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');
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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.
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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--
|
||||
|
|
|
@ -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--
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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--
|
||||
|
|
|
@ -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--
|
||||
|
|
|
@ -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 );
|
||||
?>
|
||||
|
|
|
@ -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>
|
|
@ -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--
|
||||
|
|
BIN
test/functional/inc/awc_tee_male_large.gif
Normal file
BIN
test/functional/inc/awc_tee_male_large.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
test/functional/inc/silver_chain_large.gif
Normal file
BIN
test/functional/inc/silver_chain_large.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
test/functional/inc/superlight_black_f_large.gif
Normal file
BIN
test/functional/inc/superlight_black_f_large.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
209
test/functional/inc/test_tvp_data.php
Normal file
209
test/functional/inc/test_tvp_data.php
Normal 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;
|
||||
}
|
||||
|
||||
?>
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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"]=>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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')));
|
||||
|
|
140
test/functional/pdo_sqlsrv/PDO94_Extend1_p7.phpt
Normal file
140
test/functional/pdo_sqlsrv/PDO94_Extend1_p7.phpt
Normal 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.
|
|
@ -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();
|
||||
|
|
141
test/functional/pdo_sqlsrv/PDO95_Extend2_p7.phpt
Normal file
141
test/functional/pdo_sqlsrv/PDO95_Extend2_p7.phpt
Normal 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.
|
|
@ -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))));
|
||||
|
|
152
test/functional/pdo_sqlsrv/PDO96_Extend3_p7.phpt
Normal file
152
test/functional/pdo_sqlsrv/PDO96_Extend3_p7.phpt
Normal 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.
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
166
test/functional/pdo_sqlsrv/PDO97_Extend4_p7.phpt
Normal file
166
test/functional/pdo_sqlsrv/PDO97_Extend4_p7.phpt
Normal 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.
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
181
test/functional/pdo_sqlsrv/PDO98_Extend5_p7.phpt
Normal file
181
test/functional/pdo_sqlsrv/PDO98_Extend5_p7.phpt
Normal 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()
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue