5.5.0-preview (#903)
This commit is contained in:
parent
eb679eab6a
commit
b65e28ccb6
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -3,6 +3,39 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
|
|
||||||
|
## 5.5.0-preview - 2018-12-07
|
||||||
|
Updated PECL release packages. Here is the list of updates:
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added support for PHP 7.3.0
|
||||||
|
- Added support for Linux Ubuntu 18.10 and mac OS Mojave
|
||||||
|
- Feature Request [#415](https://github.com/Microsoft/msphpsql/pull/886) - new options at connection and statement levels for both drivers for formatting decimal values in the fetched results
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Pull Request [#854](https://github.com/Microsoft/msphpsql/pull/854) - Clear Azure Key Vault data after connection attributes are successfully set or when exception is thrown
|
||||||
|
- Pull Request [#855](https://github.com/Microsoft/msphpsql/pull/855) - Improved performance by saving meta data before fetching and skipping unnecessary conversions for numeric data
|
||||||
|
- Pull Request [#865](https://github.com/Microsoft/msphpsql/pull/865) - Corrected the way SQLPutData and SQLParamData are used when sending stream data to the server
|
||||||
|
- Pull Request [#878](https://github.com/Microsoft/msphpsql/pull/878) - Modified the config files to enable Spectre Mitigations for PHP 7.1 (see related Request [#836](https://github.com/Microsoft/msphpsql/pull/836))
|
||||||
|
- Pull Request [#891](https://github.com/Microsoft/msphpsql/pull/891) - Improved performance of Unicode conversions
|
||||||
|
- Pull Request [#892](https://github.com/Microsoft/msphpsql/pull/892) - Removed warning messages while compiling extensions
|
||||||
|
|
||||||
|
### 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
|
||||||
|
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7
|
||||||
|
- When pooling is enabled in Linux or macOS
|
||||||
|
- unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
|
||||||
|
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
|
||||||
|
- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674))
|
||||||
|
- With ColumnEncryption enabled, fetching varbinary(max), varchar(max) or nvarchar(max) may fail with [ODBC Driver 17.3 CTP](https://blogs.msdn.microsoft.com/sqlnativeclient/2018/09/24/odbc-driver-17-3-preview-for-sql-server-released/)
|
||||||
|
|
||||||
## 5.4.0-preview - 2018-09-24
|
## 5.4.0-preview - 2018-09-24
|
||||||
Updated PECL release packages. Here is the list of updates:
|
Updated PECL release packages. Here is the list of updates:
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,10 @@ RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt
|
||||||
RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql17 mssql-tools
|
RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql17 mssql-tools
|
||||||
ENV PATH="/opt/mssql-tools/bin:${PATH}"
|
ENV PATH="/opt/mssql-tools/bin:${PATH}"
|
||||||
|
|
||||||
#install coveralls
|
#install coveralls (upgrade both pip and requests first)
|
||||||
RUN python -m pip install --upgrade pip && pip install cpp-coveralls
|
RUN python -m pip install --upgrade pip
|
||||||
|
RUN python -m pip install --upgrade requests
|
||||||
|
RUN python -m pip install cpp-coveralls
|
||||||
|
|
||||||
#Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html )
|
#Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html )
|
||||||
#One option is to get source from zip file of repository.
|
#One option is to get source from zip file of repository.
|
||||||
|
|
|
@ -1,24 +1,20 @@
|
||||||
# Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server
|
# Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server
|
||||||
The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft drivers for PHP for Microsoft SQL Server on Ubuntu 16.04, 17.10 and 18.04, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11, 10.12 and 10.13. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for Microsoft SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for Microsoft SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver)). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup).
|
The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 18.10, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11, 10.12, 10.13, and 10.14. 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.md##loading-the-driver-at-php-startup).
|
||||||
|
|
||||||
These instructions install PHP 7.2 by default -- see the notes at the beginning of each section to install PHP 7.0 or 7.1.
|
These instructions install PHP 7.2 by default -- see the notes at the beginning of each section to install PHP 7.0 or 7.1.
|
||||||
|
|
||||||
## Contents of this page:
|
## Contents of this page:
|
||||||
|
|
||||||
- [Installing the drivers on Ubuntu 16.04 and 18.04](#installing-the-drivers-on-ubuntu-1604-and-1804)
|
- [Installing the drivers on Ubuntu 16.04, 18.04, and 18.10](#installing-the-drivers-on-ubuntu-1604-1804-and-1810)
|
||||||
- [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7)
|
- [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7)
|
||||||
- [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9)
|
- [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9)
|
||||||
- [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12)
|
- [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12)
|
||||||
- [Installing the drivers on macOS El Capitan, Sierra and High Sierra](#installing-the-drivers-on-macos-el-capitan-sierra-and-high-sierra)
|
- [Installing the drivers on macOS El Capitan, Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-el-capitan-sierra-high-sierra-and-mojave)
|
||||||
|
|
||||||
## Installing the drivers on Ubuntu 16.04 and 18.04
|
## Installing the drivers on Ubuntu 16.04, 18.04, and 18.10
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> To install PHP 7.0 or 7.1, replace 7.2 with 7.0 or 7.1 in the following commands.
|
> To install PHP 7.0, 7.1, or 7.3, replace `7.2` with `7.0`, `7.1`, or `7.3` in the following commands.
|
||||||
> For Ubuntu 18.04, the step to add the ondrej repository is not required unless
|
|
||||||
> PHP 7.0 or 7.1 is needed. However, installing PHP 7.0 or 7.1 in Ubuntu 18.04 may
|
|
||||||
> not work as packages from the ondrej repository come with dependencies that may
|
|
||||||
> conflict with a base Ubuntu 18.04 install.
|
|
||||||
|
|
||||||
### Step 1. Install PHP
|
### Step 1. Install PHP
|
||||||
```
|
```
|
||||||
|
@ -28,9 +24,14 @@ apt-get update
|
||||||
apt-get install php7.2 php7.2-dev php7.2-xml -y --allow-unauthenticated
|
apt-get install php7.2 php7.2-dev php7.2-xml -y --allow-unauthenticated
|
||||||
```
|
```
|
||||||
### Step 2. Install prerequisites
|
### Step 2. Install prerequisites
|
||||||
Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
|
Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
|
||||||
|
|
||||||
|
For Ubuntu 18.10, follow the above steps for Ubuntu 18.04 except replace `18.04` by `18.10` and download ODBC 17.3 preview [here](https://www.microsoft.com/download/details.aspx?id=57341).
|
||||||
|
|
||||||
### Step 3. Install the PHP drivers for Microsoft SQL Server
|
### Step 3. Install the PHP drivers for Microsoft SQL Server
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3.
|
||||||
```
|
```
|
||||||
sudo pecl install sqlsrv
|
sudo pecl install sqlsrv
|
||||||
sudo pecl install pdo_sqlsrv
|
sudo pecl install pdo_sqlsrv
|
||||||
|
@ -59,22 +60,23 @@ To test your installation, see [Testing your installation](#testing-your-install
|
||||||
## Installing the drivers on Red Hat 7
|
## Installing the drivers on Red Hat 7
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> To install PHP 7.0 or 7.1, replace remi-php72 with remi-php70 or remi-php71 respectively in the following commands.
|
> To install PHP 7.0, 7.1, or 7.3, replace `remi-php72` with `remi-php70`, `remi-php71`, or `remi-php73` respectively in the following commands.
|
||||||
|
|
||||||
### Step 1. Install PHP
|
### Step 1. Install PHP
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo su
|
sudo su
|
||||||
wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
|
wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
|
||||||
wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm
|
wget https://rpms.remirepo.net/enterprise/remi-release-7.rpm
|
||||||
rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm
|
rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm
|
||||||
subscription-manager repos --enable=rhel-7-server-optional-rpms
|
subscription-manager repos --enable=rhel-7-server-optional-rpms
|
||||||
|
yum install yum-utils
|
||||||
yum-config-manager --enable remi-php72
|
yum-config-manager --enable remi-php72
|
||||||
yum update
|
yum update
|
||||||
yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc
|
yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc
|
||||||
```
|
```
|
||||||
### Step 2. Install prerequisites
|
### Step 2. Install prerequisites
|
||||||
Install the ODBC driver for Red Hat 7 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
|
Install the ODBC driver for Red Hat 7 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
|
||||||
|
|
||||||
Compiling the PHP drivers with PECL with PHP 7.2 requires a more recent GCC than the default:
|
Compiling the PHP drivers with PECL with PHP 7.2 requires a more recent GCC than the default:
|
||||||
```
|
```
|
||||||
|
@ -83,6 +85,9 @@ sudo yum install devtoolset-7
|
||||||
scl enable devtoolset-7 bash
|
scl enable devtoolset-7 bash
|
||||||
```
|
```
|
||||||
### Step 3. Install the PHP drivers for Microsoft SQL Server
|
### Step 3. Install the PHP drivers for Microsoft SQL Server
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3.
|
||||||
```
|
```
|
||||||
sudo pecl install sqlsrv
|
sudo pecl install sqlsrv
|
||||||
sudo pecl install pdo_sqlsrv
|
sudo pecl install pdo_sqlsrv
|
||||||
|
@ -103,7 +108,7 @@ sudo make install
|
||||||
```
|
```
|
||||||
You can alternatively download the prebuilt binaries from the [Github project page](https://github.com/Microsoft/msphpsql/releases), or install from the Remi repo:
|
You can alternatively download the prebuilt binaries from the [Github project page](https://github.com/Microsoft/msphpsql/releases), or install from the Remi repo:
|
||||||
```
|
```
|
||||||
sudo yum install php-sqlsrv php-pdo_sqlsrv
|
sudo yum install php-sqlsrv
|
||||||
```
|
```
|
||||||
### Step 4. Install Apache
|
### Step 4. Install Apache
|
||||||
```
|
```
|
||||||
|
@ -122,7 +127,7 @@ To test your installation, see [Testing your installation](#testing-your-install
|
||||||
## Installing the drivers on Debian 8 and 9
|
## Installing the drivers on Debian 8 and 9
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> To install PHP 7.0 or 7.1, replace 7.2 in the following commands with 7.0 or 7.1.
|
> To install PHP 7.0, 7.1, or 7.3, replace `7.2` with `7.0`, `7.1`, or `7.3` in the following commands.
|
||||||
|
|
||||||
### Step 1. Install PHP
|
### Step 1. Install PHP
|
||||||
```
|
```
|
||||||
|
@ -134,7 +139,7 @@ apt-get update
|
||||||
apt-get install -y php7.2 php7.2-dev php7.2-xml
|
apt-get install -y php7.2 php7.2-dev php7.2-xml
|
||||||
```
|
```
|
||||||
### Step 2. Install prerequisites
|
### Step 2. Install prerequisites
|
||||||
Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
|
Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
|
||||||
|
|
||||||
You may also need to generate the correct locale to get PHP output to display correctly in a browser. For example, for the en_US UTF-8 locale, run the following commands:
|
You may also need to generate the correct locale to get PHP output to display correctly in a browser. For example, for the en_US UTF-8 locale, run the following commands:
|
||||||
```
|
```
|
||||||
|
@ -144,6 +149,9 @@ locale-gen
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3. Install the PHP drivers for Microsoft SQL Server
|
### Step 3. Install the PHP drivers for Microsoft SQL Server
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3.
|
||||||
```
|
```
|
||||||
sudo pecl install sqlsrv
|
sudo pecl install sqlsrv
|
||||||
sudo pecl install pdo_sqlsrv
|
sudo pecl install pdo_sqlsrv
|
||||||
|
@ -171,21 +179,24 @@ To test your installation, see [Testing your installation](#testing-your-install
|
||||||
## Installing the drivers on Suse 12
|
## Installing the drivers on Suse 12
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> To install PHP 7.0, skip the command below adding the repository - 7.0 is the default PHP on suse 12.
|
> To install PHP 7.0 or 7.1, replace the repository URL below with one of the following URLs:
|
||||||
> To install PHP 7.1, replace the repository URL below with the following URL:
|
`https://download.opensuse.org/repositories/devel:languages:php:php70/SLE_12_SP3/devel:languages:php:php70.repo`
|
||||||
`http://download.opensuse.org/repositories/devel:/languages:/php:/php71/SLE_12/devel:languages:php:php71.repo`
|
`https://download.opensuse.org/repositories/devel:languages:php:php71/SLE_12_SP3/devel:languages:php:php71.repo`
|
||||||
|
|
||||||
### Step 1. Install PHP
|
### Step 1. Install PHP
|
||||||
```
|
```
|
||||||
sudo su
|
sudo su
|
||||||
zypper -n ar -f http://download.opensuse.org/repositories/devel:languages:php/SLE_12/devel:languages:php.repo
|
zypper -n ar -f https://download.opensuse.org/repositories/devel:languages:php/SLE_12_SP3/devel:languages:php.repo
|
||||||
zypper --gpg-auto-import-keys refresh
|
zypper --gpg-auto-import-keys refresh
|
||||||
zypper -n install php7 php7-pear php7-devel
|
zypper -n install php7 php7-pear php7-devel
|
||||||
```
|
```
|
||||||
### Step 2. Install prerequisites
|
### Step 2. Install prerequisites
|
||||||
Install the ODBC driver for Suse 12 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
|
Install the ODBC driver for Suse 12 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
|
||||||
|
|
||||||
### Step 3. Install the PHP drivers for Microsoft SQL Server
|
### Step 3. Install the PHP drivers for Microsoft SQL Server
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3.
|
||||||
```
|
```
|
||||||
sudo pecl install sqlsrv
|
sudo pecl install sqlsrv
|
||||||
sudo pecl install pdo_sqlsrv
|
sudo pecl install pdo_sqlsrv
|
||||||
|
@ -209,7 +220,7 @@ sudo systemctl restart apache2
|
||||||
```
|
```
|
||||||
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
|
To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document.
|
||||||
|
|
||||||
## Installing the drivers on macOS El Capitan, Sierra and High Sierra
|
## Installing the drivers on macOS El Capitan, Sierra, High Sierra, and Mojave
|
||||||
|
|
||||||
If you do not already have it, install brew as follows:
|
If you do not already have it, install brew as follows:
|
||||||
```
|
```
|
||||||
|
@ -217,7 +228,7 @@ If you do not already have it, install brew as follows:
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> To install PHP 7.0 or 7.1, replace php@7.2 with php@7.0 or php@7.1 respectively in the following commands.
|
> To install PHP 7.0, 7.1, or 7.3, replace `php@7.2` with `php@7.0`, `php@7.1`, or `php@7.3` respectively in the following commands.
|
||||||
|
|
||||||
### Step 1. Install PHP
|
### Step 1. Install PHP
|
||||||
|
|
||||||
|
@ -226,15 +237,13 @@ brew tap
|
||||||
brew tap homebrew/core
|
brew tap homebrew/core
|
||||||
brew install php@7.2
|
brew install php@7.2
|
||||||
```
|
```
|
||||||
|
|
||||||
PHP should now be in your path -- run `php -v` to verify that you are running the correct version of PHP. If PHP is not in your path or it is not the correct version, run the following:
|
PHP should now be in your path -- run `php -v` to verify that you are running the correct version of PHP. If PHP is not in your path or it is not the correct version, run the following:
|
||||||
|
|
||||||
```
|
```
|
||||||
brew link --force --overwrite php@7.2
|
brew link --force --overwrite php@7.2
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2. Install prerequisites
|
### Step 2. Install prerequisites
|
||||||
Install the ODBC driver for macOS by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
|
Install the ODBC driver for macOS by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server).
|
||||||
|
|
||||||
In addition, you may need to install the GNU make tools:
|
In addition, you may need to install the GNU make tools:
|
||||||
```
|
```
|
||||||
|
@ -242,6 +251,9 @@ brew install autoconf automake libtool
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3. Install the PHP drivers for Microsoft SQL Server
|
### Step 3. Install the PHP drivers for Microsoft SQL Server
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3.
|
||||||
```
|
```
|
||||||
sudo pecl install sqlsrv
|
sudo pecl install sqlsrv
|
||||||
sudo pecl install pdo_sqlsrv
|
sudo pecl install pdo_sqlsrv
|
||||||
|
@ -317,4 +329,5 @@ function formatErrors($errors)
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
```
|
```
|
||||||
Point your browser to http://localhost/testsql.php (http://localhost:8080/testsql.php on macOS). You should now be able to connect to your SQL Server/Azure SQL database.
|
Point your browser to https://localhost/testsql.php (https://localhost:8080/testsql.php on macOS). You should now be able to connect to your SQL Server/Azure SQL database.
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ Thank you for taking the time to participate in our last survey. You can continu
|
||||||
|--------------------------|--------------------------|---------------------------------------|-------------------------------------------|
|
|--------------------------|--------------------------|---------------------------------------|-------------------------------------------|
|
||||||
| [![av-image][]][av-site] | [![tv-image][]][tv-site] | [![Coverage Codecov][]][codecov-site] | [![Coverage Coveralls][]][coveralls-site] |
|
| [![av-image][]][av-site] | [![tv-image][]][tv-site] | [![Coverage Codecov][]][codecov-site] | [![Coverage Coveralls][]][coveralls-site] |
|
||||||
|
|
||||||
[av-image]: https://ci.appveyor.com/api/projects/status/xhp4nq9ouljnhxqf/branch/dev?svg=true
|
[av-image]: https://ci.appveyor.com/api/projects/status/vo4rfei6lxlamrnc?svg=true
|
||||||
[av-site]: https://ci.appveyor.com/project/Microsoft-PHPSQL/msphpsql-frhmr/branch/dev
|
[av-site]: https://ci.appveyor.com/project/msphpsql/msphpsql/branch/dev
|
||||||
[tv-image]: https://travis-ci.org/Microsoft/msphpsql.svg?branch=dev
|
[tv-image]: https://travis-ci.org/Microsoft/msphpsql.svg?branch=dev
|
||||||
[tv-site]: https://travis-ci.org/Microsoft/msphpsql/
|
[tv-site]: https://travis-ci.org/Microsoft/msphpsql/
|
||||||
[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
|
||||||
|
@ -45,8 +45,8 @@ Thank you for taking the time to participate in our last survey. You can continu
|
||||||
For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs.
|
For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs.
|
||||||
|
|
||||||
On the client machine:
|
On the client machine:
|
||||||
- PHP 7.1.x, or 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows)
|
- PHP 7.1.x, 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows), or 7.3.x
|
||||||
- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-2017)
|
- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server)
|
||||||
- If using a Web server such as Internet Information Services (IIS) or Apache, it must be configured to run PHP
|
- If using a Web server such as Internet Information Services (IIS) or Apache, it must be configured to run PHP
|
||||||
|
|
||||||
On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux.
|
On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux.
|
||||||
|
|
|
@ -24,7 +24,7 @@ environment:
|
||||||
SQL_INSTANCE: SQL2017
|
SQL_INSTANCE: SQL2017
|
||||||
PHP_VC: 15
|
PHP_VC: 15
|
||||||
PHP_MAJOR_VER: 7.2
|
PHP_MAJOR_VER: 7.2
|
||||||
PHP_MINOR_VER: latest
|
PHP_MINOR_VER: 11
|
||||||
PHP_EXE_PATH: x64\Release_TS
|
PHP_EXE_PATH: x64\Release_TS
|
||||||
THREAD: ts
|
THREAD: ts
|
||||||
platform: x64
|
platform: x64
|
||||||
|
|
|
@ -1,74 +1,73 @@
|
||||||
<?php
|
<?php
|
||||||
echo "\n";
|
echo "\n";
|
||||||
$serverName = "tcp:yourserver.database.windows.net,1433";
|
$serverName = "tcp:yourserver.database.windows.net,1433";
|
||||||
$database = "yourdatabase";
|
$database = "yourdatabase";
|
||||||
$uid = "yourusername";
|
$uid = "yourusername";
|
||||||
$pwd = "yourpassword";
|
$pwd = "yourpassword";
|
||||||
|
|
||||||
//Establishes the connection
|
//Establishes the connection
|
||||||
$conn = new PDO( "sqlsrv:server=$serverName ; Database = $database", $uid, $pwd);
|
$conn = new PDO("sqlsrv:server=$serverName ; Database = $database", $uid, $pwd);
|
||||||
|
|
||||||
//Select Query
|
//Select Query
|
||||||
$tsql = "SELECT [CompanyName] FROM SalesLT.Customer";
|
$tsql = "SELECT [CompanyName] FROM SalesLT.Customer";
|
||||||
|
|
||||||
//Executes the query
|
//Executes the query
|
||||||
$getProducts = $conn->query( $tsql );
|
$getProducts = $conn->query($tsql);
|
||||||
|
|
||||||
//Error handling
|
//Error handling
|
||||||
FormatErrors ($conn->errorInfo());
|
FormatErrors($conn->errorInfo());
|
||||||
|
|
||||||
$productCount = 0;
|
$productCount = 0;
|
||||||
$ctr = 0;
|
$ctr = 0;
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<h1> First 10 results are : </h1>
|
<h1> First 10 results are : </h1>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
while($row = $getProducts->fetch(PDO::FETCH_ASSOC))
|
while ($row = $getProducts->fetch(PDO::FETCH_ASSOC)) {
|
||||||
{
|
if ($ctr>9) {
|
||||||
if($ctr>9)
|
break;
|
||||||
break;
|
}
|
||||||
$ctr++;
|
$ctr++;
|
||||||
echo($row['CompanyName']);
|
echo($row['CompanyName']);
|
||||||
echo("<br/>");
|
echo("<br/>");
|
||||||
$productCount++;
|
$productCount++;
|
||||||
}
|
}
|
||||||
$getProducts = NULL;
|
$getProducts = null;
|
||||||
|
|
||||||
$tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.* VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())";
|
$tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.* VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())";
|
||||||
|
|
||||||
//Insert query
|
//Insert query
|
||||||
$insertReview = $conn->query( $tsql );
|
$insertReview = $conn->query($tsql);
|
||||||
FormatErrors ($conn->errorInfo());
|
FormatErrors($conn->errorInfo());
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<h1> Product Key inserted is :</h1>
|
<h1> Product Key inserted is :</h1>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
while($row = $insertReview->fetch(PDO::FETCH_ASSOC))
|
while ($row = $insertReview->fetch(PDO::FETCH_ASSOC)) {
|
||||||
{
|
echo($row['ProductID']."<br/>");
|
||||||
echo($row['ProductID']."<br/>");
|
}
|
||||||
}
|
$insertReview = null;
|
||||||
$insertReview = NULL;
|
|
||||||
|
|
||||||
//Delete Query
|
//Delete Query
|
||||||
//We are deleting the same record
|
//We are deleting the same record
|
||||||
$tsql = "DELETE FROM [SalesLT].[Product] WHERE Name=?";
|
$tsql = "DELETE FROM [SalesLT].[Product] WHERE Name=?";
|
||||||
$param = "SQL New 1";
|
$param = "SQL New 1";
|
||||||
|
|
||||||
$deleteReview = $conn->prepare($tsql);
|
$deleteReview = $conn->prepare($tsql);
|
||||||
$deleteReview->bindParam(1, $param);
|
$deleteReview->bindParam(1, $param);
|
||||||
|
|
||||||
$deleteReview->execute();
|
$deleteReview->execute();
|
||||||
FormatErrors ($deleteReview->errorInfo());
|
FormatErrors($deleteReview->errorInfo());
|
||||||
|
|
||||||
function FormatErrors( $error )
|
function FormatErrors($error)
|
||||||
{
|
{
|
||||||
/* Display error. */
|
/* Display error. */
|
||||||
echo "Error information: <br/>";
|
echo "Error information: <br/>";
|
||||||
|
|
||||||
echo "SQLSTATE: ".$error[0]."<br/>";
|
echo "SQLSTATE: ".$error[0]."<br/>";
|
||||||
echo "Code: ".$error[1]."<br/>";
|
echo "Code: ".$error[1]."<br/>";
|
||||||
echo "Message: ".$error[2]."<br/>";
|
echo "Message: ".$error[2]."<br/>";
|
||||||
}
|
}
|
||||||
?>
|
?>
|
|
@ -3,67 +3,69 @@
|
||||||
$serverName = "tcp:yourserver.database.windows.net,1433";
|
$serverName = "tcp:yourserver.database.windows.net,1433";
|
||||||
$connectionOptions = array("Database"=>"yourdatabase", "Uid"=>"yourusername", "PWD"=>"yourpassword");
|
$connectionOptions = array("Database"=>"yourdatabase", "Uid"=>"yourusername", "PWD"=>"yourpassword");
|
||||||
|
|
||||||
//Establishes the connection
|
//Establishes the connection
|
||||||
$conn = sqlsrv_connect($serverName, $connectionOptions);
|
$conn = sqlsrv_connect($serverName, $connectionOptions);
|
||||||
//Select Query
|
//Select Query
|
||||||
$tsql = "SELECT [CompanyName] FROM SalesLT.Customer";
|
$tsql = "SELECT [CompanyName] FROM SalesLT.Customer";
|
||||||
//Executes the query
|
//Executes the query
|
||||||
$getProducts = sqlsrv_query($conn, $tsql);
|
$getProducts = sqlsrv_query($conn, $tsql);
|
||||||
//Error handling
|
//Error handling
|
||||||
if ($getProducts == FALSE)
|
if ($getProducts == false) {
|
||||||
die(FormatErrors(sqlsrv_errors()));
|
die(FormatErrors(sqlsrv_errors()));
|
||||||
$productCount = 0;
|
}
|
||||||
$ctr = 0;
|
$productCount = 0;
|
||||||
?>
|
$ctr = 0;
|
||||||
|
?>
|
||||||
<h1> First 10 results are : </h1>
|
<h1> First 10 results are : </h1>
|
||||||
<?php
|
<?php
|
||||||
while($row = sqlsrv_fetch_array($getProducts, SQLSRV_FETCH_ASSOC))
|
while ($row = sqlsrv_fetch_array($getProducts, SQLSRV_FETCH_ASSOC)) {
|
||||||
{
|
if ($ctr>9) {
|
||||||
if($ctr>9)
|
break;
|
||||||
break;
|
}
|
||||||
$ctr++;
|
$ctr++;
|
||||||
echo($row['CompanyName']);
|
echo($row['CompanyName']);
|
||||||
echo("<br/>");
|
echo("<br/>");
|
||||||
$productCount++;
|
$productCount++;
|
||||||
}
|
}
|
||||||
sqlsrv_free_stmt($getProducts);
|
sqlsrv_free_stmt($getProducts);
|
||||||
|
|
||||||
$tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.ProductID VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())";
|
$tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.ProductID VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())";
|
||||||
//Insert query
|
//Insert query
|
||||||
$insertReview = sqlsrv_query($conn, $tsql);
|
$insertReview = sqlsrv_query($conn, $tsql);
|
||||||
if($insertReview == FALSE)
|
if ($insertReview == false) {
|
||||||
die(FormatErrors( sqlsrv_errors()));
|
die(FormatErrors(sqlsrv_errors()));
|
||||||
?>
|
}
|
||||||
|
?>
|
||||||
<h1> Product Key inserted is :</h1>
|
<h1> Product Key inserted is :</h1>
|
||||||
<?php
|
<?php
|
||||||
while($row = sqlsrv_fetch_array($insertReview, SQLSRV_FETCH_ASSOC))
|
while ($row = sqlsrv_fetch_array($insertReview, SQLSRV_FETCH_ASSOC)) {
|
||||||
{
|
echo($row['ProductID']);
|
||||||
echo($row['ProductID']);
|
}
|
||||||
}
|
sqlsrv_free_stmt($insertReview);
|
||||||
sqlsrv_free_stmt($insertReview);
|
//Delete Query
|
||||||
//Delete Query
|
//We are deleting the same record
|
||||||
//We are deleting the same record
|
$tsql = "DELETE FROM [SalesLT].[Product] WHERE Name=?";
|
||||||
$tsql = "DELETE FROM [SalesLT].[Product] WHERE Name=?";
|
$params = array("SQL New 1");
|
||||||
$params = array("SQL New 1");
|
|
||||||
|
|
||||||
$deleteReview = sqlsrv_prepare($conn, $tsql, $params);
|
$deleteReview = sqlsrv_prepare($conn, $tsql, $params);
|
||||||
if($deleteReview == FALSE)
|
if ($deleteReview == false) {
|
||||||
die(FormatErrors(sqlsrv_errors()));
|
die(FormatErrors(sqlsrv_errors()));
|
||||||
|
}
|
||||||
|
|
||||||
if(sqlsrv_execute($deleteReview) == FALSE)
|
if (sqlsrv_execute($deleteReview) == false) {
|
||||||
die(FormatErrors(sqlsrv_errors()));
|
die(FormatErrors(sqlsrv_errors()));
|
||||||
|
}
|
||||||
|
|
||||||
function FormatErrors( $errors )
|
function FormatErrors($errors)
|
||||||
{
|
{
|
||||||
/* Display errors. */
|
/* Display errors. */
|
||||||
echo "Error information: <br/>";
|
echo "Error information: <br/>";
|
||||||
|
|
||||||
foreach ( $errors as $error )
|
foreach ($errors as $error) {
|
||||||
{
|
echo "SQLSTATE: ".$error['SQLSTATE']."<br/>";
|
||||||
echo "SQLSTATE: ".$error['SQLSTATE']."<br/>";
|
echo "Code: ".$error['code']."<br/>";
|
||||||
echo "Code: ".$error['code']."<br/>";
|
echo "Message: ".$error['message']."<br/>";
|
||||||
echo "Message: ".$error['message']."<br/>";
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
?>
|
|
@ -4,7 +4,7 @@ dnl
|
||||||
dnl Contents: the code that will go into the configure script, indicating options,
|
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 external libraries and includes, and what source files are to be compiled.
|
||||||
dnl
|
dnl
|
||||||
dnl Microsoft Drivers 5.4 for PHP for SQL Server
|
dnl Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
dnl Copyright(c) Microsoft Corporation
|
dnl Copyright(c) Microsoft Corporation
|
||||||
dnl All rights reserved.
|
dnl All rights reserved.
|
||||||
dnl MIT License
|
dnl MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: JScript build configuration used by buildconf.bat
|
// Contents: JScript build configuration used by buildconf.bat
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -37,7 +37,14 @@ if( PHP_PDO_SQLSRV != "no" ) {
|
||||||
if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/guard:cf /O2" );
|
if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/guard:cf /O2" );
|
||||||
ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" );
|
ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" );
|
||||||
if (VCVERS >= 1913) {
|
if (VCVERS >= 1913) {
|
||||||
|
ADD_FLAG("LDFLAGS_PDO_SQLSRV", "/d2:-guardspecload");
|
||||||
ADD_FLAG("CFLAGS_PDO_SQLSRV", "/Qspectre");
|
ADD_FLAG("CFLAGS_PDO_SQLSRV", "/Qspectre");
|
||||||
|
} else if (VCVERS == 1900) {
|
||||||
|
var subver1900 = probe_binary(PHP_CL).substr(6);
|
||||||
|
if (subver1900 >= 24241) {
|
||||||
|
ADD_FLAG("LDFLAGS_PDO_SQLSRV", "/d2:-guardspecload");
|
||||||
|
ADD_FLAG('CFLAGS_PDO_SQLSRV', "/Qspectre");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo');
|
ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo');
|
||||||
EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src_class, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
|
EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src_class, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Implements the PDO object for PDO_SQLSRV
|
// Contents: Implements the PDO object for PDO_SQLSRV
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -80,7 +80,9 @@ enum PDO_STMT_OPTIONS {
|
||||||
PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE,
|
PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE,
|
||||||
PDO_STMT_OPTION_EMULATE_PREPARES,
|
PDO_STMT_OPTION_EMULATE_PREPARES,
|
||||||
PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE,
|
PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE,
|
||||||
PDO_STMT_OPTION_FETCHES_DATETIME_TYPE
|
PDO_STMT_OPTION_FETCHES_DATETIME_TYPE,
|
||||||
|
PDO_STMT_OPTION_FORMAT_DECIMALS,
|
||||||
|
PDO_STMT_OPTION_DECIMAL_PLACES
|
||||||
};
|
};
|
||||||
|
|
||||||
// List of all the statement options supported by this driver.
|
// List of all the statement options supported by this driver.
|
||||||
|
@ -95,6 +97,8 @@ const stmt_option PDO_STMT_OPTS[] = {
|
||||||
{ NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr<stmt_option_emulate_prepares>( new stmt_option_emulate_prepares ) },
|
{ NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr<stmt_option_emulate_prepares>( new stmt_option_emulate_prepares ) },
|
||||||
{ NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr<stmt_option_fetch_numeric>( new stmt_option_fetch_numeric ) },
|
{ NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr<stmt_option_fetch_numeric>( new stmt_option_fetch_numeric ) },
|
||||||
{ NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr<stmt_option_fetch_datetime>( new stmt_option_fetch_datetime ) },
|
{ NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr<stmt_option_fetch_datetime>( new stmt_option_fetch_datetime ) },
|
||||||
|
{ NULL, 0, PDO_STMT_OPTION_FORMAT_DECIMALS, std::unique_ptr<stmt_option_format_decimals>( new stmt_option_format_decimals ) },
|
||||||
|
{ NULL, 0, PDO_STMT_OPTION_DECIMAL_PLACES, std::unique_ptr<stmt_option_decimal_places>( new stmt_option_decimal_places ) },
|
||||||
|
|
||||||
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
|
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
|
||||||
};
|
};
|
||||||
|
@ -498,7 +502,9 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo
|
||||||
query_timeout( QUERY_TIMEOUT_INVALID ),
|
query_timeout( QUERY_TIMEOUT_INVALID ),
|
||||||
client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )),
|
client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )),
|
||||||
fetch_numeric( false ),
|
fetch_numeric( false ),
|
||||||
fetch_datetime( false )
|
fetch_datetime( false ),
|
||||||
|
format_decimals( false ),
|
||||||
|
decimal_places( NO_CHANGE_DECIMAL_PLACES )
|
||||||
{
|
{
|
||||||
if( client_buffer_max_size < 0 ) {
|
if( client_buffer_max_size < 0 ) {
|
||||||
client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT;
|
client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT;
|
||||||
|
@ -1068,6 +1074,27 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
|
||||||
driver_dbh->fetch_datetime = (zend_is_true(val)) ? true : false;
|
driver_dbh->fetch_datetime = (zend_is_true(val)) ? true : false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SQLSRV_ATTR_FORMAT_DECIMALS:
|
||||||
|
driver_dbh->format_decimals = (zend_is_true(val)) ? true : false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SQLSRV_ATTR_DECIMAL_PLACES:
|
||||||
|
{
|
||||||
|
// first check if the input is an integer
|
||||||
|
if (Z_TYPE_P(val) != IS_LONG) {
|
||||||
|
THROW_PDO_ERROR(driver_dbh, SQLSRV_ERROR_INVALID_DECIMAL_PLACES);
|
||||||
|
}
|
||||||
|
|
||||||
|
zend_long decimal_places = Z_LVAL_P(val);
|
||||||
|
if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) {
|
||||||
|
// ignore decimal_places as this is out of range
|
||||||
|
decimal_places = NO_CHANGE_DECIMAL_PLACES;
|
||||||
|
}
|
||||||
|
|
||||||
|
driver_dbh->decimal_places = static_cast<short>(decimal_places);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
// Not supported
|
// Not supported
|
||||||
case PDO_ATTR_FETCH_TABLE_NAMES:
|
case PDO_ATTR_FETCH_TABLE_NAMES:
|
||||||
case PDO_ATTR_FETCH_CATALOG_NAMES:
|
case PDO_ATTR_FETCH_CATALOG_NAMES:
|
||||||
|
@ -1225,6 +1252,18 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SQLSRV_ATTR_FORMAT_DECIMALS:
|
||||||
|
{
|
||||||
|
ZVAL_BOOL( return_value, driver_dbh->format_decimals );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SQLSRV_ATTR_DECIMAL_PLACES:
|
||||||
|
{
|
||||||
|
ZVAL_LONG( return_value, driver_dbh->decimal_places );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
|
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
|
||||||
|
@ -1586,6 +1625,14 @@ void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_
|
||||||
option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE;
|
option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SQLSRV_ATTR_FORMAT_DECIMALS:
|
||||||
|
option_key = PDO_STMT_OPTION_FORMAT_DECIMALS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SQLSRV_ATTR_DECIMAL_PLACES:
|
||||||
|
option_key = PDO_STMT_OPTION_DECIMAL_PLACES;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) {
|
CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) {
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: initialization routines for PDO_SQLSRV
|
// Contents: initialization routines for PDO_SQLSRV
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -286,6 +286,8 @@ namespace {
|
||||||
{ "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE },
|
{ "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE },
|
||||||
{ "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE },
|
{ "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE },
|
||||||
{ "SQLSRV_ATTR_FETCHES_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE },
|
{ "SQLSRV_ATTR_FETCHES_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE },
|
||||||
|
{ "SQLSRV_ATTR_FORMAT_DECIMALS" , SQLSRV_ATTR_FORMAT_DECIMALS },
|
||||||
|
{ "SQLSRV_ATTR_DECIMAL_PLACES" , SQLSRV_ATTR_DECIMAL_PLACES },
|
||||||
|
|
||||||
// used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int,
|
// used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int,
|
||||||
// PDO::PARAM_STR uses the size of the string in the variable
|
// PDO::PARAM_STR uses the size of the string in the variable
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
//
|
//
|
||||||
// Copyright Microsoft Corporation
|
// Copyright Microsoft Corporation
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Implements the PDOStatement object for the PDO_SQLSRV
|
// Contents: Implements the PDOStatement object for the PDO_SQLSRV
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -882,6 +882,14 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In
|
||||||
driver_stmt->fetch_datetime = ( zend_is_true( val )) ? true : false;
|
driver_stmt->fetch_datetime = ( zend_is_true( val )) ? true : false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SQLSRV_ATTR_FORMAT_DECIMALS:
|
||||||
|
driver_stmt->format_decimals = ( zend_is_true( val )) ? true : false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SQLSRV_ATTR_DECIMAL_PLACES:
|
||||||
|
core_sqlsrv_set_decimal_places(driver_stmt, val TSRMLS_CC);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
|
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
|
||||||
break;
|
break;
|
||||||
|
@ -969,6 +977,18 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SQLSRV_ATTR_FORMAT_DECIMALS:
|
||||||
|
{
|
||||||
|
ZVAL_BOOL( return_value, driver_stmt->format_decimals );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SQLSRV_ATTR_DECIMAL_PLACES:
|
||||||
|
{
|
||||||
|
ZVAL_LONG( return_value, driver_stmt->decimal_places );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
|
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
|
||||||
break;
|
break;
|
||||||
|
@ -1377,6 +1397,7 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type,
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
||||||
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SQL_FLOAT:
|
case SQL_FLOAT:
|
||||||
|
@ -1386,6 +1407,7 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type,
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
||||||
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SQL_TYPE_DATE:
|
case SQL_TYPE_DATE:
|
||||||
|
@ -1400,10 +1422,13 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type,
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SQL_BIGINT:
|
case SQL_BIGINT:
|
||||||
case SQL_CHAR:
|
|
||||||
case SQL_DECIMAL:
|
case SQL_DECIMAL:
|
||||||
case SQL_GUID:
|
|
||||||
case SQL_NUMERIC:
|
case SQL_NUMERIC:
|
||||||
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
||||||
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
|
||||||
|
break;
|
||||||
|
case SQL_CHAR:
|
||||||
|
case SQL_GUID:
|
||||||
case SQL_WCHAR:
|
case SQL_WCHAR:
|
||||||
case SQL_VARCHAR:
|
case SQL_VARCHAR:
|
||||||
case SQL_WVARCHAR:
|
case SQL_WVARCHAR:
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Utility functions used by both connection or statement functions
|
// Contents: Utility functions used by both connection or statement functions
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -437,6 +437,10 @@ pdo_error PDO_ERRORS[] = {
|
||||||
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
|
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
|
||||||
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false}
|
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
SQLSRV_ERROR_INVALID_DECIMAL_PLACES,
|
||||||
|
{ IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -92, false}
|
||||||
|
},
|
||||||
|
|
||||||
{ UINT_MAX, {} }
|
{ UINT_MAX, {} }
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Declarations for the extension
|
// Contents: Declarations for the extension
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -41,14 +41,16 @@ extern "C" {
|
||||||
// sqlsrv driver specific PDO attributes
|
// sqlsrv driver specific PDO attributes
|
||||||
enum PDO_SQLSRV_ATTR {
|
enum PDO_SQLSRV_ATTR {
|
||||||
|
|
||||||
// Currently there are only three custom attributes for this driver.
|
// The custom attributes for this driver:
|
||||||
SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC,
|
SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC,
|
||||||
SQLSRV_ATTR_QUERY_TIMEOUT,
|
SQLSRV_ATTR_QUERY_TIMEOUT,
|
||||||
SQLSRV_ATTR_DIRECT_QUERY,
|
SQLSRV_ATTR_DIRECT_QUERY,
|
||||||
SQLSRV_ATTR_CURSOR_SCROLL_TYPE,
|
SQLSRV_ATTR_CURSOR_SCROLL_TYPE,
|
||||||
SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE,
|
SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE,
|
||||||
SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,
|
SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,
|
||||||
SQLSRV_ATTR_FETCHES_DATETIME_TYPE
|
SQLSRV_ATTR_FETCHES_DATETIME_TYPE,
|
||||||
|
SQLSRV_ATTR_FORMAT_DECIMALS,
|
||||||
|
SQLSRV_ATTR_DECIMAL_PLACES
|
||||||
};
|
};
|
||||||
|
|
||||||
// valid set of values for TransactionIsolation connection option
|
// valid set of values for TransactionIsolation connection option
|
||||||
|
@ -205,6 +207,8 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn {
|
||||||
zend_long client_buffer_max_size;
|
zend_long client_buffer_max_size;
|
||||||
bool fetch_numeric;
|
bool fetch_numeric;
|
||||||
bool fetch_datetime;
|
bool fetch_datetime;
|
||||||
|
bool format_decimals;
|
||||||
|
short decimal_places;
|
||||||
|
|
||||||
pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC );
|
pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC );
|
||||||
};
|
};
|
||||||
|
@ -266,6 +270,8 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
|
||||||
direct_query = db->direct_query;
|
direct_query = db->direct_query;
|
||||||
fetch_numeric = db->fetch_numeric;
|
fetch_numeric = db->fetch_numeric;
|
||||||
fetch_datetime = db->fetch_datetime;
|
fetch_datetime = db->fetch_datetime;
|
||||||
|
format_decimals = db->format_decimals;
|
||||||
|
decimal_places = db->decimal_places;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~pdo_sqlsrv_stmt( void );
|
virtual ~pdo_sqlsrv_stmt( void );
|
||||||
|
@ -279,8 +285,6 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
|
||||||
size_t direct_query_subst_string_len; // length of query string used for direct queries
|
size_t direct_query_subst_string_len; // length of query string used for direct queries
|
||||||
HashTable* placeholders; // hashtable of named placeholders to keep track of params ordering in emulate prepare
|
HashTable* placeholders; // hashtable of named placeholders to keep track of params ordering in emulate prepare
|
||||||
|
|
||||||
// meta data for current result set
|
|
||||||
std::vector<field_meta_data*, sqlsrv_allocator< field_meta_data* > > current_meta_data;
|
|
||||||
pdo_param_type* bound_column_param_types;
|
pdo_param_type* bound_column_param_types;
|
||||||
bool fetch_numeric;
|
bool fetch_numeric;
|
||||||
bool fetch_datetime;
|
bool fetch_datetime;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Version resource
|
// Contents: Version resource
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
// Contents: Contains functions for handling Windows format strings
|
// Contents: Contains functions for handling Windows format strings
|
||||||
// and UTF-16 on non-Windows platforms
|
// and UTF-16 on non-Windows platforms
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// Contents: Contains functions for handling Windows format strings
|
// Contents: Contains functions for handling Windows format strings
|
||||||
// and UTF-16 on non-Windows platforms
|
// and UTF-16 on non-Windows platforms
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Contains functions for handling UTF-16 on non-Windows platforms
|
// Contents: Contains functions for handling UTF-16 on non-Windows platforms
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Contains functions for handling UTF-16 on non-Windows platforms
|
// Contents: Contains functions for handling UTF-16 on non-Windows platforms
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv
|
// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -244,7 +244,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
|
||||||
#endif // !_WIN32
|
#endif // !_WIN32
|
||||||
|
|
||||||
// time to free the access token, if not null
|
// time to free the access token, if not null
|
||||||
if (conn->azure_ad_access_token != NULL) {
|
if (conn->azure_ad_access_token) {
|
||||||
memset(conn->azure_ad_access_token->data, 0, conn->azure_ad_access_token->dataSize); // clear the memory
|
memset(conn->azure_ad_access_token->data, 0, conn->azure_ad_access_token->dataSize); // clear the memory
|
||||||
conn->azure_ad_access_token.reset();
|
conn->azure_ad_access_token.reset();
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,9 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
|
||||||
load_azure_key_vault( conn );
|
// After load_azure_key_vault, reset AKV related variables regardless
|
||||||
|
load_azure_key_vault(conn);
|
||||||
|
conn->ce_option.akv_reset();
|
||||||
|
|
||||||
// determine the version of the server we're connected to. The server version is left in the
|
// determine the version of the server we're connected to. The server version is left in the
|
||||||
// connection upon return.
|
// connection upon return.
|
||||||
|
@ -292,6 +294,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
catch( core::CoreException& ) {
|
catch( core::CoreException& ) {
|
||||||
|
conn->ce_option.akv_reset();
|
||||||
conn_str.clear();
|
conn_str.clear();
|
||||||
conn->invalidate();
|
conn->invalidate();
|
||||||
throw;
|
throw;
|
||||||
|
@ -862,6 +865,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
|
||||||
|
|
||||||
}
|
}
|
||||||
catch( core::CoreException& ) {
|
catch( core::CoreException& ) {
|
||||||
|
conn->ce_option.akv_reset();
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -976,18 +980,18 @@ void load_azure_key_vault(_Inout_ sqlsrv_conn* conn TSRMLS_DC)
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR(conn->ce_option.akv_id == NULL, conn, SQLSRV_ERROR_AKV_NAME_MISSING) {
|
CHECK_CUSTOM_ERROR(!conn->ce_option.akv_id, conn, SQLSRV_ERROR_AKV_NAME_MISSING) {
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR(conn->ce_option.akv_secret == NULL, conn, SQLSRV_ERROR_AKV_SECRET_MISSING) {
|
CHECK_CUSTOM_ERROR(!conn->ce_option.akv_secret, conn, SQLSRV_ERROR_AKV_SECRET_MISSING) {
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
|
||||||
char *akv_id = Z_STRVAL_P(conn->ce_option.akv_id);
|
char *akv_id = conn->ce_option.akv_id.get();
|
||||||
char *akv_secret = Z_STRVAL_P(conn->ce_option.akv_secret);
|
char *akv_secret = conn->ce_option.akv_secret.get();
|
||||||
unsigned int id_len = static_cast<unsigned int>(Z_STRLEN_P(conn->ce_option.akv_id));
|
unsigned int id_len = strnlen_s(akv_id);
|
||||||
unsigned int key_size = static_cast<unsigned int>(Z_STRLEN_P(conn->ce_option.akv_secret));
|
unsigned int 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_FLAGS, conn->ce_option.akv_mode, 0);
|
||||||
configure_azure_key_vault(conn, AKV_CONFIG_PRINCIPALID, akv_id, id_len);
|
configure_azure_key_vault(conn, AKV_CONFIG_PRINCIPALID, akv_id, id_len);
|
||||||
|
@ -1120,6 +1124,7 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval*
|
||||||
{
|
{
|
||||||
SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "Azure Key Vault keywords accept only strings.");
|
SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "Azure Key Vault keywords accept only strings.");
|
||||||
|
|
||||||
|
const char *value_str = Z_STRVAL_P(value);
|
||||||
size_t value_len = Z_STRLEN_P(value);
|
size_t value_len = Z_STRLEN_P(value);
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR(value_len <= 0, conn, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE) {
|
CHECK_CUSTOM_ERROR(value_len <= 0, conn, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE) {
|
||||||
|
@ -1130,7 +1135,6 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval*
|
||||||
{
|
{
|
||||||
case SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION:
|
case SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION:
|
||||||
{
|
{
|
||||||
char *value_str = Z_STRVAL_P(value);
|
|
||||||
if (!stricmp(value_str, "KeyVaultPassword")) {
|
if (!stricmp(value_str, "KeyVaultPassword")) {
|
||||||
conn->ce_option.akv_mode = AKVCFG_AUTHMODE_PASSWORD;
|
conn->ce_option.akv_mode = AKVCFG_AUTHMODE_PASSWORD;
|
||||||
} else if (!stricmp(value_str, "KeyVaultClientSecret")) {
|
} else if (!stricmp(value_str, "KeyVaultClientSecret")) {
|
||||||
|
@ -1145,14 +1149,19 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval*
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID:
|
case SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID:
|
||||||
{
|
|
||||||
conn->ce_option.akv_id = value;
|
|
||||||
conn->ce_option.akv_required = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SQLSRV_CONN_OPTION_KEYSTORE_SECRET:
|
case SQLSRV_CONN_OPTION_KEYSTORE_SECRET:
|
||||||
{
|
{
|
||||||
conn->ce_option.akv_secret = value;
|
// Create a new string to save a copy of the zvalue
|
||||||
|
char *pValue = static_cast<char*>(sqlsrv_malloc(value_len + 1));
|
||||||
|
memcpy_s(pValue, value_len + 1, value_str, value_len);
|
||||||
|
pValue[value_len] = '\0'; // this makes sure there will be no trailing garbage
|
||||||
|
|
||||||
|
// This will free the existing memory block before assigning the new pointer -- the user might set the value(s) more than once
|
||||||
|
if (option->conn_option_key == SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID) {
|
||||||
|
conn->ce_option.akv_id = pValue;
|
||||||
|
} else {
|
||||||
|
conn->ce_option.akv_secret = pValue;
|
||||||
|
}
|
||||||
conn->ce_option.akv_required = true;
|
conn->ce_option.akv_required = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: common initialization routines shared by PDO and sqlsrv
|
// Contents: common initialization routines shared by PDO and sqlsrv
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Result sets
|
// Contents: Result sets
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server
|
// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -173,6 +173,8 @@ const int SQL_SERVER_MAX_FIELD_SIZE = 8000;
|
||||||
const int SQL_SERVER_MAX_PRECISION = 38;
|
const int SQL_SERVER_MAX_PRECISION = 38;
|
||||||
const int SQL_SERVER_MAX_TYPE_SIZE = 0;
|
const int SQL_SERVER_MAX_TYPE_SIZE = 0;
|
||||||
const int SQL_SERVER_MAX_PARAMS = 2100;
|
const int SQL_SERVER_MAX_PARAMS = 2100;
|
||||||
|
const int SQL_SERVER_MAX_MONEY_SCALE = 4;
|
||||||
|
|
||||||
// increase the maximum message length to accommodate for the long error returned for operand type clash
|
// increase the maximum message length to accommodate for the long error returned for operand type clash
|
||||||
// or for conversion of a long string
|
// or for conversion of a long string
|
||||||
const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 2;
|
const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 2;
|
||||||
|
@ -230,6 +232,9 @@ enum SQLSRV_FETCH_TYPE {
|
||||||
// buffer size of a sql state (including the null character)
|
// buffer size of a sql state (including the null character)
|
||||||
const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1;
|
const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1;
|
||||||
|
|
||||||
|
// default value of decimal places (no formatting required)
|
||||||
|
const short NO_CHANGE_DECIMAL_PLACES = -1;
|
||||||
|
|
||||||
// buffer size allocated to retrieve data from a PHP stream. This number
|
// buffer size allocated to retrieve data from a PHP stream. This number
|
||||||
// was chosen since PHP doesn't return more than 8k at a time even if
|
// was chosen since PHP doesn't return more than 8k at a time even if
|
||||||
// the amount requested was more.
|
// the amount requested was more.
|
||||||
|
@ -1055,15 +1060,23 @@ struct stmt_option;
|
||||||
|
|
||||||
// This holds the various details of column encryption.
|
// This holds the various details of column encryption.
|
||||||
struct col_encryption_option {
|
struct col_encryption_option {
|
||||||
bool enabled; // column encryption enabled, false by default
|
bool enabled; // column encryption enabled, false by default
|
||||||
SQLINTEGER akv_mode;
|
SQLINTEGER akv_mode;
|
||||||
zval_auto_ptr akv_id;
|
sqlsrv_malloc_auto_ptr<char> akv_id;
|
||||||
zval_auto_ptr akv_secret;
|
sqlsrv_malloc_auto_ptr<char> akv_secret;
|
||||||
bool akv_required;
|
bool akv_required;
|
||||||
|
|
||||||
col_encryption_option() : enabled( false ), akv_mode(-1), akv_required( false )
|
col_encryption_option() : enabled( false ), akv_mode(-1), akv_required( false )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void akv_reset()
|
||||||
|
{
|
||||||
|
akv_id.reset();
|
||||||
|
akv_secret.reset();
|
||||||
|
akv_required = false;
|
||||||
|
akv_mode = -1;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// *** connection resource structure ***
|
// *** connection resource structure ***
|
||||||
|
@ -1099,6 +1112,8 @@ enum SQLSRV_STMT_OPTIONS {
|
||||||
SQLSRV_STMT_OPTION_SCROLLABLE,
|
SQLSRV_STMT_OPTION_SCROLLABLE,
|
||||||
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
|
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
|
||||||
SQLSRV_STMT_OPTION_DATE_AS_STRING,
|
SQLSRV_STMT_OPTION_DATE_AS_STRING,
|
||||||
|
SQLSRV_STMT_OPTION_FORMAT_DECIMALS,
|
||||||
|
SQLSRV_STMT_OPTION_DECIMAL_PLACES,
|
||||||
|
|
||||||
// Driver specific connection options
|
// Driver specific connection options
|
||||||
SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000,
|
SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000,
|
||||||
|
@ -1288,6 +1303,16 @@ struct stmt_option_date_as_string : public stmt_option_functor {
|
||||||
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC );
|
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct stmt_option_format_decimals : public stmt_option_functor {
|
||||||
|
|
||||||
|
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC );
|
||||||
|
};
|
||||||
|
|
||||||
|
struct stmt_option_decimal_places : public stmt_option_functor {
|
||||||
|
|
||||||
|
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC );
|
||||||
|
};
|
||||||
|
|
||||||
// used to hold the table for statment options
|
// used to hold the table for statment options
|
||||||
struct stmt_option {
|
struct stmt_option {
|
||||||
|
|
||||||
|
@ -1326,38 +1351,6 @@ extern php_stream_wrapper g_sqlsrv_stream_wrapper;
|
||||||
#define SQLSRV_STREAM_WRAPPER "sqlsrv"
|
#define SQLSRV_STREAM_WRAPPER "sqlsrv"
|
||||||
#define SQLSRV_STREAM "sqlsrv_stream"
|
#define SQLSRV_STREAM "sqlsrv_stream"
|
||||||
|
|
||||||
// holds the output parameter information. Strings also need the encoding and other information for
|
|
||||||
// after processing. Only integer, float, and strings are allowable output parameters.
|
|
||||||
struct sqlsrv_output_param {
|
|
||||||
|
|
||||||
zval* param_z;
|
|
||||||
SQLSRV_ENCODING encoding;
|
|
||||||
SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement
|
|
||||||
SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer
|
|
||||||
SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary
|
|
||||||
bool is_bool;
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// forward decls
|
|
||||||
struct sqlsrv_result_set;
|
|
||||||
|
|
||||||
// *** parameter metadata struct ***
|
// *** parameter metadata struct ***
|
||||||
struct param_meta_data
|
struct param_meta_data
|
||||||
{
|
{
|
||||||
|
@ -1380,6 +1373,53 @@ struct param_meta_data
|
||||||
SQLULEN get_column_size() { return column_size; }
|
SQLULEN get_column_size() { return column_size; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// holds the output parameter information. Strings also need the encoding and other information for
|
||||||
|
// after processing. Only integer, float, and strings are allowable output parameters.
|
||||||
|
struct sqlsrv_output_param {
|
||||||
|
|
||||||
|
zval* param_z;
|
||||||
|
SQLSRV_ENCODING encoding;
|
||||||
|
SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement
|
||||||
|
SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer
|
||||||
|
SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary
|
||||||
|
bool is_bool;
|
||||||
|
param_meta_data meta_data; // parameter meta data
|
||||||
|
|
||||||
|
// string output param constructor
|
||||||
|
sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len ) :
|
||||||
|
param_z(p_z), encoding(enc), param_num(num), original_buffer_len(buffer_len), is_bool(false), php_out_type(SQLSRV_PHPTYPE_INVALID)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveMetaData(SQLSMALLINT sql_type, SQLSMALLINT column_size, SQLSMALLINT decimal_digits, SQLSMALLINT nullable = SQL_NULLABLE)
|
||||||
|
{
|
||||||
|
meta_data.sql_type = sql_type;
|
||||||
|
meta_data.column_size = column_size;
|
||||||
|
meta_data.decimal_digits = decimal_digits;
|
||||||
|
meta_data.nullable = nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
param_meta_data& getMetaData()
|
||||||
|
{
|
||||||
|
return meta_data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// forward decls
|
||||||
|
struct sqlsrv_result_set;
|
||||||
|
struct field_meta_data;
|
||||||
|
|
||||||
// *** Statement resource structure ***
|
// *** Statement resource structure ***
|
||||||
struct sqlsrv_stmt : public sqlsrv_context {
|
struct sqlsrv_stmt : public sqlsrv_context {
|
||||||
|
|
||||||
|
@ -1400,6 +1440,8 @@ struct sqlsrv_stmt : public sqlsrv_context {
|
||||||
unsigned long query_timeout; // maximum allowed statement execution time
|
unsigned long query_timeout; // maximum allowed statement execution time
|
||||||
zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB)
|
zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB)
|
||||||
bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings
|
bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings
|
||||||
|
bool format_decimals; // false by default but the user can set this to true to add the missing leading zeroes and/or control number of decimal digits to show
|
||||||
|
short decimal_places; // indicates number of decimals shown in fetched results (-1 by default, which means no change to number of decimal digits)
|
||||||
|
|
||||||
// holds output pointers for SQLBindParameter
|
// 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
|
// We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving
|
||||||
|
@ -1419,6 +1461,9 @@ struct sqlsrv_stmt : public sqlsrv_context {
|
||||||
|
|
||||||
std::vector<param_meta_data> param_descriptions;
|
std::vector<param_meta_data> param_descriptions;
|
||||||
|
|
||||||
|
// meta data for current result set
|
||||||
|
std::vector<field_meta_data*, sqlsrv_allocator<field_meta_data*>> current_meta_data;
|
||||||
|
|
||||||
sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv TSRMLS_DC );
|
sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv TSRMLS_DC );
|
||||||
virtual ~sqlsrv_stmt( void );
|
virtual ~sqlsrv_stmt( void );
|
||||||
|
|
||||||
|
@ -1437,9 +1482,10 @@ struct field_meta_data {
|
||||||
SQLULEN field_precision;
|
SQLULEN field_precision;
|
||||||
SQLSMALLINT field_scale;
|
SQLSMALLINT field_scale;
|
||||||
SQLSMALLINT field_is_nullable;
|
SQLSMALLINT field_is_nullable;
|
||||||
|
bool field_is_money_type;
|
||||||
|
|
||||||
field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0),
|
field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0),
|
||||||
field_scale (0), field_is_nullable(0)
|
field_scale (0), field_is_nullable(0), field_is_money_type(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1488,7 +1534,7 @@ 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 TSRMLS_DC );
|
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
|
||||||
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC );
|
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC );
|
||||||
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit TSRMLS_DC );
|
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit TSRMLS_DC );
|
||||||
|
void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC);
|
||||||
|
|
||||||
//*********************************************************************************************************************************
|
//*********************************************************************************************************************************
|
||||||
// Result Set
|
// Result Set
|
||||||
|
@ -1668,7 +1714,6 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set {
|
||||||
|
|
||||||
// utility functions shared by multiple callers across files
|
// utility functions shared by multiple callers across files
|
||||||
bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_updates_z_(len) char** string, _Inout_ SQLLEN& len);
|
bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_updates_z_(len) char** string, _Inout_ SQLLEN& len);
|
||||||
bool convert_zval_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _Inout_ zval* value_z, _Inout_ SQLLEN& len);
|
|
||||||
bool validate_string( _In_ char* string, _In_ SQLLEN& len);
|
bool validate_string( _In_ char* string, _In_ SQLLEN& len);
|
||||||
bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_(cchInLen) const SQLWCHAR* inString, _In_ SQLINTEGER cchInLen, _Inout_updates_bytes_(cchOutLen) char** outString, _Out_ SQLLEN& cchOutLen );
|
bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_(cchInLen) const SQLWCHAR* inString, _In_ SQLINTEGER cchInLen, _Inout_updates_bytes_(cchOutLen) char** outString, _Out_ SQLLEN& cchOutLen );
|
||||||
SQLWCHAR* utf16_string_from_mbcs_string( _In_ SQLSRV_ENCODING php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len );
|
SQLWCHAR* utf16_string_from_mbcs_string( _In_ SQLSRV_ENCODING php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len );
|
||||||
|
@ -1731,6 +1776,7 @@ enum SQLSRV_ERROR_CODES {
|
||||||
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
|
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
|
||||||
SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN,
|
SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN,
|
||||||
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
|
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
|
||||||
|
SQLSRV_ERROR_INVALID_DECIMAL_PLACES,
|
||||||
|
|
||||||
// Driver specific error codes starts from here.
|
// Driver specific error codes starts from here.
|
||||||
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
|
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
|
||||||
|
@ -1898,9 +1944,10 @@ namespace core {
|
||||||
|
|
||||||
inline void check_for_mars_error( _Inout_ sqlsrv_stmt* stmt, _In_ SQLRETURN r TSRMLS_DC )
|
inline void check_for_mars_error( _Inout_ sqlsrv_stmt* stmt, _In_ SQLRETURN r TSRMLS_DC )
|
||||||
{
|
{
|
||||||
|
// Skip this if not SQL_ERROR -
|
||||||
// We check for the 'connection busy' error caused by having MultipleActiveResultSets off
|
// We check for the 'connection busy' error caused by having MultipleActiveResultSets off
|
||||||
// and return a more helpful message prepended to the ODBC errors if that error occurs
|
// and return a more helpful message prepended to the ODBC errors if that error occurs
|
||||||
if( !SQL_SUCCEEDED( r )) {
|
if (r == SQL_ERROR) {
|
||||||
|
|
||||||
SQLCHAR err_msg[SQL_MAX_MESSAGE_LENGTH + 1] = {'\0'};
|
SQLCHAR err_msg[SQL_MAX_MESSAGE_LENGTH + 1] = {'\0'};
|
||||||
SQLSMALLINT len = 0;
|
SQLSMALLINT len = 0;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv
|
// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -97,7 +97,7 @@ size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) cons
|
||||||
bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
|
bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
|
||||||
bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* convert_param_z );
|
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
|
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 TSRMLS_DC);
|
sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC);
|
||||||
// returns the ODBC C type constant that matches the PHP type and encoding given
|
// 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_ SQLSRV_ENCODING encoding TSRMLS_DC );
|
SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSRV_ENCODING encoding TSRMLS_DC );
|
||||||
void default_sql_size_and_scale( _Inout_ sqlsrv_stmt* stmt, _In_opt_ unsigned int paramno, _In_ zval* param_z, _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,
|
||||||
|
@ -107,9 +107,10 @@ void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_
|
||||||
_Out_ SQLSMALLINT& sql_type TSRMLS_DC );
|
_Out_ SQLSMALLINT& sql_type TSRMLS_DC );
|
||||||
void col_cache_dtor( _Inout_ zval* data_z );
|
void col_cache_dtor( _Inout_ zval* data_z );
|
||||||
void field_cache_dtor( _Inout_ zval* data_z );
|
void field_cache_dtor( _Inout_ zval* data_z );
|
||||||
|
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 TSRMLS_DC );
|
void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
|
||||||
void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type,
|
void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type,
|
||||||
_Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC );
|
_Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC );
|
||||||
stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] TSRMLS_DC );
|
stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] TSRMLS_DC );
|
||||||
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
|
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
|
||||||
// assure there is enough space for the output parameter string
|
// assure there is enough space for the output parameter string
|
||||||
|
@ -124,7 +125,6 @@ void send_param_streams( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
|
||||||
void sqlsrv_output_param_dtor( _Inout_ zval* data );
|
void sqlsrv_output_param_dtor( _Inout_ zval* data );
|
||||||
// called when a bound stream parameter is to be destroyed.
|
// called when a bound stream parameter is to be destroyed.
|
||||||
void sqlsrv_stream_dtor( _Inout_ zval* data );
|
void sqlsrv_stream_dtor( _Inout_ zval* data );
|
||||||
bool is_streamable_type( _In_ SQLINTEGER sql_type );
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,13 +142,15 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error
|
||||||
past_next_result_end( false ),
|
past_next_result_end( false ),
|
||||||
query_timeout( QUERY_TIMEOUT_INVALID ),
|
query_timeout( QUERY_TIMEOUT_INVALID ),
|
||||||
date_as_string(false),
|
date_as_string(false),
|
||||||
|
format_decimals(false), // no formatting needed
|
||||||
|
decimal_places(NO_CHANGE_DECIMAL_PLACES), // the default is no formatting to resultset required
|
||||||
buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ),
|
buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ),
|
||||||
param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte
|
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 ),
|
send_streams_at_exec( true ),
|
||||||
current_stream( NULL, SQLSRV_ENCODING_DEFAULT ),
|
current_stream( NULL, SQLSRV_ENCODING_DEFAULT ),
|
||||||
current_stream_read( 0 )
|
current_stream_read( 0 )
|
||||||
{
|
{
|
||||||
ZVAL_UNDEF( &active_stream );
|
ZVAL_UNDEF( &active_stream );
|
||||||
// initialize the input string parameters array (which holds zvals)
|
// initialize the input string parameters array (which holds zvals)
|
||||||
core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC );
|
core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC );
|
||||||
|
|
||||||
|
@ -260,7 +262,7 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D )
|
||||||
sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht,
|
sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht,
|
||||||
_In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver TSRMLS_DC )
|
_In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver TSRMLS_DC )
|
||||||
{
|
{
|
||||||
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> stmt;
|
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> stmt;
|
||||||
SQLHANDLE stmt_h = SQL_NULL_HANDLE;
|
SQLHANDLE stmt_h = SQL_NULL_HANDLE;
|
||||||
sqlsrv_stmt* return_stmt = NULL;
|
sqlsrv_stmt* return_stmt = NULL;
|
||||||
|
|
||||||
|
@ -278,26 +280,26 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm
|
||||||
|
|
||||||
// process the options array given to core_sqlsrv_prepare.
|
// process the options array given to core_sqlsrv_prepare.
|
||||||
if( options_ht && zend_hash_num_elements( options_ht ) > 0 && valid_stmt_opts ) {
|
if( options_ht && zend_hash_num_elements( options_ht ) > 0 && valid_stmt_opts ) {
|
||||||
zend_ulong index = -1;
|
zend_ulong index = -1;
|
||||||
zend_string *key = NULL;
|
zend_string *key = NULL;
|
||||||
zval* value_z = NULL;
|
zval* value_z = NULL;
|
||||||
|
|
||||||
ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) {
|
ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) {
|
||||||
|
|
||||||
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
|
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
|
||||||
|
|
||||||
// The driver layer should ensure a valid key.
|
// The driver layer should ensure a valid key.
|
||||||
DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." );
|
DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." );
|
||||||
|
|
||||||
const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC );
|
const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC );
|
||||||
|
|
||||||
// if the key didn't match, then return the error to the script.
|
// if the key didn't match, then return the error to the script.
|
||||||
// The driver layer should ensure that the key is valid.
|
// The driver layer should ensure that the key is valid.
|
||||||
DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." );
|
DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." );
|
||||||
|
|
||||||
// perform the actions the statement option needs done.
|
// perform the actions the statement option needs done.
|
||||||
(*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC );
|
(*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC );
|
||||||
} ZEND_HASH_FOREACH_END();
|
} ZEND_HASH_FOREACH_END();
|
||||||
}
|
}
|
||||||
|
|
||||||
return_stmt = stmt;
|
return_stmt = stmt;
|
||||||
|
@ -493,7 +495,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
ind_ptr = buffer_len;
|
ind_ptr = buffer_len;
|
||||||
if( direction != SQL_PARAM_INPUT ){
|
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
|
// 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);
|
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 TSRMLS_CC );
|
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -501,11 +503,11 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
case IS_DOUBLE:
|
case IS_DOUBLE:
|
||||||
{
|
{
|
||||||
buffer = ¶m_z->value;
|
buffer = ¶m_z->value;
|
||||||
buffer_len = sizeof( Z_DVAL_P( param_z ));
|
buffer_len = sizeof( Z_DVAL_P( param_z ));
|
||||||
ind_ptr = buffer_len;
|
ind_ptr = buffer_len;
|
||||||
if( direction != SQL_PARAM_INPUT ){
|
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
|
// 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);
|
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 TSRMLS_CC );
|
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -572,6 +574,8 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
// save the parameter to be adjusted and/or converted after the results are processed
|
// save the parameter to be adjusted and/or converted after the results are processed
|
||||||
sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast<SQLUINTEGER>( buffer_len ) );
|
sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast<SQLUINTEGER>( buffer_len ) );
|
||||||
|
|
||||||
|
output_param.saveMetaData(sql_type, column_size, decimal_digits);
|
||||||
|
|
||||||
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
||||||
|
|
||||||
// For output parameters, if we set the column_size to be same as the buffer_len,
|
// For output parameters, if we set the column_size to be same as the buffer_len,
|
||||||
|
@ -617,10 +621,10 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
zval buffer_z;
|
zval buffer_z;
|
||||||
zval format_z;
|
zval format_z;
|
||||||
zval params[1];
|
zval params[1];
|
||||||
ZVAL_UNDEF( &function_z );
|
ZVAL_UNDEF( &function_z );
|
||||||
ZVAL_UNDEF( &buffer_z );
|
ZVAL_UNDEF( &buffer_z );
|
||||||
ZVAL_UNDEF( &format_z );
|
ZVAL_UNDEF( &format_z );
|
||||||
ZVAL_UNDEF( params );
|
ZVAL_UNDEF( params );
|
||||||
|
|
||||||
bool valid_class_name_found = false;
|
bool valid_class_name_found = false;
|
||||||
|
|
||||||
|
@ -649,23 +653,23 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
|
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
|
||||||
// sql type, it lacks the timezone.
|
// sql type, it lacks the timezone.
|
||||||
if( sql_type == SQL_SS_TIMESTAMPOFFSET ){
|
if( sql_type == SQL_SS_TIMESTAMPOFFSET ){
|
||||||
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATETIMEOFFSET_FORMAT ),
|
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATETIMEOFFSET_FORMAT ),
|
||||||
DateTime::DATETIMEOFFSET_FORMAT_LEN );
|
DateTime::DATETIMEOFFSET_FORMAT_LEN );
|
||||||
}
|
}
|
||||||
else if( sql_type == SQL_TYPE_DATE ){
|
else if( sql_type == SQL_TYPE_DATE ){
|
||||||
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN );
|
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN );
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN );
|
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
|
// 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 );
|
core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 );
|
||||||
params[0] = format_z;
|
params[0] = format_z;
|
||||||
// This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the
|
// 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.
|
// DateTime object and $format_z is the format string.
|
||||||
int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC );
|
int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC );
|
||||||
zend_string_release( Z_STR( format_z ));
|
zend_string_release( Z_STR( format_z ));
|
||||||
zend_string_release( Z_STR( function_z ));
|
zend_string_release( Z_STR( function_z ));
|
||||||
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ){
|
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ){
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
@ -692,7 +696,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
}
|
}
|
||||||
|
|
||||||
core::SQLBindParameter( stmt, param_num + 1, direction,
|
core::SQLBindParameter( stmt, param_num + 1, direction,
|
||||||
c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC );
|
c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC );
|
||||||
if ( stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP )
|
if ( stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP )
|
||||||
{
|
{
|
||||||
if( decimal_digits == 3 )
|
if( decimal_digits == 3 )
|
||||||
|
@ -881,14 +885,14 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL
|
||||||
meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data();
|
meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data();
|
||||||
field_name_temp = static_cast<SQLWCHAR*>( sqlsrv_malloc( ( SS_MAXCOLNAMELEN + 1 ) * sizeof( SQLWCHAR ) ));
|
field_name_temp = static_cast<SQLWCHAR*>( sqlsrv_malloc( ( SS_MAXCOLNAMELEN + 1 ) * sizeof( SQLWCHAR ) ));
|
||||||
SQLSRV_ENCODING encoding = ( (stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding());
|
SQLSRV_ENCODING encoding = ( (stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding());
|
||||||
try{
|
try{
|
||||||
core::SQLDescribeColW( stmt, colno + 1, field_name_temp, SS_MAXCOLNAMELEN + 1, &field_len_temp,
|
core::SQLDescribeColW( stmt, colno + 1, field_name_temp, SS_MAXCOLNAMELEN + 1, &field_len_temp,
|
||||||
&( meta_data->field_type ), & ( meta_data->field_size ), & ( meta_data->field_scale ),
|
&( meta_data->field_type ), & ( meta_data->field_size ), & ( meta_data->field_scale ),
|
||||||
&( meta_data->field_is_nullable ) TSRMLS_CC );
|
&( meta_data->field_is_nullable ) TSRMLS_CC );
|
||||||
}
|
}
|
||||||
catch ( core::CoreException& e ) {
|
catch ( core::CoreException& e ) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool converted = convert_string_from_utf16( encoding, field_name_temp, field_len_temp, ( char** ) &( meta_data->field_name ), field_name_len );
|
bool converted = convert_string_from_utf16( encoding, field_name_temp, field_len_temp, ( char** ) &( meta_data->field_name ), field_name_len );
|
||||||
|
|
||||||
|
@ -922,6 +926,19 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (meta_data->field_type == SQL_DECIMAL) {
|
||||||
|
// Check if it is money type -- get the name of the data type
|
||||||
|
char field_type_name[SS_MAXCOLNAMELEN] = {'\0'};
|
||||||
|
SQLSMALLINT out_buff_len;
|
||||||
|
SQLLEN not_used;
|
||||||
|
core::SQLColAttribute(stmt, colno + 1, SQL_DESC_TYPE_NAME, field_type_name,
|
||||||
|
sizeof( field_type_name ), &out_buff_len, ¬_used TSRMLS_CC);
|
||||||
|
|
||||||
|
if (!strcmp(field_type_name, "money") || !strcmp(field_type_name, "smallmoney")) {
|
||||||
|
meta_data->field_is_money_type = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the field name lenth
|
// Set the field name lenth
|
||||||
meta_data->field_name_len = static_cast<SQLSMALLINT>( field_name_len );
|
meta_data->field_name_len = static_cast<SQLSMALLINT>( field_name_len );
|
||||||
|
|
||||||
|
@ -943,50 +960,50 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL
|
||||||
// Nothing, excpetion thrown if an error occurs
|
// Nothing, excpetion thrown if an error occurs
|
||||||
|
|
||||||
void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_phptype sqlsrv_php_type_in, _In_ bool prefer_string,
|
void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_phptype sqlsrv_php_type_in, _In_ bool prefer_string,
|
||||||
_Outref_result_bytebuffer_maybenull_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len, _In_ bool cache_field,
|
_Outref_result_bytebuffer_maybenull_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len, _In_ bool cache_field,
|
||||||
_Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC)
|
_Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// close the stream to release the resource
|
// close the stream to release the resource
|
||||||
close_active_stream(stmt TSRMLS_CC);
|
close_active_stream(stmt TSRMLS_CC);
|
||||||
|
|
||||||
// if the field has been retrieved before, return the previous result
|
// if the field has been retrieved before, return the previous result
|
||||||
field_cache* cached = NULL;
|
field_cache* cached = NULL;
|
||||||
if (NULL != ( cached = static_cast<field_cache*>( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast<zend_ulong>( field_index ))))) {
|
if (NULL != ( cached = static_cast<field_cache*>( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast<zend_ulong>( field_index ))))) {
|
||||||
// the field value is NULL
|
// the field value is NULL
|
||||||
if( cached->value == NULL ) {
|
if( cached->value == NULL ) {
|
||||||
field_value = NULL;
|
field_value = NULL;
|
||||||
*field_len = 0;
|
*field_len = 0;
|
||||||
if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; }
|
if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; }
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 );
|
field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 );
|
||||||
memcpy_s( field_value, ( cached->len * sizeof( char )), cached->value, cached->len );
|
memcpy_s( field_value, ( cached->len * sizeof( char )), cached->value, cached->len );
|
||||||
if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING) {
|
if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING) {
|
||||||
// prevent the 'string not null terminated' warning
|
// prevent the 'string not null terminated' warning
|
||||||
reinterpret_cast<char*>( field_value )[cached->len] = '\0';
|
reinterpret_cast<char*>( field_value )[cached->len] = '\0';
|
||||||
}
|
}
|
||||||
*field_len = cached->len;
|
*field_len = cached->len;
|
||||||
if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>(cached->type.typeinfo.type); }
|
if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>(cached->type.typeinfo.type); }
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in;
|
sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in;
|
||||||
|
|
||||||
SQLLEN sql_field_type = 0;
|
SQLLEN sql_field_type = 0;
|
||||||
SQLLEN sql_field_len = 0;
|
SQLLEN sql_field_len = 0;
|
||||||
|
|
||||||
// Make sure that the statement was executed and not just prepared.
|
// Make sure that the statement was executed and not just prepared.
|
||||||
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
|
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they
|
// if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they
|
||||||
// may also be retrieved.
|
// may also be retrieved.
|
||||||
if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) {
|
if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) {
|
||||||
sqlsrv_phptype invalid;
|
sqlsrv_phptype invalid;
|
||||||
invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
|
invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
|
||||||
for( int i = stmt->last_field_index + 1; i < field_index; ++i ) {
|
for( int i = stmt->last_field_index + 1; i < field_index; ++i ) {
|
||||||
|
@ -997,44 +1014,46 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i
|
||||||
efree( field_value );
|
efree( field_value );
|
||||||
field_value = NULL;
|
field_value = NULL;
|
||||||
*field_len = 0;
|
*field_len = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the php type was not specified set the php type to be the default type.
|
// If the php type was not specified set the php type to be the default type.
|
||||||
if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) {
|
if (sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID) {
|
||||||
|
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_sqlsrv_get_field - meta data vector not in sync" );
|
||||||
|
sql_field_type = stmt->current_meta_data[field_index]->field_type;
|
||||||
|
if (stmt->current_meta_data[field_index]->field_precision > 0) {
|
||||||
|
sql_field_len = stmt->current_meta_data[field_index]->field_precision;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sql_field_len = stmt->current_meta_data[field_index]->field_size;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the SQL type of the field.
|
// Get the corresponding php type from the sql type.
|
||||||
core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC );
|
sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast<SQLINTEGER>(sql_field_type), static_cast<SQLUINTEGER>(sql_field_len), prefer_string);
|
||||||
|
}
|
||||||
|
|
||||||
// Get the length of the field.
|
// Verify that we have an acceptable type to convert.
|
||||||
core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC );
|
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) {
|
||||||
|
throw core::CoreException();
|
||||||
|
}
|
||||||
|
|
||||||
// Get the corresponding php type from the sql type.
|
if( sqlsrv_php_type_out != NULL )
|
||||||
sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast<SQLINTEGER>( sql_field_type ), static_cast<SQLUINTEGER>( sql_field_len ), prefer_string );
|
*sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>( sqlsrv_php_type.typeinfo.type );
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that we have an acceptable type to convert.
|
// Retrieve the data
|
||||||
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) {
|
core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC );
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( sqlsrv_php_type_out != NULL )
|
// if the user wants us to cache the field, we'll do it
|
||||||
*sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>( sqlsrv_php_type.typeinfo.type );
|
if( cache_field ) {
|
||||||
|
field_cache cache( field_value, *field_len, sqlsrv_php_type );
|
||||||
|
core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve the data
|
catch( core::CoreException& e ) {
|
||||||
core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC );
|
throw e;
|
||||||
|
}
|
||||||
// if the user wants us to cache the field, we'll do it
|
|
||||||
if( cache_field ) {
|
|
||||||
field_cache cache( field_value, *field_len, sqlsrv_php_type );
|
|
||||||
core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
catch( core::CoreException& e ) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// core_sqlsrv_has_any_result
|
// core_sqlsrv_has_any_result
|
||||||
|
@ -1253,6 +1272,27 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// first check if the input is an integer
|
||||||
|
CHECK_CUSTOM_ERROR(Z_TYPE_P(value_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_DECIMAL_PLACES) {
|
||||||
|
throw core::CoreException();
|
||||||
|
}
|
||||||
|
|
||||||
|
zend_long decimal_places = Z_LVAL_P(value_z);
|
||||||
|
if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) {
|
||||||
|
// ignore decimal_places because it is out of range
|
||||||
|
decimal_places = NO_CHANGE_DECIMAL_PLACES;
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt->decimal_places = static_cast<short>(decimal_places);
|
||||||
|
}
|
||||||
|
catch( core::CoreException& ) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC )
|
void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC )
|
||||||
{
|
{
|
||||||
TSRMLS_C;
|
TSRMLS_C;
|
||||||
|
@ -1293,31 +1333,36 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
||||||
php_stream* param_stream = NULL;
|
php_stream* param_stream = NULL;
|
||||||
core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC );
|
core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC );
|
||||||
|
|
||||||
// if we're at the end, then release our current parameter
|
// if we're at the end, then reset both current_stream and current_stream_read
|
||||||
if( php_stream_eof( param_stream )) {
|
if (php_stream_eof(param_stream)) {
|
||||||
// if no data was actually sent prior, then send a NULL
|
// yet return to the very beginning of param_stream since SQLParamData() may ask for the same data again
|
||||||
if( stmt->current_stream_read == 0 ) {
|
int ret = php_stream_seek(param_stream, 0, SEEK_SET);
|
||||||
// send an empty string, which is what a 0 length does.
|
if (ret != 0) {
|
||||||
char buff[1]; // temp storage to hand to SQLPutData
|
LOG(SEV_ERROR, "PHP stream: stream seek failed.");
|
||||||
core::SQLPutData( stmt, buff, 0 TSRMLS_CC );
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR );
|
stmt->current_stream = sqlsrv_stream(NULL, SQLSRV_ENCODING_CHAR);
|
||||||
stmt->current_stream_read = 0;
|
stmt->current_stream_read = 0;
|
||||||
}
|
}
|
||||||
// read the data from the stream, send it via SQLPutData and track how much we've sent.
|
// read the data from the stream, send it via SQLPutData and track how much we've sent.
|
||||||
else {
|
else {
|
||||||
char buffer[PHP_STREAM_BUFFER_SIZE + 1] = {'\0'};
|
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 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 );
|
std::size_t read = php_stream_read( param_stream, buffer, buffer_size );
|
||||||
|
|
||||||
if (read > UINT_MAX)
|
if (read > UINT_MAX)
|
||||||
{
|
{
|
||||||
LOG(SEV_ERROR, "PHP stream: buffer length exceeded.");
|
LOG(SEV_ERROR, "PHP stream: buffer length exceeded.");
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
|
||||||
stmt->current_stream_read += static_cast<unsigned int>( read );
|
stmt->current_stream_read += static_cast<unsigned int>( read );
|
||||||
if( read > 0 ) {
|
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 TSRMLS_CC);
|
||||||
|
}
|
||||||
|
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
|
// 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
|
// then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it
|
||||||
// twice.
|
// twice.
|
||||||
|
@ -1329,8 +1374,8 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
||||||
// expansion of 2x the UTF-8 size.
|
// expansion of 2x the UTF-8 size.
|
||||||
SQLWCHAR wbuffer[PHP_STREAM_BUFFER_SIZE + 1] = {L'\0'};
|
SQLWCHAR wbuffer[PHP_STREAM_BUFFER_SIZE + 1] = {L'\0'};
|
||||||
int wbuffer_size = static_cast<int>( sizeof( wbuffer ) / sizeof( SQLWCHAR ));
|
int wbuffer_size = static_cast<int>( sizeof( wbuffer ) / sizeof( SQLWCHAR ));
|
||||||
DWORD last_error_code = ERROR_SUCCESS;
|
DWORD last_error_code = ERROR_SUCCESS;
|
||||||
// buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate
|
// buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
int wsize = SystemLocale::ToUtf16Strict( stmt->current_stream.encoding, buffer, static_cast<int>(read), wbuffer, wbuffer_size, &last_error_code );
|
int wsize = SystemLocale::ToUtf16Strict( stmt->current_stream.encoding, buffer, static_cast<int>(read), wbuffer, wbuffer_size, &last_error_code );
|
||||||
#else
|
#else
|
||||||
|
@ -1338,7 +1383,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
||||||
last_error_code = GetLastError();
|
last_error_code = GetLastError();
|
||||||
#endif // !_WIN32
|
#endif // !_WIN32
|
||||||
|
|
||||||
if( wsize == 0 && last_error_code == ERROR_NO_UNICODE_TRANSLATION ) {
|
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
|
// 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.
|
// in, then reattempt the conversion. If it fails the second time, then an error is returned.
|
||||||
|
@ -1415,6 +1460,21 @@ void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stmt_option_format_decimals:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC )
|
||||||
|
{
|
||||||
|
if (zend_is_true(value_z)) {
|
||||||
|
stmt->format_decimals = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stmt->format_decimals = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stmt_option_decimal_places:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC )
|
||||||
|
{
|
||||||
|
core_sqlsrv_set_decimal_places(stmt, value_z TSRMLS_CC);
|
||||||
|
}
|
||||||
|
|
||||||
// internal function to release the active stream. Called by each main API function
|
// internal function to release the active stream. Called by each main API function
|
||||||
// that will alter the statement and cancel any retrieval of data from a stream.
|
// that will alter the statement and cancel any retrieval of data from a stream.
|
||||||
void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
||||||
|
@ -1441,7 +1501,7 @@ void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
bool is_streamable_type( _In_ SQLLEN sql_type )
|
bool is_streamable_type( _In_ SQLSMALLINT sql_type )
|
||||||
{
|
{
|
||||||
switch( sql_type ) {
|
switch( sql_type ) {
|
||||||
case SQL_CHAR:
|
case SQL_CHAR:
|
||||||
|
@ -1460,6 +1520,25 @@ bool is_streamable_type( _In_ SQLLEN sql_type )
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_a_numeric_type(_In_ SQLSMALLINT sql_type)
|
||||||
|
{
|
||||||
|
switch (sql_type) {
|
||||||
|
case SQL_BIGINT:
|
||||||
|
case SQL_BIT:
|
||||||
|
case SQL_INTEGER:
|
||||||
|
case SQL_SMALLINT:
|
||||||
|
case SQL_TINYINT:
|
||||||
|
case SQL_FLOAT:
|
||||||
|
case SQL_DOUBLE:
|
||||||
|
case SQL_REAL:
|
||||||
|
case SQL_DECIMAL:
|
||||||
|
case SQL_NUMERIC:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size TSRMLS_DC )
|
void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size TSRMLS_DC )
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@ -1693,12 +1772,10 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i
|
||||||
{
|
{
|
||||||
php_stream* stream = NULL;
|
php_stream* stream = NULL;
|
||||||
sqlsrv_stream* ss = NULL;
|
sqlsrv_stream* ss = NULL;
|
||||||
SQLLEN sql_type;
|
SQLSMALLINT sql_type;
|
||||||
|
|
||||||
SQLRETURN r = SQLColAttributeW( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type );
|
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_get_field_common - meta data vector not in sync" );
|
||||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
sql_type = stmt->current_meta_data[field_index]->field_type;
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) {
|
CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) {
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
|
@ -1796,23 +1873,26 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve
|
||||||
std::size_t buffer_len = Z_STRLEN_P( input_param_z );
|
std::size_t buffer_len = Z_STRLEN_P( input_param_z );
|
||||||
int wchar_size;
|
int wchar_size;
|
||||||
|
|
||||||
if (buffer_len > INT_MAX)
|
if (buffer_len > INT_MAX)
|
||||||
{
|
{
|
||||||
LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded.");
|
LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded.");
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the string is empty, then just return that the conversion succeeded as
|
// if the string is empty, then just return that the conversion succeeded as
|
||||||
// MultiByteToWideChar will "fail" on an empty string.
|
// MultiByteToWideChar will "fail" on an empty string.
|
||||||
if( buffer_len == 0 ) {
|
if( buffer_len == 0 ) {
|
||||||
core::sqlsrv_zval_stringl( converted_param_z, "", 0 );
|
core::sqlsrv_zval_stringl( converted_param_z, "", 0 );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
wchar_size = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast<LPCSTR>( buffer ), static_cast<int>( buffer_len ), NULL, 0 );
|
// 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
|
#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 );
|
wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ), static_cast<int>( buffer_len ), NULL, 0 );
|
||||||
#endif // !_WIN32
|
#endif // !_WIN32
|
||||||
|
|
||||||
|
@ -1824,17 +1904,18 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve
|
||||||
wbuffer = reinterpret_cast<SQLWCHAR*>( sqlsrv_malloc( (wchar_size + 1) * sizeof( SQLWCHAR ) ));
|
wbuffer = reinterpret_cast<SQLWCHAR*>( sqlsrv_malloc( (wchar_size + 1) * sizeof( SQLWCHAR ) ));
|
||||||
// convert the utf-8 string to a wchar string in the new buffer
|
// convert the utf-8 string to a wchar string in the new buffer
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
int r = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast<LPCSTR>( buffer ), static_cast<int>( buffer_len ), wbuffer, wchar_size );
|
int rc = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast<LPCSTR>( buffer ), static_cast<int>( buffer_len ), wbuffer, wchar_size );
|
||||||
#else
|
#else
|
||||||
int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ), static_cast<int>( buffer_len ), wbuffer, wchar_size );
|
int rc = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ), static_cast<int>( buffer_len ), wbuffer, wchar_size );
|
||||||
#endif // !_WIN32
|
#endif // !_WIN32
|
||||||
// if there was a problem converting the string, then free the memory and return false
|
// if there was a problem converting the string, then free the memory and return false
|
||||||
if( r == 0 ) {
|
if( rc == 0 ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
wchar_size = rc;
|
||||||
|
|
||||||
// null terminate the string, set the size within the zval, and return success
|
// null terminate the string, set the size within the zval, and return success
|
||||||
wbuffer[wchar_size] = L'\0';
|
wbuffer[ wchar_size ] = L'\0';
|
||||||
core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast<char*>( wbuffer.get() ), wchar_size * sizeof( SQLWCHAR ) );
|
core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast<char*>( wbuffer.get() ), wchar_size * sizeof( SQLWCHAR ) );
|
||||||
sqlsrv_free(wbuffer);
|
sqlsrv_free(wbuffer);
|
||||||
wbuffer.transferred();
|
wbuffer.transferred();
|
||||||
|
@ -1918,7 +1999,7 @@ void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_
|
||||||
_Out_ SQLSMALLINT& sql_type TSRMLS_DC )
|
_Out_ SQLSMALLINT& sql_type TSRMLS_DC )
|
||||||
{
|
{
|
||||||
sql_type = SQL_UNKNOWN_TYPE;
|
sql_type = SQL_UNKNOWN_TYPE;
|
||||||
int php_type = Z_TYPE_P(param_z);
|
int php_type = Z_TYPE_P(param_z);
|
||||||
switch( php_type ) {
|
switch( php_type ) {
|
||||||
|
|
||||||
case IS_NULL:
|
case IS_NULL:
|
||||||
|
@ -2058,9 +2139,146 @@ void field_cache_dtor( _Inout_ zval* data_z )
|
||||||
{
|
{
|
||||||
sqlsrv_free( cache->value );
|
sqlsrv_free( cache->value );
|
||||||
}
|
}
|
||||||
sqlsrv_free( cache );
|
sqlsrv_free( cache );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To be called for formatting decimal / numeric fetched values from finalize_output_parameters() and/or get_field_as_string()
|
||||||
|
void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len)
|
||||||
|
{
|
||||||
|
// In SQL Server, the default maximum precision of numeric and decimal data types is 38
|
||||||
|
//
|
||||||
|
// Note: decimals_places is NO_CHANGE_DECIMAL_PLACES by default, which means no formatting on decimal data is necessary
|
||||||
|
// This function assumes stmt->format_decimals is true, so it first checks if it is necessary to add the leading zero.
|
||||||
|
//
|
||||||
|
// Likewise, if decimals_places is larger than the field scale, decimals_places wil be ignored. This is to ensure the
|
||||||
|
// number of decimals adheres to the column field scale. If smaller, the output value may be rounded up.
|
||||||
|
//
|
||||||
|
// Note: it's possible that the decimal data does not contain a decimal dot because the field scale is 0.
|
||||||
|
// Thus, first check if the decimal dot exists. If not, no formatting necessary, regardless of
|
||||||
|
// format_decimals and decimals_places
|
||||||
|
//
|
||||||
|
std::string str = field_value;
|
||||||
|
size_t pos = str.find_first_of('.');
|
||||||
|
|
||||||
|
// The decimal dot is not found, simply return
|
||||||
|
if (pos == std::string::npos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SQLSMALLINT num_decimals = decimals_places;
|
||||||
|
if (num_decimals > field_scale) {
|
||||||
|
num_decimals = field_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want the rounding to be consistent with php number_format(), http://php.net/manual/en/function.number-format.php
|
||||||
|
// as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is
|
||||||
|
// followed by 5 or above.
|
||||||
|
|
||||||
|
bool isNegative = false;
|
||||||
|
|
||||||
|
// If negative, remove the minus sign for now so as not to complicate the rounding process
|
||||||
|
if (str[0] == '-') {
|
||||||
|
isNegative = true;
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << str.substr(1);
|
||||||
|
str = oss.str();
|
||||||
|
pos = str.find_first_of('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds the leading zero if not exists
|
||||||
|
if (pos == 0) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << '0' << str;
|
||||||
|
str = oss.str();
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_decimals == NO_CHANGE_DECIMAL_PLACES) {
|
||||||
|
// Add the minus sign back if negative
|
||||||
|
if (isNegative) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << '-' << str.substr(0);
|
||||||
|
str = oss.str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Start formatting
|
||||||
|
size_t last = 0;
|
||||||
|
if (num_decimals == 0) {
|
||||||
|
// Chop all decimal digits, including the decimal dot
|
||||||
|
size_t pos2 = pos + 1;
|
||||||
|
short n = str[pos2] - '0';
|
||||||
|
if (n >= 5) {
|
||||||
|
// Start rounding up - starting from the digit left of the dot all the way to the first digit
|
||||||
|
bool carry_over = true;
|
||||||
|
for (short p = pos - 1; p >= 0 && carry_over; p--) {
|
||||||
|
n = str[p] - '0';
|
||||||
|
if (n == 9) {
|
||||||
|
str[p] = '0' ;
|
||||||
|
carry_over = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
n++;
|
||||||
|
carry_over = false;
|
||||||
|
str[p] = '0' + n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (carry_over) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << '1' << str.substr(0, pos);
|
||||||
|
str = oss.str();
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last = pos;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
size_t pos2 = pos + num_decimals + 1;
|
||||||
|
// No need to check if rounding is necessary when pos2 has passed the last digit in the input string
|
||||||
|
if (pos2 < str.length()) {
|
||||||
|
short n = str[pos2] - '0';
|
||||||
|
if (n >= 5) {
|
||||||
|
// Start rounding up - starting from the digit left of pos2 all the way to the first digit
|
||||||
|
bool carry_over = true;
|
||||||
|
for (short p = pos2 - 1; p >= 0 && carry_over; p--) {
|
||||||
|
if (str[p] == '.') { // Skip the dot
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
n = str[p] - '0';
|
||||||
|
if (n == 9) {
|
||||||
|
str[p] = '0' ;
|
||||||
|
carry_over = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
n++;
|
||||||
|
carry_over = false;
|
||||||
|
str[p] = '0' + n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (carry_over) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << '1' << str.substr(0, pos2);
|
||||||
|
str = oss.str();
|
||||||
|
pos2++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last = pos2;
|
||||||
|
}
|
||||||
|
// Add the minus sign back if negative
|
||||||
|
if (isNegative) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << '-' << str.substr(0, last);
|
||||||
|
str = oss.str();
|
||||||
|
} else {
|
||||||
|
str = str.substr(0, last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = str.length();
|
||||||
|
str.copy(field_value, len);
|
||||||
|
field_value[len] = '\0';
|
||||||
|
*field_len = len;
|
||||||
|
}
|
||||||
|
|
||||||
// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output
|
// 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
|
// parameters will be present until all results are processed (since output parameters can depend on results
|
||||||
|
@ -2074,13 +2292,13 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
HashTable* params_ht = Z_ARRVAL( stmt->output_params );
|
HashTable* params_ht = Z_ARRVAL( stmt->output_params );
|
||||||
zend_ulong index = -1;
|
zend_ulong index = -1;
|
||||||
zend_string* key = NULL;
|
zend_string* key = NULL;
|
||||||
void* output_param_temp = NULL;
|
void* output_param_temp = NULL;
|
||||||
|
|
||||||
ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) {
|
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 );
|
sqlsrv_output_param* output_param = static_cast<sqlsrv_output_param*>( output_param_temp );
|
||||||
zval* value_z = Z_REFVAL_P( output_param->param_z );
|
zval* value_z = Z_REFVAL_P( output_param->param_z );
|
||||||
switch( Z_TYPE_P( value_z )) {
|
switch( Z_TYPE_P( value_z )) {
|
||||||
case IS_STRING:
|
case IS_STRING:
|
||||||
{
|
{
|
||||||
|
@ -2127,22 +2345,40 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
||||||
str_len = output_param->original_buffer_len - null_size;
|
str_len = output_param->original_buffer_len - null_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it's not in the 8 bit encodings, then it's in UTF-16
|
if (output_param->encoding == SQLSRV_ENCODING_BINARY) {
|
||||||
if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) {
|
|
||||||
bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len);
|
|
||||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) {
|
|
||||||
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
|
// 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
|
// 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.
|
// original allocation null terminates the buffer already.
|
||||||
str[str_len] = '\0';
|
if (str_len < output_param->original_buffer_len) {
|
||||||
|
str[str_len] = '\0';
|
||||||
|
}
|
||||||
core::sqlsrv_zval_stringl(value_z, str, str_len);
|
core::sqlsrv_zval_stringl(value_z, str, str_len);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
core::sqlsrv_zval_stringl(value_z, str, str_len);
|
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;
|
break;
|
||||||
|
@ -2183,7 +2419,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
||||||
DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." );
|
DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
value_z = NULL;
|
value_z = NULL;
|
||||||
} ZEND_HASH_FOREACH_END();
|
} ZEND_HASH_FOREACH_END();
|
||||||
|
|
||||||
// empty the hash table since it's been processed
|
// empty the hash table since it's been processed
|
||||||
|
@ -2196,7 +2432,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
|
||||||
{
|
{
|
||||||
SQLRETURN r;
|
SQLRETURN r;
|
||||||
SQLSMALLINT c_type;
|
SQLSMALLINT c_type;
|
||||||
SQLLEN sql_field_type = 0;
|
SQLSMALLINT sql_field_type = 0;
|
||||||
SQLSMALLINT extra = 0;
|
SQLSMALLINT extra = 0;
|
||||||
SQLLEN field_len_temp = 0;
|
SQLLEN field_len_temp = 0;
|
||||||
SQLLEN sql_display_size = 0;
|
SQLLEN sql_display_size = 0;
|
||||||
|
@ -2208,9 +2444,30 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
|
||||||
DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING,
|
DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING,
|
||||||
"Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" );
|
"Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" );
|
||||||
|
|
||||||
|
col_cache* cached = NULL;
|
||||||
|
if ( NULL != ( cached = static_cast< col_cache* >( zend_hash_index_find_ptr( Z_ARRVAL( stmt->col_cache ), static_cast< zend_ulong >( field_index ))))) {
|
||||||
|
sql_field_type = cached->sql_type;
|
||||||
|
sql_display_size = cached->display_size;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "get_field_as_string - meta data vector not in sync" );
|
||||||
|
sql_field_type = stmt->current_meta_data[field_index]->field_type;
|
||||||
|
|
||||||
|
// Calculate the field size.
|
||||||
|
calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC );
|
||||||
|
|
||||||
|
col_cache cache( sql_field_type, sql_display_size );
|
||||||
|
core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the correct encoding
|
||||||
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
|
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
|
||||||
sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding();
|
sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding();
|
||||||
}
|
}
|
||||||
|
// For numbers, no need to convert
|
||||||
|
if (is_a_numeric_type(sql_field_type)) {
|
||||||
|
sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the C type and account for null characters at the end of the data.
|
// Set the C type and account for null characters at the end of the data.
|
||||||
switch( sqlsrv_php_type.typeinfo.encoding ) {
|
switch( sqlsrv_php_type.typeinfo.encoding ) {
|
||||||
|
@ -2228,22 +2485,6 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
col_cache* cached = NULL;
|
|
||||||
if ( NULL != ( cached = static_cast< col_cache* >( zend_hash_index_find_ptr( Z_ARRVAL( stmt->col_cache ), static_cast< zend_ulong >( field_index ))))) {
|
|
||||||
sql_field_type = cached->sql_type;
|
|
||||||
sql_display_size = cached->display_size;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling
|
|
||||||
core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC );
|
|
||||||
|
|
||||||
// Calculate the field size.
|
|
||||||
calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC );
|
|
||||||
|
|
||||||
col_cache cache( sql_field_type, sql_display_size );
|
|
||||||
core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 few bytes to get the actual length from SQLGetData
|
||||||
if( sql_display_size == 0 || sql_display_size == INT_MAX ||
|
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 == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) {
|
||||||
|
@ -2402,6 +2643,12 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
|
||||||
throw core::CoreException();
|
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 if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE )
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
@ -2569,24 +2816,24 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval*
|
||||||
// allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since
|
// 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
|
// 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.
|
// not having a NULL terminator on a string.
|
||||||
zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 );
|
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
|
// 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.
|
// regardless of whether the ODBC type contains the NULL or not.
|
||||||
|
|
||||||
// null terminate the string to avoid a warning in debug PHP builds
|
// null terminate the string to avoid a warning in debug PHP builds
|
||||||
ZSTR_VAL(param_z_string)[without_null_len] = '\0';
|
ZSTR_VAL(param_z_string)[without_null_len] = '\0';
|
||||||
ZVAL_NEW_STR(param_z, param_z_string);
|
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_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 when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY
|
||||||
buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra;
|
buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra;
|
||||||
|
|
||||||
// Zend string length doesn't include the null terminator
|
// Zend string length doesn't include the null terminator
|
||||||
ZSTR_LEN(Z_STR_P(param_z)) -= elem_size;
|
ZSTR_LEN(Z_STR_P(param_z)) -= elem_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer = Z_STRVAL_P(param_z);
|
buffer = Z_STRVAL_P(param_z);
|
||||||
|
|
||||||
// The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which
|
// 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,
|
// may be less than the size of the buffer since the output may be more than the input. If it is greater,
|
||||||
|
@ -2770,7 +3017,7 @@ void sqlsrv_output_param_dtor( _Inout_ zval* data )
|
||||||
{
|
{
|
||||||
sqlsrv_output_param *output_param = static_cast<sqlsrv_output_param*>( Z_PTR_P( 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
|
zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold
|
||||||
sqlsrv_free( output_param );
|
sqlsrv_free( output_param );
|
||||||
}
|
}
|
||||||
|
|
||||||
// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed
|
// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed
|
||||||
|
@ -2778,7 +3025,7 @@ void sqlsrv_stream_dtor( _Inout_ zval* data )
|
||||||
{
|
{
|
||||||
sqlsrv_stream* stream_encoding = static_cast<sqlsrv_stream*>( Z_PTR_P( 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
|
zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold
|
||||||
sqlsrv_free( stream_encoding );
|
sqlsrv_free( stream_encoding );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Implementation of PHP streams for reading SQL Server data
|
// Contents: Implementation of PHP streams for reading SQL Server data
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
//
|
//
|
||||||
// Comments: Mostly error handling and some type handling
|
// Comments: Mostly error handling and some type handling
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -91,25 +91,6 @@ bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_up
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool convert_zval_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _Inout_ zval* value_z, _Inout_ SQLLEN& len)
|
|
||||||
{
|
|
||||||
char* string = Z_STRVAL_P(value_z);
|
|
||||||
|
|
||||||
if( validate_string(string, len)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* outString = NULL;
|
|
||||||
SQLLEN outLen = 0;
|
|
||||||
bool result = convert_string_from_utf16( encoding, reinterpret_cast<const SQLWCHAR*>(string), int(len / sizeof(SQLWCHAR)), &outString, outLen );
|
|
||||||
if( result ) {
|
|
||||||
core::sqlsrv_zval_stringl( value_z, outString, outLen );
|
|
||||||
sqlsrv_free( outString );
|
|
||||||
len = outLen;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool validate_string( _In_ char* string, _In_ SQLLEN& len )
|
bool validate_string( _In_ char* string, _In_ SQLLEN& len )
|
||||||
{
|
{
|
||||||
SQLSRV_ASSERT(string != NULL, "String must be specified");
|
SQLSRV_ASSERT(string != NULL, "String must be specified");
|
||||||
|
@ -146,10 +127,13 @@ bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_(
|
||||||
flags = WC_ERR_INVALID_CHARS;
|
flags = WC_ERR_INVALID_CHARS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate the number of characters needed
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
cchOutLen = SystemLocale::FromUtf16Strict( encoding, inString, cchInLen, NULL, 0 );
|
// Allocate enough space to hold the largest possible number of bytes for UTF-8 conversion
|
||||||
|
// instead of calling FromUtf16, for performance reasons
|
||||||
|
cchOutLen = 4*cchInLen;
|
||||||
#else
|
#else
|
||||||
|
// Calculate the number of output bytes required - no performance hit here because
|
||||||
|
// WideCharToMultiByte is highly optimised
|
||||||
cchOutLen = WideCharToMultiByte( encoding, flags,
|
cchOutLen = WideCharToMultiByte( encoding, flags,
|
||||||
inString, cchInLen,
|
inString, cchInLen,
|
||||||
NULL, 0, NULL, NULL );
|
NULL, 0, NULL, NULL );
|
||||||
|
@ -161,9 +145,10 @@ bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_(
|
||||||
|
|
||||||
// Create a buffer to fit the encoded string
|
// Create a buffer to fit the encoded string
|
||||||
char* newString = reinterpret_cast<char*>( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ ));
|
char* newString = reinterpret_cast<char*>( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ ));
|
||||||
|
memset(newString, '\0', cchOutLen+1);
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
int rc = SystemLocale::FromUtf16( encoding, inString, cchInLen, newString, static_cast<int>(cchOutLen));
|
int rc = SystemLocale::FromUtf16Strict( encoding, inString, cchInLen, newString, static_cast<int>(cchOutLen));
|
||||||
#else
|
#else
|
||||||
int rc = WideCharToMultiByte( encoding, flags, inString, cchInLen, newString, static_cast<int>(cchOutLen), NULL, NULL );
|
int rc = WideCharToMultiByte( encoding, flags, inString, cchInLen, newString, static_cast<int>(cchOutLen), NULL, NULL );
|
||||||
#endif // !_WIN32
|
#endif // !_WIN32
|
||||||
|
@ -172,9 +157,13 @@ bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_(
|
||||||
sqlsrv_free( newString );
|
sqlsrv_free( newString );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
char* newString2 = reinterpret_cast<char*>( sqlsrv_malloc( rc + 1 /* NULL char*/ ));
|
||||||
|
memset(newString2, '\0', rc+1);
|
||||||
|
memcpy_s(newString2, rc, newString, rc);
|
||||||
|
sqlsrv_free( newString );
|
||||||
|
|
||||||
*outString = newString;
|
*outString = newString2;
|
||||||
newString[cchOutLen] = '\0'; // null terminate the encoded string
|
cchOutLen = rc;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// Contents: Contains functions for handling Windows format strings
|
// Contents: Contains functions for handling Windows format strings
|
||||||
// and UTF-16 on non-Windows platforms
|
// and UTF-16 on non-Windows platforms
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -261,6 +261,8 @@ class EncodingConverter
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//if a shift sequence is encountered, we need to advance output buffer
|
||||||
|
iconv_ret = iconv( m_pCvtCache->GetIConv(), NULL, NULL, &dest.m_pBytes, &dest.m_nBytesLeft );
|
||||||
}
|
}
|
||||||
|
|
||||||
return cchDest - (dest.m_nBytesLeft / sizeof(DestType));
|
return cchDest - (dest.m_nBytesLeft / sizeof(DestType));
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// Contents: Contains a portable abstraction for interlocked, atomic
|
// Contents: Contains a portable abstraction for interlocked, atomic
|
||||||
// operations on int32_t and pointer types.
|
// operations on int32_t and pointer types.
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// Contents: Contains a portable abstraction for interlocked, atomic
|
// Contents: Contains a portable abstraction for interlocked, atomic
|
||||||
// operations on int32_t and pointer types.
|
// operations on int32_t and pointer types.
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// Contents: Contains a portable abstraction for interlocked, singly
|
// Contents: Contains a portable abstraction for interlocked, singly
|
||||||
// linked list.
|
// linked list.
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Contains portable classes for localization
|
// Contents: Contains portable classes for localization
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -169,8 +169,14 @@ public:
|
||||||
static size_t FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc,
|
static size_t FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc,
|
||||||
__out_ecount_opt(cchDest) char * dest, size_t cchDest,
|
__out_ecount_opt(cchDest) char * dest, size_t cchDest,
|
||||||
bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL);
|
bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL);
|
||||||
|
// CP1252 to UTF16 conversion which does not involve iconv
|
||||||
|
static size_t CP1252ToUtf16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode );
|
||||||
|
|
||||||
|
// UTF8/16 conversion which does not involve iconv
|
||||||
|
static size_t Utf8To16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode );
|
||||||
|
static size_t Utf8From16( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode );
|
||||||
|
static size_t Utf8To16Strict( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode );
|
||||||
|
static size_t Utf8From16Strict( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode );
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Public Member Functions
|
// Public Member Functions
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Must be included in one c/cpp file per binary
|
// Must be included in one c/cpp file per binary
|
||||||
// A build error will occur if this inclusion policy is not followed
|
// A build error will occur if this inclusion policy is not followed
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -336,9 +336,300 @@ const SystemLocale & SystemLocale::Singleton()
|
||||||
return s_Default;
|
return s_Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Convert CP1252 to UTF-16 without requiring iconv or taking a lock.
|
||||||
|
// This is trivial because, except for the 80-9F range, CP1252 bytes
|
||||||
|
// directly map to the corresponding UTF-16 codepoint.
|
||||||
|
size_t SystemLocale::CP1252ToUtf16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode )
|
||||||
|
{
|
||||||
|
const static WCHAR s_1252Map[] =
|
||||||
|
{
|
||||||
|
0x20AC, 0x003F, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x003F, 0x017D, 0x003F,
|
||||||
|
0x003F, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x003F, 0x017E, 0x0178
|
||||||
|
};
|
||||||
|
const unsigned char *usrc = reinterpret_cast<const unsigned char*>(src);
|
||||||
|
const unsigned char *srcEnd = usrc + cchSrc;
|
||||||
|
const WCHAR *destEnd = dest + cchDest;
|
||||||
|
|
||||||
|
while(usrc < srcEnd && dest < destEnd)
|
||||||
|
{
|
||||||
|
DWORD ucode = *usrc++;
|
||||||
|
*dest++ = (ucode <= 127 || ucode >= 160) ? ucode : s_1252Map[ucode - 128];
|
||||||
|
}
|
||||||
|
pErrorCode && (*pErrorCode = (dest == destEnd && usrc != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS);
|
||||||
|
return cchDest - (destEnd - dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert UTF-8 to UTF-16 without requiring iconv or taking a lock.
|
||||||
|
// 0abcdefg -> 0abcdefg 00000000
|
||||||
|
// 110abcde 10fghijk -> defghijk 00000abc
|
||||||
|
// 1110abcd 10efghij 10klmnop -> ijklmnop abcdefgh
|
||||||
|
// 11110abc 10defghi 10jklmno 10pqrstu -> cdfghijk 110110ab nopqrstu 11011lm
|
||||||
|
size_t SystemLocale::Utf8To16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode )
|
||||||
|
{
|
||||||
|
const unsigned char *usrc = reinterpret_cast<const unsigned char*>(src);
|
||||||
|
const unsigned char *srcEnd = usrc + cchSrc;
|
||||||
|
const WCHAR *destEnd = dest + cchDest;
|
||||||
|
DWORD dummyError;
|
||||||
|
if (!pErrorCode)
|
||||||
|
{
|
||||||
|
pErrorCode = &dummyError;
|
||||||
|
}
|
||||||
|
*pErrorCode = 0;
|
||||||
|
|
||||||
|
while(usrc < srcEnd && dest < destEnd)
|
||||||
|
{
|
||||||
|
DWORD ucode = *usrc++;
|
||||||
|
if(ucode <= 127) // Most common case for ASCII
|
||||||
|
{
|
||||||
|
*dest++ = ucode;
|
||||||
|
}
|
||||||
|
else if(ucode < 0xC0) // unexpected trailing byte 10xxxxxx
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
else if(ucode < 0xE0) // 110abcde 10fghijk
|
||||||
|
{
|
||||||
|
if (usrc >= srcEnd || *usrc < 0x80 || *usrc > 0xBF ||
|
||||||
|
(*dest = (ucode & 0x1F)<<6 | (*usrc++ & 0x3F)) < 0x80)
|
||||||
|
{
|
||||||
|
*dest = 0xFFFD;
|
||||||
|
}
|
||||||
|
dest++;
|
||||||
|
}
|
||||||
|
else if(ucode < 0xF0) // 1110abcd 10efghij 10klmnop
|
||||||
|
{
|
||||||
|
if (usrc >= srcEnd)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
DWORD c1 = *usrc;
|
||||||
|
if (c1 < 0x80 || c1 > 0xBF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
usrc++;
|
||||||
|
if (usrc >= srcEnd)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
DWORD c2 = *usrc;
|
||||||
|
if (c2 < 0x80 || c2 > 0xBF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
usrc++;
|
||||||
|
ucode = (ucode&15)<<12 | (c1&0x3F)<<6 | (c2&0x3F);
|
||||||
|
if (ucode < 0x800 || ucode >= 0xD800 && ucode <= 0xDFFF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
*dest++ = ucode;
|
||||||
|
}
|
||||||
|
else if(ucode < 0xF8) // 11110abc 10defghi 10jklmno 10pqrstu
|
||||||
|
{
|
||||||
|
if (usrc >= srcEnd)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
DWORD c1 = *usrc;
|
||||||
|
if (c1 < 0x80 || c1 > 0xBF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
usrc++;
|
||||||
|
if (usrc >= srcEnd)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
DWORD c2 = *usrc;
|
||||||
|
if (c2 < 0x80 || c2 > 0xBF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
usrc++;
|
||||||
|
if (usrc >= srcEnd)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
DWORD c3 = *usrc;
|
||||||
|
if (c3 < 0x80 || c3 > 0xBF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
usrc++;
|
||||||
|
ucode = (ucode&7)<<18 | (c1&0x3F)<<12 | (c2&0x3F)<<6 | (c3&0x3F);
|
||||||
|
|
||||||
|
if (ucode < 0x10000 // overlong encoding
|
||||||
|
|| ucode > 0x10FFFF // exceeds Unicode range
|
||||||
|
|| ucode >= 0xD800 && ucode <= 0xDFFF) // surrogate pairs
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
if (dest >= destEnd - 1)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return cchDest - (destEnd - dest);
|
||||||
|
}
|
||||||
|
ucode -= 0x10000;
|
||||||
|
// Lead surrogate
|
||||||
|
*dest++ = 0xD800 + (ucode >> 10);
|
||||||
|
// Trail surrogate
|
||||||
|
*dest++ = 0xDC00 + (ucode & 0x3FF);
|
||||||
|
}
|
||||||
|
else // invalid
|
||||||
|
{
|
||||||
|
Invalid:
|
||||||
|
*dest++ = 0xFFFD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!*pErrorCode)
|
||||||
|
{
|
||||||
|
*pErrorCode = (dest == destEnd && usrc != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
return cchDest - (destEnd - dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SystemLocale::Utf8To16Strict( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode )
|
||||||
|
{
|
||||||
|
const unsigned char *usrc = reinterpret_cast<const unsigned char*>(src);
|
||||||
|
const unsigned char *srcEnd = usrc + cchSrc;
|
||||||
|
const WCHAR *destEnd = dest + cchDest;
|
||||||
|
DWORD dummyError;
|
||||||
|
if (!pErrorCode)
|
||||||
|
{
|
||||||
|
pErrorCode = &dummyError;
|
||||||
|
}
|
||||||
|
*pErrorCode = 0;
|
||||||
|
|
||||||
|
while(usrc < srcEnd && dest < destEnd)
|
||||||
|
{
|
||||||
|
DWORD ucode = *usrc++;
|
||||||
|
if(ucode <= 127) // Most common case for ASCII
|
||||||
|
{
|
||||||
|
*dest++ = ucode;
|
||||||
|
}
|
||||||
|
else if(ucode < 0xC0) // unexpected trailing byte 10xxxxxx
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
else if(ucode < 0xE0) // 110abcde 10fghijk
|
||||||
|
{
|
||||||
|
if (usrc >= srcEnd || *usrc < 0x80 || *usrc > 0xBF ||
|
||||||
|
(*dest = (ucode & 0x1F)<<6 | (*usrc++ & 0x3F)) < 0x80)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
dest++;
|
||||||
|
}
|
||||||
|
else if(ucode < 0xF0) // 1110abcd 10efghij 10klmnop
|
||||||
|
{
|
||||||
|
if (usrc >= srcEnd)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
DWORD c1 = *usrc;
|
||||||
|
if (c1 < 0x80 || c1 > 0xBF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
usrc++;
|
||||||
|
if (usrc >= srcEnd)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
DWORD c2 = *usrc;
|
||||||
|
if (c2 < 0x80 || c2 > 0xBF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
usrc++;
|
||||||
|
ucode = (ucode&15)<<12 | (c1&0x3F)<<6 | (c2&0x3F);
|
||||||
|
if (ucode < 0x800 || ucode >= 0xD800 && ucode <= 0xDFFF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
*dest++ = ucode;
|
||||||
|
}
|
||||||
|
else if(ucode < 0xF8) // 11110abc 10defghi 10jklmno 10pqrstu
|
||||||
|
{
|
||||||
|
if (usrc >= srcEnd)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
DWORD c1 = *usrc;
|
||||||
|
if (c1 < 0x80 || c1 > 0xBF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
usrc++;
|
||||||
|
if (usrc >= srcEnd)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
DWORD c2 = *usrc;
|
||||||
|
if (c2 < 0x80 || c2 > 0xBF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
usrc++;
|
||||||
|
if (usrc >= srcEnd)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
DWORD c3 = *usrc;
|
||||||
|
if (c3 < 0x80 || c3 > 0xBF)
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
usrc++;
|
||||||
|
ucode = (ucode&7)<<18 | (c1&0x3F)<<12 | (c2&0x3F)<<6 | (c3&0x3F);
|
||||||
|
|
||||||
|
if (ucode < 0x10000 // overlong encoding
|
||||||
|
|| ucode > 0x10FFFF // exceeds Unicode range
|
||||||
|
|| ucode >= 0xD800 && ucode <= 0xDFFF) // surrogate pairs
|
||||||
|
{
|
||||||
|
goto Invalid;
|
||||||
|
}
|
||||||
|
if (dest >= destEnd - 1)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return cchDest - (destEnd - dest);
|
||||||
|
}
|
||||||
|
ucode -= 0x10000;
|
||||||
|
// Lead surrogate
|
||||||
|
*dest++ = 0xD800 + (ucode >> 10);
|
||||||
|
// Trail surrogate
|
||||||
|
*dest++ = 0xDC00 + (ucode & 0x3FF);
|
||||||
|
}
|
||||||
|
else // invalid
|
||||||
|
{
|
||||||
|
Invalid:
|
||||||
|
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION;
|
||||||
|
return 0 ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!*pErrorCode)
|
||||||
|
{
|
||||||
|
*pErrorCode = (dest == destEnd && usrc != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
return cchDest - (destEnd - dest);
|
||||||
|
}
|
||||||
|
|
||||||
size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode )
|
size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode )
|
||||||
{
|
{
|
||||||
srcCodePage = ExpandSpecialCP( srcCodePage );
|
srcCodePage = ExpandSpecialCP( srcCodePage );
|
||||||
|
if ( dest )
|
||||||
|
{
|
||||||
|
if ( srcCodePage == CP_UTF8 )
|
||||||
|
{
|
||||||
|
return SystemLocale::Utf8To16( src, cchSrc < 0 ? (1+strlen(src)) : cchSrc, dest, cchDest, pErrorCode );
|
||||||
|
}
|
||||||
|
else if ( srcCodePage == 1252 )
|
||||||
|
{
|
||||||
|
return SystemLocale::CP1252ToUtf16( src, cchSrc < 0 ? (1+strlen(src)) : cchSrc, dest, cchDest, pErrorCode );
|
||||||
|
}
|
||||||
|
}
|
||||||
EncodingConverter cvt( CP_UTF16, srcCodePage );
|
EncodingConverter cvt( CP_UTF16, srcCodePage );
|
||||||
if ( !cvt.Initialize() )
|
if ( !cvt.Initialize() )
|
||||||
{
|
{
|
||||||
|
@ -354,6 +645,17 @@ size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc
|
||||||
size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode )
|
size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode )
|
||||||
{
|
{
|
||||||
srcCodePage = ExpandSpecialCP( srcCodePage );
|
srcCodePage = ExpandSpecialCP( srcCodePage );
|
||||||
|
if ( dest )
|
||||||
|
{
|
||||||
|
if ( srcCodePage == CP_UTF8 )
|
||||||
|
{
|
||||||
|
return SystemLocale::Utf8To16Strict( src, cchSrc < 0 ? (1+strlen(src)) : cchSrc, dest, cchDest, pErrorCode );
|
||||||
|
}
|
||||||
|
else if ( srcCodePage == 1252 )
|
||||||
|
{
|
||||||
|
return SystemLocale::CP1252ToUtf16( src, cchSrc < 0 ? (1+strlen(src)) : cchSrc, dest, cchDest, pErrorCode );
|
||||||
|
}
|
||||||
|
}
|
||||||
EncodingConverter cvt( CP_UTF16, srcCodePage );
|
EncodingConverter cvt( CP_UTF16, srcCodePage );
|
||||||
if ( !cvt.Initialize() )
|
if ( !cvt.Initialize() )
|
||||||
{
|
{
|
||||||
|
@ -366,9 +668,282 @@ size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T
|
||||||
return cvt.Convert( dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode );
|
return cvt.Convert( dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t SystemLocale::Utf8From16( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode )
|
||||||
|
{
|
||||||
|
const WCHAR *srcEnd = src + cchSrc;
|
||||||
|
char *destEnd = dest + cchDest;
|
||||||
|
DWORD dummyError;
|
||||||
|
if (!pErrorCode)
|
||||||
|
{
|
||||||
|
pErrorCode = &dummyError;
|
||||||
|
}
|
||||||
|
*pErrorCode = 0;
|
||||||
|
|
||||||
|
// null dest is a special mode to calculate the output size required.
|
||||||
|
if (!dest)
|
||||||
|
{
|
||||||
|
size_t cbOut = 0;
|
||||||
|
while (src < srcEnd)
|
||||||
|
{
|
||||||
|
DWORD wch = *src++;
|
||||||
|
if (wch < 128) // most common case.
|
||||||
|
{
|
||||||
|
cbOut++;
|
||||||
|
}
|
||||||
|
else if (wch < 0x800) // 127 to 2047: 2 bytes
|
||||||
|
{
|
||||||
|
cbOut += 2;
|
||||||
|
}
|
||||||
|
else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes
|
||||||
|
{
|
||||||
|
cbOut += 3;
|
||||||
|
}
|
||||||
|
else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes
|
||||||
|
{
|
||||||
|
if (src >= srcEnd)
|
||||||
|
{
|
||||||
|
cbOut += 3; // lone surrogate at end
|
||||||
|
}
|
||||||
|
else if (*src < 0xDC00 || *src > 0xDFFF)
|
||||||
|
{
|
||||||
|
cbOut += 3; // low surrogate not followed by high
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cbOut += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // unexpected trail surrogate
|
||||||
|
{
|
||||||
|
cbOut += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cbOut;
|
||||||
|
}
|
||||||
|
while ( src < srcEnd && dest < destEnd )
|
||||||
|
{
|
||||||
|
DWORD wch = *src++;
|
||||||
|
if (wch < 128) // most common case.
|
||||||
|
{
|
||||||
|
*dest++ = wch;
|
||||||
|
}
|
||||||
|
else if (wch < 0x800) // 127 to 2047: 2 bytes
|
||||||
|
{
|
||||||
|
if (destEnd - dest < 2)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*dest++ = 0xC0 | (wch >> 6);
|
||||||
|
*dest++ = 0x80 | (wch & 0x3F);
|
||||||
|
}
|
||||||
|
else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes
|
||||||
|
{
|
||||||
|
if (destEnd - dest < 3)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*dest++ = 0xE0 | (wch >> 12);
|
||||||
|
*dest++ = 0x80 | (wch >> 6)&0x3F;
|
||||||
|
*dest++ = 0x80 | (wch &0x3F);
|
||||||
|
}
|
||||||
|
else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes
|
||||||
|
{
|
||||||
|
if (src >= srcEnd)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end
|
||||||
|
if (destEnd - dest < 3)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*dest++ = 0xEF;
|
||||||
|
*dest++ = 0xBF;
|
||||||
|
*dest++ = 0xBD;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*src < 0xDC00 || *src > 0xDFFF)
|
||||||
|
{
|
||||||
|
// low surrogate not followed by high
|
||||||
|
if (destEnd - dest < 3)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*dest++ = 0xEF;
|
||||||
|
*dest++ = 0xBF;
|
||||||
|
*dest++ = 0xBD;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
wch = 0x10000 + ((wch - 0xD800)<<10) + *src++ - 0xDC00;
|
||||||
|
if (destEnd - dest < 4)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*dest++ = 0xF0 | (wch >> 18);
|
||||||
|
*dest++ = 0x80 | (wch >>12)&0x3F;
|
||||||
|
*dest++ = 0x80 | (wch >> 6)&0x3F;
|
||||||
|
*dest++ = 0x80 | wch&0x3F;
|
||||||
|
}
|
||||||
|
else // unexpected trail surrogate
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end
|
||||||
|
if (destEnd - dest < 3)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*dest++ = 0xEF;
|
||||||
|
*dest++ = 0xBF;
|
||||||
|
*dest++ = 0xBD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!*pErrorCode)
|
||||||
|
{
|
||||||
|
*pErrorCode = (dest == destEnd && src != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
return *pErrorCode == ERROR_INSUFFICIENT_BUFFER ? 0 : cchDest - (destEnd - dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SystemLocale::Utf8From16Strict( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode )
|
||||||
|
{
|
||||||
|
const WCHAR *srcEnd = src + cchSrc;
|
||||||
|
char *destEnd = dest + cchDest;
|
||||||
|
DWORD dummyError;
|
||||||
|
if (!pErrorCode)
|
||||||
|
{
|
||||||
|
pErrorCode = &dummyError;
|
||||||
|
}
|
||||||
|
*pErrorCode = 0;
|
||||||
|
|
||||||
|
// null dest is a special mode to calculate the output size required.
|
||||||
|
if (!dest)
|
||||||
|
{
|
||||||
|
size_t cbOut = 0;
|
||||||
|
while (src < srcEnd)
|
||||||
|
{
|
||||||
|
DWORD wch = *src++;
|
||||||
|
if (wch < 128) // most common case.
|
||||||
|
{
|
||||||
|
cbOut++;
|
||||||
|
}
|
||||||
|
else if (wch < 0x800) // 127 to 2047: 2 bytes
|
||||||
|
{
|
||||||
|
cbOut += 2;
|
||||||
|
}
|
||||||
|
else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes
|
||||||
|
{
|
||||||
|
cbOut += 3;
|
||||||
|
}
|
||||||
|
else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes
|
||||||
|
{
|
||||||
|
if (src >= srcEnd)
|
||||||
|
{
|
||||||
|
cbOut += 3; // lone surrogate at end
|
||||||
|
}
|
||||||
|
else if (*src < 0xDC00 || *src > 0xDFFF)
|
||||||
|
{
|
||||||
|
cbOut += 3; // low surrogate not followed by high
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cbOut += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // unexpected trail surrogate
|
||||||
|
{
|
||||||
|
cbOut += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cbOut;
|
||||||
|
}
|
||||||
|
while ( src < srcEnd && dest < destEnd )
|
||||||
|
{
|
||||||
|
DWORD wch = *src++;
|
||||||
|
if (wch < 128) // most common case.
|
||||||
|
{
|
||||||
|
*dest++ = wch;
|
||||||
|
}
|
||||||
|
else if (wch < 0x800) // 127 to 2047: 2 bytes
|
||||||
|
{
|
||||||
|
if (destEnd - dest < 2)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*dest++ = 0xC0 | (wch >> 6);
|
||||||
|
*dest++ = 0x80 | (wch & 0x3F);
|
||||||
|
}
|
||||||
|
else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes
|
||||||
|
{
|
||||||
|
if (destEnd - dest < 3)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*dest++ = 0xE0 | (wch >> 12);
|
||||||
|
*dest++ = 0x80 | (wch >> 6)&0x3F;
|
||||||
|
*dest++ = 0x80 | (wch &0x3F);
|
||||||
|
}
|
||||||
|
else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes
|
||||||
|
{
|
||||||
|
if (src >= srcEnd)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end
|
||||||
|
if (destEnd - dest < 3)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (*src < 0xDC00 || *src > 0xDFFF)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // low surrogate not followed by high
|
||||||
|
if (destEnd - dest < 3)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
wch = 0x10000 + ((wch - 0xD800)<<10) + *src++ - 0xDC00;
|
||||||
|
if (destEnd - dest < 4)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*dest++ = 0xF0 | (wch >> 18);
|
||||||
|
*dest++ = 0x80 | (wch >>12)&0x3F;
|
||||||
|
*dest++ = 0x80 | (wch >> 6)&0x3F;
|
||||||
|
*dest++ = 0x80 | wch&0x3F;
|
||||||
|
}
|
||||||
|
else // unexpected trail surrogate
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end
|
||||||
|
if (destEnd - dest < 3)
|
||||||
|
{
|
||||||
|
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!*pErrorCode)
|
||||||
|
{
|
||||||
|
*pErrorCode = (dest == destEnd && src != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
return *pErrorCode == ERROR_INSUFFICIENT_BUFFER ? 0 : cchDest - (destEnd - dest);
|
||||||
|
}
|
||||||
|
|
||||||
size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode )
|
size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode )
|
||||||
{
|
{
|
||||||
destCodePage = ExpandSpecialCP( destCodePage );
|
destCodePage = ExpandSpecialCP( destCodePage );
|
||||||
|
if ( destCodePage == CP_UTF8 )
|
||||||
|
{
|
||||||
|
pHasDataLoss && (*pHasDataLoss = 0);
|
||||||
|
return SystemLocale::Utf8From16( src, cchSrc < 0 ? 1+mplat_wcslen(src) : cchSrc, dest, cchDest, pErrorCode );
|
||||||
|
}
|
||||||
EncodingConverter cvt( destCodePage, CP_UTF16 );
|
EncodingConverter cvt( destCodePage, CP_UTF16 );
|
||||||
if ( !cvt.Initialize() )
|
if ( !cvt.Initialize() )
|
||||||
{
|
{
|
||||||
|
@ -384,6 +959,11 @@ size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cc
|
||||||
size_t SystemLocale::FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode)
|
size_t SystemLocale::FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode)
|
||||||
{
|
{
|
||||||
destCodePage = ExpandSpecialCP(destCodePage);
|
destCodePage = ExpandSpecialCP(destCodePage);
|
||||||
|
if ( destCodePage == CP_UTF8 )
|
||||||
|
{
|
||||||
|
pHasDataLoss && (*pHasDataLoss = 0);
|
||||||
|
return SystemLocale::Utf8From16Strict( src, cchSrc < 0 ? 1+mplat_wcslen(src) : cchSrc, dest, cchDest, pErrorCode );
|
||||||
|
}
|
||||||
EncodingConverter cvt(destCodePage, CP_UTF16);
|
EncodingConverter cvt(destCodePage, CP_UTF16);
|
||||||
if (!cvt.Initialize())
|
if (!cvt.Initialize())
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
// pecuniary loss) arising out of the use of or inability to use
|
// pecuniary loss) arising out of the use of or inability to use
|
||||||
// this SDK, even if Microsoft has been advised of the possibility
|
// this SDK, even if Microsoft has been advised of the possibility
|
||||||
// of such damages.
|
// of such damages.
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Contains the minimal definitions to build on non-Windows platforms
|
// Contents: Contains the minimal definitions to build on non-Windows platforms
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//---------------------------------------------------------------------------------------------------------------------------------
|
//---------------------------------------------------------------------------------------------------------------------------------
|
||||||
// File: typedefs_for_linux.h
|
// File: typedefs_for_linux.h
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// File: version.h
|
// File: version.h
|
||||||
// Contents: Version number constants
|
// Contents: Version number constants
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
// Increase Minor with backward compatible new functionalities and API changes.
|
// Increase Minor with backward compatible new functionalities and API changes.
|
||||||
// Increase Patch for backward compatible fixes.
|
// Increase Patch for backward compatible fixes.
|
||||||
#define SQLVERSION_MAJOR 5
|
#define SQLVERSION_MAJOR 5
|
||||||
#define SQLVERSION_MINOR 4
|
#define SQLVERSION_MINOR 5
|
||||||
#define SQLVERSION_PATCH 0
|
#define SQLVERSION_PATCH 0
|
||||||
#define SQLVERSION_BUILD 0
|
#define SQLVERSION_BUILD 0
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: include for definition of Windows types for non-Windows platforms
|
// Contents: include for definition of Windows types for non-Windows platforms
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// Contents: This module defines helper functions to prevent
|
// Contents: This module defines helper functions to prevent
|
||||||
// integer overflow bugs.
|
// integer overflow bugs.
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Contains the minimal definitions to build on non-Windows platforms
|
// Contents: Contains the minimal definitions to build on non-Windows platforms
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Contains the minimal definitions to build on non-Windows platforms
|
// Contents: Contains the minimal definitions to build on non-Windows platforms
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -4,7 +4,7 @@ dnl
|
||||||
dnl Contents: the code that will go into the configure script, indicating options,
|
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 external libraries and includes, and what source files are to be compiled.
|
||||||
dnl
|
dnl
|
||||||
dnl Microsoft Drivers 5.4 for PHP for SQL Server
|
dnl Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
dnl Copyright(c) Microsoft Corporation
|
dnl Copyright(c) Microsoft Corporation
|
||||||
dnl All rights reserved.
|
dnl All rights reserved.
|
||||||
dnl MIT License
|
dnl MIT License
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: JScript build configuration used by buildconf.bat
|
// Contents: JScript build configuration used by buildconf.bat
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -36,11 +36,18 @@ if( PHP_SQLSRV != "no" ) {
|
||||||
ADD_FLAG( "CFLAGS_SQLSRV", "/GS" );
|
ADD_FLAG( "CFLAGS_SQLSRV", "/GS" );
|
||||||
ADD_FLAG( "CFLAGS_SQLSRV", "/Zi" );
|
ADD_FLAG( "CFLAGS_SQLSRV", "/Zi" );
|
||||||
if (VCVERS >= 1913) {
|
if (VCVERS >= 1913) {
|
||||||
|
ADD_FLAG("LDFLAGS_SQLSRV", "/d2:-guardspecload");
|
||||||
ADD_FLAG("CFLAGS_SQLSRV", "/Qspectre");
|
ADD_FLAG("CFLAGS_SQLSRV", "/Qspectre");
|
||||||
|
} else if (VCVERS == 1900) {
|
||||||
|
var subver1900 = probe_binary(PHP_CL).substr(6);
|
||||||
|
if (subver1900 >= 24241) {
|
||||||
|
ADD_FLAG("LDFLAGS_SQLSRV", "/d2:-guardspecload");
|
||||||
|
ADD_FLAG('CFLAGS_SQLSRV', "/Qspectre");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_SQLSRV", "/guard:cf /O2" );
|
if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_SQLSRV", "/guard:cf /O2" );
|
||||||
EXTENSION("sqlsrv", sqlsrv_src_class , PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
|
EXTENSION("sqlsrv", sqlsrv_src_class , PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
|
||||||
} else {
|
} else {
|
||||||
WARNING("sqlsrv not enabled; libraries and headers not found");
|
WARNING("sqlsrv not enabled; libraries and headers not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Routines that use connection handles
|
// Contents: Routines that use connection handles
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -46,6 +46,45 @@ struct date_as_string_func {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct format_decimals_func
|
||||||
|
{
|
||||||
|
static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC)
|
||||||
|
{
|
||||||
|
TSRMLS_C; // show as used to avoid a warning
|
||||||
|
|
||||||
|
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
|
||||||
|
|
||||||
|
if (zend_is_true(value)) {
|
||||||
|
ss_conn->format_decimals = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ss_conn->format_decimals = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct decimal_places_func
|
||||||
|
{
|
||||||
|
|
||||||
|
static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC)
|
||||||
|
{
|
||||||
|
TSRMLS_C; // show as used to avoid a warning
|
||||||
|
|
||||||
|
// first check if the input is an integer
|
||||||
|
if (Z_TYPE_P(value) != IS_LONG) {
|
||||||
|
THROW_SS_ERROR(conn, SQLSRV_ERROR_INVALID_DECIMAL_PLACES);
|
||||||
|
}
|
||||||
|
|
||||||
|
zend_long decimal_places = Z_LVAL_P(value);
|
||||||
|
if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) {
|
||||||
|
decimal_places = NO_CHANGE_DECIMAL_PLACES;
|
||||||
|
}
|
||||||
|
|
||||||
|
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
|
||||||
|
ss_conn->decimal_places = static_cast<short>(decimal_places);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct conn_char_set_func {
|
struct conn_char_set_func {
|
||||||
|
|
||||||
static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
|
static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
|
||||||
|
@ -174,6 +213,8 @@ namespace SSStmtOptionNames {
|
||||||
const char SCROLLABLE[] = "Scrollable";
|
const char SCROLLABLE[] = "Scrollable";
|
||||||
const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT;
|
const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT;
|
||||||
const char DATE_AS_STRING[] = "ReturnDatesAsStrings";
|
const char DATE_AS_STRING[] = "ReturnDatesAsStrings";
|
||||||
|
const char FORMAT_DECIMALS[] = "FormatDecimals";
|
||||||
|
const char DECIMAL_PLACES[] = "DecimalPlaces";
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace SSConnOptionNames {
|
namespace SSConnOptionNames {
|
||||||
|
@ -191,6 +232,8 @@ const char ConnectionPooling[] = "ConnectionPooling";
|
||||||
const char ConnectRetryCount[] = "ConnectRetryCount";
|
const char ConnectRetryCount[] = "ConnectRetryCount";
|
||||||
const char ConnectRetryInterval[] = "ConnectRetryInterval";
|
const char ConnectRetryInterval[] = "ConnectRetryInterval";
|
||||||
const char Database[] = "Database";
|
const char Database[] = "Database";
|
||||||
|
const char DecimalPlaces[] = "DecimalPlaces";
|
||||||
|
const char FormatDecimals[] = "FormatDecimals";
|
||||||
const char DateAsString[] = "ReturnDatesAsStrings";
|
const char DateAsString[] = "ReturnDatesAsStrings";
|
||||||
const char Driver[] = "Driver";
|
const char Driver[] = "Driver";
|
||||||
const char Encrypt[] = "Encrypt";
|
const char Encrypt[] = "Encrypt";
|
||||||
|
@ -216,6 +259,8 @@ const char WSID[] = "WSID";
|
||||||
enum SS_CONN_OPTIONS {
|
enum SS_CONN_OPTIONS {
|
||||||
|
|
||||||
SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC,
|
SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC,
|
||||||
|
SS_CONN_OPTION_FORMAT_DECIMALS,
|
||||||
|
SS_CONN_OPTION_DECIMAL_PLACES,
|
||||||
};
|
};
|
||||||
|
|
||||||
//List of all statement options supported by this driver
|
//List of all statement options supported by this driver
|
||||||
|
@ -250,6 +295,18 @@ const stmt_option SS_STMT_OPTS[] = {
|
||||||
SQLSRV_STMT_OPTION_DATE_AS_STRING,
|
SQLSRV_STMT_OPTION_DATE_AS_STRING,
|
||||||
std::unique_ptr<stmt_option_date_as_string>( new stmt_option_date_as_string )
|
std::unique_ptr<stmt_option_date_as_string>( new stmt_option_date_as_string )
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
SSStmtOptionNames::FORMAT_DECIMALS,
|
||||||
|
sizeof( SSStmtOptionNames::FORMAT_DECIMALS ),
|
||||||
|
SQLSRV_STMT_OPTION_FORMAT_DECIMALS,
|
||||||
|
std::unique_ptr<stmt_option_format_decimals>( new stmt_option_format_decimals )
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SSStmtOptionNames::DECIMAL_PLACES,
|
||||||
|
sizeof( SSStmtOptionNames::DECIMAL_PLACES),
|
||||||
|
SQLSRV_STMT_OPTION_DECIMAL_PLACES,
|
||||||
|
std::unique_ptr<stmt_option_decimal_places>( new stmt_option_decimal_places )
|
||||||
|
},
|
||||||
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
|
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -508,6 +565,24 @@ const connection_option SS_CONN_OPTS[] = {
|
||||||
CONN_ATTR_BOOL,
|
CONN_ATTR_BOOL,
|
||||||
date_as_string_func::func
|
date_as_string_func::func
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
SSConnOptionNames::FormatDecimals,
|
||||||
|
sizeof( SSConnOptionNames::FormatDecimals),
|
||||||
|
SS_CONN_OPTION_FORMAT_DECIMALS,
|
||||||
|
SSConnOptionNames::FormatDecimals,
|
||||||
|
sizeof( SSConnOptionNames::FormatDecimals),
|
||||||
|
CONN_ATTR_BOOL,
|
||||||
|
format_decimals_func::func
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SSConnOptionNames::DecimalPlaces,
|
||||||
|
sizeof( SSConnOptionNames::DecimalPlaces),
|
||||||
|
SS_CONN_OPTION_DECIMAL_PLACES,
|
||||||
|
SSConnOptionNames::DecimalPlaces,
|
||||||
|
sizeof( SSConnOptionNames::DecimalPlaces),
|
||||||
|
CONN_ATTR_INT,
|
||||||
|
decimal_places_func::func
|
||||||
|
},
|
||||||
{ NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table
|
{ NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// File: init.cpp
|
// File: init.cpp
|
||||||
// Contents: initialization routines for the extension
|
// Contents: initialization routines for the extension
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
//
|
//
|
||||||
// Comments: Also contains "internal" declarations shared across source files.
|
// Comments: Also contains "internal" declarations shared across source files.
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -131,6 +131,8 @@ struct ss_sqlsrv_conn : sqlsrv_conn
|
||||||
{
|
{
|
||||||
HashTable* stmts;
|
HashTable* stmts;
|
||||||
bool date_as_string;
|
bool date_as_string;
|
||||||
|
bool format_decimals; // flag set to turn on formatting for values of decimal / numeric types
|
||||||
|
short decimal_places; // number of decimal digits to show in a result set unless format_numbers is false
|
||||||
bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls
|
bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls
|
||||||
|
|
||||||
// static variables used in process_params
|
// static variables used in process_params
|
||||||
|
@ -142,6 +144,8 @@ struct ss_sqlsrv_conn : sqlsrv_conn
|
||||||
sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ),
|
sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ),
|
||||||
stmts( NULL ),
|
stmts( NULL ),
|
||||||
date_as_string( false ),
|
date_as_string( false ),
|
||||||
|
format_decimals( false ),
|
||||||
|
decimal_places( NO_CHANGE_DECIMAL_PLACES ),
|
||||||
in_transaction( false )
|
in_transaction( false )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Routines that use statement handles
|
// Contents: Routines that use statement handles
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -91,7 +91,7 @@ const char SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF[] = "Variable parameter %d not pa
|
||||||
/* internal functions */
|
/* internal functions */
|
||||||
|
|
||||||
void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval );
|
void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval );
|
||||||
|
SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt* stmt);
|
||||||
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names
|
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names
|
||||||
TSRMLS_DC );
|
TSRMLS_DC );
|
||||||
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size,
|
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size,
|
||||||
|
@ -110,6 +110,15 @@ bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_p
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// query options for cursor types
|
||||||
namespace SSCursorTypes {
|
namespace SSCursorTypes {
|
||||||
|
|
||||||
|
@ -130,13 +139,18 @@ ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_
|
||||||
{
|
{
|
||||||
core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC );
|
core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC );
|
||||||
|
|
||||||
// initialize date_as_string based on the corresponding connection option
|
// inherit other values based on the corresponding connection options
|
||||||
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
|
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
|
||||||
date_as_string = ss_conn->date_as_string;
|
date_as_string = ss_conn->date_as_string;
|
||||||
|
format_decimals = ss_conn->format_decimals;
|
||||||
|
decimal_places = ss_conn->decimal_places;
|
||||||
}
|
}
|
||||||
|
|
||||||
ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void )
|
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 ) {
|
if( fetch_field_names != NULL ) {
|
||||||
|
|
||||||
for( int i=0; i < fetch_fields_count; ++i ) {
|
for( int i=0; i < fetch_fields_count; ++i ) {
|
||||||
|
@ -459,27 +473,24 @@ PHP_FUNCTION( sqlsrv_field_metadata )
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// get the number of fields in the resultset
|
// get the number of fields in the resultset and its metadata if not exists
|
||||||
num_cols = core::SQLNumResultCols( stmt TSRMLS_CC );
|
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
|
||||||
|
|
||||||
zval result_meta_data;
|
zval result_meta_data;
|
||||||
ZVAL_UNDEF( &result_meta_data );
|
ZVAL_UNDEF( &result_meta_data );
|
||||||
core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC );
|
core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC );
|
||||||
|
|
||||||
for( SQLSMALLINT f = 0; f < num_cols; ++f ) {
|
for( SQLSMALLINT f = 0; f < num_cols; ++f ) {
|
||||||
|
field_meta_data* core_meta_data = stmt->current_meta_data[f];
|
||||||
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;
|
|
||||||
core_meta_data = core_sqlsrv_field_metadata( stmt, f TSRMLS_CC );
|
|
||||||
|
|
||||||
// initialize the array
|
// initialize the array
|
||||||
zval field_array;
|
zval field_array;
|
||||||
ZVAL_UNDEF( &field_array );
|
ZVAL_UNDEF( &field_array );
|
||||||
core::sqlsrv_array_init( *stmt, &field_array TSRMLS_CC );
|
core::sqlsrv_array_init( *stmt, &field_array TSRMLS_CC );
|
||||||
|
|
||||||
core::sqlsrv_add_assoc_string( *stmt, &field_array, FieldMetaData::NAME,
|
// add the field name to the associative array but keep a copy
|
||||||
reinterpret_cast<char*>( core_meta_data->field_name.get() ), 0 TSRMLS_CC );
|
core::sqlsrv_add_assoc_string(*stmt, &field_array, FieldMetaData::NAME,
|
||||||
|
reinterpret_cast<char*>(core_meta_data->field_name.get()), 1 TSRMLS_CC);
|
||||||
core_meta_data->field_name.transferred();
|
|
||||||
|
|
||||||
core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC );
|
core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC );
|
||||||
|
|
||||||
|
@ -519,9 +530,6 @@ PHP_FUNCTION( sqlsrv_field_metadata )
|
||||||
|
|
||||||
// add this field's meta data to the result set meta data
|
// add this field's meta data to the result set meta data
|
||||||
core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC );
|
core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC );
|
||||||
|
|
||||||
// always good to call destructor for allocations done through placement new operator.
|
|
||||||
core_meta_data->~field_meta_data();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return our built collection and transfer ownership
|
// return our built collection and transfer ownership
|
||||||
|
@ -567,6 +575,10 @@ PHP_FUNCTION( sqlsrv_next_result )
|
||||||
|
|
||||||
core_sqlsrv_next_result( stmt TSRMLS_CC, true );
|
core_sqlsrv_next_result( stmt TSRMLS_CC, 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();
|
||||||
|
|
||||||
if( stmt->past_next_result_end ) {
|
if( stmt->past_next_result_end ) {
|
||||||
|
|
||||||
RETURN_NULL();
|
RETURN_NULL();
|
||||||
|
@ -1084,7 +1096,7 @@ PHP_FUNCTION( sqlsrv_get_field )
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// validate that the field index is within range
|
// validate that the field index is within range
|
||||||
int num_cols = core::SQLNumResultCols( stmt TSRMLS_CC );
|
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
|
||||||
|
|
||||||
if( field_index < 0 || field_index >= num_cols ) {
|
if( field_index < 0 || field_index >= num_cols ) {
|
||||||
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
|
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
|
||||||
|
@ -1622,10 +1634,13 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_
|
||||||
|
|
||||||
switch( sql_type ) {
|
switch( sql_type ) {
|
||||||
case SQL_BIGINT:
|
case SQL_BIGINT:
|
||||||
case SQL_CHAR:
|
|
||||||
case SQL_DECIMAL:
|
case SQL_DECIMAL:
|
||||||
case SQL_GUID:
|
|
||||||
case SQL_NUMERIC:
|
case SQL_NUMERIC:
|
||||||
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
||||||
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
|
||||||
|
break;
|
||||||
|
case SQL_CHAR:
|
||||||
|
case SQL_GUID:
|
||||||
case SQL_WCHAR:
|
case SQL_WCHAR:
|
||||||
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
||||||
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
||||||
|
@ -1647,6 +1662,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_
|
||||||
case SQL_SMALLINT:
|
case SQL_SMALLINT:
|
||||||
case SQL_TINYINT:
|
case SQL_TINYINT:
|
||||||
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
|
||||||
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
|
||||||
break;
|
break;
|
||||||
case SQL_BINARY:
|
case SQL_BINARY:
|
||||||
case SQL_LONGVARBINARY:
|
case SQL_LONGVARBINARY:
|
||||||
|
@ -1676,6 +1692,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_
|
||||||
case SQL_FLOAT:
|
case SQL_FLOAT:
|
||||||
case SQL_REAL:
|
case SQL_REAL:
|
||||||
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
|
||||||
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
|
||||||
break;
|
break;
|
||||||
case SQL_TYPE_DATE:
|
case SQL_TYPE_DATE:
|
||||||
case SQL_SS_TIMESTAMPOFFSET:
|
case SQL_SS_TIMESTAMPOFFSET:
|
||||||
|
@ -1759,6 +1776,37 @@ void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt)
|
||||||
|
{
|
||||||
|
// get the numer of columns in the result set
|
||||||
|
SQLSMALLINT num_cols = -1;
|
||||||
|
|
||||||
|
num_cols = stmt->current_meta_data.size();
|
||||||
|
bool getMetaData = false;
|
||||||
|
|
||||||
|
if (num_cols == 0) {
|
||||||
|
getMetaData = true;
|
||||||
|
num_cols = core::SQLNumResultCols(stmt TSRMLS_CC);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (getMetaData) {
|
||||||
|
for (int i = 0; i < num_cols; i++) {
|
||||||
|
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;
|
||||||
|
core_meta_data = core_sqlsrv_field_metadata(stmt, i TSRMLS_CC);
|
||||||
|
stmt->current_meta_data.push_back(core_meta_data.get());
|
||||||
|
core_meta_data.transferred();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch( core::CoreException& ) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
SQLSRV_ASSERT(num_cols > 0 && stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" );
|
||||||
|
|
||||||
|
return num_cols;
|
||||||
|
}
|
||||||
|
|
||||||
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names
|
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names
|
||||||
TSRMLS_DC )
|
TSRMLS_DC )
|
||||||
{
|
{
|
||||||
|
@ -1772,38 +1820,23 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ
|
||||||
throw ss::SSException();
|
throw ss::SSException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the numer of columns in the result set
|
// get the numer of columns in the result set and its metadata if not exists
|
||||||
SQLSMALLINT num_cols = core::SQLNumResultCols(stmt TSRMLS_CC);
|
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
|
||||||
|
|
||||||
// if this is the first fetch in a new result set, then get the field names and
|
// if this is the first fetch in a new result set, then get the field names and
|
||||||
// store them off for successive fetches.
|
// store them off for successive fetches.
|
||||||
if(( fetch_type & SQLSRV_FETCH_ASSOC ) && stmt->fetch_field_names == NULL ) {
|
if ((fetch_type & SQLSRV_FETCH_ASSOC) && stmt->fetch_field_names == NULL) {
|
||||||
|
|
||||||
SQLLEN field_name_len = 0;
|
SQLLEN field_name_len = 0;
|
||||||
SQLSMALLINT field_name_len_w = 0;
|
|
||||||
SQLWCHAR field_name_w[( SS_MAXCOLNAMELEN + 1 ) * 2] = {L'\0'};
|
|
||||||
sqlsrv_malloc_auto_ptr<char> field_name;
|
|
||||||
sqlsrv_malloc_auto_ptr<sqlsrv_fetch_field_name> field_names;
|
sqlsrv_malloc_auto_ptr<sqlsrv_fetch_field_name> field_names;
|
||||||
field_names = static_cast<sqlsrv_fetch_field_name*>( sqlsrv_malloc( num_cols * sizeof( sqlsrv_fetch_field_name )));
|
field_names = static_cast<sqlsrv_fetch_field_name*>(sqlsrv_malloc(num_cols * sizeof(sqlsrv_fetch_field_name)));
|
||||||
SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding());
|
for (int i = 0; i < num_cols; ++i) {
|
||||||
for( int i = 0; i < num_cols; ++i ) {
|
// The meta data field name is already null-terminated, and the field name len is correct.
|
||||||
|
field_name_len = stmt->current_meta_data[i]->field_name_len;
|
||||||
core::SQLColAttributeW ( stmt, i + 1, SQL_DESC_NAME, field_name_w, ( SS_MAXCOLNAMELEN + 1 ) * 2, &field_name_len_w, NULL TSRMLS_CC );
|
field_names[i].name = static_cast<char*>(sqlsrv_malloc(field_name_len, sizeof(char), 1));
|
||||||
|
memcpy_s((void*)field_names[i].name, (field_name_len * sizeof(char)), (void*)stmt->current_meta_data[i]->field_name, field_name_len);
|
||||||
//Conversion function expects size in characters
|
field_names[i].name[field_name_len] = '\0'; // null terminate the field name after the memcpy
|
||||||
field_name_len_w = field_name_len_w / sizeof ( SQLWCHAR );
|
field_names[i].len = field_name_len; // field_name_len should not need to include the null char
|
||||||
bool converted = convert_string_from_utf16( encoding, field_name_w,
|
|
||||||
field_name_len_w, ( char** ) &field_name, field_name_len );
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
field_names[i].name = static_cast<char*>( sqlsrv_malloc( field_name_len, sizeof( char ), 1 ));
|
|
||||||
memcpy_s(( void* )field_names[i].name, ( field_name_len * sizeof( char )) , ( void* ) field_name, field_name_len );
|
|
||||||
field_names[i].name[field_name_len] = '\0'; // null terminate the field name since SQLColAttribute doesn't.
|
|
||||||
field_names[i].len = field_name_len + 1;
|
|
||||||
field_name.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stmt->fetch_field_names = field_names;
|
stmt->fetch_field_names = field_names;
|
||||||
|
@ -1840,12 +1873,12 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ
|
||||||
|
|
||||||
if( fetch_type & SQLSRV_FETCH_ASSOC ) {
|
if( fetch_type & SQLSRV_FETCH_ASSOC ) {
|
||||||
|
|
||||||
CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 1 && !allow_empty_field_names ), stmt,
|
CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 0 && !allow_empty_field_names ), stmt,
|
||||||
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) {
|
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) {
|
||||||
throw ss::SSException();
|
throw ss::SSException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if( stmt->fetch_field_names[i].len > 1 || allow_empty_field_names ) {
|
if( stmt->fetch_field_names[i].len > 0 || allow_empty_field_names ) {
|
||||||
|
|
||||||
zr = add_assoc_zval( &fields, stmt->fetch_field_names[i].name, &field );
|
zr = add_assoc_zval( &fields, stmt->fetch_field_names[i].name, &field );
|
||||||
CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) {
|
CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Contents: Version resource
|
// Contents: Version resource
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
//
|
//
|
||||||
// Comments: Mostly error handling and some type handling
|
// Comments: Mostly error handling and some type handling
|
||||||
//
|
//
|
||||||
// Microsoft Drivers 5.4 for PHP for SQL Server
|
// Microsoft Drivers 5.5 for PHP for SQL Server
|
||||||
// Copyright(c) Microsoft Corporation
|
// Copyright(c) Microsoft Corporation
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
@ -428,6 +428,10 @@ ss_error SS_ERRORS[] = {
|
||||||
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
|
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
|
||||||
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -116, false}
|
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -116, false}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
SQLSRV_ERROR_INVALID_DECIMAL_PLACES,
|
||||||
|
{ IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -117, false}
|
||||||
|
},
|
||||||
|
|
||||||
// terminate the list of errors/warnings
|
// terminate the list of errors/warnings
|
||||||
{ UINT_MAX, {} }
|
{ UINT_MAX, {} }
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
$int_col = array(1, 2);
|
$int_col = array(1, 2);
|
||||||
|
|
||||||
$bin = fopen('php://memory', 'a');
|
$bin = fopen('php://memory', 'a');
|
||||||
fwrite($bin, '00');
|
fwrite($bin, hex2bin('6162636465')); // 'abcde'
|
||||||
rewind($bin);
|
rewind($bin);
|
||||||
$binary_col = array($bin, $bin);
|
$binary_col = array($bin, $bin);
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ emalloc (which only allocate memory in the memory space allocated for the PHP pr
|
||||||
--ENV--
|
--ENV--
|
||||||
PHPT_EXEC=true
|
PHPT_EXEC=true
|
||||||
--SKIPIF--
|
--SKIPIF--
|
||||||
<?php require('skipif.inc'); ?>
|
<?php require('skipif_azure.inc'); ?>
|
||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
include 'MsCommon.inc';
|
include 'MsCommon.inc';
|
||||||
|
|
|
@ -4,7 +4,7 @@ verify github issue52 is fixed.
|
||||||
This test only works in previous versions of SQL Servers. Full-text search features are
|
This test only works in previous versions of SQL Servers. Full-text search features are
|
||||||
deprecated starting in SQL Server 2016.
|
deprecated starting in SQL Server 2016.
|
||||||
--SKIPIF--
|
--SKIPIF--
|
||||||
<?php require('skipif.inc'); ?>
|
<?php require('skipif_azure.inc'); ?>
|
||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
require_once 'MsCommon.inc';
|
require_once 'MsCommon.inc';
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
--TEST--
|
--TEST--
|
||||||
Test connection keywords for Azure Key Vault for Always Encrypted.
|
Test connection keywords for Azure Key Vault for Always Encrypted.
|
||||||
--SKIPIF--
|
--SKIPIF--
|
||||||
<?php require('skipif_not_akv.inc'); ?>
|
<?php require('skipif_azure.inc');
|
||||||
|
require('skipif_mid-refactor.inc'); ?>
|
||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
require_once('pdo_ae_azure_key_vault_common.php');
|
require_once('pdo_ae_azure_key_vault_common.php');
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,92 @@
|
||||||
|
--TEST--
|
||||||
|
PDOStatement::BindParam for binary types with empty strings and non-empty ones
|
||||||
|
--DESCRIPTION--
|
||||||
|
PDOStatement::BindParam for binary types with empty strings and non-empty ones
|
||||||
|
Related to GitHub PR 865 - verify that the same binary data can be reused rather
|
||||||
|
than flushed after the first use
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_mid-refactor.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once("MsCommon_mid-refactor.inc");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$conn = connect();
|
||||||
|
$tableName = "pdoEmptyBinary";
|
||||||
|
$size = 6;
|
||||||
|
|
||||||
|
$colMetaArr = array(new ColumnMeta("binary($size)", "BinaryCol"),
|
||||||
|
new ColumnMeta("varbinary($size)", "VarBinaryCol"),
|
||||||
|
new ColumnMeta("varbinary(max)", "VarBinaryMaxCol"));
|
||||||
|
createTable($conn, $tableName, $colMetaArr);
|
||||||
|
|
||||||
|
// Insert two rows, first empty strings and the second not empty
|
||||||
|
$inputs = array('', 'ABC');
|
||||||
|
|
||||||
|
$bin = fopen('php://memory', 'a');
|
||||||
|
fwrite($bin, $inputs[0]); // an empty string will be 0x in hex
|
||||||
|
rewind($bin);
|
||||||
|
|
||||||
|
$query = "INSERT INTO $tableName VALUES(?, ?, ?)";
|
||||||
|
$stmt = $conn->prepare($query);
|
||||||
|
$stmt->bindParam(1, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
|
||||||
|
$stmt->bindParam(2, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
|
||||||
|
$stmt->bindParam(3, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
fclose($bin);
|
||||||
|
|
||||||
|
$bin2 = fopen('php://memory', 'a');
|
||||||
|
fwrite($bin2, $inputs[1]); // 'ABC' will be 0x414243 in hex
|
||||||
|
rewind($bin2);
|
||||||
|
|
||||||
|
$stmt->bindParam(1, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
|
||||||
|
$stmt->bindParam(2, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
|
||||||
|
$stmt->bindParam(3, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
fclose($bin2);
|
||||||
|
|
||||||
|
// Verify the data by fetching and comparing against the inputs
|
||||||
|
$query = "SELECT * FROM $tableName";
|
||||||
|
$stmt = $conn->query($query);
|
||||||
|
$rowset = $stmt->fetchAll();
|
||||||
|
|
||||||
|
for ($i = 0; $i < 2; $i++) {
|
||||||
|
for ($j = 0; $j < 3; $j++) {
|
||||||
|
$str = $rowset[$i][$j];
|
||||||
|
$len = strlen($str);
|
||||||
|
$failed = false;
|
||||||
|
|
||||||
|
if ($j == 0) {
|
||||||
|
// binary fields have fixed size, unlike varbinary ones
|
||||||
|
if ($len !== $size || trim($str) !== $inputs[$i]) {
|
||||||
|
$failed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($len !== strlen($inputs[$i]) || $str !== $inputs[$i]) {
|
||||||
|
$failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($failed) {
|
||||||
|
$row = $i + 1;
|
||||||
|
$col = $j + 1;
|
||||||
|
echo "Unexpected value returned from row $row and column $col: \n";
|
||||||
|
var_dump($str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
unset($stmt);
|
||||||
|
unset($conn);
|
||||||
|
|
||||||
|
echo "Done\n";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
var_dump($e);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
Binary file not shown.
Binary file not shown.
268
test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt
Normal file
268
test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
--TEST--
|
||||||
|
Test connection and statement attributes for formatting decimal and numeric data (feature request issue 415)
|
||||||
|
--DESCRIPTION--
|
||||||
|
Test the attributes PDO::SQLSRV_ATTR_FORMAT_DECIMALS and PDO::SQLSRV_ATTR_DECIMAL_PLACES, the latter affects money types only, not decimal or numeric types (feature request issue 415).
|
||||||
|
Money, decimal or numeric types are always fetched as strings to preserve accuracy and precision, unlike other primitive numeric types, where there is an option to retrieve them as numbers.
|
||||||
|
|
||||||
|
Setting PDO::SQLSRV_ATTR_FORMAT_DECIMALS to false will turn off all formatting, regardless of PDO::SQLSRV_ATTR_DECIMAL_PLACES value. Also, any negative PDO::SQLSRV_ATTR_DECIMAL_PLACES value will be ignored. Likewise, since money or smallmoney fields have scale 4, if PDO::SQLSRV_ATTR_DECIMAL_PLACES value is larger than 4, it will be ignored as well.
|
||||||
|
|
||||||
|
1. By default, data will be returned with the original precision and scale
|
||||||
|
2. Set PDO::SQLSRV_ATTR_FORMAT_DECIMALS to true to add the leading zeroes to money and decimal types, if missing.
|
||||||
|
3. No support for output params
|
||||||
|
|
||||||
|
The attributes PDO::SQLSRV_ATTR_FORMAT_DECIMALS and PDO::SQLSRV_ATTR_DECIMAL_PLACES will only format the
|
||||||
|
fetched results and have no effect on other operations like insertion or update.
|
||||||
|
--ENV--
|
||||||
|
PHPT_EXEC=true
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_mid-refactor.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once("MsCommon_mid-refactor.inc");
|
||||||
|
|
||||||
|
function checkException($exception, $expected)
|
||||||
|
{
|
||||||
|
if (strpos($exception->getMessage(), $expected) === false) {
|
||||||
|
print_r($exception->getMessage());
|
||||||
|
echo "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testErrorCases($conn)
|
||||||
|
{
|
||||||
|
$expected = 'Expected an integer to specify number of decimals to format the output values of decimal data types';
|
||||||
|
$query = "SELECT 0.0001";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$conn->setAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS, 0);
|
||||||
|
$format = $conn->getAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS);
|
||||||
|
if ($format !== false) {
|
||||||
|
echo 'The value of PDO::SQLSRV_ATTR_FORMAT_DECIMALS should be false\n';
|
||||||
|
var_dump($format);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->setAttribute(PDO::SQLSRV_ATTR_DECIMAL_PLACES, 1.5);
|
||||||
|
} catch (PdoException $e) {
|
||||||
|
checkException($e, $expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => 0.9);
|
||||||
|
$stmt = $conn->prepare($query, $options);
|
||||||
|
} catch (PdoException $e) {
|
||||||
|
checkException($e, $expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => true);
|
||||||
|
$stmt = $conn->prepare($query, $options);
|
||||||
|
} catch (PdoException $e) {
|
||||||
|
checkException($e, $expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareNumbers($actual, $input, $column, $fieldScale, $format = true)
|
||||||
|
{
|
||||||
|
$matched = false;
|
||||||
|
if ($actual === $input) {
|
||||||
|
$matched = true;
|
||||||
|
trace("Matched: $actual, $input\n");
|
||||||
|
} else {
|
||||||
|
// if no formatting, there will be no leading zero
|
||||||
|
$expected = number_format($input, $fieldScale);
|
||||||
|
if (!$format) {
|
||||||
|
if (abs($input) < 1) {
|
||||||
|
// Since no formatting, the leading zero should not be there
|
||||||
|
trace("Drop leading zero of $input: ");
|
||||||
|
$expected = str_replace('0.', '.', $expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace("With number_format: $actual, $expected\n");
|
||||||
|
if ($actual === $expected) {
|
||||||
|
$matched = true;
|
||||||
|
} else {
|
||||||
|
echo "For $column ($fieldScale): expected $expected ($input) but the value is $actual\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testNoOption($conn, $tableName, $inputs, $columns)
|
||||||
|
{
|
||||||
|
// Without the statement option, should return decimal values as they are
|
||||||
|
$query = "SELECT * FROM $tableName";
|
||||||
|
$stmt = $conn->query($query);
|
||||||
|
|
||||||
|
// Compare values
|
||||||
|
$results = $stmt->fetch(PDO::FETCH_NUM);
|
||||||
|
trace("\ntestNoOption:\n");
|
||||||
|
for ($i = 0; $i < count($inputs); $i++) {
|
||||||
|
compareNumbers($results[$i], $inputs[$i], $columns[$i], $i, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testStmtOption($conn, $tableName, $inputs, $columns, $decimalPlaces, $withBuffer)
|
||||||
|
{
|
||||||
|
// Decimal values should NOT be affected by the statement
|
||||||
|
// attribute PDO::SQLSRV_ATTR_FORMAT_DECIMALS
|
||||||
|
$query = "SELECT * FROM $tableName";
|
||||||
|
if ($withBuffer){
|
||||||
|
$options = array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL,
|
||||||
|
PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,
|
||||||
|
PDO::SQLSRV_ATTR_DECIMAL_PLACES => $decimalPlaces);
|
||||||
|
} else {
|
||||||
|
$options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => $decimalPlaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = count($inputs);
|
||||||
|
$stmt = $conn->prepare($query, $options);
|
||||||
|
|
||||||
|
// Fetch by getting one field at a time
|
||||||
|
trace("\ntestStmtOption: $decimalPlaces and buffered $withBuffer\n");
|
||||||
|
for ($i = 0; $i < $size; $i++) {
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$stmt->bindColumn($columns[$i], $field);
|
||||||
|
$result = $stmt->fetch(PDO::FETCH_BOUND);
|
||||||
|
|
||||||
|
compareNumbers($field, $inputs[$i], $columns[$i], $i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $inout)
|
||||||
|
{
|
||||||
|
$outString = '';
|
||||||
|
$numDigits = 2;
|
||||||
|
|
||||||
|
$outSql = getCallProcSqlPlaceholders($storedProcName, 1);
|
||||||
|
|
||||||
|
$options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => $numDigits);
|
||||||
|
$stmt = $conn->prepare($outSql, $options);
|
||||||
|
|
||||||
|
$len = 1024;
|
||||||
|
if ($inout) {
|
||||||
|
$paramType = PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT;
|
||||||
|
|
||||||
|
// For inout parameters the input type should match the output one
|
||||||
|
$outString = '0.0';
|
||||||
|
} else {
|
||||||
|
$paramType = PDO::PARAM_STR;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bindParam(1, $outString, $paramType, $len);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
// The output param value should be unaffected by the attr
|
||||||
|
// PDO::SQLSRV_ATTR_DECIMAL_PLACES. Without ColumnEncryption, the
|
||||||
|
// output param is treated as a regular string (not a decimal), so
|
||||||
|
// no missing leading zeroes.
|
||||||
|
// If ColumnEncryption is enabled, in which case the driver is able
|
||||||
|
// to derive the decimal type, leading zero will be added if missing.
|
||||||
|
if (isAEConnected()) {
|
||||||
|
trace("\ngetOutputParam ($inout) with AE:\n");
|
||||||
|
$column = 'outputParamAE';
|
||||||
|
compareNumbers($outString, $inputValue, $column, $scale);
|
||||||
|
} else {
|
||||||
|
trace("\ngetOutputParam ($inout) without AE:\n");
|
||||||
|
$column = 'outputParam';
|
||||||
|
compareNumbers($outString, $inputValue, $column, $scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes, $inout = false)
|
||||||
|
{
|
||||||
|
for ($i = 0, $p = 3; $i < count($columns); $i++, $p++) {
|
||||||
|
// Create the stored procedure first
|
||||||
|
$storedProcName = "spFormatDecimals" . $i;
|
||||||
|
$procArgs = "@col $dataTypes[$i] OUTPUT";
|
||||||
|
$procCode = "SELECT @col = $columns[$i] FROM $tableName";
|
||||||
|
createProc($conn, $storedProcName, $procArgs, $procCode);
|
||||||
|
|
||||||
|
// Call stored procedure to retrieve output param
|
||||||
|
getOutputParam($conn, $storedProcName, $inputs[$i], $p, $i, $inout);
|
||||||
|
|
||||||
|
dropProc($conn, $storedProcName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION
|
||||||
|
$conn = connect();
|
||||||
|
|
||||||
|
// Test some error conditions
|
||||||
|
testErrorCases($conn);
|
||||||
|
|
||||||
|
// Create the test table of decimal / numeric data columns
|
||||||
|
$tableName = 'pdoFormatDecimals';
|
||||||
|
|
||||||
|
$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6');
|
||||||
|
$dataTypes = array('decimal(3,0)', 'decimal(4,1)', 'decimal(5,2)', 'numeric(6,3)', 'numeric(7,4)', 'numeric(8, 5)');
|
||||||
|
|
||||||
|
$colMeta = array(new ColumnMeta($dataTypes[0], $columns[0]),
|
||||||
|
new ColumnMeta($dataTypes[1], $columns[1]),
|
||||||
|
new ColumnMeta($dataTypes[2], $columns[2]),
|
||||||
|
new ColumnMeta($dataTypes[3], $columns[3]),
|
||||||
|
new ColumnMeta($dataTypes[4], $columns[4]),
|
||||||
|
new ColumnMeta($dataTypes[5], $columns[5]));
|
||||||
|
createTable($conn, $tableName, $colMeta);
|
||||||
|
|
||||||
|
// Generate random input values based on precision and scale
|
||||||
|
trace("\nGenerating random input values: \n");
|
||||||
|
$values = array();
|
||||||
|
$max = 1;
|
||||||
|
for ($s = 0, $p = 3; $s < count($columns); $s++, $p++) {
|
||||||
|
// First get a random number
|
||||||
|
$n = rand(1, 6);
|
||||||
|
$neg = ($n % 2 == 0) ? -1 : 1;
|
||||||
|
|
||||||
|
// $n1, a tiny number, which may or may not be negative,
|
||||||
|
$n1 = rand(0, 5) * $neg;
|
||||||
|
|
||||||
|
if ($s > 0) {
|
||||||
|
$max *= 10;
|
||||||
|
$n2 = rand(0, $max);
|
||||||
|
$number = sprintf("%d.%d", $n1, $n2);
|
||||||
|
} else {
|
||||||
|
$number = sprintf("%d", $n1);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace("$s: $number\n");
|
||||||
|
array_push($values, $number);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?)";
|
||||||
|
$stmt = $conn->prepare($query);
|
||||||
|
for ($i = 0; $i < count($columns); $i++) {
|
||||||
|
$stmt->bindParam($i+1, $values[$i]);
|
||||||
|
}
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
testNoOption($conn, $tableName, $values, $columns, true);
|
||||||
|
|
||||||
|
// Turn on formatting, which only add leading zeroes, if missing
|
||||||
|
// decimal and numeric types should be unaffected by
|
||||||
|
// PDO::SQLSRV_ATTR_DECIMAL_PLACES whatsoever
|
||||||
|
$conn->setAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS, true);
|
||||||
|
|
||||||
|
// Now try with setting number decimals to 3 then 2
|
||||||
|
testStmtOption($conn, $tableName, $values, $columns, 3, false);
|
||||||
|
testStmtOption($conn, $tableName, $values, $columns, 3, true);
|
||||||
|
|
||||||
|
testStmtOption($conn, $tableName, $values, $columns, 2, false);
|
||||||
|
testStmtOption($conn, $tableName, $values, $columns, 2, true);
|
||||||
|
|
||||||
|
// Test output parameters
|
||||||
|
testOutputParam($conn, $tableName, $values, $columns, $dataTypes);
|
||||||
|
testOutputParam($conn, $tableName, $values, $columns, $dataTypes, true);
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
echo "Done\n";
|
||||||
|
|
||||||
|
unset($stmt);
|
||||||
|
unset($conn);
|
||||||
|
} catch (PdoException $e) {
|
||||||
|
echo $e->getMessage() . PHP_EOL;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
162
test/functional/pdo_sqlsrv/pdostatement_format_money_scales.phpt
Normal file
162
test/functional/pdo_sqlsrv/pdostatement_format_money_scales.phpt
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
--TEST--
|
||||||
|
Test various decimal places of money values (feature request issue 415)
|
||||||
|
--DESCRIPTION--
|
||||||
|
In SQL Server, the maximum precision of money type is 19 with scale 4. Generate a long numeric string and get rid of the last digit to make it a 15-digit-string. Then replace one digit at a time with a dot '.' to make it a decimal input string for testing.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
string(15) ".23456789098765"
|
||||||
|
string(15) "1.3456789098765"
|
||||||
|
string(15) "12.456789098765"
|
||||||
|
string(15) "123.56789098765"
|
||||||
|
string(15) "1234.6789098765"
|
||||||
|
...
|
||||||
|
string(15) "1234567890987.5"
|
||||||
|
string(15) "12345678909876."
|
||||||
|
|
||||||
|
The inserted money data will be
|
||||||
|
0.2346
|
||||||
|
1.3457
|
||||||
|
12.4568
|
||||||
|
123.5679
|
||||||
|
1234.6789
|
||||||
|
...
|
||||||
|
1234567890987.5000
|
||||||
|
12345678909876.0000
|
||||||
|
|
||||||
|
--ENV--
|
||||||
|
PHPT_EXEC=true
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_mid-refactor.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once("MsCommon_mid-refactor.inc");
|
||||||
|
|
||||||
|
$prec = 19;
|
||||||
|
$scale = 4;
|
||||||
|
$dot = '.';
|
||||||
|
|
||||||
|
function createTestTable($conn)
|
||||||
|
{
|
||||||
|
global $prec, $scale;
|
||||||
|
|
||||||
|
// Create the test table
|
||||||
|
$tableName = "pdoFormatMoneyScales";
|
||||||
|
$colMeta = array();
|
||||||
|
|
||||||
|
$max = $prec - $scale;
|
||||||
|
for ($i = 0; $i < $max; $i++) {
|
||||||
|
$column = "col_$i";
|
||||||
|
$dataType = 'money';
|
||||||
|
|
||||||
|
array_push($colMeta, new ColumnMeta($dataType, $column));
|
||||||
|
}
|
||||||
|
createTable($conn, $tableName, $colMeta);
|
||||||
|
|
||||||
|
return $tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertTestData($conn, $tableName)
|
||||||
|
{
|
||||||
|
global $prec, $scale, $dot;
|
||||||
|
|
||||||
|
$digits = substr('1234567890987654321', 0, $prec - $scale);
|
||||||
|
|
||||||
|
$inputData = array();
|
||||||
|
$max = $prec - $scale;
|
||||||
|
|
||||||
|
// Generate input strings - replace the $i-th digit with a dot '.'
|
||||||
|
for ($i = 0; $i < $max; $i++) {
|
||||||
|
$d = $digits[$i];
|
||||||
|
$digits[$i] = $dot;
|
||||||
|
|
||||||
|
$column = "col_$i";
|
||||||
|
$inputData = array_merge($inputData, array($column => $digits));
|
||||||
|
|
||||||
|
// Restore the $i-th digit with its original digit
|
||||||
|
$digits[$i] = $d;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = insertRow($conn, $tableName, $inputData);
|
||||||
|
unset($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberFormat($value, $numDecimals)
|
||||||
|
{
|
||||||
|
return number_format($value, $numDecimals, '.', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/****
|
||||||
|
The function testVariousScales() will fetch one column at a time, using scale from 0 up to 4 allowed for that column type.
|
||||||
|
|
||||||
|
For example, if the input string is
|
||||||
|
1234567890.2345
|
||||||
|
|
||||||
|
When fetching data, using scale from 0 to 4, the following values are expected to return:
|
||||||
|
1234567890
|
||||||
|
1234567890.2
|
||||||
|
1234567890.23
|
||||||
|
1234567890.235
|
||||||
|
1234567890.2345
|
||||||
|
****/
|
||||||
|
function testVariousScales($conn, $tableName)
|
||||||
|
{
|
||||||
|
global $prec, $scale;
|
||||||
|
$max = $prec - $scale;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $max; $i++) {
|
||||||
|
$column = "col_$i";
|
||||||
|
|
||||||
|
$query = "SELECT $column as col1 FROM $tableName";
|
||||||
|
|
||||||
|
// Default case: no formatting
|
||||||
|
$stmt = $conn->query($query);
|
||||||
|
if ($obj = $stmt->fetchObject()) {
|
||||||
|
trace("\n$obj->col1\n");
|
||||||
|
$input = $obj->col1;
|
||||||
|
} else {
|
||||||
|
echo "In testVariousScales: fetchObject failed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, format how many decimals to be displayed
|
||||||
|
$query = "SELECT $column FROM $tableName";
|
||||||
|
for ($j = 0; $j <= $scale; $j++) {
|
||||||
|
$options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => true, PDO::SQLSRV_ATTR_DECIMAL_PLACES => $j);
|
||||||
|
$stmt = $conn->prepare($query, $options);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$stmt->bindColumn($column, $value);
|
||||||
|
if ($stmt->fetch(PDO::FETCH_BOUND)) {
|
||||||
|
trace("$value\n");
|
||||||
|
|
||||||
|
$expected = numberFormat($input, $j);
|
||||||
|
if ($value !== $expected) {
|
||||||
|
echo "testVariousScales ($j): Expected $expected but got $value\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "Round $i scale $j: fetch failed\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION
|
||||||
|
// Default is no formatting, but set it to false anyway
|
||||||
|
$conn = connect();
|
||||||
|
$conn->setAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS, false);
|
||||||
|
|
||||||
|
$tableName = createTestTable($conn);
|
||||||
|
insertTestData($conn, $tableName);
|
||||||
|
testVariousScales($conn, $tableName);
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
|
||||||
|
echo "Done\n";
|
||||||
|
|
||||||
|
unset($conn);
|
||||||
|
} catch (PdoException $e) {
|
||||||
|
echo $e->getMessage() . PHP_EOL;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
244
test/functional/pdo_sqlsrv/pdostatement_format_money_types.phpt
Normal file
244
test/functional/pdo_sqlsrv/pdostatement_format_money_types.phpt
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
--TEST--
|
||||||
|
Test connection attributes for formatting money data (feature request issue 415)
|
||||||
|
--DESCRIPTION--
|
||||||
|
Test how money data in the fetched values can be formatted by using the connection attributes PDO::SQLSRV_ATTR_FORMAT_DECIMALS and PDO::SQLSRV_ATTR_DECIMAL_PLACES, the latter works only with integer values. No effect on other operations like insertion or update.
|
||||||
|
|
||||||
|
The PDO::SQLSRV_ATTR_DECIMAL_PLACES attribute only affects money/smallmoney fields. If its value is out of range, for example, it's negative or larger than the original scale, then its value will be ignored.
|
||||||
|
|
||||||
|
The underlying data will not be altered, but formatted results may likely be rounded up (e.g. .2954 will be displayed as 0.30 if the user wants only two decimals). For this reason, it is not recommended to use formatted money values as inputs to any calculation.
|
||||||
|
|
||||||
|
The corresponding statement attributes always override the inherited values from the connection object. Setting PDO::SQLSRV_ATTR_FORMAT_DECIMALS to false will automatically turn off any formatting of decimal data in the result set, ignoring PDO::SQLSRV_ATTR_DECIMAL_PLACES value.
|
||||||
|
|
||||||
|
By only setting PDO::SQLSRV_ATTR_FORMAT_DECIMALS to true will add the leading zeroes, if missing.
|
||||||
|
|
||||||
|
Do not support output params.
|
||||||
|
--ENV--
|
||||||
|
PHPT_EXEC=true
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_mid-refactor.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once("MsCommon_mid-refactor.inc");
|
||||||
|
|
||||||
|
function numberFormat($value, $numDecimals)
|
||||||
|
{
|
||||||
|
return number_format($value, $numDecimals, '.', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFloatTypes($conn, $numDigits)
|
||||||
|
{
|
||||||
|
// This test with the float types of various number of bits, which are retrieved
|
||||||
|
// as numbers by default. When fetched as strings, no formatting is done,
|
||||||
|
// because the connection attributes for formatting have no effect
|
||||||
|
$epsilon = 0.001;
|
||||||
|
$nColumns = 5;
|
||||||
|
|
||||||
|
$values = array();
|
||||||
|
for ($i = 0; $i < $nColumns; $i++) {
|
||||||
|
$n1 = rand(1, 100);
|
||||||
|
$n2 = rand(1, 100);
|
||||||
|
$neg = ($i % 2 == 0) ? -1 : 1;
|
||||||
|
|
||||||
|
$n = $neg * $n1 / $n2;
|
||||||
|
array_push($values, $n);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = "SELECT CONVERT(float(1), $values[0]),
|
||||||
|
CONVERT(float(12), $values[1]),
|
||||||
|
CONVERT(float(24), $values[2]),
|
||||||
|
CONVERT(float(36), $values[3]),
|
||||||
|
CONVERT(float(53), $values[4])";
|
||||||
|
|
||||||
|
$stmt = $conn->query($query);
|
||||||
|
$floats = $stmt->fetch(PDO::FETCH_NUM);
|
||||||
|
unset($stmt);
|
||||||
|
|
||||||
|
// By default the floating point numbers are fetched as strings
|
||||||
|
$stmt = $conn->prepare($query);
|
||||||
|
for ($i = 0; $i < 5; $i++) {
|
||||||
|
$stmt->execute();
|
||||||
|
$floatStr = $stmt->fetchColumn($i);
|
||||||
|
|
||||||
|
$floatVal = floatVal($floats[$i]);
|
||||||
|
$floatVal1 = floatval($floatStr);
|
||||||
|
|
||||||
|
trace("testFloatTypes: $floatVal1, $floatVal\n");
|
||||||
|
|
||||||
|
// Check if the numbers of decimal digits are the same
|
||||||
|
// It is highly unlikely but not impossible
|
||||||
|
$numbers = explode('.', $floatStr);
|
||||||
|
$len = strlen($numbers[1]);
|
||||||
|
if ($len == $numDigits && $floatVal1 != $floatVal) {
|
||||||
|
echo "Expected $floatVal but $floatVal1 returned. \n";
|
||||||
|
} else {
|
||||||
|
$diff = abs($floatVal1 - $floatVal) / $floatVal;
|
||||||
|
if ($diff > $epsilon) {
|
||||||
|
echo "$diff: Expected $floatVal but $floatVal1 returned. \n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyMoneyFormatting($conn, $query, $values, $format)
|
||||||
|
{
|
||||||
|
if ($format) {
|
||||||
|
// Set SQLSRV_ATTR_FORMAT_DECIMALS to true but
|
||||||
|
// set SQLSRV_ATTR_DECIMAL_PLACES to a negative number
|
||||||
|
// to override the inherited attribute
|
||||||
|
$options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => -1, PDO::SQLSRV_ATTR_FORMAT_DECIMALS => true);
|
||||||
|
} else {
|
||||||
|
// Set SQLSRV_ATTR_FORMAT_DECIMALS to false will
|
||||||
|
// turn off any formatting -- overriding the inherited
|
||||||
|
// attributes
|
||||||
|
$options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($query, $options);
|
||||||
|
$stmt->execute();
|
||||||
|
$results = $stmt->fetch(PDO::FETCH_NUM);
|
||||||
|
|
||||||
|
trace("\verifyMoneyFormatting:\n");
|
||||||
|
for ($i = 0; $i < count($values); $i++) {
|
||||||
|
// money types have a scale of 4
|
||||||
|
$default = numberFormat($values[$i], 4);
|
||||||
|
if (!$format) {
|
||||||
|
// No formatting - should drop the leading zero, if exists
|
||||||
|
if (abs($values[$i]) < 1) {
|
||||||
|
$default = str_replace('0.', '.', $default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($default !== $results[$i]) {
|
||||||
|
echo "verifyMoneyFormatting ($format): Expected $default but got $results[$i]\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyMoneyValues($conn, $numDigits, $query, $values, $override)
|
||||||
|
{
|
||||||
|
if ($override) {
|
||||||
|
$options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => $numDigits);
|
||||||
|
$stmt = $conn->prepare($query, $options);
|
||||||
|
} else {
|
||||||
|
// Use the connection defaults
|
||||||
|
$stmt = $conn->prepare($query);
|
||||||
|
}
|
||||||
|
$stmt->execute();
|
||||||
|
$results = $stmt->fetch(PDO::FETCH_NUM);
|
||||||
|
|
||||||
|
trace("\nverifyMoneyValues:\n");
|
||||||
|
for ($i = 0; $i < count($values); $i++) {
|
||||||
|
$value = numberFormat($values[$i], $numDigits);
|
||||||
|
trace("$results[$i], $value\n");
|
||||||
|
|
||||||
|
if ($value !== $results[$i]) {
|
||||||
|
echo "testMoneyTypes ($override, $numDigits): Expected $value but got $results[$i]\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testMoneyTypes($conn, $numDigits)
|
||||||
|
{
|
||||||
|
// With money and smallmoney types, which are essentially decimal types
|
||||||
|
// As of today, ODBC driver does not support Always Encrypted feature with money / smallmoney
|
||||||
|
$values = array();
|
||||||
|
$nColumns = 6;
|
||||||
|
for ($i = 0; $i < $nColumns; $i++) {
|
||||||
|
// First get a random number
|
||||||
|
$n = rand(0, 10);
|
||||||
|
$neg = ($n % 2 == 0) ? -1 : 1;
|
||||||
|
|
||||||
|
// $n1 may or may not be negative
|
||||||
|
$max = 10;
|
||||||
|
$n1 = rand(0, $max) * $neg;
|
||||||
|
$n2 = rand(1, $max * 1000);
|
||||||
|
|
||||||
|
$number = sprintf("%d.%d", $n1, $n2);
|
||||||
|
array_push($values, $number);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = "SELECT CONVERT(smallmoney, $values[0]),
|
||||||
|
CONVERT(money, $values[1]),
|
||||||
|
CONVERT(smallmoney, $values[2]),
|
||||||
|
CONVERT(money, $values[3]),
|
||||||
|
CONVERT(smallmoney, $values[4]),
|
||||||
|
CONVERT(money, $values[5])";
|
||||||
|
|
||||||
|
// Do not override the connection attributes
|
||||||
|
verifyMoneyValues($conn, $numDigits, $query, $values, false);
|
||||||
|
// Next, override statement attribute to set number of
|
||||||
|
// decimal places
|
||||||
|
verifyMoneyValues($conn, 0, $query, $values, true);
|
||||||
|
|
||||||
|
// Set Formatting attribute to true then false
|
||||||
|
verifyMoneyFormatting($conn, $query, $values, true);
|
||||||
|
verifyMoneyFormatting($conn, $query, $values, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connGetAttributes($conn, $numDigits)
|
||||||
|
{
|
||||||
|
$format = $conn->getAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS);
|
||||||
|
if ($format !== true) {
|
||||||
|
echo "The returned value of SQLSRV_ATTR_FORMAT_DECIMALS, $format, is wrong\n";
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$digits = $conn->getAttribute(PDO::SQLSRV_ATTR_DECIMAL_PLACES);
|
||||||
|
if ($digits != $numDigits) {
|
||||||
|
echo "The returned value of SQLSRV_ATTR_DECIMAL_PLACES, $digits, is wrong\n";
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectWithAttrs($numDigits)
|
||||||
|
{
|
||||||
|
$attr = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => true,
|
||||||
|
PDO::SQLSRV_ATTR_DECIMAL_PLACES => $numDigits);
|
||||||
|
|
||||||
|
// This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION
|
||||||
|
$conn = connect('', $attr);
|
||||||
|
|
||||||
|
if (connGetAttributes($conn, $numDigits)) {
|
||||||
|
// First test with money types
|
||||||
|
testMoneyTypes($conn, $numDigits);
|
||||||
|
|
||||||
|
// Also test using regular floats
|
||||||
|
testFloatTypes($conn, $numDigits);
|
||||||
|
}
|
||||||
|
unset($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectSetAttrs($numDigits)
|
||||||
|
{
|
||||||
|
// This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION
|
||||||
|
$conn = connect();
|
||||||
|
$conn->setAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS, true);
|
||||||
|
$conn->setAttribute(PDO::SQLSRV_ATTR_DECIMAL_PLACES, $numDigits);
|
||||||
|
|
||||||
|
if (connGetAttributes($conn, $numDigits)) {
|
||||||
|
// First test with money types
|
||||||
|
testMoneyTypes($conn, $numDigits);
|
||||||
|
|
||||||
|
// Also test using regular floats
|
||||||
|
testFloatTypes($conn, $numDigits);
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
connectWithAttrs(2);
|
||||||
|
connectSetAttrs(3);
|
||||||
|
|
||||||
|
echo "Done\n";
|
||||||
|
|
||||||
|
unset($conn);
|
||||||
|
} catch (PdoException $e) {
|
||||||
|
echo $e->getMessage() . PHP_EOL;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
Binary file not shown.
|
@ -4,6 +4,10 @@ if (!extension_loaded("pdo") || !extension_loaded('pdo_sqlsrv')) {
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once("MsSetup.inc");
|
require_once("MsSetup.inc");
|
||||||
|
|
||||||
|
if ($keystore != 'akv')
|
||||||
|
die ( 'skip - the test requires valid Azure Key Vault credentials.' );
|
||||||
|
|
||||||
if ($driver != "ODBC Driver 17 for SQL Server") {
|
if ($driver != "ODBC Driver 17 for SQL Server") {
|
||||||
// the testing is not set to use ODBC 17
|
// the testing is not set to use ODBC 17
|
||||||
die("skip - AE feature not supported in the current environment.");
|
die("skip - AE feature not supported in the current environment.");
|
||||||
|
|
|
@ -449,6 +449,10 @@ function handleErrors()
|
||||||
|
|
||||||
function setUSAnsiLocale()
|
function setUSAnsiLocale()
|
||||||
{
|
{
|
||||||
|
// Do not run locale tests in Azure
|
||||||
|
if (isDaasMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!isWindows()) {
|
if (!isWindows()) {
|
||||||
// macOS the locale names are different in Linux or macOS
|
// macOS the locale names are different in Linux or macOS
|
||||||
$locale = strtoupper(PHP_OS) === 'LINUX' ? "en_US.ISO-8859-1" : "en_US.ISO8859-1";
|
$locale = strtoupper(PHP_OS) === 'LINUX' ? "en_US.ISO-8859-1" : "en_US.ISO8859-1";
|
||||||
|
@ -459,6 +463,10 @@ function setUSAnsiLocale()
|
||||||
|
|
||||||
function resetLocaleToDefault()
|
function resetLocaleToDefault()
|
||||||
{
|
{
|
||||||
|
// Do not run locale tests in Azure
|
||||||
|
if (isDaasMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Like setUSAnsiLocale() above, this method is only needed in non-Windows environment
|
// Like setUSAnsiLocale() above, this method is only needed in non-Windows environment
|
||||||
if (!isWindows()) {
|
if (!isWindows()) {
|
||||||
setlocale(LC_ALL, null);
|
setlocale(LC_ALL, null);
|
||||||
|
@ -472,6 +480,10 @@ function isLocaleSupported()
|
||||||
if (isWindows()) {
|
if (isWindows()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Do not run locale tests in Azure
|
||||||
|
if (isDaasMode()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (AE\isDataEncrypted()) {
|
if (AE\isDataEncrypted()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@ emalloc (which only allocate memory in the memory space allocated for the PHP pr
|
||||||
--ENV--
|
--ENV--
|
||||||
PHPT_EXEC=true
|
PHPT_EXEC=true
|
||||||
--SKIPIF--
|
--SKIPIF--
|
||||||
<?php require('skipif_versions_old.inc'); ?>
|
<?php require('skipif_azure.inc');
|
||||||
|
require('skipif_versions_old.inc'); ?>
|
||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
require_once('MsCommon.inc');
|
require_once('MsCommon.inc');
|
||||||
|
|
|
@ -4,7 +4,7 @@ verify github issue52 is fixed.
|
||||||
This test only works in previous versions of SQL Servers. Full-text search features are
|
This test only works in previous versions of SQL Servers. Full-text search features are
|
||||||
deprecated starting in SQL Server 2016.
|
deprecated starting in SQL Server 2016.
|
||||||
--SKIPIF--
|
--SKIPIF--
|
||||||
<?php require('skipif.inc'); ?>
|
<?php require('skipif_azure.inc'); ?>
|
||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ if (! extension_loaded("sqlsrv")) {
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once("MsSetup.inc");
|
require_once("MsSetup.inc");
|
||||||
|
if ($keystore != 'akv')
|
||||||
|
die ( 'skip - the test requires valid Azure Key Vault credentials.' );
|
||||||
|
|
||||||
if ($driver != "ODBC Driver 17 for SQL Server") {
|
if ($driver != "ODBC Driver 17 for SQL Server") {
|
||||||
// the testing is not set to use ODBC 17
|
// the testing is not set to use ODBC 17
|
||||||
die("skip - AE feature not supported in the current environment.");
|
die("skip - AE feature not supported in the current environment.");
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
--TEST--
|
--TEST--
|
||||||
Test connection keywords for Azure Key Vault for Always Encrypted.
|
Test connection keywords for Azure Key Vault for Always Encrypted.
|
||||||
--SKIPIF--
|
--SKIPIF--
|
||||||
<?php require('skipif_not_akv.inc'); ?>
|
<?php require('skipif_azure.inc');
|
||||||
|
require('skipif_versions_old.inc'); ?>
|
||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
require_once('sqlsrv_ae_azure_key_vault_common.php');
|
require_once('sqlsrv_ae_azure_key_vault_common.php');
|
||||||
|
|
|
@ -70,7 +70,7 @@ $azurePassword = $adPassword;
|
||||||
if ($azureServer != 'TARGET_AD_SERVER')
|
if ($azureServer != 'TARGET_AD_SERVER')
|
||||||
{
|
{
|
||||||
$connectionInfo = array( "UID"=>$azureUsername, "PWD"=>$azurePassword,
|
$connectionInfo = array( "UID"=>$azureUsername, "PWD"=>$azurePassword,
|
||||||
"Authentication"=>'ActiveDirectoryPassword', "TrustServerCertificate"=>true );
|
"Authentication"=>'ActiveDirectoryPassword', "TrustServerCertificate"=>false );
|
||||||
|
|
||||||
$conn = sqlsrv_connect( $azureServer, $connectionInfo );
|
$conn = sqlsrv_connect( $azureServer, $connectionInfo );
|
||||||
if( $conn === false )
|
if( $conn === false )
|
||||||
|
|
105
test/functional/sqlsrv/sqlsrv_param_empty_binary.phpt
Normal file
105
test/functional/sqlsrv/sqlsrv_param_empty_binary.phpt
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
--TEST--
|
||||||
|
Test for inserting empty strings and non-empty ones into binary types
|
||||||
|
--DESCRIPTION--
|
||||||
|
Test for inserting empty strings and non-empty ones into binary types
|
||||||
|
Related to GitHub PR 865 - verify that the same binary data can be reused rather
|
||||||
|
than flushed after the first use
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_versions_old.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once('MsCommon.inc');
|
||||||
|
|
||||||
|
$conn = AE\connect();
|
||||||
|
|
||||||
|
$tableName = "sqlsrvEmptyBinary";
|
||||||
|
$size = 6;
|
||||||
|
|
||||||
|
$colMetaArr = array(new AE\ColumnMeta("binary($size)", "BinaryCol"),
|
||||||
|
new AE\ColumnMeta("varbinary($size)", "VarBinaryCol"),
|
||||||
|
new AE\ColumnMeta("varbinary(max)", "VarBinaryMaxCol"));
|
||||||
|
AE\createTable($conn, $tableName, $colMetaArr);
|
||||||
|
|
||||||
|
// Insert two rows, first empty strings and the second not empty
|
||||||
|
$inputValues = array('', 'ABC');
|
||||||
|
|
||||||
|
$inputs = array(new AE\BindParamOption($inputValues[0],
|
||||||
|
null,
|
||||||
|
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
|
||||||
|
"SQLSRV_SQLTYPE_BINARY($size)"),
|
||||||
|
new AE\BindParamOption($inputValues[0],
|
||||||
|
null,
|
||||||
|
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
|
||||||
|
"SQLSRV_SQLTYPE_VARBINARY($size)"),
|
||||||
|
new AE\BindParamOption($inputValues[0],
|
||||||
|
null,
|
||||||
|
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
|
||||||
|
"SQLSRV_SQLTYPE_VARBINARY('max')"));
|
||||||
|
$r;
|
||||||
|
$stmt = AE\insertRow($conn, $tableName, array("BinaryCol" => $inputs[0], "VarBinaryCol" => $inputs[1], "VarBinaryMaxCol" => $inputs[2]), $r, AE\INSERT_PREPARE_PARAMS);
|
||||||
|
|
||||||
|
|
||||||
|
$inputs = array(new AE\BindParamOption($inputValues[1],
|
||||||
|
null,
|
||||||
|
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
|
||||||
|
"SQLSRV_SQLTYPE_BINARY($size)"),
|
||||||
|
new AE\BindParamOption($inputValues[1],
|
||||||
|
null,
|
||||||
|
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
|
||||||
|
"SQLSRV_SQLTYPE_VARBINARY($size)"),
|
||||||
|
new AE\BindParamOption($inputValues[1],
|
||||||
|
null,
|
||||||
|
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
|
||||||
|
"SQLSRV_SQLTYPE_VARBINARY('max')"));
|
||||||
|
$r;
|
||||||
|
$stmt = AE\insertRow($conn, $tableName, array("BinaryCol" => $inputs[0], "VarBinaryCol" => $inputs[1], "VarBinaryMaxCol" => $inputs[2]), $r, AE\INSERT_PREPARE_PARAMS);
|
||||||
|
|
||||||
|
// Verify the data by fetching and comparing against the inputs
|
||||||
|
$query = "SELECT * FROM $tableName";
|
||||||
|
$stmt = sqlsrv_query($conn, $query);
|
||||||
|
if (!$stmt) {
|
||||||
|
fatalError("Failed to retrieve data from $tableName");
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < 2; $i++) {
|
||||||
|
$rowNum = $i + 1;
|
||||||
|
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
|
||||||
|
if (!$row) {
|
||||||
|
fatalError("Failed in sqlsrv_fetch_array for row $rowNum");
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($j = 0; $j < 3; $j++) {
|
||||||
|
$str = $row[$j];
|
||||||
|
$len = strlen($str);
|
||||||
|
$failed = false;
|
||||||
|
|
||||||
|
if ($j == 0) {
|
||||||
|
// binary fields have fixed size, unlike varbinary ones
|
||||||
|
if ($len !== $size || trim($str) !== $inputValues[$i]) {
|
||||||
|
$failed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$inputLen = strlen($inputValues[$i]);
|
||||||
|
if ($len !== $inputLen || $str !== $inputValues[$i]) {
|
||||||
|
$failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($failed) {
|
||||||
|
$colNum = $j + 1;
|
||||||
|
echo "Unexpected value returned from row $rowNum and column $colNum: \n";
|
||||||
|
var_dump($str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
|
||||||
|
sqlsrv_free_stmt($stmt);
|
||||||
|
sqlsrv_close($conn);
|
||||||
|
|
||||||
|
echo "Done\n";
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
288
test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt
Normal file
288
test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
--TEST--
|
||||||
|
Test connection and statement attributes for formatting decimal and numeric data (feature request issue 415)
|
||||||
|
--DESCRIPTION--
|
||||||
|
Test the connection and statement options, FormatDecimals and
|
||||||
|
DecimalPlaces, the latter affects money types only, not
|
||||||
|
decimal or numeric types (feature request issue 415).
|
||||||
|
Money, decimal or numeric types are always fetched as strings to preserve accuracy and precision, unlike other primitive numeric types, where there is an option to retrieve them as numbers.
|
||||||
|
|
||||||
|
Setting FormatDecimals to false will turn off all formatting, regardless of DecimalPlaces value. Also, any negative DecimalPlaces value will be ignored. Likewise, since money or smallmoney fields have scale 4, if DecimalPlaces value is larger than 4, it will be ignored as well.
|
||||||
|
|
||||||
|
1. By default, data will be returned with the original precision and scale
|
||||||
|
2. Set FormatDecimals to true to add the leading zeroes to money and decimal types, if missing.
|
||||||
|
3. For output params, leading zeroes will be added for any decimal fields if FormatDecimals is true, but only if either SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC is set correctly to match the original column type and its precision / scale.
|
||||||
|
|
||||||
|
FormatDecimals and DecimalPlaces will only format the fetched results and have no effect on other operations like insertion or update.
|
||||||
|
--ENV--
|
||||||
|
PHPT_EXEC=true
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_versions_old.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once('MsCommon.inc');
|
||||||
|
|
||||||
|
function compareNumbers($actual, $input, $column, $fieldScale, $format = true)
|
||||||
|
{
|
||||||
|
$matched = false;
|
||||||
|
if ($actual === $input) {
|
||||||
|
$matched = true;
|
||||||
|
trace("Matched: $actual, $input\n");
|
||||||
|
} else {
|
||||||
|
// If no formatting, there will be no leading zero
|
||||||
|
$expected = number_format($input, $fieldScale);
|
||||||
|
if (!$format) {
|
||||||
|
if (abs($input) < 1) {
|
||||||
|
// Since no formatting, the leading zero should not be there
|
||||||
|
trace("Drop leading zero of $input: ");
|
||||||
|
$expected = str_replace('0.', '.', $expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace("With number_format: $actual, $expected\n");
|
||||||
|
if ($actual === $expected) {
|
||||||
|
$matched = true;
|
||||||
|
} else {
|
||||||
|
echo "For $column ($fieldScale): expected $expected ($input) but the value is $actual\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testErrorCases($conn)
|
||||||
|
{
|
||||||
|
$query = "SELECT 0.0001";
|
||||||
|
$message = 'Expected an integer to specify number of decimals to format the output values of decimal data types.';
|
||||||
|
|
||||||
|
$options = array('DecimalPlaces' => 1.5);
|
||||||
|
$stmt = sqlsrv_query($conn, $query, array(), $options);
|
||||||
|
if ($stmt) {
|
||||||
|
fatalError("Case 1: expected query to fail!!");
|
||||||
|
} else {
|
||||||
|
$error = sqlsrv_errors()[0]['message'];
|
||||||
|
if (strpos($error, $message) === false) {
|
||||||
|
print_r(sqlsrv_errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = array('DecimalPlaces' => true);
|
||||||
|
$stmt = sqlsrv_query($conn, $query, array(), $options);
|
||||||
|
if ($stmt) {
|
||||||
|
fatalError("Case 2: expected query to fail!!");
|
||||||
|
} else {
|
||||||
|
$error = sqlsrv_errors()[0]['message'];
|
||||||
|
if (strpos($error, $message) === false) {
|
||||||
|
print_r(sqlsrv_errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testNoOption($conn, $tableName, $inputs, $columns, $exec)
|
||||||
|
{
|
||||||
|
// This should return decimal values as they are
|
||||||
|
$query = "SELECT * FROM $tableName";
|
||||||
|
if ($exec) {
|
||||||
|
$stmt = sqlsrv_query($conn, $query);
|
||||||
|
} else {
|
||||||
|
$stmt = sqlsrv_prepare($conn, $query);
|
||||||
|
sqlsrv_execute($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare values
|
||||||
|
$results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
|
||||||
|
for ($i = 0; $i < count($inputs); $i++) {
|
||||||
|
compareNumbers($results[$i], $inputs[$i], $columns[$i], $i, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testStmtOption($conn, $tableName, $inputs, $columns, $decimalPlaces, $withBuffer)
|
||||||
|
{
|
||||||
|
// Decimal values should NOT be affected by the statement
|
||||||
|
// option DecimalPlaces
|
||||||
|
$query = "SELECT * FROM $tableName";
|
||||||
|
if ($withBuffer){
|
||||||
|
$options = array('Scrollable' => 'buffered', 'DecimalPlaces' => $decimalPlaces);
|
||||||
|
} else {
|
||||||
|
$options = array('DecimalPlaces' => $decimalPlaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = count($inputs);
|
||||||
|
$stmt = sqlsrv_prepare($conn, $query, array(), $options);
|
||||||
|
|
||||||
|
// Fetch by getting one field at a time
|
||||||
|
sqlsrv_execute($stmt);
|
||||||
|
|
||||||
|
if (sqlsrv_fetch($stmt) === false) {
|
||||||
|
fatalError("Failed in retrieving data\n");
|
||||||
|
}
|
||||||
|
for ($i = 0; $i < $size; $i++) {
|
||||||
|
$field = sqlsrv_get_field($stmt, $i); // Expect a string
|
||||||
|
compareNumbers($field, $inputs[$i], $columns[$i], $i, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $numeric, $inout)
|
||||||
|
{
|
||||||
|
$outString = '';
|
||||||
|
$numDigits = 2;
|
||||||
|
$dir = SQLSRV_PARAM_OUT;
|
||||||
|
|
||||||
|
// The output param value should be the same as the input,
|
||||||
|
// unaffected by the statement attr DecimalPlaces. If
|
||||||
|
// the correct sql type is specified or ColumnEncryption
|
||||||
|
// is enabled, in which case the driver is able to derive
|
||||||
|
// the correct field type, leading zero will be added
|
||||||
|
// if missing
|
||||||
|
$sqlType = null;
|
||||||
|
if (!AE\isColEncrypted()) {
|
||||||
|
$type = ($numeric) ? 'SQLSRV_SQLTYPE_NUMERIC' : 'SQLSRV_SQLTYPE_DECIMAL';
|
||||||
|
$sqlType = call_user_func($type, $prec, $scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For inout parameters the input type should match the output one
|
||||||
|
if ($inout) {
|
||||||
|
$dir = SQLSRV_PARAM_INOUT;
|
||||||
|
$outString = '0.0';
|
||||||
|
}
|
||||||
|
|
||||||
|
$outSql = AE\getCallProcSqlPlaceholders($storedProcName, 1);
|
||||||
|
$stmt = sqlsrv_prepare($conn, $outSql,
|
||||||
|
array(array(&$outString, $dir, null, $sqlType)),
|
||||||
|
array('DecimalPlaces' => $numDigits));
|
||||||
|
if (!$stmt) {
|
||||||
|
fatalError("getOutputParam: failed when preparing to call $storedProcName");
|
||||||
|
}
|
||||||
|
if (!sqlsrv_execute($stmt)) {
|
||||||
|
fatalError("getOutputParam: failed to execute procedure $storedProcName");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify value of output param
|
||||||
|
$column = 'outputParam';
|
||||||
|
compareNumbers($outString, $inputValue, $column, $scale, true);
|
||||||
|
sqlsrv_free_stmt($stmt);
|
||||||
|
|
||||||
|
if (!AE\isColEncrypted()) {
|
||||||
|
// With ColumnEncryption enabled, the driver is able to derive the decimal type,
|
||||||
|
// so skip this part of the test
|
||||||
|
$outString2 = $inout ? '0.0' : '';
|
||||||
|
$stmt = sqlsrv_prepare($conn, $outSql,
|
||||||
|
array(array(&$outString2, $dir)),
|
||||||
|
array('DecimalPlaces' => $numDigits));
|
||||||
|
if (!$stmt) {
|
||||||
|
fatalError("getOutputParam2: failed when preparing to call $storedProcName");
|
||||||
|
}
|
||||||
|
if (!sqlsrv_execute($stmt)) {
|
||||||
|
fatalError("getOutputParam2: failed to execute procedure $storedProcName");
|
||||||
|
}
|
||||||
|
|
||||||
|
$column = 'outputParam2';
|
||||||
|
compareNumbers($outString2, $inputValue, $column, $scale, true);
|
||||||
|
sqlsrv_free_stmt($stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes, $inout = false)
|
||||||
|
{
|
||||||
|
for ($i = 0, $p = 3; $i < count($columns); $i++, $p++) {
|
||||||
|
// Create the stored procedure first
|
||||||
|
$storedProcName = "spFormatDecimals" . $i;
|
||||||
|
$procArgs = "@col $dataTypes[$i] OUTPUT";
|
||||||
|
$procCode = "SELECT @col = $columns[$i] FROM $tableName";
|
||||||
|
createProc($conn, $storedProcName, $procArgs, $procCode);
|
||||||
|
|
||||||
|
// Call stored procedure to retrieve output param
|
||||||
|
getOutputParam($conn, $storedProcName, $inputs[$i], $p, $i, $i > 2, $inout);
|
||||||
|
|
||||||
|
dropProc($conn, $storedProcName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
sqlsrv_configure('WarningsReturnAsErrors', 1);
|
||||||
|
|
||||||
|
$conn = AE\connect();
|
||||||
|
if (!$conn) {
|
||||||
|
fatalError("Could not connect.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test error conditions
|
||||||
|
testErrorCases($conn);
|
||||||
|
|
||||||
|
// Create the test table of decimal / numeric data columns
|
||||||
|
$tableName = 'sqlsrvFormatDecimals';
|
||||||
|
|
||||||
|
$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6');
|
||||||
|
$dataTypes = array('decimal(3,0)', 'decimal(4,1)', 'decimal(5,2)', 'numeric(6,3)', 'numeric(7,4)', 'numeric(8, 5)');
|
||||||
|
|
||||||
|
$colMeta = array(new AE\ColumnMeta($dataTypes[0], $columns[0]),
|
||||||
|
new AE\ColumnMeta($dataTypes[1], $columns[1]),
|
||||||
|
new AE\ColumnMeta($dataTypes[2], $columns[2]),
|
||||||
|
new AE\ColumnMeta($dataTypes[3], $columns[3]),
|
||||||
|
new AE\ColumnMeta($dataTypes[4], $columns[4]),
|
||||||
|
new AE\ColumnMeta($dataTypes[5], $columns[5]));
|
||||||
|
AE\createTable($conn, $tableName, $colMeta);
|
||||||
|
|
||||||
|
// Generate random input values based on precision and scale
|
||||||
|
$values = array();
|
||||||
|
$max2 = 1;
|
||||||
|
for ($s = 0, $p = 3; $s < count($columns); $s++, $p++) {
|
||||||
|
// First get a random number
|
||||||
|
$n = rand(1, 6);
|
||||||
|
$neg = ($n % 2 == 0) ? -1 : 1;
|
||||||
|
|
||||||
|
// $n1 is a tiny number, which may or may not be negative
|
||||||
|
$max1 = 5;
|
||||||
|
$n1 = rand(0, $max1) * $neg;
|
||||||
|
|
||||||
|
if ($s > 0) {
|
||||||
|
$max2 *= 10;
|
||||||
|
$n2 = rand(0, $max2);
|
||||||
|
$number = sprintf("%d.%d", $n1, $n2);
|
||||||
|
} else {
|
||||||
|
$number = sprintf("%d", $n1);
|
||||||
|
}
|
||||||
|
|
||||||
|
array_push($values, $number);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert data values as strings
|
||||||
|
$inputData = array($colMeta[0]->colName => $values[0],
|
||||||
|
$colMeta[1]->colName => $values[1],
|
||||||
|
$colMeta[2]->colName => $values[2],
|
||||||
|
$colMeta[3]->colName => $values[3],
|
||||||
|
$colMeta[4]->colName => $values[4],
|
||||||
|
$colMeta[5]->colName => $values[5]);
|
||||||
|
$stmt = AE\insertRow($conn, $tableName, $inputData);
|
||||||
|
if (!$stmt) {
|
||||||
|
var_dump($values);
|
||||||
|
fatalError("Failed to insert data.\n");
|
||||||
|
}
|
||||||
|
sqlsrv_free_stmt($stmt);
|
||||||
|
|
||||||
|
testNoOption($conn, $tableName, $values, $columns, true);
|
||||||
|
testNoOption($conn, $tableName, $values, $columns, false);
|
||||||
|
|
||||||
|
sqlsrv_close($conn);
|
||||||
|
|
||||||
|
// Reconnect with FormatDecimals option set to true
|
||||||
|
$conn = AE\connect(array('FormatDecimals' => true));
|
||||||
|
if (!$conn) {
|
||||||
|
fatalError("Could not connect.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try with setting number decimals to 3 then 2
|
||||||
|
testStmtOption($conn, $tableName, $values, $columns, 3, false);
|
||||||
|
testStmtOption($conn, $tableName, $values, $columns, 3, true);
|
||||||
|
|
||||||
|
testStmtOption($conn, $tableName, $values, $columns, 2, false);
|
||||||
|
testStmtOption($conn, $tableName, $values, $columns, 2, true);
|
||||||
|
|
||||||
|
// Test output parameters
|
||||||
|
testOutputParam($conn, $tableName, $values, $columns, $dataTypes);
|
||||||
|
testOutputParam($conn, $tableName, $values, $columns, $dataTypes, true);
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
sqlsrv_close($conn);
|
||||||
|
|
||||||
|
echo "Done\n";
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
167
test/functional/sqlsrv/sqlsrv_statement_format_money_scales.phpt
Normal file
167
test/functional/sqlsrv/sqlsrv_statement_format_money_scales.phpt
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
--TEST--
|
||||||
|
Test various decimal places of money values (feature request issue 415)
|
||||||
|
--DESCRIPTION--
|
||||||
|
In SQL Server, the maximum precision of money type is 19 with scale 4. Generate a long numeric string and get rid of the last digit to make it a 15-digit-string. Then replace one digit at a time with a dot '.' to make it a decimal input string for testing.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
string(15) ".23456789098765"
|
||||||
|
string(15) "1.3456789098765"
|
||||||
|
string(15) "12.456789098765"
|
||||||
|
string(15) "123.56789098765"
|
||||||
|
string(15) "1234.6789098765"
|
||||||
|
...
|
||||||
|
string(15) "1234567890987.5"
|
||||||
|
string(15) "12345678909876."
|
||||||
|
|
||||||
|
The inserted money data will be
|
||||||
|
0.2346
|
||||||
|
1.3457
|
||||||
|
12.4568
|
||||||
|
123.5679
|
||||||
|
1234.6789
|
||||||
|
...
|
||||||
|
1234567890987.5000
|
||||||
|
12345678909876.0000
|
||||||
|
|
||||||
|
--ENV--
|
||||||
|
PHPT_EXEC=true
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_versions_old.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once('MsCommon.inc');
|
||||||
|
|
||||||
|
$prec = 19;
|
||||||
|
$scale = 4;
|
||||||
|
$dot = '.';
|
||||||
|
|
||||||
|
function createTestTable($conn)
|
||||||
|
{
|
||||||
|
global $prec, $scale;
|
||||||
|
|
||||||
|
// Create the test table
|
||||||
|
$tableName = "sqlsrvFormatMoneyScales";
|
||||||
|
$colMeta = array();
|
||||||
|
|
||||||
|
$max = $prec - $scale;
|
||||||
|
for ($i = 0; $i < $max; $i++) {
|
||||||
|
$column = "col_$i";
|
||||||
|
$dataType = 'money';
|
||||||
|
|
||||||
|
array_push($colMeta, new AE\ColumnMeta($dataType, $column, null, true, true));
|
||||||
|
}
|
||||||
|
AE\createTable($conn, $tableName, $colMeta);
|
||||||
|
|
||||||
|
return $tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertTestData($conn, $tableName)
|
||||||
|
{
|
||||||
|
global $prec, $scale, $dot;
|
||||||
|
|
||||||
|
$digits = substr('1234567890987654321', 0, $prec - $scale);
|
||||||
|
|
||||||
|
$inputData = array();
|
||||||
|
$max = $prec - $scale;
|
||||||
|
|
||||||
|
// Generate input strings - replace the $i-th digit with a dot '.'
|
||||||
|
for ($i = 0; $i < $max; $i++) {
|
||||||
|
$d = $digits[$i];
|
||||||
|
$digits[$i] = $dot;
|
||||||
|
|
||||||
|
$column = "col_$i";
|
||||||
|
$inputData = array_merge($inputData, array($column => $digits));
|
||||||
|
trace($digits);
|
||||||
|
|
||||||
|
// Restore the $i-th digit with its original digit
|
||||||
|
$digits[$i] = $d;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = AE\insertRow($conn, $tableName, $inputData);
|
||||||
|
if (!$stmt) {
|
||||||
|
fatalError("Failed to insert data\n");
|
||||||
|
}
|
||||||
|
sqlsrv_free_stmt($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberFormat($value, $numDecimals)
|
||||||
|
{
|
||||||
|
return number_format($value, $numDecimals, '.', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/****
|
||||||
|
The function testVariousScales() will fetch one column at a time, using scale from 0 up to 4 allowed for that column type.
|
||||||
|
|
||||||
|
For example, if the input string is
|
||||||
|
1234567890.2345
|
||||||
|
|
||||||
|
When fetching data, using scale from 0 to 4, the following values are expected to return:
|
||||||
|
1234567890
|
||||||
|
1234567890.2
|
||||||
|
1234567890.23
|
||||||
|
1234567890.235
|
||||||
|
1234567890.2345
|
||||||
|
****/
|
||||||
|
function testVariousScales($conn, $tableName)
|
||||||
|
{
|
||||||
|
global $prec, $scale;
|
||||||
|
$max = $prec - $scale;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $max; $i++) {
|
||||||
|
$column = "col_$i";
|
||||||
|
|
||||||
|
$query = "SELECT $column as col1 FROM $tableName";
|
||||||
|
// Default case: no formatting
|
||||||
|
$stmt = sqlsrv_query($conn, $query);
|
||||||
|
if (!$stmt) {
|
||||||
|
fatalError("In testVariousScales: failed in default case\n");
|
||||||
|
}
|
||||||
|
if ($obj = sqlsrv_fetch_object($stmt)) {
|
||||||
|
trace("\n$obj->col1\n");
|
||||||
|
$input = $obj->col1;
|
||||||
|
} else {
|
||||||
|
fatalError("In testVariousScales: sqlsrv_fetch_object failed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, format how many decimals to be displayed
|
||||||
|
$query = "SELECT $column FROM $tableName";
|
||||||
|
for ($j = 0; $j <= $scale; $j++) {
|
||||||
|
$options = array('FormatDecimals' => true,'DecimalPlaces' => $j);
|
||||||
|
$stmt = sqlsrv_query($conn, $query, array(), $options);
|
||||||
|
|
||||||
|
if (sqlsrv_fetch($stmt)) {
|
||||||
|
$value = sqlsrv_get_field($stmt, 0);
|
||||||
|
trace("$value\n");
|
||||||
|
|
||||||
|
$expected = numberFormat($input, $j);
|
||||||
|
if ($value !== $expected) {
|
||||||
|
echo "testVariousScales ($j): Expected $expected but got $value\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fatalError("Round $i scale $j: sqlsrv_fetch failed\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
sqlsrv_configure('WarningsReturnAsErrors', 1);
|
||||||
|
|
||||||
|
// Default is no formatting, but set it to false anyway
|
||||||
|
$conn = AE\connect(array('FormatDecimals' => false));
|
||||||
|
if (!$conn) {
|
||||||
|
fatalError("Could not connect.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
$tableName = createTestTable($conn);
|
||||||
|
insertTestData($conn, $tableName);
|
||||||
|
testVariousScales($conn, $tableName);
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
|
||||||
|
sqlsrv_close($conn);
|
||||||
|
|
||||||
|
echo "Done\n";
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
261
test/functional/sqlsrv/sqlsrv_statement_format_money_types.phpt
Normal file
261
test/functional/sqlsrv/sqlsrv_statement_format_money_types.phpt
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
--TEST--
|
||||||
|
Test the options for formatting money data (feature request issue 415)
|
||||||
|
--DESCRIPTION--
|
||||||
|
Test how money data in the fetched values can be formatted by using the connection
|
||||||
|
option FormatDecimals and DecimalPlaces, the latter works only with integer
|
||||||
|
values. No effect on other operations like insertion or update.
|
||||||
|
|
||||||
|
The option DecimalPlaces only affects money/smallmoney fields. If its value is out of range, for example, it's negative or larger than the original scale, then its value will be ignored.
|
||||||
|
|
||||||
|
The underlying data will not be altered, but formatted results may likely be rounded up (e.g. .2954 will be displayed as 0.30 if the user wants only two decimals). For this reason, it is not recommended to use formatted money values as inputs to any calculation.
|
||||||
|
|
||||||
|
The corresponding statement options always override the inherited values from the connection object. Setting FormatDecimals to false will automatically turn off any formatting of decimal data in the result set, ignoring DecimalPlaces value.
|
||||||
|
|
||||||
|
By only setting FormatDecimals to true will add the leading zeroes, if missing. For output params, missing zeroes will be added if either SQLSRV_SQLTYPE_MONEY or SQLSRV_SQLTYPE_SMALLMONEY is set as the SQLSRV SQL Type.
|
||||||
|
--ENV--
|
||||||
|
PHPT_EXEC=true
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_versions_old.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once('MsCommon.inc');
|
||||||
|
|
||||||
|
function numberFormat($value, $numDecimals)
|
||||||
|
{
|
||||||
|
return number_format($value, $numDecimals, '.', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFloatTypes($conn)
|
||||||
|
{
|
||||||
|
global $numDigits; // inherited from connection option
|
||||||
|
|
||||||
|
// This test with the float types of various number of bits, which are retrieved
|
||||||
|
// as numbers by default. When fetched as strings, no formatting is done,
|
||||||
|
// because connection options for formatting have no effect
|
||||||
|
$epsilon = 0.001;
|
||||||
|
$nColumns = 5;
|
||||||
|
|
||||||
|
$values = array();
|
||||||
|
for ($i = 0; $i < $nColumns; $i++) {
|
||||||
|
$n1 = rand(1, 100);
|
||||||
|
$n2 = rand(1, 100);
|
||||||
|
$neg = ($i % 2 == 0) ? -1 : 1;
|
||||||
|
|
||||||
|
$n = $neg * $n1 / $n2;
|
||||||
|
array_push($values, $n);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = "SELECT CONVERT(float(1), $values[0]),
|
||||||
|
CONVERT(float(12), $values[1]),
|
||||||
|
CONVERT(float(24), $values[2]),
|
||||||
|
CONVERT(float(36), $values[3]),
|
||||||
|
CONVERT(float(53), $values[4])";
|
||||||
|
|
||||||
|
$stmt = sqlsrv_query($conn, $query);
|
||||||
|
$floats = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
|
||||||
|
if (!$floats) {
|
||||||
|
echo "testFloatTypes: sqlsrv_fetch_array failed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number of decimals in each of the results will vary
|
||||||
|
$stmt = sqlsrv_query($conn, $query);
|
||||||
|
if (sqlsrv_fetch($stmt)) {
|
||||||
|
for ($i = 0; $i < count($values); $i++) {
|
||||||
|
$floatStr = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
|
||||||
|
$floatVal = floatval($floatStr);
|
||||||
|
|
||||||
|
// Check if the numbers of decimal digits are the same
|
||||||
|
// It is highly unlikely but not impossible
|
||||||
|
$numbers = explode('.', $floatStr);
|
||||||
|
$len = strlen($numbers[1]);
|
||||||
|
if ($len == $numDigits && $floatVal != $floats[$i]) {
|
||||||
|
echo "Expected $floats[$i] but returned ";
|
||||||
|
var_dump($floatVal);
|
||||||
|
} else {
|
||||||
|
$diff = abs($floatVal - $floats[$i]) / $floats[$i];
|
||||||
|
if ($diff > $epsilon) {
|
||||||
|
echo "$diff: Expected $floats[$i] but returned ";
|
||||||
|
var_dump($floatVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "testFloatTypes: sqlsrv_fetch failed\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyMoneyValues($conn, $numDigits, $query, $values, $override)
|
||||||
|
{
|
||||||
|
if ($override) {
|
||||||
|
$options = array('DecimalPlaces' => $numDigits);
|
||||||
|
$stmt = sqlsrv_query($conn, $query, array(), $options);
|
||||||
|
} else {
|
||||||
|
$stmt = sqlsrv_query($conn, $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
|
||||||
|
trace("\nverifyMoneyValues:\n");
|
||||||
|
for ($i = 0; $i < count($values); $i++) {
|
||||||
|
$value = numberFormat($values[$i], $numDigits);
|
||||||
|
trace("$results[$i], $value\n");
|
||||||
|
|
||||||
|
if ($value !== $results[$i]) {
|
||||||
|
echo "verifyMoneyValues ($override, $numDigits): Expected $value but got $results[$i]\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyMoneyFormatting($conn, $query, $values, $format)
|
||||||
|
{
|
||||||
|
if ($format) {
|
||||||
|
// Set FormatDecimals to true to turn on formatting, but setting
|
||||||
|
// DecimalPlaces to a negative number, which will be ignored.
|
||||||
|
$nDigits = -1;
|
||||||
|
$options = array('FormatDecimals' => true, 'DecimalPlaces' => $nDigits);
|
||||||
|
$stmt = sqlsrv_query($conn, $query, array(), $options);
|
||||||
|
} else {
|
||||||
|
// Set FormatDecimals to false to turn off formatting.
|
||||||
|
// This should override the inherited connection
|
||||||
|
// options, and by default, money and smallmoney types
|
||||||
|
// have scale of 4 digits
|
||||||
|
$options = array('FormatDecimals' => false);
|
||||||
|
$stmt = sqlsrv_query($conn, $query, array(), $options);
|
||||||
|
}
|
||||||
|
$results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($values); $i++) {
|
||||||
|
$default = numberFormat($values[$i], 4);
|
||||||
|
if (!$format) {
|
||||||
|
// No formatting - should drop the leading zero, if exists
|
||||||
|
if (abs($values[$i]) < 1) {
|
||||||
|
$default = str_replace('0.', '.', $default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($default !== $results[$i]) {
|
||||||
|
echo "verifyMoneyFormatting ($format): Expected default $default but got $results[$i]\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutputParam($conn, $spProcName, $input, $money, $inout)
|
||||||
|
{
|
||||||
|
$outString = '0.0';
|
||||||
|
$dir = ($inout) ? SQLSRV_PARAM_INOUT : SQLSRV_PARAM_OUT;
|
||||||
|
$sqlType = ($money) ? SQLSRV_SQLTYPE_MONEY : SQLSRV_SQLTYPE_SMALLMONEY;
|
||||||
|
|
||||||
|
$outSql = AE\getCallProcSqlPlaceholders($spProcName, 1);
|
||||||
|
$stmt = sqlsrv_prepare($conn, $outSql,
|
||||||
|
array(array(&$outString, $dir, null, $sqlType)));
|
||||||
|
if (!$stmt) {
|
||||||
|
fatalError("getOutputParam: failed when preparing to call $spProcName");
|
||||||
|
}
|
||||||
|
if (!sqlsrv_execute($stmt)) {
|
||||||
|
fatalError("getOutputParam: failed to execute procedure $spProcName");
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatDecimals only add leading zeroes, but do
|
||||||
|
// not support controlling decimal places, so
|
||||||
|
// use scale 4 for money/smallmoney types
|
||||||
|
$expected = numberFormat($input, 4);
|
||||||
|
trace("getOutputParam result is $outString and expected $expected\n");
|
||||||
|
|
||||||
|
if ($outString !== $expected) {
|
||||||
|
echo "getOutputParam ($inout): Expected $expected but got $outString\n";
|
||||||
|
var_dump($expected);
|
||||||
|
var_dump($outString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testOutputParam($conn)
|
||||||
|
{
|
||||||
|
// Create a table for testing output param
|
||||||
|
$tableName = 'sqlsrvMoneyFormats';
|
||||||
|
$values = array(0.12345, 0.34567);
|
||||||
|
$query = "SELECT CONVERT(smallmoney, $values[0]) AS m1,
|
||||||
|
CONVERT(money, $values[1]) AS m2
|
||||||
|
INTO $tableName";
|
||||||
|
|
||||||
|
$stmt = sqlsrv_query($conn, $query);
|
||||||
|
for ($i = 0; $i < 2; $i++) {
|
||||||
|
// Create the stored procedure first
|
||||||
|
$storedProcName = "spMoneyFormats" . $i;
|
||||||
|
$dataType = ($i == 0) ? 'smallmoney' : 'money';
|
||||||
|
$procArgs = "@col $dataType OUTPUT";
|
||||||
|
$column = 'm' . ($i + 1);
|
||||||
|
$procCode = "SELECT @col = $column FROM $tableName";
|
||||||
|
createProc($conn, $storedProcName, $procArgs, $procCode);
|
||||||
|
|
||||||
|
getOutputParam($conn, $storedProcName, $values[$i], $i, false);
|
||||||
|
getOutputParam($conn, $storedProcName, $values[$i], $i, true);
|
||||||
|
|
||||||
|
dropProc($conn, $storedProcName);
|
||||||
|
}
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testMoneyTypes($conn)
|
||||||
|
{
|
||||||
|
global $numDigits; // inherited from connection option
|
||||||
|
|
||||||
|
// With money and smallmoney types, which are essentially decimal types
|
||||||
|
// As of today, ODBC driver does not support Always Encrypted feature with money / smallmoney
|
||||||
|
$values = array();
|
||||||
|
$nColumns = 6;
|
||||||
|
for ($i = 0; $i < $nColumns; $i++) {
|
||||||
|
// First get a random number
|
||||||
|
$n = rand(0, 10);
|
||||||
|
$neg = ($n % 2 == 0) ? -1 : 1;
|
||||||
|
|
||||||
|
// $n1 may or may not be negative
|
||||||
|
$max = 10;
|
||||||
|
$n1 = rand(0, $max) * $neg;
|
||||||
|
$n2 = rand(1, $max * 1000);
|
||||||
|
|
||||||
|
$number = sprintf("%d.%d", $n1, $n2);
|
||||||
|
array_push($values, $number);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = "SELECT CONVERT(smallmoney, $values[0]),
|
||||||
|
CONVERT(money, $values[1]),
|
||||||
|
CONVERT(smallmoney, $values[2]),
|
||||||
|
CONVERT(money, $values[3]),
|
||||||
|
CONVERT(smallmoney, $values[4]),
|
||||||
|
CONVERT(money, $values[5])";
|
||||||
|
|
||||||
|
// Do not override the connection attributes
|
||||||
|
verifyMoneyValues($conn, $numDigits, $query, $values, false);
|
||||||
|
// Next, override statement attribute to set number of
|
||||||
|
// decimal places to 0
|
||||||
|
verifyMoneyValues($conn, 0, $query, $values, true);
|
||||||
|
|
||||||
|
// Set Formatting attribute to true then false
|
||||||
|
verifyMoneyFormatting($conn, $query, $values, true);
|
||||||
|
verifyMoneyFormatting($conn, $query, $values, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
sqlsrv_configure('WarningsReturnAsErrors', 1);
|
||||||
|
|
||||||
|
$numDigits = 2;
|
||||||
|
|
||||||
|
$conn = AE\connect(array('FormatDecimals' => true, 'DecimalPlaces' => $numDigits));
|
||||||
|
if (!$conn) {
|
||||||
|
fatalError("Could not connect.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// First to test if leading zero is added
|
||||||
|
testMoneyTypes($conn);
|
||||||
|
|
||||||
|
// Also test using regular floats
|
||||||
|
testFloatTypes($conn);
|
||||||
|
|
||||||
|
// Test output params
|
||||||
|
testOutputParam($conn);
|
||||||
|
|
||||||
|
sqlsrv_close($conn);
|
||||||
|
|
||||||
|
echo "Done\n";
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
|
@ -42,6 +42,12 @@ class my_stream {
|
||||||
|
|
||||||
function stream_seek($offset, $whence)
|
function stream_seek($offset, $whence)
|
||||||
{
|
{
|
||||||
|
// For the purpose of this test only support SEEK_SET to $offset 0
|
||||||
|
if ($whence == SEEK_SET && $offset == 0) {
|
||||||
|
$this->total_read = $offset;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue