diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..de98b18d0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,34 @@ +sudo: required + +os: linux +dist: trusty + +services: + - docker + +env: + global: + - REPORT_EXIT_STATUS=1 + - ACCEPT_EULA=Y + - PHPSQLDIR=/REPO/msphpsql-PHP-7.0-Linux + - SQLSERVERHOSTNAME=sql + +before_install: + - docker pull microsoft/mssql-server-linux + +install: + - docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql . + - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=' -p 1433:1433 --name=$SQLSERVERHOSTNAME -d microsoft/mssql-server-linux + +script: + - docker run -e TRAVIS_JOB_ID -t -d -w $PHPSQLDIR --link $SQLSERVERHOSTNAME --name=client msphpsql-dev + - docker ps -a + - docker exec client php ./source/pdo_sqlsrv/run-tests.php ./test/pdo_sqlsrv/*.phpt + - docker exec client php ./source/sqlsrv/run-tests.php ./test/sqlsrv/*.phpt + - docker exec client coveralls -e ./source/shared/ --gcov-options '\-lp' + - docker stop client + - docker ps -a + + +notifications: + email: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..3b78e5999 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,212 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) + +## Windows/Linux 4.1.6 - 2017-02-03 +Updated PECL release packages. Here is the list of updates: +### Added +- Merged Linux and Windows code. +- Enabled connection pooling with unixODBC. To enable pooling: + - in odbcinst.ini, add `Pooling=Yes` to the `[ODBC]` section and a positive `CPTimeout` value to `[ODBC Driver 13 for SQL Server]` section. See http://www.unixodbc.org/doc/conn_pool.html for detailed instructions. + +###Fixed +- Fixed issues with sqlsrv_has_rows() to prevent it from moving statement cursor ([issue #37](https://github.com/Microsoft/msphpsql/issues/37)). +- Fixed sqlsrv client buffer size to only allow positive integers ([issue #228](https://github.com/Microsoft/msphpsql/issues/228)). +- Fixed PECL installation errors when PHP was installed from source ([issue #213](https://github.com/Microsoft/msphpsql/issues/213)). +- Fixed segmentation fault with PDOStatement::getColumnMeta() when the supplied column index is out of range ([issue #224](https://github.com/Microsoft/msphpsql/issues/224)). +- Fixed the assertion error (Linux) when fetching data from a binary column using the binary encoding ([issue #226](https://github.com/Microsoft/msphpsql/issues/226)). + +## Windows 4.1.5 - 2017-01-19 +Updated Windows drivers (4.1.5) compiled with PHP 7.0.14 and 7.1 are available. Here is the list of updates: + +### Added +- Added Unicode Column name support([issue #138](https://github.com/Microsoft/msphpsql/issues/138)). + +###Fixed +- Fixed issue output parameters bound to empty string ([issue #182](https://github.com/Microsoft/msphpsql/issues/182)). +- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)). + + +### Changed +- Code structure is updated to facilitate the development; shared codes between both drivers are moved to "shared" folder to avoid code duplication issues in development. To build the driver from source: + - if you are building the driver from source using PHP source, copy the "shared" folder as a subfolder to both the sqlsrv and pdo_sqlsrv folders. + +## Linux 4.0.8 - 2016-12-19 +Production release of Linux drivers is available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. Here is the list of updates: + +### Added +- Added `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` attribute support in PDO_SQLSRV driver.`SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag handles numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, + `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` + If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. + Note for exceptions: + - When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string. + - When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off. +- Added Unicode Column name support([issue #138](https://github.com/Microsoft/msphpsql/issues/138)). + +###Fixed +- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)). +- Fixed precision issues when double data type returned as strings using buffered queries in PDO_SQLSRV driver. +- Fixed issue with buffered cursor in PDO_SQLSRV driver when CharacterSet is UTF-8 ([issue #192](https://github.com/Microsoft/msphpsql/issues/192)). +- Fixed segmentation fault in error cases when error message is returned with emulate prepare attribute is set to true in PDO_SQLSRV driver. +- Fixed issue with empty output parameters on stored procedure([issue #182](https://github.com/Microsoft/msphpsql/issues/182)). +- Fixed memory leaks in buffered queries. + + +## Linux 4.0.7 - 2016-11-23 +Linux drivers compiled with PHP 7.0.13 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. + +### Added +- Ported buffered cursor to Linux. + +### Changed +- Code structure is updated to facilitate the development; shared codes between both drivers are moved to "shared" folder to avoid code duplication issues in development. To build the driver from source, use "packagize" script as follows: + - if you are using the phpize, clone or download the �source�, run the script within the �source� directory and then run phpize. + - if you are building the driver from source using PHP source, give the path to the PHP source to the script. + +### Fixed + - Fixed string truncation error when inserting long strings. + - Fixed querying from large column name. + - Fixed issue with trailing garbled characters in string retrieval. + - Fixed issue with detecting invalid UTF-16 strings coming from server. + - Fixed issues with binding input text, ntext, and image parameters. + +## Linux 4.0.6 - 2016-10-25 +Linux drivers compiled with PHP 7.0.12 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. + +### Changed + - Drivers versioning has been redesigned as Major#.Minor#.Release#.Build#. Build number is specific to binaries and it doesn't match with the number on the source. + - Compiler C++ 11 is enabled in config file. + +### Fixed + - Fixed the issue with duplicate warning messages in PDO_SQLSRV drivers when error mode is set to PDO::ERRMODE_WARNING. + - Fixed the issue with invalid UTF-8 strings, those are detected before executing any queries and proper error message is returned. + - Fixed segmentation fault in sqlsrv_fetch_object and sqlsrv_fetch_array function. + +## Windows 4.1.4 - 2016-10-25 +Windows drivers compiled with PHP 7.0.12 and 7.1 are available. Here is the list of updates: + +### Changed + - Drivers versioning has been redesigned as Major#.Minor#.Release#.Build#. Build number is specific to binaries and it doesn't match with the number on the source. + +### Fixed + - Fixed the issue with duplicate warning messages in PDO_SQLSRV drivers when error mode is set to PDO::ERRMODE_WARNING. + +## Linux 4.0.5 - 2016-10-04 +Linux drivers compiled with PHP 7.0.11 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. + +### Fixed + - Fixed segmentation fault when calling PDOStatement::getColumnMeta on RedHat 7.2. + - Fixed segmentation fault when fetch mode is set to ATTR_EMULATE_PREPARES on RedHat 7.2. + - Fixed [issue #139](https://github.com/Microsoft/msphpsql/issues/139) : sqlsrv_fetch_object calls custom class constructor in static context and outputs an error. + +## Windows 4.1.3 - 2016-10-04 +Updated Windows drivers (4.1.3) compiled with PHP 7.0.11 and 7.1.0RC3 are available. Here is the list of updates: + +### Fixed +- Fixed [issue #139](https://github.com/Microsoft/msphpsql/issues/139) : sqlsrv_fetch_object calls custom class constructor in static context and outputs an error. + +##Linux 4.0.4 - 2016-09-09 +Linux drivers compiled with PHP 7.0.10 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. + +### Added +- Added Support for EMULATE_PREPARE feature. +- Added following integer SQL Types constants for cases which function-like SQL types constants cannot be used e.g. type comparison: + + SQLSRV constant | Typical SQL Server data type | SQL type identifier + ------------ | ----------------------- | ---------------------- + SQLSRV_SQLTYPE_DECIMAL | decimal | SQL_DECIMAL + SQLSRV_SQLTYPE_NUMERIC | numeric | SQL_NUMERIC + SQLSRV_SQLTYPE_CHAR | char | SQL_CHAR + SQLSRV_SQLTYPE_NCHAR | nchar | SQL_WCHAR + SQLSRV_SQLTYPE_VARCHAR | varchar | SQL_VARCHAR + SQLSRV_SQLTYPE_NVARCHAR | nvarchar | SQL_WVARCHAR + SQLSRV_SQLTYPE_BINARY | binary | SQL_BINARY + SQLSRV_SQLTYPE_VARBINARY | varbinary | SQL_VARBINARY + + Note: These constants should be used in type comparison operations (refer to issue [#87](https://github.com/Microsoft/msphpsql/issues/87) and [#99](https://github.com/Microsoft/msphpsql/issues/99) ), and don't replace the function like constants with similar syntax. For binding parameters you should use the function-like constants, otherwise you'll get an error. + +### Fixed + - Fixed undefined symbols at SQL* error when loading the drivers. + - Fixed undefined symbol issues at LocalAlloc and LocalFree on RedHat7.2. + - Fixed [issue #144](https://github.com/Microsoft/msphpsql/issues/144) (floating point exception). + - Fixed [issue #119](https://github.com/Microsoft/msphpsql/issues/119) (modifying class name in sqlsrv_fetch_object). + +## Windows 4.1.2 - 2016-09-09 +Updated Windows drivers (4.1.2) compiled with PHP 7.0.10 are available. Here is the list of updates: + +### Added +- Added following integer SQL Types constants for cases which function-like SQL types constants cannot be used e.g. type comparison: + + SQLSRV constant | Typical SQL Server data type | SQL type identifier + ------------ | ----------------------- | ---------------------- + SQLSRV_SQLTYPE_DECIMAL | decimal | SQL_DECIMAL + SQLSRV_SQLTYPE_NUMERIC | numeric | SQL_NUMERIC + SQLSRV_SQLTYPE_CHAR | char | SQL_CHAR + SQLSRV_SQLTYPE_NCHAR | nchar | SQL_WCHAR + SQLSRV_SQLTYPE_VARCHAR | varchar | SQL_VARCHAR + SQLSRV_SQLTYPE_NVARCHAR | nvarchar | SQL_WVARCHAR + SQLSRV_SQLTYPE_BINARY | binary | SQL_BINARY + SQLSRV_SQLTYPE_VARBINARY | varbinary | SQL_VARBINARY + + Note: These constants should be used in type comparison operations (refer to issue [#87](https://github.com/Microsoft/msphpsql/issues/87) and [#99](https://github.com/Microsoft/msphpsql/issues/99) ), and don't replace the function like constants with similar syntax. For binding parameters you should use the function-like constants, otherwise you'll get an error. + +### Fixed + - Fixed [issue #119](https://github.com/Microsoft/msphpsql/issues/119) (modifying class name in sqlsrv_fetch_object). + + +## Linux 4.0.3 - 2016-08-23 +Linux drivers compiled with PHP 7.0.9 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. + +### Fixed + - Fixed data corruption in binding integer parameters. + - Fixed invalid sql_display_size error. + - Fixed issue with invalid statement options. + - Fixed binding bit parameters. + +## Windows 4.1.1 - 2016-08-22 +Updated Windows drivers(4.1.1) compiled with PHP 7.0.9 are available and include a couple of bug fixes: + +### Fixed +- Fixed issue with storing integers in varchar field. +- Fixed issue with invalid connection handler if one connection fails. +- Fixed crash when emulate prepare is on. + + +## Linux 4.0.2 - 2016-07-29 + +### Fixed + - The PDO_SQLSRV driver no longer requires PDO to be built as a shared extension. + - Fixed an issue with format specifiers in error messages. + - Fixed a segmentation fault when using buffered cursors. + - Fixed an issue whereby calling sqlsrv_rows_affected on an empty result set would return a null result instead of 0. + - Fixed an issue with error messages when there is an error in sizes in SQLSRV_SQLTYPE_*. + +## Windows 4.1.0 - 2016-07-28 + +### Fixed + - `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag is added to PDO_SQLSRV driver to handle numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, + `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` + If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. + Note for exceptions: + - When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string. + - When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off. + - Fixed float truncation when using buffered query. + - Fixed handling of Unicode strings and binary when emulate prepare is on in `PDOStatement::bindParam`. To bind a unicode string, `PDO::SQLSRV_ENCODING_UTF8` should be set using `$driverOption`, and to bind a string to column of Sql type binary, `PDO::SQLSRV_ENCODING_BINARY` should be set. + - Fixed string truncation in bind output parameters when the size is not set and the length of initialized variable is less than the output. + - Fixed bind string parameters as bidirectional parameters (`PDO::PARAM_INPUT_OUTPUT `) in PDO_SQLSRV driver. Note for output or bidirectional parameters, `PDOStatement::closeCursor` should be called to get the output value. + + +## Linux 4.0.1 - 2016-07-09 + +### Added +- Added support for PDO_SQLSRV driver on RedHat 7. + +###Changed +- Improved handling varchar(MAX). +- Improved handling basic stream operations. + +## Linux 4.0.0 - 2016-06-11 + +### Added +- The early technical preview (ETP) for SQLSRV and PDO_SQLSRV drivers for Linux with basic functionalities is now available. The SQLSRV driver has been built and tested on Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2, and PDO_SQLSRV driver has been built and tested on Ubuntu 15.04, Ubuntu 16.04. diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql new file mode 100644 index 000000000..4c65931f9 --- /dev/null +++ b/Dockerfile-msphpsql @@ -0,0 +1,72 @@ +#Download base image ubuntu 16.04 + +FROM ubuntu:16.04 + +# Update Ubuntu Software repository +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \ + apt-get -y install \ + apt-transport-https \ + apt-utils \ + autoconf \ + curl \ + g++ \ + gcc \ + git \ + lcov \ + libxml2-dev \ + locales \ + make \ + php7.0 \ + php7.0-dev \ + python-pip \ + re2c \ + unixodbc-dev \ + unzip && apt-get clean + +ARG PHPSQLDIR=/REPO/msphpsql-PHP-7.0-Linux + +# set locale to utf-8 +RUN locale-gen en_US.UTF-8 +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' + +#install ODBC driver +RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - +RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list + +#RUN echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/mssql-ubuntu-xenial-release/ xenial main" > /etc/apt/sources.list.d/mssqlpreview.list +#RUN apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893 +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql mssql-tools + +#install coveralls +RUN pip install --upgrade pip && pip install cpp-coveralls + +#Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html ) +#One option is to get source from zip file of repository. + +#another option is to copy source to build directory on image +RUN mkdir -p $PHPSQLDIR +COPY . $PHPSQLDIR + +WORKDIR $PHPSQLDIR/source/ + +RUN chmod +x ./packagize.sh +RUN /bin/bash -c "./packagize.sh" + +RUN echo "extension = pdo_sqlsrv.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` +RUN echo "extension = sqlsrv.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` + +WORKDIR $PHPSQLDIR/source/sqlsrv +RUN phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install + +WORKDIR $PHPSQLDIR/source/pdo_sqlsrv +RUN phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install + +# set name of sql server host to use +WORKDIR $PHPSQLDIR/test/pdo_sqlsrv +RUN sed -i -e 's/localhost/sql/g' autonomous_setup.php + +WORKDIR $PHPSQLDIR/test/sqlsrv +RUN sed -i -e 's/localhost/sql/g' autonomous_setup.php + +ENV REPORT_EXIT_STATUS 1 +ENV TEST_PHP_EXECUTABLE /usr/bin/php diff --git a/LICENSE b/LICENSE index d332c60cd..b52d9d433 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright(c) 2016 Microsoft Corporation +Copyright(c) 2017 Microsoft Corporation All rights reserved. MIT License diff --git a/README.md b/README.md index 2b5eaa0c4..b87ad74c5 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,39 @@ # Microsoft Drivers for PHP for SQL Server -**Welcome to the Microsoft Drivers for PHP for SQL Server PHP 7 on Windows** - -**Note:** For the PHP 5 project, see the PHP 5 branch, and for Linux project, see PHP 7.0-Linux branch. +**Welcome to the Microsoft Drivers for PHP for SQL Server PHP 7** The Microsoft Drivers for PHP for SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PDO for accessing data in all editions of SQL Server 2005 and later (including Azure SQL DB). These drivers rely on the Microsoft ODBC Driver for SQL Server to handle the low-level communication with SQL Server. This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7 with improvements on both drivers and some limitations (see Limitations below for details). Upcoming release(s) will contain more functionality, bug fixes, and more (see Plans below for more details). -The Microsoft Drivers for PHP for SQL Server Team +SQL Server Team +###Status of Most Recent Builds +| AppVeyor (Windows) |Travis CI (Linux) | Coverage Status +|-------------------------|--------------------------| ------------------ +| [![av-image][]][av-site]| [![tv-image][]][tv-site] |[![Coverage Status][]][coveralls-site] + +[av-image]: https://ci.appveyor.com/api/projects/status/github/Microsoft/msphpsql?branch=dev&svg=true +[av-site]: https://ci.appveyor.com/project/Microsoft-PHPSQL/msphpsql +[tv-image]: https://travis-ci.org/Microsoft/msphpsql.svg?branch=dev +[tv-site]: https://travis-ci.org/Microsoft/msphpsql/ +[Coverage Status]: https://coveralls.io/repos/github/Microsoft/msphpsql/badge.svg?branch=dev +[coveralls-site]: https://coveralls.io/github/Microsoft/msphpsql?branch=dev ##Get Started + * [**Ubuntu + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php-ubuntu) * [**RedHat + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php-rhel) * [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php-windows) -##Announcements - -**August 22, 2016** (4.1.1): Updated Windows drivers built and compiled with PHP 7.0.9 are available and include a couple of bug fixes: -- Fixed issue with storing integers in varchar field. -- Fixed issue with invalid connection handler if one connection fails. -- Fixed crash when emulate prepare is on. - -**July 28, 2016** (4.1.0): Thanks to the community's input, this release expands drivers functionalities and also includes some bug fixes: +##Announcements - - `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag is added to PDO_SQLSRV driver to handle numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, - `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` - If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. - Note for exceptions: - - When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string. - - When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off. - - Fixed float truncation when using buffered query. - - Fixed handling of Unicode strings and binary when emulate prepare is on in `PDOStatement::bindParam`. To bind a unicode string, `PDO::SQLSRV_ENCODING_UTF8` should be set using `$driverOption`, and to bind a string to column of Sql type binary, `PDO::SQLSRV_ENCODING_BINARY` should be set. - - Fixed string truncation in bind output parameters when the size is not set and the length of initialized variable is less than the output. - - Fixed bind string parameters as bidirectional parameters (`PDO::PARAM_INPUT_OUTPUT `) in PDO_SQLSRV driver. Note for output or bidirectional parameters, `PDOStatement::closeCursor` should be called to get the output value. +**December 19, 2016**: We are delighted announce that production release for PHP Linux Driver for SQL Server is available. PECL packages (4.0.8) are updated with the latest changes, and Linux binaries (4.0.8) compiled with PHP 7.0.14 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. For complete list of changes please visit [CHANGELOG](https://github.com/Microsoft/msphpsql/blob/dev/CHANGELOG.md) file. -**July 06, 2016**: PHP Driver 4.0 for SQL Server with PHP 7 support is now GA. You can get the binaries [HERE](https://github.com/Azure/msphpsql/releases) or download the exe from the [Microsoft Download Center](https://www.microsoft.com/en-us/download/details.aspx?id=20098). - Please visit the [blog][blog] for more announcements. -## Build +## Build (Windows) Note: if you prefer, you can use the pre-compiled binary found [HERE](https://github.com/Azure/msphpsql/releases) @@ -65,7 +57,7 @@ You must first be able to build PHP 7 without including these extensions. For h This software has been compiled and tested under PHP 7.0.8 using the Visual C++ 2015 compiler. -## Install +## Install (Windows) ####Prerequisites @@ -80,6 +72,243 @@ This software has been compiled and tested under PHP 7.0.8 using the Visual C++ 3. Restart the Web server. +## Install (Linux) +Following instructions shows how to install PHP 7.x, Microsoft ODBC driver, apache, and Microsoft PHP drivers on Ubuntu 15, 16 and RedHat 7. To see how to get PHP SQLSRV drivers running on Debian, please visit [Wiki](https://github.com/Microsoft/msphpsql/wiki/Dockerfile-for-getting-pdo_sqlsrv-for-PHP-7.0-on-Debian-in-3-ways). Note that Debian is not officially supported and this instruction hasn't been tested in our test lab. + +### Step 1: Install PHP (unless already installed) + +#### PHP 7.0 + +**Ubuntu 15.04, Ubuntu 15.10** + + sudo su + sh -c 'echo "deb http://packages.dotdeb.org jessie all \ndeb-src http://packages.dotdeb.org jessie all" >> /etc/apt/sources.list' + apt-get update + apt-get install php7.0 php7.0-fpm php-pear php7.0-dev mcrypt php7.0-mcrypt php-mbstring php7.0-xml re2c gcc g++ + + +**Ubuntu 16.04** + + sudo su + apt-get update + apt-get -y install php7.0 mcrypt php7.0-mcrypt php-mbstring php-pear php7.0-dev php7.0-xml re2c gcc g++ + + +**RedHat 7** + + sudo su + wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm + rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm + subscription-manager repos --enable=rhel-7-server-optional-rpms + yum-config-manager --enable remi-php70 + yum update + yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc + + + +#### PHP 7.1 + + +**Ubuntu 16.04** + + sudo su + add-apt-repository ppa:ondrej/php + apt-get update + apt-get -y install php7.1 mcrypt php7.1-mcrypt php-mbstring php-pear php7.1-dev + +**RedHat 7** + + sudo su + wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm + rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm + subscription-manager repos --enable=rhel-7-server-optional-rpms + yum-config-manager --enable remi-php71 + yum update + yum install php php-pdo php-xml php-pear php-devel + + + + +### Step 2: Install pre-requisites + +**Ubuntu 15.04** + + sudo su + sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/mssql-ubuntu-vivid-release/ vivid main" > /etc/apt/sources.list.d/mssqlpreview.list' + sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893 + apt-get update + apt-get install msodbcsql + #for silent install use ACCEPT_EULA=Y apt-get install msodbcsql + sudo apt-get install unixodbc-dev-utf16 + +**Ubuntu 15.10** + + sudo su + curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - + curl https://packages.microsoft.com/config/ubuntu/15.10/prod.list > /etc/apt/sources.list.d/mssql-release.list + exit + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install msodbcsql mssql-tools + sudo apt-get install unixodbc-dev-utf16 + + +**Ubuntu 16.04** + + sudo su + curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - + curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list + exit + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install msodbcsql mssql-tools + sudo apt-get install unixodbc-dev-utf16 + +**RedHat 7** + + sudo su + curl https://packages.microsoft.com/config/rhel/7/prod.repo > /etc/yum.repos.d/mssql-release.repo + exit + sudo yum update + sudo yum remove unixODBC #to avoid conflicts + sudo ACCEPT_EULA=Y yum install msodbcsql mssql-tools + sudo yum install unixODBC-utf16-devel + + + + +*Note: On Ubuntu, you need to make sure you install PHP 7 before you proceed to step 2. The Microsoft PHP Drivers for SQL Server will only work for PHP 7+. + +### Step 3: Install Apache + +####PHP 7.0 + +**Ubuntu** + + sudo apt-get install libapache2-mod-php7.0 + sudo apt-get install apache2 + +**RedHat** + + sudo yum install httpd + +####PHP 7.1 + +**Ubuntu** + + sudo apt-get install libapache2-mod-php7.1 + sudo apt-get install apache2 + +**RedHat** + + sudo yum install httpd + + +### Step 4: Install the Microsoft PHP Drivers for SQL Server + + sudo pecl install sqlsrv + sudo pecl install pdo_sqlsrv + +*Note: it installs the stable version, for specific version you should set the version. For example, `sudo pecl install sqlsrv-4.0.8` + + +### Step 5: Add the Microsoft PHP Drivers for SQL Server to php.ini + + +####PHP 7.0 + +**Ubuntu** + + echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/apache2/php.ini + echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/apache2/php.ini + echo "extension=/usr/lib/php/20151012/sqlsrv.so" >> /etc/php/7.0/cli/php.ini + echo "extension=/usr/lib/php/20151012/pdo_sqlsrv.so" >> /etc/php/7.0/cli/php.ini + + +**RedHat** + + echo "extension= /usr/lib64/php/modules/sqlsrv.so" > /etc/php.d/sqlsrv.ini + echo "extension= /usr/lib64/php/modules/pdo_sqlsrv.so" > /etc/php.d/pdo_sqlsrv.ini + + +####PHP 7.1 + + +**Ubuntu 16.04** + + echo "extension=/usr/lib/php/20160303/sqlsrv.so" >> /etc/php/7.1/apache2/php.ini + echo "extension=/usr/lib/php/20160303/pdo_sqlsrv.so" >> /etc/php/7.1/apache2/php.ini + echo "extension=/usr/lib/php/20160303/sqlsrv.so" >> /etc/php/7.1/cli/php.ini + echo "extension=/usr/lib/php/20160303/pdo_sqlsrv.so" >> /etc/php/7.1/cli/php.ini + + + +**RedHat** + + echo "extension= /usr/lib64/php/modules/sqlsrv.so" > /etc/php.d/sqlsrv.ini + echo "extension= /usr/lib64/php/modules/pdo_sqlsrv.so" > /etc/php.d/pdo_sqlsrv.ini + + + +### Step 6: Restart Apache to load the new php.ini file + +**Ubuntu** + + sudo service apache2 restart + +**RedHat** + + sudo apachectl restart + +### Step 7: Create your sample app +Navigate to `/var/www/html` and create a new file called testsql.php. Copy and paste the following code in tetsql.php and change the servername, username, password and databasename. + + "yourDatabase", + "Uid" => "yourUsername", + "PWD" => "yourPassword" + ); + //Establishes the connection + $conn = sqlsrv_connect($serverName, $connectionOptions); + //Select Query + $tsql= "SELECT @@Version as SQL_VERSION"; + //Executes the query + $getResults= sqlsrv_query($conn, $tsql); + //Error handling + + if ($getResults == FALSE) + die(FormatErrors(sqlsrv_errors())); + ?> +

Results :

+ "); + } + sqlsrv_free_stmt($getResults); + function FormatErrors( $errors ) + { + /* Display errors. */ + echo "Error information:
"; + + foreach ( $errors as $error ) + { + echo "SQLSTATE: ".$error['SQLSTATE']."
"; + echo "Code: ".$error['code']."
"; + echo "Message: ".$error['message']."
"; + } + } + ?> + +### Step 8: Run your sample app + +Go to your browser and type in http://localhost/testsql.php +You should be able to connect to your SQL Server/Azure SQL Database. + +The drivers are distributed as shared binary extensions for PHP. They are available in thread safe (*_ts.so) and-non thread safe (*_nts.so) versions. The source code for the drivers is also available, and you can choose whether to compile them as thread safe or non-thread safe versions. The thread safety configuration of your web server will determine which version you need. + ## Sample Code For samples, please see the sample folder. For setup instructions, see [here] [phpazure] @@ -87,14 +316,25 @@ For samples, please see the sample folder. For setup instructions, see [here] [ - This release contains the PHP 7 port of the SQLSRV and PDO_SQLSRV drivers, and does not provide backwards compatibility with PHP 5. - Binding output parameter using emulate prepare is not supported. +- Linux + - ODBC 3.52 is supported but not 3.8. + - Connection using named instances using '\' is not supported. + - Local encodings other than UTF-8 are not supported, and SQLSRV_ENC_CHAR only supports ASCII characters with ASCII code of 0 to 127. ## Known Issues - User defined data types and SQL_VARIANT. +- Binary column binding with emulate prepare ([issue#140](https://github.com/Microsoft/msphpsql/issues/140) ) +- Linux + - The following features are not supported with connection pooling: + - Unicode connection strings + - sqlsrv_server_info and sqlsrv_client_info return false + - In certain scenarios a generic error message maybe returned instead of a specific error when pooling is disabled + - When retrieving data from columns with a data type of XML, varchar(max), nvarchar(max), or varbinary(max) no data maybe returned or the data maybe truncated depending on the length of the data in the source table. ## Future Plans -- Expand SQL 16 Feature Support (example: Always Encrypted) -- Build Verification/Fundamental Tests -- Bug Fixes +- Expand SQL 16 Feature Support (example: Always Encrypted). +- Build Verification/Fundamental Tests. +- Bug Fixes. ## Guidelines for Reporting Issues We appreciate you taking the time to test the driver, provide feedback and report any issues. It would be extremely helpful if you: @@ -102,6 +342,7 @@ We appreciate you taking the time to test the driver, provide feedback and repor - Report each issue as a new issue (but check first if it's already been reported) - Try to be detailed in your report. Useful information for good bug reports include: * What you are seeing and what the expected behaviour is + * Can you connect to SQL Server via `sqlcmd`? * Which driver: SQLSRV or PDO_SQLSRV? * Environment details: e.g. PHP version, thread safe (TS) or non-thread safe (NTS), 32-bit &/or 64-bit? * Table schema (for some issues the data types make a big difference!) @@ -117,7 +358,7 @@ Thank you! **Q:** What's next? -**A:** On Jan 29, 2016 we released an early technical preview for our PHP Driver and several since. We will continue to release frequently to improve the quality of our driver. +**A:** On July 20, 2016 we released the early technical preview for our PHP Driver. We will continue releasing frequent technical previews until we reach production quality. **Q:** Is Microsoft taking pull requests for this project? @@ -135,7 +376,7 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf ## Resources -**Documentation**: [MSDN Online Documentation][phpdoc]. +**Documentation**: [MSDN Online Documentation][phpdoc]. Please note that this documentation is not yet updated for PHP 7. **Team Blog**: Browse our blog for comments and announcements from the team in the [team blog][blog]. @@ -151,11 +392,24 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf [phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild -[phpdoc]: http://msdn.microsoft.com/en-us/library/dd903047%28SQL.11%29.aspx +[phpdoc]: http://msdn.microsoft.com/library/dd903047%28SQL.11%29.aspx + +[odbc11]: https://www.microsoft.com/download/details.aspx?id=36434 + +[odbc13]: https://www.microsoft.com/download/details.aspx?id=50420 + +[odbcLinux]: https://msdn.microsoft.com/library/hh568454(v=sql.110).aspx + +[phpazure]: https://azure.microsoft.com/documentation/articles/sql-database-develop-php-simple-windows/ + +[PHPMan]: http://php.net/manual/install.unix.php + +[LinuxDM]: https://msdn.microsoft.com/library/hh568449(v=sql.110).aspx -[odbc11]: https://www.microsoft.com/en-us/download/details.aspx?id=36434 +[httpd_source]: http://httpd.apache.org/ -[odbc13]: https://www.microsoft.com/en-us/download/details.aspx?id=50420 +[apr_source]: http://apr.apache.org/ -[phpazure]: https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-php-simple-windows/ +[httpdconf]: http://php.net/manual/en/install.unix.apache2.php +[ODBCinstallers]: https://blogs.msdn.microsoft.com/sqlnativeclient/2016/09/06/preview-release-of-the-sql-server-cc-odbc-driver-13-0-0-for-linux diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..f45dc84d4 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,180 @@ +version: '{branch}.{build}' + +branches: + # whitelist + #only: + + # blacklist + except: + - PHP-7.0-Linux + - PHP5 + +environment: + # MSSQL credentials from https://www.appveyor.com/docs/services-databases/ + MSSQL_PASSWORD: Password12! + MSSQL_USERNAME: sa + PHP_DEPSVER: 7.0 + PHP_SDK: c:\projects\php + matrix: + - BUILD_PLATFORM: x64 + MSSQL_SERVERNAME: (local)\SQL2012SP1 + SQL_INSTANCE: SQL2012SP1 + PHP_VC: 14 + PHP_MAJOR_VER: 7.0 + PHP_MINOR_VER: latest + PHP_SDK_DIR: c:\projects\php\x64 + PHP_INSTALL_DIR: c:\projects\php\x64\bin + PHP_ZTS: --disable-zts + platform: x64 + - BUILD_PLATFORM: x86 + MSSQL_SERVERNAME: (local)\SQL2014 + SQL_INSTANCE: SQL2014 + PHP_VC: 14 + PHP_MAJOR_VER: 7.0 + PHP_MINOR_VER: latest + PHP_SDK_DIR: c:\projects\php\x86 + PHP_INSTALL_DIR: c:\projects\php\x86\bin + platform: x86 + - BUILD_PLATFORM: x64 + MSSQL_SERVERNAME: (local)\SQL2016 + SQL_INSTANCE: SQL2016 + PHP_VC: 14 + PHP_MAJOR_VER: 7.1 + PHP_MINOR_VER: latest + PHP_SDK_DIR: c:\projects\php\x64 + PHP_INSTALL_DIR: c:\projects\php\x64\bin + platform: x64 + - BUILD_PLATFORM: x86 + MSSQL_SERVERNAME: (local)\SQL2008R2SP2 + SQL_INSTANCE: SQL2008R2SP2 + PHP_VC: 14 + PHP_MAJOR_VER: 7.1 + PHP_MINOR_VER: latest + PHP_SDK_DIR: c:\projects\php\x86 + PHP_INSTALL_DIR: c:\projects\php\x86\bin + PHP_ZTS: --disable-zts + platform: x86 + + +# PHP_MAJOR_VER is PHP major version to build (7.0, 7.1) +# PHP_MINOR_VER is PHP point release number (or latest for latest release) +# PHP_INSTALL_DIR is where the built PHP binaries go +# PHP_SDK_DIR is where PHP source is extracted to (e.g. PHP_SDK_DIR\php-7.0.14-src) +# PHP_SDK is where PHP sdk binary tools are extracted to +# PHP_VC is the Visual C++ version +# PHP_ZTS is defined to disable thread safe build + +# Build worker image (VM template) +image: Visual Studio 2015 + +matrix: + fast_finish: true + +#services: + #- mssql2012sp1 + +# clone directory (or %APPVEYOR_BUILD_FOLDER%) +clone_folder: c:\projects\sqlphp + +build: + parallel: true # enable MSBuild parallel builds + +install: + - echo start SQL Server + # Based on http://www.appveyor.com/docs/services-databases + - ps: >- + [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null ; + [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null ; + + $instanceName = $env:SQL_INSTANCE; + $uri = "ManagedComputer[@Name='$env:COMPUTERNAME']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']"; + $wmi = New-Object ('Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer'); + $tcp = $wmi.GetSmoObject($uri); + $tcp.IsEnabled = $true; + $tcp.Alter(); + Start-Service "MSSQL`$$instanceName"; + + Set-Service SQLBrowser -StartupType Manual; + Start-Service SQLBrowser; + - echo Set PHP version... + - appveyor DownloadFile http://windows.php.net/downloads/releases/sha1sum.txt + # determine latest PHP versions + - ps: >- + If ($env:PHP_MINOR_VER -Match "latest") { + $env:PHP_VERSION=type sha1sum.txt | where { $_ -match "php-($env:PHP_MAJOR_VER\.\d+)-src" } | foreach { $matches[1] } ; + } Else { + $env:PHP_VERSION=$env:PHP_MAJOR_VER + '.' + $env:PHP_MINOR_VER; + } + - echo Downloading PHP-SDK + - appveyor DownloadFile http://windows.php.net/downloads/php-sdk/php-sdk-binary-tools-20110915.zip + - move php-sdk-binary-tools-20110915.zip .. + - echo Downloading PHP source code [%PHP_VERSION%] + - ps: (new-object net.webclient).DownloadFile('http://windows.php.net/downloads/releases/php-' + ${env:PHP_VERSION} + '-src.zip', ${env:APPVEYOR_BUILD_FOLDER} + '\..\php.zip') + #- echo Downloading PHP deps [%PHP_DEPSVER%] + #- ps: (new-object net.webclient).DownloadFile('http://windows.php.net/downloads/php-sdk/deps-' + ${env:PHP_DEPSVER} + '-vc' + ${env:PHP_VC} + '-' + ${env:BUILD_PLATFORM} + '.7z', ${env:APPVEYOR_BUILD_FOLDER} + '\..\deps.7z') + - echo Downloading MSODBCSQL 13 + - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/1/E/7/1E7B1181-3974-4B29-9A47-CC857B271AA2/English/' + ${env:BUILD_PLATFORM} + '/msodbcsql.msi', 'msodbcsql.msi') + - ps: msiexec /i msodbcsql.msi /quiet /qn /norestart + - cd .. + - cd + - 7z x -y php-sdk-binary-tools-20110915.zip -o%PHP_SDK% + - 7z x -y php.zip -o%PHP_SDK_DIR% + - echo update SQL connection string + - ps: (Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\pdo_sqlsrv\autonomous_setup.php) | ForEach-Object { $_ -replace "localhost", ${env:MSSQL_SERVERNAME} -replace "", ${env:MSSQL_PASSWORD} } | Set-Content ${env:APPVEYOR_BUILD_FOLDER}\test\pdo_sqlsrv\autonomous_setup.php + - ps: Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\pdo_sqlsrv\autonomous_setup.php + - ps: (Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\sqlsrv\autonomous_setup.php) | ForEach-Object { $_ -replace "localhost", ${env:MSSQL_SERVERNAME} -replace "", ${env:MSSQL_PASSWORD} } | Set-Content ${env:APPVEYOR_BUILD_FOLDER}\test\sqlsrv\autonomous_setup.php + - ps: Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\sqlsrv\autonomous_setup.php + +build_script: + - '"C:\\Program Files (x86)\\Microsoft Visual Studio %PHP_VC%.0\\VC\\vcvarsall.bat" %BUILD_PLATFORM%' + - Echo copy msphp code to ext folder + - mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv + - mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv\shared + - mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv + - mkdir %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv\shared + - copy /Y %APPVEYOR_BUILD_FOLDER%\source\sqlsrv %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv + - copy /Y %APPVEYOR_BUILD_FOLDER%\source\shared %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv\shared + - copy /Y %APPVEYOR_BUILD_FOLDER%\source\shared %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv\shared + - copy /Y %APPVEYOR_BUILD_FOLDER%\source\pdo_sqlsrv %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv + - cd %PHP_SDK_DIR%\php-%PHP_VERSION%-src + - cd + - dir + - '%PHP_SDK%\bin\phpsdk_setvars.bat' + - buildconf.bat + # only build CLI and MSSQL extensions + - configure.bat --disable-all %PHP_ZTS% --enable-cli --enable-sqlsrv=shared --with-pdo-sqlsrv=shared --enable-pdo=shared --with-prefix=%PHP_INSTALL_DIR% + - copy php.ini-development php.ini + - echo extension_dir=%PHP_INSTALL_DIR%\ext >> php.ini + - echo extension=php_sqlsrv.dll >> php.ini + - echo extension=php_pdo_sqlsrv.dll >> php.ini + - nmake + - nmake install + - Echo copy php.ini and run-tests.php from php source to install directory. + - copy php.ini %PHP_INSTALL_DIR% + - copy run-tests.php %PHP_INSTALL_DIR% + - dir %PHP_INSTALL_DIR% + +test_script: + - cd %PHP_INSTALL_DIR% + - php --ini + - php -i + - php run-tests.php -p php.exe %APPVEYOR_BUILD_FOLDER%\test\sqlsrv\*.phpt > %APPVEYOR_BUILD_FOLDER%\test\sqlsrv.log 2>&1 + - type %APPVEYOR_BUILD_FOLDER%\test\sqlsrv.log + - php run-tests.php -p php.exe %APPVEYOR_BUILD_FOLDER%\test\pdo_sqlsrv\*.phpt > %APPVEYOR_BUILD_FOLDER%\test\pdo_sqlsrv.log 2>&1 + - type %APPVEYOR_BUILD_FOLDER%\test\pdo_sqlsrv.log + +after_test: + - cd %APPVEYOR_BUILD_FOLDER%\test\ + - python output.py + - ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\nativeresult1.xml)) + - ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\nativeresult2.xml)) + - ps: >- + [xml]$results = Get-Content nativeresult1.xml ; + [xml]$results2 = Get-Content nativeresult2.xml ; + $failure = $results.SelectSingleNode("//failure"); + $failure2 = $results2.SelectSingleNode("//failure"); + if ($failure -ne $null -Or $failure2 -ne $null) + { + $host.SetShouldExit(1); + Write-Host "Forcing build failure due to phpt unit test failure(s)"; + } \ No newline at end of file diff --git a/binaries/x64/php_pdo_sqlsrv_7_nts.dll b/binaries/x64/php_pdo_sqlsrv_7_nts.dll deleted file mode 100644 index ec4951ea9..000000000 Binary files a/binaries/x64/php_pdo_sqlsrv_7_nts.dll and /dev/null differ diff --git a/binaries/x64/php_pdo_sqlsrv_7_ts.dll b/binaries/x64/php_pdo_sqlsrv_7_ts.dll deleted file mode 100644 index 502f573d0..000000000 Binary files a/binaries/x64/php_pdo_sqlsrv_7_ts.dll and /dev/null differ diff --git a/binaries/x64/php_sqlsrv_7_nts.dll b/binaries/x64/php_sqlsrv_7_nts.dll deleted file mode 100644 index bdd4c248f..000000000 Binary files a/binaries/x64/php_sqlsrv_7_nts.dll and /dev/null differ diff --git a/binaries/x64/php_sqlsrv_7_ts.dll b/binaries/x64/php_sqlsrv_7_ts.dll deleted file mode 100644 index 05a3c68f2..000000000 Binary files a/binaries/x64/php_sqlsrv_7_ts.dll and /dev/null differ diff --git a/binaries/x86/php_pdo_sqlsrv_7_nts.dll b/binaries/x86/php_pdo_sqlsrv_7_nts.dll deleted file mode 100644 index 54aba82d0..000000000 Binary files a/binaries/x86/php_pdo_sqlsrv_7_nts.dll and /dev/null differ diff --git a/binaries/x86/php_pdo_sqlsrv_7_ts.dll b/binaries/x86/php_pdo_sqlsrv_7_ts.dll deleted file mode 100644 index 20d3f1fa6..000000000 Binary files a/binaries/x86/php_pdo_sqlsrv_7_ts.dll and /dev/null differ diff --git a/binaries/x86/php_sqlsrv_7_nts.dll b/binaries/x86/php_sqlsrv_7_nts.dll deleted file mode 100644 index d75692d9a..000000000 Binary files a/binaries/x86/php_sqlsrv_7_nts.dll and /dev/null differ diff --git a/binaries/x86/php_sqlsrv_7_ts.dll b/binaries/x86/php_sqlsrv_7_ts.dll deleted file mode 100644 index 45f4edf3f..000000000 Binary files a/binaries/x86/php_sqlsrv_7_ts.dll and /dev/null differ diff --git a/pdo_sqlsrv/CREDITS b/pdo_sqlsrv/CREDITS deleted file mode 100644 index dc85ce17e..000000000 --- a/pdo_sqlsrv/CREDITS +++ /dev/null @@ -1 +0,0 @@ -Microsoft Drivers 4.1 for PHP for SQL Server (PDO driver) diff --git a/pdo_sqlsrv/core_sqlsrv.h b/pdo_sqlsrv/core_sqlsrv.h deleted file mode 100644 index c511b057c..000000000 --- a/pdo_sqlsrv/core_sqlsrv.h +++ /dev/null @@ -1,2263 +0,0 @@ -#ifndef CORE_SQLSRV_H -#define CORE_SQLSRV_H - -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_sqlsrv.h -// -// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -//********************************************************************************************************************************* -// Includes -//********************************************************************************************************************************* - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef PHP_WIN32 -#define PHP_SQLSRV_API __declspec(dllexport) -#else -#define PHP_SQLSRV_API -#endif - -// OACR is an internal Microsoft static code analysis tool -#if defined(OACR) -#include -OACR_WARNING_PUSH -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) -OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." ) -#else -// define to eliminate static analysis hints in the code -#define OACR_WARNING_SUPPRESS( warning, msg ) -#endif - -extern "C" { - -#pragma warning(push) -#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) - -#ifdef ZTS -#include "TSRM.h" -#endif - -#if _MSC_VER >= 1400 -// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. -// php.h defines this constant as unsigned int which causes a compile error -// in ws2tcpip.h. Fortunately php.h allows an override by defining -// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define -// socklen_t here and override the php.h version. -typedef int socklen_t; -#define HAVE_SOCKLEN_T -#endif - -#include "php.h" -#include "php_globals.h" -#include "php_ini.h" -#include "ext/standard/php_standard.h" -#include "ext/standard/info.h" - -#pragma warning(pop) - -#if ZEND_DEBUG -// debug build causes warning C4505 to pop up from the Zend header files -#pragma warning( disable: 4505 ) -#endif - -} // extern "C" - -#if defined(OACR) -OACR_WARNING_POP -#endif - -#include -#include - -#if !defined(WC_ERR_INVALID_CHARS) -// imported from winnls.h as it isn't included by 5.3.0 -#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars -#endif - -// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when -// we use std::copy, which causes compilation to fail since we compile with warnings as errors. -#if defined(ZEND_DEBUG) && defined(inline) -#undef inline -#endif - -#include -#include -#include -#include -#include -#include -#include -// included for SQL Server specific constants -#include "msodbcsql.h" - -//********************************************************************************************************************************* -// Constants and Types -//********************************************************************************************************************************* - -// constants for maximums in SQL Server -const int SS_MAXCOLNAMELEN = 128; -const int SQL_SERVER_MAX_FIELD_SIZE = 8000; -const int SQL_SERVER_MAX_PRECISION = 38; -const int SQL_SERVER_MAX_TYPE_SIZE = 0; -const int SQL_SERVER_MAX_PARAMS = 2100; - -// max size of a date time string when converting from a DateTime object to a string -const int MAX_DATETIME_STRING_LEN = 256; - -// precision and scale for the date time types between servers -const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; -const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; -const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; -const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; - -// types for conversions on output parameters (though they can be used for input parameters, they are ignored) -enum SQLSRV_PHPTYPE { - MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type - SQLSRV_PHPTYPE_NULL = 1, - SQLSRV_PHPTYPE_INT, - SQLSRV_PHPTYPE_FLOAT, - SQLSRV_PHPTYPE_STRING, - SQLSRV_PHPTYPE_DATETIME, - SQLSRV_PHPTYPE_STREAM, - MAX_SQLSRV_PHPTYPE, // highest value for a php type - SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid -}; - -// encodings supported by this extension. These basically translate into the use of SQL_C_CHAR or SQL_C_BINARY when getting -// information as a string or a stream. -enum SQLSRV_ENCODING { - SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables. - SQLSRV_ENCODING_DEFAULT, // use what is the connection's default for a statement, use system if a connection - SQLSRV_ENCODING_BINARY, // use SQL_C_BINARY when using SQLGetData - SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData - SQLSRV_ENCODING_SYSTEM = SQLSRV_ENCODING_CHAR, - SQLSRV_ENCODING_UTF8 = CP_UTF8, -}; - -// the array keys used when returning a row via sqlsrv_fetch_array and sqlsrv_fetch_object. -enum SQLSRV_FETCH_TYPE { - MIN_SQLSRV_FETCH = 1, // lowest value for fetch type - SQLSRV_FETCH_NUMERIC = 1, // return an array with only numeric indices - SQLSRV_FETCH_ASSOC = 2, // return an array with keys made from the field names - SQLSRV_FETCH_BOTH = 3, // return an array indexed with both numbers and keys - MAX_SQLSRV_FETCH = 3, // highest value for fetch type -}; - -// buffer size of a sql state (including the null character) -const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1; - -// buffer size allocated to retrieve data from a PHP stream. This number -// was chosen since PHP doesn't return more than 8k at a time even if -// the amount requested was more. -const int PHP_STREAM_BUFFER_SIZE = 8192; - -// SQL types for parameters encoded in an integer. The type corresponds to the SQL type ODBC constants. -// The size is the column size or precision, and scale is the decimal digits for precise numeric types. - -union sqlsrv_sqltype { - struct typeinfo_t { - int type:9; - int size:14; - int scale:8; - } typeinfo; - - zend_long value; -}; - - -// SQLSRV PHP types (as opposed to the Zend PHP type constants). Contains the type (see SQLSRV_PHPTYPE) -// and the encoding for strings and streams (see SQLSRV_ENCODING) - -union sqlsrv_phptype { - - struct typeinfo_t { - unsigned type:8; - unsigned encoding:16; - } typeinfo; - - zend_long value; -}; - -// static assert for enforcing compile time conditions -template -struct sqlsrv_static_assert; - -template <> -struct sqlsrv_static_assert { static const int value = 1; }; - -#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() ) - - -//********************************************************************************************************************************* -// Logging -//********************************************************************************************************************************* -// log_callback -// a driver specific callback for logging messages -// severity - severity of the message: notice, warning, or error -// msg - the message to log in a FormatMessage style formatting -// print_args - args to the message -typedef void (*log_callback)( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); - -// each driver must register a log callback. This should be the first thing a driver does. -void core_sqlsrv_register_logger( log_callback ); - -// a simple wrapper around a PHP error logging function. -void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ... ); - -// a macro to make it convenient to use the function. -#define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, __VA_ARGS__ ) - -// mask for filtering which severities are written to the log -enum logging_severity { - SEV_ERROR = 0x01, - SEV_WARNING = 0x02, - SEV_NOTICE = 0x04, - SEV_ALL = -1, -}; - -// Kill the PHP process and log the message to PHP -void die( const char* msg, ... ); -#define DIE( msg, ... ) { die( msg, __VA_ARGS__ ); } - - -//********************************************************************************************************************************* -// Resource/Memory Management -//********************************************************************************************************************************* - -// the macro max is defined and overrides the call to max in the allocator class -#pragma push_macro( "max" ) -#undef max - -// new memory allocation/free debugging facilities to help us verify that all allocations are being -// released in a timely manner and not just at the end of the script. -// Zend has memory logging and checking, but it can generate a lot of noise for just one extension. -// It's meant for internal use but might be useful for people adding features to our extension. -// To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases -// must be done with sqlsrv_malloc and sqlsrv_free. -// #define SQLSRV_MEM_DEBUG 1 -#if defined( PHP_DEBUG ) && !defined( ZTS ) && defined( SQLSRV_MEM_DEBUG ) - -inline void* sqlsrv_malloc_trace( size_t size, const char* file, int line ) -{ - void* ptr = emalloc( size ); - LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); - return ptr; -} - -inline void* sqlsrv_malloc_trace( size_t element_count, size_t element_size, size_t extra, const char* file, int line ) -{ - OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); - - if(( element_count > 0 && element_size > 0 ) && - ( element_count > element_size * element_count || element_size > element_size * element_count )) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count > element_size * element_count + extra ) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count + extra == 0 ) { - DIE( "Allocation size must be more than 0" ); - } - - void* ptr = emalloc( element_size * element_count + extra ); - LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); - return ptr; -} - -inline void* sqlsrv_realloc_trace( void* buffer, size_t size, const char* file, int line ) -{ - void* ptr = erealloc( original, size ); - LOG( SEV_NOTICE, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original ); - return ptr; -} - -inline void sqlsrv_free_trace( void* ptr, const char* file, int line ) -{ - LOG( SEV_NOTICE, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line ); - efree( ptr ); -} - -#define sqlsrv_malloc( size ) sqlsrv_malloc_trace( size, __FILE__, __LINE__ ) -#define sqlsrv_malloc( count, size, extra ) sqlsrv_malloc_trace( count, size, extra, __FILE__, __LINE__ ) -#define sqlsrv_realloc( buffer, size ) sqlsrv_realloc_trace( buffer, size, __FILE__, __LINE__ ) -#define sqlsrv_free( ptr ) sqlsrv_free_trace( ptr, __FILE__, __LINE__ ) - -#else - -inline void* sqlsrv_malloc( size_t size ) -{ - return emalloc( size ); -} - -inline void* sqlsrv_malloc( size_t element_count, size_t element_size, size_t extra ) -{ - OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); - - if(( element_count > 0 && element_size > 0 ) && - ( element_count > element_size * element_count || element_size > element_size * element_count )) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count > element_size * element_count + extra ) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count + extra == 0 ) { - DIE( "Allocation size must be more than 0" ); - } - - return emalloc( element_size * element_count + extra ); -} - -inline void* sqlsrv_realloc( void* buffer, size_t size ) -{ - return erealloc( buffer, size ); -} - -inline void sqlsrv_free( void* ptr ) -{ - efree( ptr ); -} - -#endif - -// trait class that allows us to assign const types to an auto_ptr -template -struct remove_const { - typedef T type; -}; - -template -struct remove_const { - typedef T* type; -}; - -// allocator that uses the zend memory manager to manage memory -// this allows us to use STL classes that still work with Zend objects -template -struct sqlsrv_allocator { - - // typedefs used by the STL classes - typedef T value_type; - typedef value_type* pointer; - typedef const value_type* const_pointer; - typedef value_type& reference; - typedef const value_type& const_reference; - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; - - // conversion typedef (used by list and other STL classes) - template - struct rebind { - typedef sqlsrv_allocator other; - }; - - inline sqlsrv_allocator() {} - inline ~sqlsrv_allocator() {} - inline sqlsrv_allocator( sqlsrv_allocator const& ) {} - template - inline sqlsrv_allocator( sqlsrv_allocator const& ) {} - - // address (doesn't work if the class defines operator&) - inline pointer address( reference r ) - { - return &r; - } - - inline const_pointer address( const_reference r ) - { - return &r; - } - - // memory allocation/deallocation - inline pointer allocate( size_type cnt, - typename std::allocator::const_pointer = 0 ) - { - return reinterpret_cast( sqlsrv_malloc(cnt, sizeof (T), 0)); - } - - inline void deallocate( pointer p, size_type ) - { - sqlsrv_free(p); - } - - // size - inline size_type max_size( void ) const - { - return std::numeric_limits::max() / sizeof(T); - } - - // object construction/destruction - inline void construct( pointer p, const T& t ) - { - new(p) T(t); - } - - inline void destroy(pointer p) - { - p->~T(); - } - - // equality operators - inline bool operator==( sqlsrv_allocator const& ) - { - return true; - } - - inline bool operator!=( sqlsrv_allocator const& a ) - { - return !operator==(a); - } -}; - - -// base class for auto_ptrs that we define below. It provides common operators and functions -// used by all the classes. -template -class sqlsrv_auto_ptr { - -public: - - sqlsrv_auto_ptr( void ) : _ptr( NULL ) - { - } - - ~sqlsrv_auto_ptr( void ) - { - static_cast(this)->reset( NULL ); - } - - // call when ownership is transferred - void transferred( void ) - { - _ptr = NULL; - } - - // explicit function to get the pointer. - T* get( void ) const - { - return _ptr; - } - - // cast operator to allow auto_ptr to be used where a normal const * can be. - operator const T* () const - { - return _ptr; - } - - // cast operator to allow auto_ptr to be used where a normal pointer can be. - operator typename remove_const::type () const - { - return _ptr; - } - - operator bool() const - { - return _ptr != NULL; - } - - // there are a number of places where we allocate a block intended to be accessed as - // an array of elements, so this operator allows us to treat the memory as such. - T& operator[]( int index ) const - { - return _ptr[ index ]; - } - - // there are a number of places where we allocate a block intended to be accessed as - // an array of elements, so this operator allows us to treat the memory as such. - T& operator[]( unsigned int index ) const - { - return _ptr[ index ]; - } - - // there are a number of places where we allocate a block intended to be accessed as - // an array of elements, so this operator allows us to treat the memory as such. - T& operator[]( long index ) const - { - return _ptr[ index ]; - } - - - #ifdef __WIN64 - // there are a number of places where we allocate a block intended to be accessed as - // an array of elements, so this operator allows us to treat the memory as such. - T& operator[](std::size_t index) const - { - return _ptr[index]; - } - #endif - - // there are a number of places where we allocate a block intended to be accessed as - // an array of elements, so this operator allows us to treat the memory as such. - T& operator[]( unsigned short index ) const - { - return _ptr[ index ]; - } - - // access elements of a structure through the auto ptr - T* const operator->( void ) const - { - return _ptr; - } - - // value from reference operator (i.e., i = *(&i); or *i = blah;) - T& operator*() const - { - return *_ptr; - } - - // allow the use of the address-of operator to simulate a **. - // Note: this operator conflicts with storing these within an STL container. If you need - // to do that, then redefine this as getpp and change instances of &auto_ptr to auto_ptr.getpp() - T** operator&( void ) - { - return &_ptr; - } - -protected: - - sqlsrv_auto_ptr( T* ptr ) : - _ptr( ptr ) - { - } - - sqlsrv_auto_ptr( sqlsrv_auto_ptr& src ) - { - if( _ptr ) { - static_cast(this)->reset( src._ptr ); - } - src.transferred(); - } - - // assign a new pointer to the auto_ptr. It will free the previous memory block - // because ownership is deemed finished. - T* operator=( T* ptr ) - { - static_cast( this )->reset( ptr ); - - return ptr; - } - - T* _ptr; -}; - -// an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer -// in a variable of sqlsrv_malloc_auto_ptr. sqlsrv_malloc_auto_ptr will "own" that block and assure that it is -// freed until the variable is destroyed (out of scope) or ownership is transferred using the function -// "transferred". -// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. - -template -class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr > { - -public: - - sqlsrv_malloc_auto_ptr( void ) : - sqlsrv_auto_ptr >( NULL ) - { - } - - sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) : - sqlsrv_auto_ptr >( src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( T* ptr = NULL ) - { - if( _ptr ) - sqlsrv_free( (void*) _ptr ); - _ptr = ptr; - } - - T* operator=( T* ptr ) - { - return sqlsrv_auto_ptr >::operator=( ptr ); - } - - void operator=( sqlsrv_malloc_auto_ptr& src ) - { - T* p = src.get(); - src.transferred(); - this->_ptr = p; - } - - // DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. - // has the same parameter list as sqlsrv_realloc: new_size is the size in bytes of the newly allocated buffer - void resize( size_t new_size ) - { - _ptr = reinterpret_cast( sqlsrv_realloc( _ptr, new_size )); - } -}; - - -// auto ptr for Zend hash tables. Used to clean up a hash table allocated when -// something caused an early exit from the function. This is used when the hash_table is -// allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr. - -class hash_auto_ptr : public sqlsrv_auto_ptr { - -public: - - hash_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( HashTable* ptr = NULL ) - { - if( _ptr ) { - zend_hash_destroy( _ptr ); - FREE_HASHTABLE( _ptr ); - } - _ptr = ptr; - } - - HashTable* operator=( HashTable* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - -private: - - hash_auto_ptr( HashTable const& hash ); - - hash_auto_ptr( hash_auto_ptr const& hash ); -}; - - -// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr. -// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed -// (out of scope) or ownership is transferred using the function "transferred". - -class zval_auto_ptr : public sqlsrv_auto_ptr { - -public: - - zval_auto_ptr( void ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( zval* ptr = NULL ) - { - if( _ptr ) - zval_ptr_dtor(_ptr ); - _ptr = ptr; - } - - zval* operator=( zval* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - - -private: - - zval_auto_ptr( const zval_auto_ptr& src ); -}; - -#pragma pop_macro( "max" ) - - -//********************************************************************************************************************************* -// sqlsrv_error -//********************************************************************************************************************************* - -// *** PHP specific errors *** -// sqlsrv errors are held in a structure of this type used by the driver handle_error functions -// format is a flag that tells the driver error handler functions if there are parameters to use with FormatMessage -// into the error message before returning it. - -// base class which can be instatiated with aggregates (see error constants) -struct sqlsrv_error_const { - - SQLCHAR* sqlstate; - SQLCHAR* native_message; - SQLINTEGER native_code; - bool format; -}; - -// subclass which is used by the core layer to instantiate ODBC errors -struct sqlsrv_error : public sqlsrv_error_const { - - sqlsrv_error( void ) - { - sqlstate = NULL; - native_message = NULL; - native_code = -1; - format = false; - } - - sqlsrv_error( SQLCHAR* sql_state, SQLCHAR* message, SQLINTEGER code, bool printf_format = false ) - { - sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); - strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); - strcpy_s( reinterpret_cast( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast( message )); - native_code = code; - format = printf_format; - } - - sqlsrv_error( sqlsrv_error_const const& prototype ) - { - sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format ); - } - - ~sqlsrv_error( void ) - { - if( sqlstate != NULL ) { - sqlsrv_free( sqlstate ); - } - if( native_message != NULL ) { - sqlsrv_free( native_message ); - } - } -}; - - -// an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete -class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr { - -public: - - sqlsrv_error_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - sqlsrv_error_auto_ptr( sqlsrv_error_auto_ptr const& src ) : - sqlsrv_auto_ptr( (sqlsrv_error_auto_ptr&) src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( sqlsrv_error* ptr = NULL ) - { - if( _ptr ) { - _ptr->~sqlsrv_error(); - sqlsrv_free( (void*) _ptr ); - } - _ptr = ptr; - } - - sqlsrv_error* operator=( sqlsrv_error* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - - // unlike traditional assignment operators, the chained assignment of an auto_ptr doesn't make much - // sense. Only the last one would have anything in it. - void operator=( sqlsrv_error_auto_ptr& src ) - { - sqlsrv_error* p = src.get(); - src.transferred(); - this->_ptr = p; - } -}; - - -//********************************************************************************************************************************* -// Context -//********************************************************************************************************************************* - -class sqlsrv_context; -struct sqlsrv_conn; - -// error_callback -// a driver specific callback for processing errors. -// ctx - the context holding the handles -// sqlsrv_error_code - specific error code to return. -typedef bool (*error_callback)( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool error TSRMLS_DC, va_list* print_args ); - -// sqlsrv_context -// a context holds relevant information to be passed with a connection and statement objects. - -class sqlsrv_context { - - public: - - sqlsrv_context( SQLSMALLINT type, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : - handle_( SQL_NULL_HANDLE ), - handle_type_( type ), - err_( e ), - name_( NULL ), - driver_( drv ), - last_error_(), - encoding_( encoding ) - { - } - - sqlsrv_context( SQLHANDLE h, SQLSMALLINT t, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : - handle_( h ), - handle_type_( t ), - err_( e ), - name_( NULL ), - driver_( drv ), - last_error_(), - encoding_( encoding ) - { - } - - sqlsrv_context( sqlsrv_context const& ctx ) : - handle_( ctx.handle_ ), - handle_type_( ctx.handle_type_ ), - err_( ctx.err_ ), - name_( ctx.name_ ), - driver_( ctx.driver_ ), - last_error_( ctx.last_error_ ) - { - } - - void set_func( const char* f ) - { - name_ = f; - } - - void set_last_error( sqlsrv_error_auto_ptr& last_error ) - { - last_error_ = last_error; - } - - sqlsrv_error_auto_ptr& last_error( void ) - { - return last_error_; - } - - // since the primary responsibility of a context is to hold an ODBC handle, we - // provide these convenience operators for using them interchangeably - operator SQLHANDLE ( void ) const - { - return handle_; - } - - error_callback error_handler( void ) const - { - return err_; - } - - SQLHANDLE handle( void ) const - { - return handle_; - } - - SQLSMALLINT handle_type( void ) const - { - return handle_type_; - } - - const char* func( void ) const - { - return name_; - } - - void* driver( void ) const - { - return driver_; - } - - void set_driver( void* driver ) - { - this->driver_ = driver; - } - - void invalidate( void ) - { - if( handle_ != SQL_NULL_HANDLE ) { - ::SQLFreeHandle( handle_type_, handle_ ); - - last_error_.reset(); - } - handle_ = SQL_NULL_HANDLE; - } - - bool valid( void ) - { - return handle_ != SQL_NULL_HANDLE; - } - - SQLSRV_ENCODING encoding( void ) const - { - return encoding_; - } - - void set_encoding( SQLSRV_ENCODING e ) - { - encoding_ = e; - } - - private: - SQLHANDLE handle_; // ODBC handle for this context - SQLSMALLINT handle_type_; // type of the ODBC handle - const char* name_; // function name currently executing this context - error_callback err_; // driver error callback if error occurs in core layer - void* driver_; // points back to the driver for PDO - sqlsrv_error_auto_ptr last_error_; // last error that happened on this object - SQLSRV_ENCODING encoding_; // encoding of the context -}; - -const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista - -// maps an IANA encoding to a code page -struct sqlsrv_encoding { - - const char* iana; - size_t iana_len; - unsigned int code_page; - bool not_for_connection; - - sqlsrv_encoding( const char* iana, unsigned int code_page, bool not_for_conn = false ): - iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn ) - { - } -}; - - -//********************************************************************************************************************************* -// Initialization -//********************************************************************************************************************************* - -// variables set during initialization -extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in -extern HashTable* g_encodings; // encodings supported by this driver - -void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ); -void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ); - -// environment context used by sqlsrv_connect for when a connection error occurs. -struct sqlsrv_henv { - - sqlsrv_context ctx; - - sqlsrv_henv( SQLHANDLE handle, error_callback e, void* drv ) : - ctx( handle, SQL_HANDLE_ENV, e, drv ) - { - } -}; - - -//********************************************************************************************************************************* -// Connection -//********************************************************************************************************************************* - -// supported server versions (determined at connection time) -enum SERVER_VERSION { - SERVER_VERSION_UNKNOWN = -1, - SERVER_VERSION_2000 = 8, - SERVER_VERSION_2005, - SERVER_VERSION_2008, // use this for anything 2008 or later -}; - -// supported driver versions. -enum DRIVER_VERSION : size_t { - MIN = 0, - ODBC_DRIVER_13 = MIN, - ODBC_DRIVER_11 = 1, - MAX = ODBC_DRIVER_11, -}; - -// forward decl -struct sqlsrv_stmt; -struct stmt_option; - -// *** connection resource structure *** -// this is the resource structure returned when a connection is made. -struct sqlsrv_conn : public sqlsrv_context { - - // instance variables - SERVER_VERSION server_version; // version of the server that we're connected to - - DRIVER_VERSION driver_version; - - // initialize with default values - sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) : - sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) - { - } - - // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be - // called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating - // the sqlsrv_conn with a connection. -}; - -enum SQLSRV_STMT_OPTIONS { - - SQLSRV_STMT_OPTION_INVALID, - SQLSRV_STMT_OPTION_QUERY_TIMEOUT, - SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, - SQLSRV_STMT_OPTION_SCROLLABLE, - SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, - - // Driver specific connection options - SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, - -}; - -namespace ODBCConnOptions { - -const char APP[] = "APP"; -const char ApplicationIntent[] = "ApplicationIntent"; -const char AttachDBFileName[] = "AttachDbFileName"; -const char CharacterSet[] = "CharacterSet"; -const char ConnectionPooling[] = "ConnectionPooling"; -const char Database[] = "Database"; -const char Encrypt[] = "Encrypt"; -const char Failover_Partner[] = "Failover_Partner"; -const char LoginTimeout[] = "LoginTimeout"; -const char MARS_ODBC[] = "MARS_Connection"; -const char MultiSubnetFailover[] = "MultiSubnetFailover"; -const char QuotedId[] = "QuotedId"; -const char TraceFile[] = "TraceFile"; -const char TraceOn[] = "TraceOn"; -const char TrustServerCertificate[] = "TrustServerCertificate"; -const char TransactionIsolation[] = "TransactionIsolation"; -const char WSID[] = "WSID"; -const char UID[] = "UID"; -const char PWD[] = "PWD"; -const char SERVER[] = "Server"; - -} - -enum SQLSRV_CONN_OPTIONS { - - SQLSRV_CONN_OPTION_INVALID, - SQLSRV_CONN_OPTION_APP, - SQLSRV_CONN_OPTION_CHARACTERSET, - SQLSRV_CONN_OPTION_CONN_POOLING, - SQLSRV_CONN_OPTION_DATABASE, - SQLSRV_CONN_OPTION_ENCRYPT, - SQLSRV_CONN_OPTION_FAILOVER_PARTNER, - SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, - SQLSRV_CONN_OPTION_MARS, - SQLSRV_CONN_OPTION_QUOTED_ID, - SQLSRV_CONN_OPTION_TRACE_FILE, - SQLSRV_CONN_OPTION_TRACE_ON, - SQLSRV_CONN_OPTION_TRANS_ISOLATION, - SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, - SQLSRV_CONN_OPTION_WSID, - SQLSRV_CONN_OPTION_ATTACHDBFILENAME, - SQLSRV_CONN_OPTION_APPLICATION_INTENT, - SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, - - // Driver specific connection options - SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, - -}; - - -#define NO_ATTRIBUTE -1 - -// type of connection attributes -enum CONN_ATTR_TYPE { - CONN_ATTR_INT, - CONN_ATTR_BOOL, - CONN_ATTR_STRING, - CONN_ATTR_INVALID, -}; - -// a connection option that includes the callback function that handles that option (e.g., adds it to the connection string or -// sets an attribute) -struct connection_option { - // the name of the option as passed in by the user - const char * sqlsrv_name; - unsigned int sqlsrv_len; - - unsigned int conn_option_key; - // the name of the option in the ODBC connection string - const char * odbc_name; - unsigned int odbc_len; - enum CONN_ATTR_TYPE value_type; - - // process the connection type - // return whether or not the function was successful in processing the connection option - void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC ); -}; - -// connection attribute functions -template -struct str_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), - static_cast(Z_STRLEN_P( value )) TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } - } -}; - -// simply add the parsed value to the connection string -struct conn_str_append_func { - - static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); -}; - -struct conn_null_func { - - static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ - TSRMLS_DC ); -}; - -// factory to create a connection (since they are subclassed to instantiate statements) -typedef sqlsrv_conn* (*driver_conn_factory)( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); - -// *** connection functions *** -sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, - const char* server, const char* uid, const char* pwd, - HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[], - void* driver, const char* driver_func TSRMLS_DC ); -void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ); -void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC ); -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ); -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); -bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); -size_t core_str_zval_is_true( zval* str_zval ); - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -struct stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ); -}; - -struct stmt_option_query_timeout : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_send_at_exec : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_buffered_query_limit : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -// used to hold the table for statment options -struct stmt_option { - - const char * name; // name of the statement option - unsigned int name_len; // name length - unsigned int key; - std::unique_ptr func; // callback that actually handles the work of the option - -}; - -// holds the stream param and the encoding that it was assigned -struct sqlsrv_stream { - - zval* stream_z; - SQLSRV_ENCODING encoding; - SQLUSMALLINT field_index; - SQLSMALLINT sql_type; - sqlsrv_stmt* stmt; - std::size_t stmt_index; - - sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) : - stream_z( str_z ), encoding( enc ) - { - } - - sqlsrv_stream() : stream_z( NULL ), encoding( SQLSRV_ENCODING_INVALID ), stmt( NULL ) - { - } -}; - -// close any active stream -void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); - -extern php_stream_wrapper g_sqlsrv_stream_wrapper; - -// resource constants used when registering the stream type with PHP -#define SQLSRV_STREAM_WRAPPER "sqlsrv" -#define SQLSRV_STREAM "sqlsrv_stream" - -// holds the output parameter information. Strings also need the encoding and other information for -// after processing. Only integer, float, and strings are allowable output parameters. -struct sqlsrv_output_param { - - zval* param_z; - SQLSRV_ENCODING encoding; - SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement - SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer - bool is_bool; - - // string output param constructor - sqlsrv_output_param( zval* p_z, SQLSRV_ENCODING enc, int num, SQLUINTEGER buffer_len ) : - param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false ) - { - } - - // every other type output parameter constructor - sqlsrv_output_param( zval* p_z, int num, bool is_bool ) : - param_z( p_z ), - param_num( num ), - encoding( SQLSRV_ENCODING_INVALID ), - original_buffer_len( -1 ), - is_bool( is_bool ) - { - } -}; - -// forward decls -struct sqlsrv_result_set; - -// *** Statement resource structure *** -struct sqlsrv_stmt : public sqlsrv_context { - - void free_param_data( TSRMLS_D ); - virtual void new_result_set( TSRMLS_D ); - - sqlsrv_conn* conn; // Connection that created this statement - - bool executed; // Whether the statement has been executed yet (used for error messages) - bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row - sqlsrv_result_set* current_results; // Current result set - SQLULEN cursor_type; // Type of cursor for the current result set - bool has_rows; // Has_rows is set if there are actual rows in the row set - bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called - int last_field_index; // last field retrieved by core_sqlsrv_get_field - bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the - // last results - unsigned long query_timeout; // maximum allowed statement execution time - zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) - - // holds output pointers for SQLBindParameter - // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving - // memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold - std::deque param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter - zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP - zval output_params; // hold all the output parameters - zval param_streams; // track which streams to send data to the server - zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects - bool send_streams_at_exec; // send all stream data right after execution before returning - sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter - unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string - // to the server) - zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals - zval active_stream; // the currently active stream reading data from the database - - sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); - virtual ~sqlsrv_stmt( void ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string = false ) = 0; - -}; - -// *** field metadata struct *** -struct field_meta_data { - - sqlsrv_malloc_auto_ptr field_name; - SQLSMALLINT field_name_len; - SQLSMALLINT field_type; - SQLULEN field_size; - SQLULEN field_precision; - SQLSMALLINT field_scale; - SQLSMALLINT field_is_nullable; - - field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0), - field_scale (0), field_is_nullable(0) - { - } - - ~field_meta_data() - { - } -}; - -// *** statement constants *** -// unknown column size used by core_sqlsrv_bind_param when the user doesn't supply a value -const SQLULEN SQLSRV_UNKNOWN_SIZE = 0xffffffff; -const int SQLSRV_DEFAULT_SIZE = -1; // size given for an output parameter that doesn't really need one (e.g., int) - -// uninitialized query timeout value -const unsigned int QUERY_TIMEOUT_INVALID = 0xffffffff; - -// special buffered query constant -const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant - -// factory to create a statement -typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); - -// *** statement functions *** -sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, - const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ); -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, - SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, - SQLSMALLINT decimal_digits TSRMLS_DC ); -void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 ); -field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ); -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ); -void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, - _Out_ void*& field_value, _Out_ SQLLEN* field_length, bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC); -bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ); -void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true ); -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC ); -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ); -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ); -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); - - -//********************************************************************************************************************************* -// Result Set -//********************************************************************************************************************************* - -// Abstract the result set so that a result set can either be used as is from ODBC or buffered. -// This is not a complete abstraction of a result set. Only enough is abstracted to allow for -// information and capabilities normally not available when a result set is not buffered -// (e.g., forward only vs buffered means row count is available and cursor movement is possible). -// Otherwise, normal ODBC calls are still valid and should be used to get information about the -// result set (e.g., SQLNumResultCols). - -struct sqlsrv_result_set { - - sqlsrv_stmt* odbc; - - explicit sqlsrv_result_set( sqlsrv_stmt* ); - virtual ~sqlsrv_result_set( void ) { } - - virtual bool cached( int field_index ) = 0; - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0; - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC )= 0; - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0; - virtual SQLLEN row_count( TSRMLS_D ) = 0; -}; - -struct sqlsrv_odbc_result_set : public sqlsrv_result_set { - - explicit sqlsrv_odbc_result_set( sqlsrv_stmt* ); - virtual ~sqlsrv_odbc_result_set( void ); - - virtual bool cached( int field_index ) { return false; } - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ); - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); - virtual SQLLEN row_count( TSRMLS_D ); - - private: - // prevent invalid instantiations and assignments - sqlsrv_odbc_result_set( void ); - sqlsrv_odbc_result_set( sqlsrv_odbc_result_set& ); - sqlsrv_odbc_result_set& operator=( sqlsrv_odbc_result_set& ); -}; - -struct sqlsrv_buffered_result_set : public sqlsrv_result_set { - - struct meta_data { - SQLSMALLINT type; - SQLSMALLINT c_type; // convenience - SQLULEN offset; // in bytes - SQLULEN length; // in bytes - SQLSMALLINT scale; - - static const SQLULEN SIZE_UNKNOWN = 0; - }; - - // default maximum amount of memory that a buffered query can consume - #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings - static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB - static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; - - explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC ); - virtual ~sqlsrv_buffered_result_set( void ); - - virtual bool cached( int field_index ) { return true; } - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ); - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); - virtual SQLLEN row_count( TSRMLS_D ); - - // buffered result set specific - SQLSMALLINT column_count( void ) - { - return col_count; - } - - struct meta_data& col_meta_data( SQLSMALLINT i ) - { - return meta[i]; - } - - private: - // prevent invalid instantiations and assignments - sqlsrv_buffered_result_set( void ); - sqlsrv_buffered_result_set( sqlsrv_buffered_result_set& ); - sqlsrv_buffered_result_set& operator=( sqlsrv_buffered_result_set& ); - - HashTable* cache; // rows of data kept in index based hash table - SQLSMALLINT col_count; // number of columns in the current result set - meta_data* meta; // metadata for fields in the cache - SQLLEN current; // 1 based, 0 means before first row - sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here - SQLUSMALLINT last_field_index; // the last field data retrieved from - SQLLEN read_so_far; // position within string to read from (for partial reads of strings) - sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use - SQLLEN temp_length; // number of bytes in the temp conversion buffer - - typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; - - // two dimentional sparse matrix that holds the [from][to] functions that do conversions - static conv_matrix_t conv_matrix; - - // string conversion functions - SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // long conversion functions - SQLRETURN to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // double conversion functions - SQLRETURN to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // string to number conversion functions - // Future: See if these can be converted directly to template member functions - SQLRETURN string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // utility functions for conversions - unsigned char* get_row( void ); -}; - -//********************************************************************************************************************************* -// Utility -//********************************************************************************************************************************* - -// Simple macro to alleviate unused variable warnings. These are optimized out by the compiler. -// We use this since the unused variables are buried in the PHP_FUNCTION macro. -#define SQLSRV_UNUSED( var ) var; - -// do a heap check in debug mode, but only print errors, not all of the allocations -#define MEMCHECK_SILENT 1 - -// utility functions shared by multiple callers across files -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len); -bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len); -bool validate_string(char* string, SQLLEN& len); -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ); -wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, - unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); - -//********************************************************************************************************************************* -// Error handling routines and Predefined Errors -//********************************************************************************************************************************* - -enum SQLSRV_ERROR_CODES { - - SQLSRV_ERROR_ODBC, - SQLSRV_ERROR_DRIVER_NOT_INSTALLED, - SQLSRV_ERROR_ZEND_HASH, - SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, - SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, - SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, - SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, - SQLSRV_ERROR_ZEND_STREAM, - SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, - SQLSRV_ERROR_FETCH_PAST_END, - SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, - SQLSRV_ERROR_NO_FIELDS, - SQLSRV_ERROR_INVALID_TYPE, - SQLSRV_ERROR_FETCH_NOT_CALLED, - SQLSRV_ERROR_NO_DATA, - SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, - SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, - SQLSRV_ERROR_NEXT_RESULT_PAST_END, - SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, - SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, - SQLSRV_ERROR_INVALID_CONNECTION_KEY, - SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, - SQLSRV_ERROR_INVALID_OPTION_KEY, - SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, - SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, - SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, - SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, - SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, - SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, - SQLSRV_ERROR_STREAM_CREATE, - SQLSRV_ERROR_MARS_OFF, - SQLSRV_ERROR_FIELD_INDEX_ERROR, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, - SQLSRV_ERROR_INVALID_BUFFER_LIMIT, - - // Driver specific error codes starts from here. - SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, - -}; - -// the message returned by ODBC Driver 11 for SQL Server -static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command", - "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" }; - -// SQLSTATE for all internal errors -extern SQLCHAR IMSSP[]; - -// SQLSTATE for all internal warnings -extern SQLCHAR SSPWARN[]; - -// flags passed to sqlsrv_errors to filter its return values -enum error_handling_flags { - SQLSRV_ERR_ERRORS, - SQLSRV_ERR_WARNINGS, - SQLSRV_ERR_ALL -}; - -// *** internal error macros and functions *** -// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the -// errno is 1 based. It returns it as an array with 3 members: -// 1/SQLSTATE) sqlstate -// 2/code) driver specific error code -// 3/message) driver specific error message -// The fetch type determines if the indices are numeric, associative, or both. -bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, _Out_ sqlsrv_error_auto_ptr& error, - logging_severity severity TSRMLS_DC ); - -// format and return a driver specfic error -void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, - sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ); - - -// return the message for the HRESULT returned by GetLastError. Some driver errors use this to -// return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails. -const char* get_last_error_message( DWORD last_error = 0 ); - -// a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer -DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ); - -// convenience functions that overload either a reference or a pointer so we can use -// either in the CHECK_* functions. -inline bool call_error_handler( sqlsrv_context& ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) -{ - va_list print_params; - va_start( print_params, warning ); - bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); - va_end( print_params ); - return ignored; -} - -inline bool call_error_handler( sqlsrv_context* ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) -{ - va_list print_params; - va_start( print_params, warning ); - bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); - va_end( print_params ); - return ignored; -} - -// PHP equivalent of ASSERT. C asserts cause a dialog to show and halt the process which -// we don't want on a web server - -#define SQLSRV_ASSERT( condition, msg, ...) if( !(condition)) DIE( msg, __VA_ARGS__ ); - -#if defined( PHP_DEBUG ) - -#define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) \ - if( !(condition)) { \ - DIE (msg, __VA_ARGS__ ); \ - } - -#else - - #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) ((void)0) - -#endif - -// check to see if the sqlstate is 01004, truncated field retrieved. Used for retrieving large fields. -inline bool is_truncated_warning( SQLCHAR* state ) -{ -#if defined(ZEND_DEBUG) - if( state == NULL || strlen( reinterpret_cast( state )) != 5 ) { \ - DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \ - } -#endif - return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4'); -} - -// Macros for handling errors. These macros are simplified if statements that take boilerplate -// code down to a single line to avoid distractions in the code. - -#define CHECK_ERROR_EX( unique, condition, context, ssphp, ... ) \ - bool flag##unique = (condition); \ - bool ignored##unique = true; \ - if (flag##unique) { \ - ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, __VA_ARGS__ ); \ - } \ - if( !ignored##unique ) - -#define CHECK_ERROR_UNIQUE( unique, condition, context, ssphp, ...) \ - CHECK_ERROR_EX( unique, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_ERROR( condition, context, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, NULL, __VA_ARGS__ ) - -#define CHECK_CUSTOM_ERROR( condition, context, ssphp, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_SQL_ERROR( result, context, ... ) \ - SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ - CHECK_ERROR( result == SQL_ERROR, context, __VA_ARGS__ ) - -#define CHECK_WARNING_AS_ERROR_UNIQUE( unique, condition, context, ssphp, ... ) \ - bool ignored##unique = true; \ - if( condition ) { \ - ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, __VA_ARGS__ ); \ - } \ - if( !ignored##unique ) - -#define CHECK_SQL_WARNING_AS_ERROR( result, context, ... ) \ - CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__,( result == SQL_SUCCESS_WITH_INFO ), context, SQLSRV_ERROR_ODBC, __VA_ARGS__ ) - -#define CHECK_SQL_WARNING( result, context, ... ) \ - if( result == SQL_SUCCESS_WITH_INFO ) { \ - (void)call_error_handler( context, NULL TSRMLS_CC, /*warning*/ true, __VA_ARGS__ ); \ - } - -#define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \ - CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_ZEND_ERROR( zr, ctx, error, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, ( zr == FAILURE ), ctx, error, __VA_ARGS__ ) \ - -#define CHECK_SQL_ERROR_OR_WARNING( result, context, ... ) \ - SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ - bool ignored = true; \ - if( result == SQL_ERROR ) { \ - ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, false, __VA_ARGS__ ); \ - } \ - else if( result == SQL_SUCCESS_WITH_INFO ) { \ - ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, true TSRMLS_CC, __VA_ARGS__ ); \ - } \ - if( !ignored ) - -// throw an exception after it has been hooked into the custom error handler -#define THROW_CORE_ERROR( ctx, custom, ... ) \ - (void)call_error_handler( ctx, custom TSRMLS_CC, /*warning*/ false, __VA_ARGS__ ); \ - throw core::CoreException(); - -//********************************************************************************************************************************* -// ODBC/Zend function wrappers -//********************************************************************************************************************************* - -namespace core { - - // base exception for the driver - struct CoreException : public std::exception { - - CoreException() - { - } - }; - - inline void check_for_mars_error( sqlsrv_stmt* stmt, SQLRETURN r TSRMLS_DC ) - { - // We check for the 'connection busy' error caused by having MultipleActiveResultSets off - // and return a more helpful message prepended to the ODBC errors if that error occurs - if( !SQL_SUCCEEDED( r )) { - - SQLCHAR err_msg[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLSMALLINT len = 0; - - SQLRETURN r = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, - err_msg, SQL_MAX_MESSAGE_LENGTH, &len ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - - throw CoreException(); - } - std::size_t driver_version = stmt->conn->driver_version; - if( !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); - } - } - } - - // *** ODBC wrappers *** - - // wrap the ODBC functions to throw exceptions rather than use the return value to signal errors - // some of the signatures have been altered to be more convenient since the return value is no longer - // required to return the status of the call (e.g., SQLNumResultCols). - // These functions take the sqlsrv_context type. However, since the error handling code can alter - // the context to hold the error, they are not passed as const. - - inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) - { - SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, - diag_info_buffer, buffer_length, out_buffer_length ); - - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - - return r; - } - - inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle, - _Out_writes_(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); - CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) { - throw CoreException(); - } - } - - inline void SQLBindParameter( sqlsrv_stmt* stmt, - SQLUSMALLINT ParameterNumber, - SQLSMALLINT InputOutputType, - SQLSMALLINT ValueType, - SQLSMALLINT ParameterType, - SQLULEN ColumnSize, - SQLSMALLINT DecimalDigits, - _Inout_ SQLPOINTER ParameterValuePtr, - SQLLEN BufferLength, - _Inout_ SQLLEN * StrLen_Or_IndPtr - TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, - DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, - _Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC ) - { - SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, - buffer_length, out_buffer_length, field_type_num ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLCHAR* col_name, SQLSMALLINT col_name_length, - _Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size, - _Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, - data_type, col_size, decimal_digits, nullable); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLEndTran( SQLSMALLINT handleType, sqlsrv_conn* conn, SQLSMALLINT completionType TSRMLS_DC ) - { - SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success - inline SQLRETURN SQLExecDirect( sqlsrv_stmt* stmt, char* sql TSRMLS_DC ) - { - SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast( sql ), SQL_NTS ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - - throw CoreException(); - } - return r; - } - - inline SQLRETURN SQLExecDirectW( sqlsrv_stmt* stmt, wchar_t* wsql TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast( wsql ), SQL_NTS ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - // SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success - inline SQLRETURN SQLExecute( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLExecute( stmt->handle() ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return r; - } - - inline SQLRETURN SQLFetchScroll( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) - { - SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - - // wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine - inline void SQLFreeHandle( sqlsrv_context& ctx TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() ); - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} - } - - inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) - { - SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); - - if( r == SQL_NO_DATA ) - return r; - - CHECK_SQL_ERROR( r, stmt ) { - throw CoreException(); - } - - if( handle_warning ) { - CHECK_SQL_WARNING_AS_ERROR( r, stmt ) { - throw CoreException(); - } - } - - return r; - } - - - inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, _Out_ SQLPOINTER info_value, SQLSMALLINT buffer_len, - _Out_ SQLSMALLINT* str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - - inline void SQLGetTypeInfo( sqlsrv_stmt* stmt, SQLUSMALLINT data_type TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLGetTypeInfo( stmt->handle(), data_type ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - // SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set. - inline SQLRETURN SQLMoreResults( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r = ::SQLMoreResults( stmt->handle() ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return r; - } - - inline SQLSMALLINT SQLNumResultCols( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - SQLSMALLINT num_cols; - r = ::SQLNumResultCols( stmt->handle(), &num_cols ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return num_cols; - } - - // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more - // parameters or when the parameters are all processed. - inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, _Out_ SQLPOINTER* value_ptr_ptr TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - inline void SQLPrepareW( sqlsrv_stmt* stmt, SQLWCHAR * sql, SQLINTEGER sql_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLPrepareW( stmt->handle(), sql, sql_len ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLPutData( sqlsrv_stmt* stmt, SQLPOINTER data_ptr, SQLLEN strlen_or_ind TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline SQLLEN SQLRowCount( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - SQLLEN rows_affected; - - r = ::SQLRowCount( stmt->handle(), &rows_affected ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return rows_affected; - } - - - inline void SQLSetConnectAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - } - - - inline void SQLSetEnvAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len ); - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - } - - inline void SQLSetConnectAttr( sqlsrv_conn* conn, SQLINTEGER attribute, SQLPOINTER value_ptr, SQLINTEGER value_len TSRMLS_DC ) - { - SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - inline void SQLSetStmtAttr( sqlsrv_stmt* stmt, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - // *** zend wrappers *** - - //zend_resource_dtor sets the type of destroyed resources to -1 - #define RSRC_INVALID_TYPE -1 - - // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string - // so allocated memory inside of value_z should be released before assigning it to the new string - inline void sqlsrv_zval_stringl(zval* value_z, const char* str, const std::size_t str_len) - { - if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { - zend_string* temp_zstr = zend_string_init(str, str_len, 0); - zend_string_release(Z_STR_P(value_z)); - ZVAL_NEW_STR(value_z, temp_zstr); - } - else { - ZVAL_STRINGL(value_z, str, str_len); - } - } - - - // exception thrown when a zend function wrapped here fails. - - // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw - // exceptions when an error occurs. They are prefaced with sqlsrv_ because many of the zend functions are - // actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system. - // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error - // that can be thrown from it. - - inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, zend_ulong index, zval* value TSRMLS_DC) - { - int zr = ::add_index_zval( array, index, value ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_next_index_zval( sqlsrv_context& ctx, zval* array, zval* value TSRMLS_DC) - { - int zr = ::add_next_index_zval( array, value ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, const char* key TSRMLS_DC ) - { - int zr = ::add_assoc_null( array_z, key ); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, const char* key, zend_long val TSRMLS_DC ) - { - int zr = ::add_assoc_long( array_z, key, val ); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, const char* key, char* val, bool duplicate TSRMLS_DC ) - { - int zr = ::add_assoc_string(array_z, key, val); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - if (duplicate == 0) { - sqlsrv_free(val); - } - } - - inline void sqlsrv_array_init( sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) - { - int zr = ::array_init(new_array); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval* stream_z TSRMLS_DC ) - { - // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment - php_stream_from_zval_no_verify( stream, stream_z ); - CHECK_CUSTOM_ERROR( stream == NULL, ctx, SQLSRV_ERROR_ZEND_STREAM ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_get_current_data(sqlsrv_context& ctx, HashTable* ht, _Out_ zval*& output_data TSRMLS_DC) - { - int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_get_current_data_ptr(sqlsrv_context& ctx, HashTable* ht, _Out_ void*& output_data TSRMLS_DC) - { - int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, zend_ulong index TSRMLS_DC ) - { - int zr = ::zend_hash_index_del( ht, index ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, zend_ulong index, zval* data_z TSRMLS_DC ) - { - int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_update_ptr(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData TSRMLS_DC) - { - int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - - inline void sqlsrv_zend_hash_index_update_mem(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData, std::size_t size TSRMLS_DC) - { - int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, zval* data TSRMLS_DC ) - { - int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert_mem(sqlsrv_context& ctx, HashTable* ht, void* data, uint data_size TSRMLS_DC) - { - int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert_ptr(sqlsrv_context& ctx, HashTable* ht, void* data TSRMLS_DC) - { - int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, HashTable* ht, uint32_t initial_size, - dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) - { - ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); - } - - inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, zend_string* key, unsigned int key_len, zval* data, - unsigned int data_size, zval* pDest TSRMLS_DC ) - { - int zr = (pDest = ::zend_hash_add(ht, key, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - -template -sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) -{ - return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC ); -} - -template -sqlsrv_conn* allocate_conn( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) -{ - return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC ); -} - -} // namespace core - -#endif // CORE_SQLSRV_H diff --git a/pdo_sqlsrv/core_stmt.cpp b/pdo_sqlsrv/core_stmt.cpp deleted file mode 100644 index a4ef0d9dd..000000000 --- a/pdo_sqlsrv/core_stmt.cpp +++ /dev/null @@ -1,2471 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_stmt.cpp -// -// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -namespace { - -// certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the -// results of every field request, and if it is out of order, we cache those for preceding fields. -struct field_cache { - - void* value; - SQLLEN len; - sqlsrv_phptype type; - - field_cache( void* field_value, SQLLEN field_len, sqlsrv_phptype t ) - : type( t ) - { - // if the value is NULL, then just record a NULL pointer - if( field_value != NULL ) { - value = sqlsrv_malloc( field_len ); - memcpy_s( value, field_len, field_value, field_len ); - len = field_len; - } - else { - value = NULL; - len = 0; - } - } - - // no destructor because we don't want to release the memory when it goes out of scope, but instead we - // rely on the hash table destructor to free the memory -}; - -const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field - -// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads -const unsigned int UTF8_MIDBYTE_MASK = 0xc0; -const unsigned int UTF8_MIDBYTE_TAG = 0x80; -const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0; -const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0; -const unsigned int UTF8_3BYTESEQ_TAG = 0xe0; -const unsigned int UTF8_4BYTESEQ_TAG = 0xf0; -const unsigned int UTF8_NBYTESEQ_MASK = 0xf0; - -// constants used to convert from a DateTime object to a string which is sent to the server. -// Using the format defined by the ODBC documentation at http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx -namespace DateTime { - -const char DATETIME_CLASS_NAME[] = "DateTime"; -const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1; -const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P"; -const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT ); -const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u"; -const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT ); -const char DATE_FORMAT[] = "Y-m-d"; -const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT ); - -} - -// *** internal functions *** -// Only declarations are put here. Functions contain the documentation they need at their definition sites. -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ); -size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ); -bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC ); -bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z ); -void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC); -// returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); -void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); -// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) -void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); -void field_cache_dtor( zval* data_z ); -void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ); -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ); -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ); -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); -// assure there is enough space for the output parameter string -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, - SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, - SQLLEN& buffer_len TSRMLS_DC ); -void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ); -// send all the stream data -void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ); -// called when a bound output string parameter is to be destroyed -void sqlsrv_output_param_dtor( zval* data ); -// called when a bound stream parameter is to be destroyed. -void sqlsrv_stream_dtor( zval* data ); -bool is_streamable_type( SQLINTEGER sql_type ); - -} - -// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier. -sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : - sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ), - conn( c ), - executed( false ), - past_fetch_end( false ), - current_results( NULL ), - cursor_type( SQL_CURSOR_FORWARD_ONLY ), - has_rows( false ), - fetch_called( false ), - last_field_index( -1 ), - past_next_result_end( false ), - param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte - send_streams_at_exec( true ), - current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), - current_stream_read( 0 ), - query_timeout( QUERY_TIMEOUT_INVALID ), - buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) -{ - ZVAL_UNDEF( &active_stream ); - // initialize the input string parameters array (which holds zvals) - core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); - - // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) - ZVAL_NEW_ARR( ¶m_streams ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC); - - // initialize the (input only) datetime parameters of converted date time objects to strings - array_init( ¶m_datetime_buffers ); - - // initialize the output string parameters (which holds sqlsrv_output_param structures) - ZVAL_NEW_ARR( &output_params ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC); - - // initialize the field cache - ZVAL_NEW_ARR( &field_cache ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC); -} - -// desctructor for sqlsrv statement. -sqlsrv_stmt::~sqlsrv_stmt( void ) -{ - if( Z_TYPE( active_stream ) != IS_UNDEF ) { - TSRMLS_FETCH(); - close_active_stream( this TSRMLS_CC ); - } - - // delete any current results - if( current_results ) { - current_results->~sqlsrv_result_set(); - efree( current_results ); - current_results = NULL; - } - - invalidate(); - zval_ptr_dtor( ¶m_input_strings ); - zval_ptr_dtor( &output_params ); - zval_ptr_dtor( ¶m_streams ); - zval_ptr_dtor( ¶m_datetime_buffers ); - zval_ptr_dtor( &field_cache ); -} - - -// centralized place to release (without destroying the hash tables -// themselves) all the parameter data that accrues during the -// execution phase. -void sqlsrv_stmt::free_param_data( TSRMLS_D ) -{ - SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY, - "sqlsrv_stmt::free_param_data: Param zvals aren't arrays." ); - zend_hash_clean( Z_ARRVAL( param_input_strings )); - zend_hash_clean( Z_ARRVAL( output_params )); - zend_hash_clean( Z_ARRVAL( param_streams )); - zend_hash_clean( Z_ARRVAL( param_datetime_buffers )); - zend_hash_clean( Z_ARRVAL( field_cache )); -} - - -// to be called whenever a new result set is created, such as after an -// execute or next_result. Resets the state variables. - -void sqlsrv_stmt::new_result_set( TSRMLS_D ) -{ - this->fetch_called = false; - this->has_rows = false; - this->past_next_result_end = false; - this->past_fetch_end = false; - this->last_field_index = -1; - - // delete any current results - if( current_results ) { - current_results->~sqlsrv_result_set(); - efree( current_results ); - current_results = NULL; - } - - // create a new result set - if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { - current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC ); - } - else { - current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this ); - } -} - -// core_sqlsrv_create_stmt -// Common code to allocate a statement from either driver. Returns a valid driver statement object or -// throws an exception if an error occurs. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// stmt_factory - factory method to create a statement. -// options_ht - A HashTable of user provided options to be set on the statement. -// valid_stmt_opts - An array of valid driver supported statement options. -// err - callback for error handling -// driver - reference to caller -// Return -// Returns the created statement - -sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, - const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ) -{ - sqlsrv_malloc_auto_ptr stmt; - SQLHANDLE stmt_h = SQL_NULL_HANDLE; - - try { - - core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC ); - - stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC ); - - stmt->conn = conn; - - // handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free - // in the catch block below. - stmt_h = SQL_NULL_HANDLE; - - // process the options array given to core_sqlsrv_prepare. - if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - zend_ulong index = -1; - zend_string *key = NULL; - zval* value_z = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) { - - int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); - - const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); - - // if the key didn't match, then return the error to the script. - // The driver layer should ensure that the key is valid. - DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); - - // perform the actions the statement option needs done. - (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - } - - sqlsrv_stmt* return_stmt = stmt; - stmt.transferred(); - - return return_stmt; - } - catch( core::CoreException& ) - { - if( stmt ) { - - conn->set_last_error( stmt->last_error() ); - stmt->~sqlsrv_stmt(); - } - - // if allocating the handle failed before the statement was allocated, free the handle - if( stmt_h != SQL_NULL_HANDLE) { - ::SQLFreeHandle( SQL_HANDLE_STMT, stmt_h ); - } - - throw; - } - catch( ... ) { - - DIE( "core_sqlsrv_allocate_stmt: Unknown exception caught." ); - } -} - - -// core_sqlsrv_bind_param -// Binds a parameter using SQLBindParameter. It allocates memory and handles other details -// in translating between the driver and ODBC. -// Parameters: -// param_num - number of the parameter, 0 based -// param_z - zval of the parameter -// php_out_type - type to return for output parameter -// sql_type - ODBC constant for the SQL Server type (SQL_UNKNOWN_TYPE = 0 means not known, so infer defaults) -// column_size - length of the field on the server (SQLSRV_UKNOWN_SIZE means not known, so infer defaults) -// decimal_digits - if column_size is valid and the type contains a scale, this contains the scale -// Return: -// Nothing, though an exception is thrown if an error occurs -// The php type of the parameter is taken from the zval. -// The sql type is given as a hint if the driver provides it. - -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, - SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, - SQLSMALLINT decimal_digits TSRMLS_DC ) -{ - SQLSMALLINT c_type; - SQLPOINTER buffer = NULL; - SQLLEN buffer_len = 0; - - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT, - "core_sqlsrv_bind_param: Invalid parameter direction." ); - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || php_out_type != SQLSRV_PHPTYPE_INVALID, - "core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param." ); - - try { - - // check is only < because params are 0 based - CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) { - throw core::CoreException(); - } - - // resize the statements array of int_ptrs if the parameter isn't already set. - if( stmt->param_ind_ptrs.size() < static_cast(param_num + 1) ) { - stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA ); - } - SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ]; - - zval* param_ref = param_z; - if ( Z_ISREF_P( param_z ) ) { - ZVAL_DEREF( param_z ); - } - bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL ); - bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE ); - // if the user asks for for a specific type for input and output, make sure the data type we send matches the data we - // type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so - // we always let that match if they want a string back. - if( direction == SQL_PARAM_INPUT_OUTPUT ) { - bool match = false; - switch( php_out_type ) { - case SQLSRV_PHPTYPE_INT: - if( zval_was_null || zval_was_bool ) { - convert_to_long( param_z ); - } - match = Z_TYPE_P( param_z ) == IS_LONG; - break; - case SQLSRV_PHPTYPE_FLOAT: - if( zval_was_null ) { - convert_to_double( param_z ); - } - match = Z_TYPE_P( param_z ) == IS_DOUBLE; - break; - case SQLSRV_PHPTYPE_STRING: - // anything can be converted to a string - convert_to_string( param_z ); - match = true; - break; - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_DATETIME: - case SQLSRV_PHPTYPE_STREAM: - SQLSRV_ASSERT( false, "Invalid type for an output parameter." ); - break; - default: - SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." ); - break; - } - CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) { - throw core::CoreException(); - } - } - - // if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so - // when the buffer is filled, the type is correct - if( direction == SQL_PARAM_OUTPUT ) { - switch( php_out_type ) { - case SQLSRV_PHPTYPE_INT: - convert_to_long( param_z ); - break; - case SQLSRV_PHPTYPE_FLOAT: - convert_to_double( param_z ); - break; - case SQLSRV_PHPTYPE_STRING: - convert_to_string( param_z ); - break; - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_DATETIME: - case SQLSRV_PHPTYPE_STREAM: - SQLSRV_ASSERT( false, "Invalid type for an output parameter" ); - break; - default: - SQLSRV_ASSERT( false, "Uknown SQLSRV_PHPTYPE_* constant given" ); - break; - } - } - - SQLSRV_ASSERT(( Z_TYPE_P( param_z ) != IS_STRING && Z_TYPE_P( param_z ) != IS_RESOURCE ) || - ( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 || - encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" ); - - // if the sql type is unknown, then set the default based on the PHP type passed in - if( sql_type == SQL_UNKNOWN_TYPE ) { - default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); - } - - // if the size is unknown, then set the default based on the PHP type passed in - if( column_size == SQLSRV_UNKNOWN_SIZE ) { - default_sql_size_and_scale( stmt, static_cast( param_num ), param_z, encoding, column_size, decimal_digits TSRMLS_CC ); - } - - // determine the ODBC C type - c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC ); - - // set the buffer based on the PHP parameter type - switch( Z_TYPE_P( param_z )) { - - case IS_NULL: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - ind_ptr = SQL_NULL_DATA; - buffer = NULL; - buffer_len = 0; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - { - // if it is boolean, set the lval to 0 or 1 - convert_to_long( param_z ); - buffer = ¶m_z->value; - buffer_len = sizeof( Z_LVAL_P( param_z )); - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool ); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - } - } - break; - case IS_DOUBLE: - { - buffer = ¶m_z->value; - buffer_len = sizeof( Z_DVAL_P( param_z )); - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_ref, static_cast( param_num ), false ); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - } - } - break; - case IS_STRING: - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) - if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) { - - zval wbuffer_z; - ZVAL_NULL( &wbuffer_z ); - - bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - param_num + 1, get_last_error_message() ) { - throw core::CoreException(); - } - buffer = Z_STRVAL_P( &wbuffer_z ); - buffer_len = Z_STRLEN_P( &wbuffer_z ); - core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC); - } - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, - // we reallocate the string if it's interned - if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) { - core::sqlsrv_zval_stringl( param_z, static_cast(buffer), buffer_len ); - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - } - - // if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR) - // or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type, - // convert it to wchar first - if( direction == SQL_PARAM_INPUT_OUTPUT && - ( c_type == SQL_C_WCHAR || - ( c_type == SQL_C_BINARY && - ( sql_type == SQL_WCHAR || - sql_type == SQL_WVARCHAR || - sql_type == SQL_WLONGVARCHAR )))) { - - bool converted = convert_input_param_to_utf16( param_z, param_z ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - param_num + 1, get_last_error_message() ) { - throw core::CoreException(); - } - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - ind_ptr = buffer_len; - } - - // since this is an output string, assure there is enough space to hold the requested size and - // set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr) - resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, - buffer, buffer_len TSRMLS_CC ); - - // save the parameter to be adjusted and/or converted after the results are processed - sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len )); - - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - - // For output parameters, if we set the column_size to be same as the buffer_len, - // then if there is a truncation due to the data coming from the server being - // greater than the column_size, we don't get any truncation error. In order to - // avoid this silent truncation, we set the column_size to be "MAX" size for - // string types. This will guarantee that there is no silent truncation for - // output parameters. - if( direction == SQL_PARAM_OUTPUT ) { - - switch( sql_type ) { - - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_WVARCHAR: - column_size = SQL_SS_LENGTH_UNLIMITED; - break; - - default: - break; - } - } - } - break; - case IS_RESOURCE: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - sqlsrv_stream stream_encoding( param_z, encoding ); - HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC ); - buffer = reinterpret_cast( param_num ); - Z_TRY_ADDREF_P( param_z ); // so that it doesn't go away while we're using it - buffer_len = 0; - ind_ptr = SQL_DATA_AT_EXEC; - } - break; - case IS_OBJECT: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - zval function_z; - zval buffer_z; - zval format_z; - zval params[1]; - ZVAL_UNDEF( &function_z ); - ZVAL_UNDEF( &buffer_z ); - ZVAL_UNDEF( &format_z ); - ZVAL_UNDEF( params ); - - bool valid_class_name_found = false; - - zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC ); - - while( class_entry != NULL ) { - - if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && - stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) { - valid_class_name_found = true; - break; - } - - else { - - // Check the parent - class_entry = class_entry->parent; - } - } - - CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - - // if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error' - // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' - // sql type, it lacks the timezone. - if( sql_type == SQL_SS_TIMESTAMPOFFSET ) { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), - DateTime::DATETIMEOFFSET_FORMAT_LEN ); - } - else if( sql_type == SQL_TYPE_DATE ) { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); - } - else { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); - } - // call the DateTime::format member function to convert the object to a string that SQL Server understands - core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); - params[0] = format_z; - // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the - // DateTime object and $format_z is the format string. - int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); - zend_string_release( Z_STR( format_z )); - zend_string_release( Z_STR( function_z )); - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - buffer = Z_STRVAL( buffer_z ); - zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z ); - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - buffer_len = Z_STRLEN( buffer_z ) - 1; - ind_ptr = buffer_len; - break; - } - case IS_ARRAY: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ); - break; - default: - DIE( "core_sqlsrv_bind_param: Unsupported PHP type. Only string, float, int, and streams (resource) are supported. " - "It is the responsibilty of the driver layer to convert a parameter to one of these types." ); - break; - } - - if( zval_was_null ) { - ind_ptr = SQL_NULL_DATA; - } - - core::SQLBindParameter( stmt, param_num + 1, direction, - c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); - } - catch( core::CoreException& e ) { - stmt->free_param_data( TSRMLS_C ); - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - throw e; - } -} - - -// core_sqlsrv_execute -// Executes the statement previously prepared -// Parameters: -// stmt - the core sqlsrv_stmt structure that contains the ODBC handle -// Return: -// true if there is data, false if there is not - -void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len ) -{ - SQLRETURN r; - - try { - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - if( sql ) { - - sqlsrv_malloc_auto_ptr wsql_string; - unsigned int wsql_len = 0; - if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { - wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t ))); - wsql_string[0] = L'\0'; - wsql_len = 0; - } - else { - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : - stmt->encoding() ); - wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - sql_len, &wsql_len ); - CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - get_last_error_message() ) { - throw core::CoreException(); - } - } - r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC ); - } - else { - r = core::SQLExecute( stmt TSRMLS_CC ); - } - - // if data is needed (streams were bound) and they should be sent at execute time, then do so now - if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) { - - send_param_streams( stmt TSRMLS_CC ); - } - - stmt->new_result_set( TSRMLS_C ); - stmt->executed = true; - - // if all the data has been sent and no data was returned then finalize the output parameters - if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { - - finalize_output_parameters( stmt TSRMLS_CC ); - } - // stream parameters are sent, clean the Hashtable - if ( stmt->send_streams_at_exec ) { - zend_hash_clean( Z_ARRVAL( stmt->param_streams )); - } - } - catch( core::CoreException& e ) { - - // if the statement executed but failed in a subsequent operation before returning, - // we need to cancel the statement and deref the output and stream parameters - if ( stmt->send_streams_at_exec ) { - zend_hash_clean( Z_ARRVAL( stmt->output_params )); - zend_hash_clean( Z_ARRVAL( stmt->param_streams )); - } - if( stmt->executed ) { - SQLCancel( stmt->handle() ); - // stmt->executed = false; should this be reset if something fails? - } - - throw e; - } -} - - -// core_sqlsrv_fetch -// Moves the cursor according to the parameters (by default, moves to the next row) -// Parameters: -// stmt - the sqlsrv_stmt of the cursor -// fetch_orientation - method to move the cursor -// fetch_offset - if the method has a parameter (such as number of rows to move or literal row number) -// Returns: -// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the -// user scrolls past a non-scrollable result set - -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ) -{ - // pre-condition check - SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE, - "core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." ); - - try { - - // clear the field cache of the previous fetch - zend_hash_clean( Z_ARRVAL( stmt->field_cache )); - - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) { - throw core::CoreException(); - } - - SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); - - CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { - throw core::CoreException(); - } - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - // if the statement has rows and is not scrollable but doesn't yet have - // fetch_called, this must be the first time we've called sqlsrv_fetch. - if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->has_rows && !stmt->fetch_called ) { - stmt->fetch_called = true; - return true; - } - - // move to the record requested. For absolute records, we use a 0 based offset, so +1 since - // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. - SQLRETURN r = stmt->current_results->fetch( fetch_orientation, - ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 - TSRMLS_CC ); - if( r == SQL_NO_DATA ) { - // if this is a forward only cursor, mark that we've passed the end so future calls result in an error - if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { - stmt->past_fetch_end = true; - } - return false; - } - - // mark that we called fetch (which get_field, et. al. uses) and reset our last field retrieved - stmt->fetch_called = true; - stmt->last_field_index = -1; - stmt->has_rows = true; // since we made it this far, we must have at least one row - } - catch (core::CoreException& e) { - throw e; - } - catch ( ... ) { - DIE( "core_sqlsrv_fetch: Unexpected exception occurred." ); - } - - return true; -} - - -// Retrieves metadata for a field of a prepared statement. -// Parameters: -// colno - the index of the field for which to return the metadata. columns are 0 based in PDO -// Return: -// A field_meta_data* consisting of the field metadata. - -field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ) -{ - // pre-condition check - SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." ); - - sqlsrv_malloc_auto_ptr meta_data; - SQLSMALLINT field_name_len = 0; - - meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data(); - meta_data->field_name = static_cast( sqlsrv_malloc( SS_MAXCOLNAMELEN + 1 )); - - try { - core::SQLDescribeCol( stmt, colno + 1, meta_data->field_name.get(), SS_MAXCOLNAMELEN, &field_name_len, - &(meta_data->field_type), &(meta_data->field_size), &(meta_data->field_scale), - &(meta_data->field_is_nullable) TSRMLS_CC ); - } - catch( core::CoreException& e ) { - throw e; - } - - // depending on field type, we add the values into size or precision/scale. - switch( meta_data->field_type ) { - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_BIT: - case SQL_TINYINT: - case SQL_SMALLINT: - case SQL_INTEGER: - case SQL_BIGINT: - case SQL_REAL: - case SQL_FLOAT: - case SQL_DOUBLE: - { - meta_data->field_precision = meta_data->field_size; - meta_data->field_size = 0; - break; - } - default: { - break; - } - } - - // Set the field name lenth - meta_data->field_name_len = field_name_len; - - field_meta_data* result_field_meta_data = meta_data; - meta_data.transferred(); - return result_field_meta_data; -} - - -// core_sqlsrv_get_field -// Return the value of a column from ODBC -// Parameters: -// stmt - the sqlsrv_stmt from which to retrieve the column -// field_index - 0 based index for the column to retrieve -// sqlsrv_php_type_in - sqlsrv_php_type structure that tells what format to return the data in -// field_value - pointer to the data retrieved -// field_len - length of the data in the field_value buffer -// Returns: -// Nothing, excpetion thrown if an error occurs - -void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string, - _Out_ void*& field_value, _Out_ SQLLEN* field_len, bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) -{ - try { - - // close the stream to release the resource - close_active_stream(stmt TSRMLS_CC); - - // if the field has been retrieved before, return the previous result - field_cache* cached = NULL; - if (NULL != ( cached = static_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast( field_index ))))) { - // the field value is NULL - if( cached->value == NULL ) { - field_value = NULL; - *field_len = 0; - if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } - } - else { - - field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); - memcpy_s( field_value, ( cached->len * sizeof( char )), cached->value, cached->len ); - if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING) { - // prevent the 'string not null terminated' warning - reinterpret_cast( field_value )[ cached->len ] = '\0'; - } - *field_len = cached->len; - if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } - } - return; - } - - sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; - - SQLLEN sql_field_type = 0; - SQLLEN sql_field_len = 0; - - // Make sure that the statement was executed and not just prepared. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they - // may also be retrieved. - if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { - sqlsrv_phptype invalid; - invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { - SQLSRV_ASSERT((cached = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->field_cache), i))) == NULL, - "Field already cached." ); - core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, - sqlsrv_php_type_out TSRMLS_CC ); - // delete the value returned since we only want it cached, not the actual value - if( field_value ) { - efree( field_value ); - field_value = NULL; - *field_len = 0; - } - } - } - - // If the php type was not specified set the php type to be the default type. - if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { - - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Get the length of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); - - // Get the corresponding php type from the sql type. - sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast( sql_field_type ), static_cast( sql_field_len ), prefer_string ); - } - - // Verify that we have an acceptable type to convert. - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { - throw core::CoreException(); - } - - if( sqlsrv_php_type_out != NULL ) - *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); - - // Retrieve the data - core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - - // if the user wants us to cache the field, we'll do it - if( cache_field ) { - field_cache cache( field_value, *field_len, sqlsrv_php_type ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); - } - } - - catch( core::CoreException& e ) { - throw e; - } -} - -// core_sqlsrv_has_any_result -// return if any result set or rows affected message is waiting -// to be consumed and moved over by sqlsrv_next_result. -// Parameters: -// stmt - The statement object on which to check for results. -// Return: -// true if any results are present, false otherwise. - -bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - // Use SQLNumResultCols to determine if we have rows or not. - SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - // use SQLRowCount to determine if there is a rows status waiting - SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); - return (num_cols != 0) || (rows_affected > 0); -} - -// core_sqlsrv_next_result -// Advances to the next result set from the last executed query -// Parameters -// stmt - the sqlsrv_stmt structure -// Returns -// Nothing, exception thrown if problem occurs - -void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params, bool throw_on_errors ) -{ - try { - - // make sure that the statement has been executed. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) { - throw core::CoreException(); - } - - close_active_stream( stmt TSRMLS_CC ); - - SQLRETURN r; - if( throw_on_errors ) { - r = core::SQLMoreResults( stmt TSRMLS_CC ); - } - else { - r = SQLMoreResults( stmt->handle() ); - } - - if( r == SQL_NO_DATA ) { - - if( &(stmt->output_params) && finalize_output_params ) { - // if we're finished processing result sets, handle the output parameters - finalize_output_parameters( stmt TSRMLS_CC ); - } - - // mark we are past the end of all results - stmt->past_next_result_end = true; - return; - } - - stmt->new_result_set( TSRMLS_C ); - } - catch( core::CoreException& e ) { - - SQLCancel( stmt->handle() ); - throw e; - } -} - - -// core_sqlsrv_post_param -// Performs any actions post execution for each parameter. For now it cleans up input parameters memory from the statement -// Parameters: -// stmt - the sqlsrv_stmt structure -// param_num - 0 based index of the parameter -// param_z - parameter value itself. -// Returns: -// Nothing, exception thrown if problem occurs - -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* param_z TSRMLS_DC ) -{ - SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); - SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); - - // if the parameter was an input string, delete it from the array holding input parameter strings - if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) { - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC ); - } - - // if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams - // PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it - // with sqlsrv_send_stream_data. - if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) { - sqlsrv_stream* stream_encoding = NULL; - stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->param_streams), param_num)); - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC ); - } -} - -//Calls SQLSetStmtAttr to set a cursor. -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ) -{ - try { - - switch( cursor_type ) { - - case SQL_CURSOR_STATIC: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_DYNAMIC: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_KEYSET_DRIVEN: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_FORWARD_ONLY: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQLSRV_CURSOR_BUFFERED: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); - break; - } - - stmt->cursor_type = cursor_type; - - } - catch( core::CoreException& ) { - throw; - } -} - -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - if( Z_TYPE_P( value_z ) != IS_LONG ) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); - } - - core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); -} - -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ) -{ - if( limit <= 0 ) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); - } - - stmt->buffered_query_limit = limit; -} - - -// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout -// which accepts timeout parameter as a long. If the zval is not of type long -// than throws error. -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - try { - - // validate the value - if( Z_TYPE_P( value_z ) != IS_LONG || Z_LVAL_P( value_z ) < 0 ) { - - convert_to_string( value_z ); - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) ); - } - - core_sqlsrv_set_query_timeout( stmt, static_cast( Z_LVAL_P( value_z )) TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } -} - -// Overloaded. Accepts the timeout as a long. -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." ); - - // set the statement attribute - core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC ); - - // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which - // is represented by -1. - long lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); - - // set the LOCK_TIMEOUT on the server. - char lock_timeout_sql[ 32 ]; - int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", - lock_timeout ); - - SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), - "stmt_option_query_timeout: sprintf_s failed. Shouldn't ever fail." ); - - core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC ); - - stmt->query_timeout = timeout; - } - catch( core::CoreException& ) { - throw; - } -} - -void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - TSRMLS_C; - - // zend_is_true does not fail. It either returns true or false. - stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false; -} - - -// core_sqlsrv_send_stream_packet -// send a single packet from a stream parameter to the database using -// ODBC. This will also handle the transition between parameters. It -// returns true if it is not done sending, false if it is finished. -// return_value is what should be returned to the script if it is -// given. Any errors that occur are posted here. -// Parameters: -// stmt - query to send the next packet for -// Returns: -// true if more data remains to be sent, false if all data processed - -bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - SQLRETURN r = SQL_SUCCESS; - - // if there no current parameter to process, get the next one - // (probably because this is the first call to sqlsrv_send_stream_data) - if( stmt->current_stream.stream_z == NULL ) { - - if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) { - - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); - stmt->current_stream_read = 0; - return false; - } - } - - try { - - // get the stream from the zval we bound - php_stream* param_stream = NULL; - core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); - - // if we're at the end, then release our current parameter - if( php_stream_eof( param_stream )) { - // if no data was actually sent prior, then send a NULL - if( stmt->current_stream_read == 0 ) { - // send an empty string, which is what a 0 length does. - char buff[1]; // temp storage to hand to SQLPutData - core::SQLPutData( stmt, buff, 0 TSRMLS_CC ); - } - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); - stmt->current_stream_read = 0; - } - // read the data from the stream, send it via SQLPutData and track how much we've sent. - else { - char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; - std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character - std::size_t read = php_stream_read( param_stream, buffer, buffer_size ); - - if (read > UINT_MAX) - { - LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); - throw core::CoreException(); - } - - stmt->current_stream_read += static_cast( read ); - if( read > 0 ) { - // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character - // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it - // twice. - // If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion - // since all other MBCS supported by SQL Server are 2 byte maximum size. - if( stmt->current_stream.encoding == CP_UTF8 ) { - - // the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a - // expansion of 2x the UTF-8 size. - wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; - // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate - int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, static_cast( read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); - if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) { - - // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more - // in, then reattempt the conversion. If it fails the second time, then an error is returned. - size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC ); - // read the missing bytes - size_t new_read = php_stream_read( param_stream, static_cast( buffer ) + read, - need_to_read ); - // if the bytes couldn't be read, then we return an error - CHECK_CUSTOM_ERROR( new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { - throw core::CoreException(); - } - // try the conversion again with the complete character - wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, static_cast( read + new_read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); - // something else must be wrong if it failed - CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { - throw core::CoreException(); - } - } - core::SQLPutData( stmt, wbuffer, wsize * sizeof( wchar_t ) TSRMLS_CC ); - } - else { - core::SQLPutData( stmt, buffer, read TSRMLS_CC ); - } - } - } - - } - catch( core::CoreException& e ) { - stmt->free_param_data( TSRMLS_C ); - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - SQLCancel( stmt->handle() ); - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT ); - stmt->current_stream_read = 0; - throw e; - } - - return true; -} - -void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) -{ - TSRMLS_C; - - // This implementation should never get called. - DIE( "Not implemented." ); -} - -void stmt_option_query_timeout:: operator()( sqlsrv_stmt* stmt, stmt_option const* /**/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_send_at_exec:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC ); -} - - -// internal function to release the active stream. Called by each main API function -// that will alter the statement and cancel any retrieval of data from a stream. -void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) -{ - // if there is no active stream, return - if( Z_TYPE( stmt->active_stream ) == IS_UNDEF ) { - return; - } - - php_stream* stream = NULL; - - // we use no verify since verify would return immediately and we want to assert, not return. - php_stream_from_zval_no_verify( stream, &( stmt->active_stream )); - - SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." ); - - php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here. - - SQLSRV_ASSERT( Z_TYPE( stmt->active_stream ) == IS_UNDEF, "close_active_stream: Active stream not closed." ); - -} - -// local routines not shared by other files (arranged alphabetically) - -namespace { - -bool is_streamable_type( SQLLEN sql_type ) -{ - switch( sql_type ) { - case SQL_CHAR: - case SQL_WCHAR: - case SQL_BINARY: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_SS_XML: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - return true; - } - - return false; -} - -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ) -{ - try { - - switch( sql_type ) { - // for types that are fixed in size or for which the size is unknown, return the display size. - case SQL_BIGINT: - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - case SQL_GUID: - case SQL_FLOAT: - case SQL_DOUBLE: - case SQL_REAL: - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_BINARY: - case SQL_CHAR: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_SS_XML: - case SQL_SS_UDT: - case SQL_WLONGVARCHAR: - case SQL_DATETIME: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - { - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC ); - break; - } - - // for wide char types for which the size is known, return the octet length instead, since it will include the - // the number of bytes necessary for the string, not just the characters - case SQL_WCHAR: - case SQL_WVARCHAR: - { - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC ); - break; - } - - default: - DIE ( "Unexpected SQL type encountered in calc_string_size." ); - } - } - catch( core::CoreException& e ) { - throw e; - } -} - - -// calculates how many characters were cut off from the end of a buffer when reading -// in UTF-8 encoded text - -size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ) -{ - const char* last_char = buffer + buffer_end - 1; - size_t need_to_read = 0; - - // rewind until we are at the byte that starts the cut off character - while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) { - --last_char; - ++need_to_read; - } - - // determine how many bytes we need to read in based on the number of bytes in the character - // (# of high bits set) versus the number of bytes we've already read. - switch( *last_char & UTF8_NBYTESEQ_MASK ) { - case UTF8_2BYTESEQ_TAG1: - case UTF8_2BYTESEQ_TAG2: - need_to_read = 1 - need_to_read; - break; - case UTF8_3BYTESEQ_TAG: - need_to_read = 2 - need_to_read; - break; - case UTF8_4BYTESEQ_TAG: - need_to_read = 3 - need_to_read; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )); - break; - } - - return need_to_read; -} - - -// Caller is responsible for freeing the memory allocated for the field_value. -// The memory allocation has to happen in the core layer because otherwise -// the driver layer would have to calculate size of the field_value -// to decide the amount of memory allocation. -void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) -{ - try { - - close_active_stream( stmt TSRMLS_CC ); - - // make sure that fetch is called before trying to retrieve. - CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { - throw core::CoreException(); - } - - // make sure that fields are not retrieved incorrectly. - CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, - stmt->last_field_index ) { - throw core::CoreException(); - } - - switch( sqlsrv_php_type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_INT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( *field_len == SQL_NULL_DATA ) { - field_value = NULL; - break; - } - - field_value = field_value_temp; - field_value_temp.transferred(); - break; - } - - case SQLSRV_PHPTYPE_FLOAT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( *field_len == SQL_NULL_DATA ) { - field_value = NULL; - break; - } - - field_value = field_value_temp; - field_value_temp.transferred(); - break; - } - - case SQLSRV_PHPTYPE_STRING: - { - get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - break; - } - - // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and - // convert it to a DateTime object and return the created object - case SQLSRV_PHPTYPE_DATETIME: - { - char field_value_temp[ MAX_DATETIME_STRING_LEN ]; - zval params[1]; - zval field_value_temp_z; - zval function_z; - - ZVAL_UNDEF( &field_value_temp_z ); - ZVAL_UNDEF( &function_z ); - ZVAL_UNDEF( params ); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, - MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - zval_auto_ptr return_value_z; - return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); - ZVAL_UNDEF( return_value_z ); - - if( *field_len == SQL_NULL_DATA ) { - ZVAL_NULL( return_value_z ); - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - break; - } - - // Convert the string date to a DateTime object - core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); - core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 ); - params[0] = field_value_temp_z; - - if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, - params TSRMLS_CC ) == FAILURE) { - THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); - } - - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - zend_string_free( Z_STR( field_value_temp_z )); - zend_string_free( Z_STR( function_z )); - break; - } - - // create a stream wrapper around the field and return that object to the PHP script. calls to fread - // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file - // for how these fields are used. - case SQLSRV_PHPTYPE_STREAM: - { - - php_stream* stream = NULL; - sqlsrv_stream* ss = NULL; - SQLLEN sql_type; - - SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { - throw core::CoreException(); - } - - stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); - - CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { - throw core::CoreException(); - } - - ss = static_cast( stream->abstract ); - ss->stmt = stmt; - ss->field_index = field_index; - ss->sql_type = static_cast( sql_type ); - ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); - - zval_auto_ptr return_value_z; - return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); - ZVAL_UNDEF( return_value_z ); - - // turn our stream into a zval to be returned - php_stream_to_zval( stream, return_value_z ); - - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - break; - } - - case SQLSRV_PHPTYPE_NULL: - field_value = NULL; - *field_len = 0; - break; - - default: - DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); - break; - } - - // sucessfully retrieved the field, so update our last retrieved field - if( stmt->last_field_index < field_index ) { - stmt->last_field_index = field_index; - } - } - catch( core::CoreException& e ) { - throw e; - } -} - - -// check_for_next_stream_parameter -// see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise -// returns false -bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) -{ - int stream_index = 0; - SQLRETURN r = SQL_SUCCESS; - sqlsrv_stream* stream_encoding = NULL; - zval* param_z = NULL; - - // get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param - r = core::SQLParamData( stmt, reinterpret_cast( &stream_index ) TSRMLS_CC ); - // if no more data, we've exhausted the bound parameters, so return that we're done - if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - - // we're all done, so return false - return false; - } - - HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); - - // pull out the sqlsrv_encoding struct - stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(streams_ht, stream_index)); - SQLSRV_ASSERT(stream_encoding != NULL, "Stream parameter does not exist"); // if the index isn't in the hash, that's a serious error - - param_z = stream_encoding->stream_z; - - // make the next stream current - stmt->current_stream = sqlsrv_stream( param_z, stream_encoding->encoding ); - stmt->current_stream_read = 0; - - // there are more parameters - return true; -} - - -// utility routine to convert an input parameter from UTF-8 to UTF-16 - -bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z ) -{ - SQLSRV_ASSERT( input_param_z == converted_param_z || Z_TYPE_P( converted_param_z ) == IS_NULL, - "convert_input_param_z called with invalid parameter states" ); - - const char* buffer = Z_STRVAL_P( input_param_z ); - std::size_t buffer_len = Z_STRLEN_P( input_param_z ); - int wchar_size; - - if (buffer_len > INT_MAX) - { - LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); - throw core::CoreException(); - } - - // if the string is empty, then just return that the conversion succeeded as - // MultiByteToWideChar will "fail" on an empty string. - if( buffer_len == 0 ) { - core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); - return true; - } - - // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string - wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, - reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); - - // if there was a problem determining the size of the string, return false - if( wchar_size == 0 ) { - return false; - } - sqlsrv_malloc_auto_ptr wbuffer; - wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) )); - // convert the utf-8 string to a wchar string in the new buffer - int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), - static_cast( buffer_len ), wbuffer, wchar_size ); - // if there was a problem converting the string, then free the memory and return false - if( r == 0 ) { - return false; - } - - // null terminate the string, set the size within the zval, and return success - wbuffer[ wchar_size ] = L'\0'; - core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), - wchar_size * sizeof( wchar_t ) ); - sqlsrv_free(wbuffer); - wbuffer.transferred(); - - return true; -} - -// returns the ODBC C type constant that matches the PHP type and encoding given - -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) -{ - SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P( param_z ); - - switch( php_type ) { - - case IS_NULL: - switch( encoding ) { - // The c type is set to match to the corresponding sql_type. For NULL cases, if the server type - // is a binary type, than the server expects the sql_type to be binary type as well, otherwise - // an error stating "Implicit conversion not allowed.." is thrown by the server. - // For all other server types, setting the sql_type to sql_char works fine. - case SQLSRV_ENCODING_BINARY: - sql_c_type = SQL_C_BINARY; - break; - default: - sql_c_type = SQL_C_CHAR; - break; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - //ODBC 64-bit long and integer type are 4 byte values. - if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) - { - sql_c_type = SQL_C_SBIGINT; - } - else - { - sql_c_type = SQL_C_SLONG; - } - break; - case IS_DOUBLE: - sql_c_type = SQL_C_DOUBLE; - break; - case IS_STRING: - case IS_RESOURCE: - switch( encoding ) { - case SQLSRV_ENCODING_CHAR: - sql_c_type = SQL_C_CHAR; - break; - case SQLSRV_ENCODING_BINARY: - sql_c_type = SQL_C_BINARY; - break; - case CP_UTF8: - sql_c_type = SQL_C_WCHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); - break; - } - break; - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in core_sqlsrv_bind_param. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - sql_c_type = SQL_C_CHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } - - return sql_c_type; -} - - -// given a zval and encoding, determine the appropriate sql type - -void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLSMALLINT& sql_type TSRMLS_DC ) -{ - sql_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P(param_z); - switch( php_type ) { - - case IS_NULL: - switch( encoding ) { - // Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases, - // if the server type is a binary type, than the server expects the sql_type to be binary type - // as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the - // server. For all other server types, setting the sql_type to sql_char works fine. - case SQLSRV_ENCODING_BINARY: - sql_type = SQL_BINARY; - break; - default: - sql_type = SQL_CHAR; - break; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - //ODBC 64-bit long and integer type are 4 byte values. - if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) - { - sql_type = SQL_BIGINT; - } - else - { - sql_type = SQL_INTEGER; - } - - break; - case IS_DOUBLE: - sql_type = SQL_FLOAT; - break; - case IS_RESOURCE: - case IS_STRING: - switch( encoding ) { - case SQLSRV_ENCODING_CHAR: - sql_type = SQL_VARCHAR; - break; - case SQLSRV_ENCODING_BINARY: - sql_type = SQL_VARBINARY; - break; - case CP_UTF8: - sql_type = SQL_WVARCHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); - break; - } - break; - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in the calling function. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - // if the user is sending this type to SQL Server 2005 or earlier, make it appear - // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET - // since these are the date types of the highest precision for their respective server versions - if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { - sql_type = SQL_TYPE_TIMESTAMP; - } - else { - sql_type = SQL_SS_TIMESTAMPOFFSET; - } - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } - -} - - -// given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate) - -void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) -{ - int php_type = Z_TYPE_P( param_z ); - column_size = 0; - decimal_digits = 0; - - switch( php_type ) { - - case IS_NULL: - column_size = 1; - break; - // size is not necessary for these types, they are inferred by ODBC - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - case IS_DOUBLE: - case IS_RESOURCE: - break; - case IS_STRING: - { - size_t char_size = ( encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( wchar_t ) : sizeof( char ); - SQLULEN byte_len = Z_STRLEN_P( param_z ) * char_size; - if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { - column_size = SQL_SERVER_MAX_TYPE_SIZE; - } - else { - column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size; - } - break; - } - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in the calling function. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - // if the user is sending this type to SQL Server 2005 or earlier, make it appear - // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET - // since these are the date types of the highest precision for their respective server versions - if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { - column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION; - decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE; - } - else { - column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION; - decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE; - } - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } -} - -void field_cache_dtor( zval* data_z ) -{ - field_cache* cache = static_cast( Z_PTR_P( data_z )); - if( cache->value ) - { - sqlsrv_free( cache->value ); - } - sqlsrv_free( cache ); -} - - -// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output -// parameters will be present until all results are processed (since output parameters can depend on results -// while being processed). This function updates the lengths of output parameter strings from the ind_ptr -// parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary. -// For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server - -void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - if( Z_ISUNDEF(stmt->output_params) ) - return; - - bool converted = true; - HashTable* params_ht = Z_ARRVAL( stmt->output_params ); - zend_ulong index = -1; - zend_string* key = NULL; - void* output_param_temp = NULL; - - ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { - sqlsrv_output_param* output_param = static_cast( output_param_temp ); - zval* value_z = Z_REFVAL_P( output_param->param_z ); - switch( Z_TYPE_P( value_z )) { - case IS_STRING: - { - // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter - char* str = Z_STRVAL_P( value_z ); - SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ]; - if( str_len == SQL_NULL_DATA ) { - zend_string_release( Z_STR_P( value_z )); - ZVAL_NULL( value_z ); - continue; - } - - // if there was more to output than buffer size to hold it, then throw a truncation error - int null_size = 0; - switch( output_param->encoding ) { - case SQLSRV_ENCODING_UTF8: - null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16 - break; - case SQLSRV_ENCODING_SYSTEM: - null_size = 1; - break; - case SQLSRV_ENCODING_BINARY: - null_size = 0; - break; - default: - SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); - break; - } - CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { - throw core::CoreException(); - } - - // if it's not in the 8 bit encodings, then it's in UTF-16 - if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { - bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { - // ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated - // so we do that here if the length of the returned data is less than the original allocation. The - // original allocation null terminates the buffer already. - str[ str_len ] = '\0'; - core::sqlsrv_zval_stringl(value_z, str, str_len); - } - else { - core::sqlsrv_zval_stringl(value_z, str, str_len); - } - } - break; - case IS_LONG: - // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so - if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) { - ZVAL_NULL( value_z ); - } - else if( output_param->is_bool ) { - convert_to_boolean( value_z ); - } - else - { - ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); - } - break; - case IS_DOUBLE: - // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so - if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) { - ZVAL_NULL( value_z ); - } - break; - default: - DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); - break; - } - value_z = NULL; - } ZEND_HASH_FOREACH_END(); - - // empty the hash table since it's been processed - zend_hash_clean( Z_ARRVAL( stmt->output_params )); - return; -} - -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) -{ - SQLRETURN r; - SQLSMALLINT c_type; - SQLLEN sql_field_type = 0; - SQLSMALLINT extra = 0; - SQLLEN field_len_temp; - SQLLEN sql_display_size = 0; - char* field_value_temp = NULL; - - try { - - DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, - "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); - - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); - } - - // Set the C type and account for null characters at the end of the data. - switch( sqlsrv_php_type.typeinfo.encoding ) { - case CP_UTF8: - c_type = SQL_C_WCHAR; - extra = sizeof( SQLWCHAR ); - break; - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - extra = 0; - break; - default: - c_type = SQL_C_CHAR; - extra = sizeof( SQLCHAR ); - break; - } - - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Calculate the field size. - calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); - - // if this is a large type, then read the first few bytes to get the actual length from SQLGetData - if( sql_display_size == 0 || sql_display_size == INT_MAX || - sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { - - field_len_temp = INITIAL_FIELD_STRING_LEN; - - field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); - - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), - &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( field_len_temp == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( r == SQL_SUCCESS_WITH_INFO ) { - - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - - stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - - if( is_truncated_warning( state )) { - - SQLLEN dummy_field_len; - - // for XML (and possibly other conditions) the field length returned is not the real field length, so - // in every pass, we double the allocation size to retrieve all the contents. - if( field_len_temp == SQL_NO_TOTAL ) { - - // reset the field_len_temp - field_len_temp = INITIAL_FIELD_STRING_LEN; - - do { - SQLLEN initial_field_len = field_len_temp; - // Double the size. - field_len_temp *= 2; - - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - field_len_temp -= initial_field_len; - - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, - field_len_temp + extra, &dummy_field_len, - false /*handle_warning*/ TSRMLS_CC ); - - // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL - // so we calculate the actual length of the string with that. - if( dummy_field_len != SQL_NO_TOTAL ) - field_len_temp += dummy_field_len; - else - field_len_temp += initial_field_len; - - if( r == SQL_SUCCESS_WITH_INFO ) { - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - } - - } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); - } - else { - - // We got the field_len_temp from SQLGetData call. - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - // We have already recieved INITIAL_FIELD_STRING_LEN size data. - field_len_temp -= INITIAL_FIELD_STRING_LEN; - - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, - field_len_temp + extra, &dummy_field_len, - true /*handle_warning*/ TSRMLS_CC ); - - if( dummy_field_len == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - field_len_temp += INITIAL_FIELD_STRING_LEN; - } - - } // if( is_truncation_warning ( state ) ) - else { - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - } - } // if( r == SQL_SUCCESS_WITH_INFO ) - - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { - - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) - - else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { - - // only allow binary retrievals for char and binary types. All others get a string converted - // to the encoding type they asked for. - - // null terminator - if( c_type == SQL_C_CHAR ) { - sql_display_size += sizeof( SQLCHAR ); - } - - // For WCHAR multiply by sizeof(WCHAR) and include the null terminator - else if( c_type == SQL_C_WCHAR ) { - sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); - } - - field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); - - // get the data - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, - &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); - CHECK_SQL_ERROR( r, stmt ) { - throw core::CoreException(); - } - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( field_len_temp == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { - - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) - - else { - - DIE( "Invalid sql_display_size" ); - return; // to eliminate a warning - } - - field_value = field_value_temp; - *field_len = field_len_temp; - - // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP - // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. - // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary - // operator to set add 1 to fill the null terminator - field_value_temp[field_len_temp] = '\0'; - } - - catch( core::CoreException& ) { - - field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - catch ( ... ) { - - field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - -} - - -// return the option from the stmt_opts array that matches the key. If no option found, -// NULL is returned. - -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ) -{ - for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { - - // if we find the key we're looking for, return it - if( key == stmt_opts[ i ].key ) { - return &stmt_opts[ i ]; - } - } - - return NULL; // no option found -} - -// is_fixed_size_type -// returns true if the SQL data type is a fixed length, as opposed to a variable length data type such as varchar or varbinary - -bool is_fixed_size_type( SQLINTEGER sql_type ) -{ - switch( sql_type ) { - - case SQL_BINARY: - case SQL_CHAR: - case SQL_WCHAR: - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_VARBINARY: - case SQL_LONGVARBINARY: - case SQL_SS_XML: - case SQL_SS_UDT: - return false; - } - - return true; -} - -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) -{ - switch( type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - case SQLSRV_PHPTYPE_DATETIME: - return true; - case SQLSRV_PHPTYPE_STRING: - case SQLSRV_PHPTYPE_STREAM: - { - if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR - || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - return true; - } - break; - } - } - - return false; -} - - -// verify there is enough space to hold the output string parameter, and allocate it if needed. The param_z -// is updated to have the new buffer with the correct size and its reference is incremented. The output -// string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and -// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter - -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, - SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, - SQLLEN& buffer_len TSRMLS_DC ) -{ - SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); - buffer_len = Z_STRLEN_P( param_z ); - SQLLEN expected_len; - SQLLEN buffer_null_extra; - SQLLEN elem_size; - SQLLEN without_null_len; - - // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, - // as is a n(var)char/ntext field being returned as a binary field. - elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1; - - // account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning - expected_len = column_size * elem_size + elem_size; - - // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations - buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; - - // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter - without_null_len = column_size * elem_size; - - // increment to include the null terminator since the Zend length doesn't include the null terminator - buffer_len += elem_size; - - // if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new - // length. - if( buffer_len < expected_len ) { - SQLSRV_ASSERT( expected_len >= expected_len - buffer_null_extra, - "Integer overflow/underflow caused a corrupt field length." ); - - // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since - // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about - // not having a NULL terminator on a string. - zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); - - // A zval string len doesn't include the null. This calculates the length it should be - // regardless of whether the ODBC type contains the NULL or not. - - // null terminate the string to avoid a warning in debug PHP builds - ZSTR_VAL(param_z_string)[without_null_len] = '\0'; - ZVAL_NEW_STR(param_z, param_z_string); - - // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the - // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY - buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; - - // Zend string length doesn't include the null terminator - ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; - } - - buffer = Z_STRVAL_P(param_z); - - // The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which - // may be less than the size of the buffer since the output may be more than the input. If it is greater, - // than the error 22001 is returned by ODBC. - if( stmt->param_ind_ptrs[ paramno ] > buffer_len - (elem_size - buffer_null_extra)) { - stmt->param_ind_ptrs[ paramno ] = buffer_len - (elem_size - buffer_null_extra); - } -} - -// output parameters have their reference count incremented so that they do not disappear -// while the query is executed and processed. They are saved in the statement so that -// their reference count may be decremented later (after results are processed) - -void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ) -{ - HashTable* param_ht = Z_ARRVAL( stmt->output_params ); - zend_ulong paramno = static_cast( param.param_num ); - core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, ¶m, sizeof( sqlsrv_output_param )); - Z_TRY_ADDREF_P( param.param_z ); // we have a reference to the param -} - - -// send all the stream data - -void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { } -} - - -// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed -void sqlsrv_output_param_dtor( zval* data ) -{ - sqlsrv_output_param *output_param = static_cast( Z_PTR_P( data )); - zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold - sqlsrv_free( output_param ); -} - -// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed -void sqlsrv_stream_dtor( zval* data ) -{ - sqlsrv_stream* stream_encoding = static_cast( Z_PTR_P( data )); - zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold - sqlsrv_free( stream_encoding ); -} - -} diff --git a/pdo_sqlsrv/core_util.cpp b/pdo_sqlsrv/core_util.cpp deleted file mode 100644 index 8bc410f3c..000000000 --- a/pdo_sqlsrv/core_util.cpp +++ /dev/null @@ -1,400 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_util.cpp -// -// Contents: Utility functions used by both connection or statement functions for both the PDO and sqlsrv drivers -// -// Comments: Mostly error handling and some type handling -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include - -namespace { - -// *** internal constants *** -log_callback g_driver_log; -// internal error that says that FormatMessage failed -SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; -// buffer used to hold a formatted log message prior to actually logging it. -char last_err_msg[ 2048 ]; // 2k to hold the error messages - -// routine used by utf16_string_from_mbcs_string -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, - unsigned int mbcs_len, - _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, - unsigned int utf16_len ); -} - -// SQLSTATE for all internal errors -SQLCHAR IMSSP[] = "IMSSP"; - -// SQLSTATE for all internal warnings -SQLCHAR SSPWARN[] = "01SSP"; - -// write to the php log if the severity and subsystem match the filters currently set in the INI or -// the script (sqlsrv_configure). -void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ...) -{ - SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." ); - - va_list args; - va_start( args, msg ); - - g_driver_log( severity TSRMLS_CC, msg, &args ); - - va_end( args ); -} - -void core_sqlsrv_register_logger( log_callback driver_logger ) -{ - g_driver_log = driver_logger; -} - - -// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new -// length in the len parameter. If no errors occurred during convertion, true is returned and the original -// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed -// and false is returned. - -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len) -{ - SQLSRV_ASSERT( string != NULL, "String must be specified" ); - - if (validate_string(*string, len)) { - return true; - } - - char* outString = NULL; - SQLLEN outLen = 0; - - bool result = convert_string_from_utf16( encoding, - reinterpret_cast(*string), int(len / sizeof(wchar_t)), &outString, outLen); - - if (result) - { - sqlsrv_free( *string ); - *string = outString; - len = outLen; - } - - return result; -} - -bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len) -{ - char* string = Z_STRVAL_P(value_z); - - if (validate_string(string, len)) { - return true; - } - - char* outString = NULL; - SQLLEN outLen = 0; - - bool result = convert_string_from_utf16(encoding, - reinterpret_cast(string), int(len / sizeof(wchar_t)), &outString, outLen); - - if (result) - { - core::sqlsrv_zval_stringl(value_z, outString, outLen); - sqlsrv_free(outString); - len = outLen; - } - - return result; -} - -bool validate_string(char* string, SQLLEN& len) -{ - SQLSRV_ASSERT(string != NULL, "String must be specified"); - - //for the empty string, we simply returned we converted it - if (len == 0 && string[0] == '\0') { - return true; - } - - if ((len / sizeof(wchar_t)) > INT_MAX) - { - LOG(SEV_ERROR, "UTP-16 (wide character) string mapping: buffer length exceeded."); - throw core::CoreException(); - } - - return false; -} - -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ) -{ - SQLSRV_ASSERT( inString != NULL, "Input string must be specified" ); - SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" ); - SQLSRV_ASSERT( *outString == NULL, "Output buffer pointer must not be set" ); - - if (cchInLen == 0 && inString[0] == L'\0') { - *outString = reinterpret_cast( sqlsrv_malloc ( 1 ) ); - *outString[0] = '\0'; - cchOutLen = 0; - return true; - } - - // flags set to 0 by default, which means that any invalid characters are dropped rather than causing - // an error. This happens only on XP. - DWORD flags = 0; - if( encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { - // Vista (and later) will detect invalid UTF-16 characters and raise an error. - flags = WC_ERR_INVALID_CHARS; - } - - // calculate the number of characters needed - cchOutLen = WideCharToMultiByte( encoding, flags, - inString, cchInLen, - NULL, 0, NULL, NULL ); - if( cchOutLen == 0 ) { - return false; - } - - // Create a buffer to fit the encoded string - char* newString = reinterpret_cast( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ )); - int rc = WideCharToMultiByte( encoding, flags, - inString, cchInLen, - newString, static_cast(cchOutLen), NULL, NULL ); - if( rc == 0 ) { - cchOutLen = 0; - sqlsrv_free( newString ); - return false; - } - - *outString = newString; - newString[cchOutLen] = '\0'; // null terminate the encoded string - - return true; -} - -// thin wrapper around convert_string_from_default_encoding that handles -// allocation of the destination string. An empty string passed in returns -// failure since it's a failure case for convert_string_from_default_encoding. -wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len, - unsigned int* utf16_len ) -{ - *utf16_len = (mbcs_len + 1); - wchar_t* utf16_string = reinterpret_cast( sqlsrv_malloc( *utf16_len * sizeof( wchar_t ))); - *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, - utf16_string, *utf16_len ); - if( *utf16_len == 0 ) { - // we preserve the error and reset it because sqlsrv_free resets the last error - DWORD last_error = GetLastError(); - sqlsrv_free( utf16_string ); - SetLastError( last_error ); - return NULL; - } - - return utf16_string; -} - -// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the -// errno is 1 based. It returns it as an array with 3 members: -// 1/SQLSTATE) sqlstate -// 2/code) driver specific error code -// 3/message) driver specific error message -// The fetch type determines if the indices are numeric, associative, or both. - -bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_error_auto_ptr& error, logging_severity severity - TSRMLS_DC ) -{ - SQLHANDLE h = ctx.handle(); - SQLSMALLINT h_type = ctx.handle_type(); - - if( h == NULL ) { - return false; - } - - zval* ssphp_z = NULL; - int zr = SUCCESS; - zval* temp = NULL; - SQLRETURN r = SQL_SUCCESS; - SQLSMALLINT wmessage_len = 0; - SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ]; - SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLSRV_ENCODING enc = ctx.encoding(); - - switch( h_type ) { - - case SQL_HANDLE_STMT: - { - sqlsrv_stmt* stmt = static_cast( &ctx ); - if( stmt->current_results != NULL ) { - - error = stmt->current_results->get_diag_rec( record_number ); - // don't use the CHECK* macros here since it will trigger reentry into the error handling system - if( error == NULL ) { - return false; - } - break; - } - - // convert the error into the encoding of the context - if( enc == SQLSRV_ENCODING_DEFAULT ) { - enc = stmt->conn->encoding(); - } - } - - - default: - - error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); - r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message, - SQL_MAX_MESSAGE_LENGTH + 1, &wmessage_len ); - // don't use the CHECK* macros here since it will trigger reentry into the error handling system - if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - return false; - } - - SQLLEN sqlstate_len = 0; - convert_string_from_utf16(enc, wsqlstate, sizeof(wsqlstate), (char**)&error->sqlstate, sqlstate_len); - - SQLLEN message_len = 0; - convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); - break; - } - - - // log the error first - LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate ); - LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), error->native_code ); - LOG( severity, "%1!s!: message = %2!s!", ctx.func(), error->native_message ); - - error->format = false; - - return true; -} - -// format and return a driver specfic error -void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, - sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ) -{ - // allocate space for the formatted message - formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); - formatted_error->sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - formatted_error->native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast( custom_error->native_message ), 0, 0, - reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, args ); - if( rc == 0 ) { - strcpy_s( reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, - reinterpret_cast( INTERNAL_FORMAT_ERROR )); - } - - strcpy_s( reinterpret_cast( formatted_error->sqlstate ), SQL_SQLSTATE_BUFSIZE, - reinterpret_cast( custom_error->sqlstate )); - formatted_error->native_code = custom_error->native_code; - - // log the error - LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), formatted_error->sqlstate ); - LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), formatted_error->native_code ); - LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); -} - -DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ) -{ - va_list format_args; - va_start( format_args, format ); - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast(output_buffer), SQL_MAX_MESSAGE_LENGTH, &format_args ); - - va_end( format_args ); - - return rc; -} - -// return an error message for GetLastError using FormatMessage. -// this function returns the msg pointer so that it may be used within -// another function call such as handle_error -const char* get_last_error_message( DWORD last_error ) -{ - if( last_error == 0 ) { - last_error = GetLastError(); - } - - DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), - last_err_msg, sizeof( last_err_msg ), NULL ); - - if( r == 0 ) { - SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( last_err_msg )); - std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), last_err_msg ); - } - - return last_err_msg; -} - - -// die -// Terminate the PHP request with an error message -// We use this function rather than php_error directly because we use the FormatMessage syntax in most other -// places within the extension (e.g., LOG), whereas php_error uses the printf format syntax. There were -// places where we were using the FormatMessage syntax inadvertently with DIE which left messages without -// proper information. Rather than convert those messages and try and remember the difference between LOG and -// DIE, it is simpler to make the format syntax common between them. -void die( const char* msg, ... ) -{ - va_list format_args; - va_start( format_args, msg ); - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, last_err_msg, sizeof( last_err_msg ), &format_args ); - - va_end( format_args ); - - if( rc == 0 ) { - php_error( E_ERROR, reinterpret_cast( INTERNAL_FORMAT_ERROR )); - } - - php_error( E_ERROR, last_err_msg ); -} - -namespace { - -// convert from the default encoding specified by the "CharacterSet" -// connection option to UTF-16. mbcs_len and utf16_len are sizes in -// bytes. The return is the number of UTF-16 characters in the string -// returned in utf16_out_string. An empty string passed in will result as -// a failure since MBTWC returns 0 for both an empty string and failure -// to convert. -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, - unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, - unsigned int utf16_len ) -{ - unsigned int win_encoding = CP_ACP; - switch( php_encoding ) { - case SQLSRV_ENCODING_CHAR: - win_encoding = CP_ACP; - break; - // this shouldn't ever be set - case SQLSRV_ENCODING_BINARY: - DIE( "Invalid encoding." ); - break; - default: - win_encoding = php_encoding; - break; - } - unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len, - utf16_out_string, utf16_len ); - if( required_len == 0 ) { - return 0; - } - utf16_out_string[ required_len ] = '\0'; - - return required_len; -} - -} diff --git a/pdo_sqlsrv/msodbcsql.h b/pdo_sqlsrv/msodbcsql.h deleted file mode 100644 index 8bcf83d0a..000000000 --- a/pdo_sqlsrv/msodbcsql.h +++ /dev/null @@ -1,2343 +0,0 @@ -//----------------------------------------------------------------------------- -// File: msodbcsql.h -// -// Copyright: Copyright (c) Microsoft Corporation -// -// Contents: ODBC driver for SQL Server specific definitions. -// -//----------------------------------------------------------------------------- -#ifndef __msodbcsql_h__ -#define __msodbcsql_h__ - -#if !defined(SQLODBC_VER) -#define SQLODBC_VER 1100 -#endif - -#if SQLODBC_VER >= 1100 - -#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server" - -#define SQLODBC_FILE_NAME_ANSI "msodbcsql" -#define SQLODBC_FILE_NAME_VER_ANSI "msodbcsql11" -#define SQLODBC_FILE_NAME_FULL_ANSI "msodbcsql11.dll" - -#define SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_FULL_UNICODE L"Microsoft ODBC Driver for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE L"ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_UNICODE L"ODBC Driver for SQL Server" - -#define SQLODBC_FILE_NAME_UNICODE L"msodbcsql" -#define SQLODBC_FILE_NAME_VER_UNICODE L"msodbcsql11" -#define SQLODBC_FILE_NAME_FULL_UNICODE L"msodbcsql11.dll" - -// define the character type agnostic constants -#if defined(_UNICODE) || defined(UNICODE) - -#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE -#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_UNICODE -#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE -#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_UNICODE - -#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_UNICODE -#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_UNICODE -#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_UNICODE - -#else // _UNICODE || UNICODE - -#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI -#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI -#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI -#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI - -#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_ANSI -#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_ANSI -#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_ANSI - -#endif // _UNICODE || UNICODE - -#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER - -#endif // SQLODBC_VER - -#ifndef __sqlncli_h__ - -#if !defined(SQLNCLI_VER) -#define SQLNCLI_VER 1100 -#endif - -#if SQLNCLI_VER >= 1100 - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli11" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli11.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli11" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli11.dll" - -#elif SQLNCLI_VER >= 1000 - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli10" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli10.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli10" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli10.dll" - -#else - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli.dll" - -#endif // SQLNCLI_VER >= 1100 - -// define the character type agnostic constants -#if defined(_UNICODE) || defined(UNICODE) - -#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE -#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_UNICODE -#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE -#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_UNICODE - -#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_UNICODE -#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_UNICODE -#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_UNICODE - - -#else // _UNICODE || UNICODE - -#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI -#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_ANSI -#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI -#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_ANSI - -#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_ANSI -#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_ANSI -#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_ANSI - -#endif // _UNICODE || UNICODE - -#define SQLNCLI_DRIVER_NAME SQLNCLI_PRODUCT_NAME_SHORT_VER - - -#ifdef ODBCVER - -#ifdef __cplusplus -extern "C" { -#endif - -// max SQL Server identifier length -#define SQL_MAX_SQLSERVERNAME 128 - -// SQLSetConnectAttr driver specific defines. -// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage. -// Connection attributes -#define SQL_COPT_SS_BASE 1200 -#define SQL_COPT_SS_REMOTE_PWD (SQL_COPT_SS_BASE+1) // dbrpwset SQLSetConnectOption only -#define SQL_COPT_SS_USE_PROC_FOR_PREP (SQL_COPT_SS_BASE+2) // Use create proc for SQLPrepare -#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) // Force integrated security on login -#define SQL_COPT_SS_PRESERVE_CURSORS (SQL_COPT_SS_BASE+4) // Preserve server cursors after SQLTransact -#define SQL_COPT_SS_USER_DATA (SQL_COPT_SS_BASE+5) // dbgetuserdata/dbsetuserdata -#define SQL_COPT_SS_ENLIST_IN_DTC SQL_ATTR_ENLIST_IN_DTC // Enlist in a DTC transaction -#define SQL_COPT_SS_ENLIST_IN_XA SQL_ATTR_ENLIST_IN_XA // Enlist in a XA transaction -#define SQL_COPT_SS_FALLBACK_CONNECT (SQL_COPT_SS_BASE+10) // Enables FallBack connections -#define SQL_COPT_SS_PERF_DATA (SQL_COPT_SS_BASE+11) // Used to access SQL Server ODBC driver performance data -#define SQL_COPT_SS_PERF_DATA_LOG (SQL_COPT_SS_BASE+12) // Used to set the logfile name for the Performance data -#define SQL_COPT_SS_PERF_QUERY_INTERVAL (SQL_COPT_SS_BASE+13) // Used to set the query logging threshold in milliseconds. -#define SQL_COPT_SS_PERF_QUERY_LOG (SQL_COPT_SS_BASE+14) // Used to set the logfile name for saving queryies. -#define SQL_COPT_SS_PERF_QUERY (SQL_COPT_SS_BASE+15) // Used to start and stop query logging. -#define SQL_COPT_SS_PERF_DATA_LOG_NOW (SQL_COPT_SS_BASE+16) // Used to make a statistics log entry to disk. -#define SQL_COPT_SS_QUOTED_IDENT (SQL_COPT_SS_BASE+17) // Enable/Disable Quoted Identifiers -#define SQL_COPT_SS_ANSI_NPW (SQL_COPT_SS_BASE+18) // Enable/Disable ANSI NULL, Padding and Warnings -#define SQL_COPT_SS_BCP (SQL_COPT_SS_BASE+19) // Allow BCP usage on connection -#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) // Perform code page translation -#define SQL_COPT_SS_ATTACHDBFILENAME (SQL_COPT_SS_BASE+21) // File name to be attached as a database -#define SQL_COPT_SS_CONCAT_NULL (SQL_COPT_SS_BASE+22) // Enable/Disable CONCAT_NULL_YIELDS_NULL -#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) // Allow strong encryption for data -#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) // Multiple active result set per connection -#define SQL_COPT_SS_FAILOVER_PARTNER (SQL_COPT_SS_BASE+25) // Failover partner server -#define SQL_COPT_SS_OLDPWD (SQL_COPT_SS_BASE+26) // Old Password, used when changing password during login -#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) // Used to set/get any driver-specific or ODBC-defined TXN iso level -#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) // Trust server certificate -#define SQL_COPT_SS_SERVER_SPN (SQL_COPT_SS_BASE+29) // Server SPN -#define SQL_COPT_SS_FAILOVER_PARTNER_SPN (SQL_COPT_SS_BASE+30) // Failover partner server SPN -#define SQL_COPT_SS_INTEGRATED_AUTHENTICATION_METHOD (SQL_COPT_SS_BASE+31) // The integrated authentication method used for the connection -#define SQL_COPT_SS_MUTUALLY_AUTHENTICATED (SQL_COPT_SS_BASE+32) // Used to decide if the connection is mutually authenticated -#define SQL_COPT_SS_CLIENT_CONNECTION_ID (SQL_COPT_SS_BASE+33) // Post connection attribute used to get the ConnectionID -// Define old names -#define SQL_REMOTE_PWD SQL_COPT_SS_REMOTE_PWD -#define SQL_USE_PROCEDURE_FOR_PREPARE SQL_COPT_SS_USE_PROC_FOR_PREP -#define SQL_INTEGRATED_SECURITY SQL_COPT_SS_INTEGRATED_SECURITY -#define SQL_PRESERVE_CURSORS SQL_COPT_SS_PRESERVE_CURSORS - -// SQLSetStmtAttr SQL Server Native Client driver specific defines. -// Statement attributes -#define SQL_SOPT_SS_BASE 1225 -#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) // Text pointer logging -#define SQL_SOPT_SS_CURRENT_COMMAND (SQL_SOPT_SS_BASE+1) // dbcurcmd SQLGetStmtOption only -#define SQL_SOPT_SS_HIDDEN_COLUMNS (SQL_SOPT_SS_BASE+2) // Expose FOR BROWSE hidden columns -#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) // Set NOBROWSETABLE option -#define SQL_SOPT_SS_REGIONALIZE (SQL_SOPT_SS_BASE+4) // Regionalize output character conversions -#define SQL_SOPT_SS_CURSOR_OPTIONS (SQL_SOPT_SS_BASE+5) // Server cursor options -#define SQL_SOPT_SS_NOCOUNT_STATUS (SQL_SOPT_SS_BASE+6) // Real vs. Not Real row count indicator -#define SQL_SOPT_SS_DEFER_PREPARE (SQL_SOPT_SS_BASE+7) // Defer prepare until necessary -#define SQL_SOPT_SS_QUERYNOTIFICATION_TIMEOUT (SQL_SOPT_SS_BASE+8) // Notification timeout -#define SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT (SQL_SOPT_SS_BASE+9) // Notification message text -#define SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS (SQL_SOPT_SS_BASE+10)// SQL service broker name -#define SQL_SOPT_SS_PARAM_FOCUS (SQL_SOPT_SS_BASE+11)// Direct subsequent calls to parameter related methods to set properties on constituent columns/parameters of container types -#define SQL_SOPT_SS_NAME_SCOPE (SQL_SOPT_SS_BASE+12)// Sets name scope for subsequent catalog function calls -#define SQL_SOPT_SS_MAX_USED SQL_SOPT_SS_NAME_SCOPE -// Define old names -#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING -#define SQL_COPT_SS_BASE_EX 1240 -#define SQL_COPT_SS_BROWSE_CONNECT (SQL_COPT_SS_BASE_EX+1) // Browse connect mode of operation -#define SQL_COPT_SS_BROWSE_SERVER (SQL_COPT_SS_BASE_EX+2) // Single Server browse request. -#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) // Issues warning when data from the server had a loss during code page conversion. -#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) // dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check -#define SQL_COPT_SS_BROWSE_CACHE_DATA (SQL_COPT_SS_BASE_EX+5) // Determines if we should cache browse info. Used when returned buffer is greater then ODBC limit (32K) -#define SQL_COPT_SS_RESET_CONNECTION (SQL_COPT_SS_BASE_EX+6) // When this option is set, we will perform connection reset on next packet -#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) // Application Intent -#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) // Multi-subnet Failover -#define SQL_COPT_SS_EX_MAX_USED SQL_COPT_SS_MULTISUBNET_FAILOVER - -// SQLColAttributes driver specific defines. -// SQLSetDescField/SQLGetDescField driver specific defines. -// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_CA_SS_BASE 1200 -#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) // dbcoltype/dbalttype -#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) // dbcolutype/dbaltutype -#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) // dbnumorders -#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) // dbordercol -#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) // dbvarylen -#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) // dbnumcompute -#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) // dbnextrow status return -#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) // dbbylist -#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) // dbaltcolid -#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) // dbaltop -#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) // dbcollen -#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) // Column is hidden (FOR BROWSE) -#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) // Column is key column (FOR BROWSE) -//#define SQL_DESC_BASE_COLUMN_NAME_OLD (SQL_CA_SS_BASE+13) // This is defined at another location. -#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) // Column collation (only for chars) -#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15) -#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16) -#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17) - -// XML, CLR UDT, and table valued parameter related metadata -#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) // UDT catalog name -#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) // UDT schema name -#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) // UDT type name -#define SQL_CA_SS_UDT_ASSEMBLY_TYPE_NAME (SQL_CA_SS_BASE+21) // Qualified name of the assembly containing the UDT class -#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) // Name of the catalog that contains XML Schema collection -#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) // Name of the schema that contains XML Schema collection -#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) // Name of the XML Schema collection -#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) // Catalog name -#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) // Schema name -#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) // Type name - -// table valued parameter related metadata -#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) // column is computed -#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) // column is part of a unique key -#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) // column sort order -#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) // column sort ordinal -#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) // column has default value for all rows of the table valued parameter - -// sparse column related metadata -#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) // column is a column-set column for sparse columns - -// Legacy datetime related metadata -#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) // column type to send on the wire for datetime types - -#define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+36) - -// Defines returned by SQL_ATTR_CURSOR_TYPE/SQL_CURSOR_TYPE -#define SQL_CURSOR_FAST_FORWARD_ONLY 8 // Only returned by SQLGetStmtAttr/Option -// Defines for use with SQL_COPT_SS_USE_PROC_FOR_PREP -#define SQL_UP_OFF 0L // Procedures won't be used for prepare -#define SQL_UP_ON 1L // Procedures will be used for prepare -#define SQL_UP_ON_DROP 2L // Temp procedures will be explicitly dropped -#define SQL_UP_DEFAULT SQL_UP_ON -// Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only -#define SQL_IS_OFF 0L // Integrated security isn't used -#define SQL_IS_ON 1L // Integrated security is used -#define SQL_IS_DEFAULT SQL_IS_OFF -// Defines for use with SQL_COPT_SS_PRESERVE_CURSORS -#define SQL_PC_OFF 0L // Cursors are closed on SQLTransact -#define SQL_PC_ON 1L // Cursors remain open on SQLTransact -#define SQL_PC_DEFAULT SQL_PC_OFF -// Defines for use with SQL_COPT_SS_USER_DATA -#define SQL_UD_NOTSET NULL // No user data pointer set -// Defines for use with SQL_COPT_SS_TRANSLATE -#define SQL_XL_OFF 0L // Code page translation is not performed -#define SQL_XL_ON 1L // Code page translation is performed -#define SQL_XL_DEFAULT SQL_XL_ON -// Defines for use with SQL_COPT_SS_FALLBACK_CONNECT - Pre-Connect Option only -#define SQL_FB_OFF 0L // FallBack connections are disabled -#define SQL_FB_ON 1L // FallBack connections are enabled -#define SQL_FB_DEFAULT SQL_FB_OFF -// Defines for use with SQL_COPT_SS_BCP - Pre-Connect Option only -#define SQL_BCP_OFF 0L // BCP is not allowed on connection -#define SQL_BCP_ON 1L // BCP is allowed on connection -#define SQL_BCP_DEFAULT SQL_BCP_OFF -// Defines for use with SQL_COPT_SS_QUOTED_IDENT -#define SQL_QI_OFF 0L // Quoted identifiers are enable -#define SQL_QI_ON 1L // Quoted identifiers are disabled -#define SQL_QI_DEFAULT SQL_QI_ON -// Defines for use with SQL_COPT_SS_ANSI_NPW - Pre-Connect Option only -#define SQL_AD_OFF 0L // ANSI NULLs, Padding and Warnings are enabled -#define SQL_AD_ON 1L // ANSI NULLs, Padding and Warnings are disabled -#define SQL_AD_DEFAULT SQL_AD_ON -// Defines for use with SQL_COPT_SS_CONCAT_NULL - Pre-Connect Option only -#define SQL_CN_OFF 0L // CONCAT_NULL_YIELDS_NULL is off -#define SQL_CN_ON 1L // CONCAT_NULL_YIELDS_NULL is on -#define SQL_CN_DEFAULT SQL_CN_ON -// Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING -#define SQL_TL_OFF 0L // No logging on text pointer ops -#define SQL_TL_ON 1L // Logging occurs on text pointer ops -#define SQL_TL_DEFAULT SQL_TL_ON -// Defines for use with SQL_SOPT_SS_HIDDEN_COLUMNS -#define SQL_HC_OFF 0L // FOR BROWSE columns are hidden -#define SQL_HC_ON 1L // FOR BROWSE columns are exposed -#define SQL_HC_DEFAULT SQL_HC_OFF -// Defines for use with SQL_SOPT_SS_NOBROWSETABLE -#define SQL_NB_OFF 0L // NO_BROWSETABLE is off -#define SQL_NB_ON 1L // NO_BROWSETABLE is on -#define SQL_NB_DEFAULT SQL_NB_OFF -// Defines for use with SQL_SOPT_SS_REGIONALIZE -#define SQL_RE_OFF 0L // No regionalization occurs on output character conversions -#define SQL_RE_ON 1L // Regionalization occurs on output character conversions -#define SQL_RE_DEFAULT SQL_RE_OFF -// Defines for use with SQL_SOPT_SS_CURSOR_OPTIONS -#define SQL_CO_OFF 0L // Clear all cursor options -#define SQL_CO_FFO 1L // Fast-forward cursor will be used -#define SQL_CO_AF 2L // Autofetch on cursor open -#define SQL_CO_FFO_AF (SQL_CO_FFO|SQL_CO_AF) // Fast-forward cursor with autofetch -#define SQL_CO_FIREHOSE_AF 4L // Auto fetch on fire-hose cursors -#define SQL_CO_DEFAULT SQL_CO_OFF -//SQL_SOPT_SS_NOCOUNT_STATUS -#define SQL_NC_OFF 0L -#define SQL_NC_ON 1L -//SQL_SOPT_SS_DEFER_PREPARE -#define SQL_DP_OFF 0L -#define SQL_DP_ON 1L -//SQL_SOPT_SS_NAME_SCOPE -#define SQL_SS_NAME_SCOPE_TABLE 0L -#define SQL_SS_NAME_SCOPE_TABLE_TYPE 1L -#define SQL_SS_NAME_SCOPE_EXTENDED 2L -#define SQL_SS_NAME_SCOPE_SPARSE_COLUMN_SET 3L -#define SQL_SS_NAME_SCOPE_DEFAULT SQL_SS_NAME_SCOPE_TABLE -//SQL_COPT_SS_ENCRYPT -#define SQL_EN_OFF 0L -#define SQL_EN_ON 1L -//SQL_COPT_SS_TRUST_SERVER_CERTIFICATE -#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L -#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L -//SQL_COPT_SS_BROWSE_CONNECT -#define SQL_MORE_INFO_NO 0L -#define SQL_MORE_INFO_YES 1L -//SQL_COPT_SS_BROWSE_CACHE_DATA -#define SQL_CACHE_DATA_NO 0L -#define SQL_CACHE_DATA_YES 1L -//SQL_COPT_SS_RESET_CONNECTION -#define SQL_RESET_YES 1L -//SQL_COPT_SS_WARN_ON_CP_ERROR -#define SQL_WARN_NO 0L -#define SQL_WARN_YES 1L -//SQL_COPT_SS_MARS_ENABLED -#define SQL_MARS_ENABLED_NO 0L -#define SQL_MARS_ENABLED_YES 1L -/* SQL_TXN_ISOLATION_OPTION bitmasks */ -#define SQL_TXN_SS_SNAPSHOT 0x00000020L - -// The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER -#define SQL_SS_ORDER_UNSPECIFIED 0L -#define SQL_SS_DESCENDING_ORDER 1L -#define SQL_SS_ASCENDING_ORDER 2L -#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED - -// Driver specific SQL data type defines. -// Microsoft has -150 thru -199 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_SS_VARIANT (-150) -#define SQL_SS_UDT (-151) -#define SQL_SS_XML (-152) -#define SQL_SS_TABLE (-153) -#define SQL_SS_TIME2 (-154) -#define SQL_SS_TIMESTAMPOFFSET (-155) - -// Local types to be used with SQL_CA_SS_SERVER_TYPE -#define SQL_SS_TYPE_DEFAULT 0L -#define SQL_SS_TYPE_SMALLDATETIME 1L -#define SQL_SS_TYPE_DATETIME 2L - -// Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager. -#define SQL_C_TYPES_EXTENDED 0x04000L -#define SQL_C_SS_TIME2 (SQL_C_TYPES_EXTENDED+0) -#define SQL_C_SS_TIMESTAMPOFFSET (SQL_C_TYPES_EXTENDED+1) - -#ifndef SQLNCLI_NO_BCP -// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application -// and you want to exclude the BCP-related definitions in this header file. - -// SQL Server Data Type defines. -// New types for SQL 6.0 and later servers -#define SQLTEXT 0x23 -#define SQLVARBINARY 0x25 -#define SQLINTN 0x26 -#define SQLVARCHAR 0x27 -#define SQLBINARY 0x2d -#define SQLIMAGE 0x22 -#define SQLCHARACTER 0x2f -#define SQLINT1 0x30 -#define SQLBIT 0x32 -#define SQLINT2 0x34 -#define SQLINT4 0x38 -#define SQLMONEY 0x3c -#define SQLDATETIME 0x3d -#define SQLFLT8 0x3e -#define SQLFLTN 0x6d -#define SQLMONEYN 0x6e -#define SQLDATETIMN 0x6f -#define SQLFLT4 0x3b -#define SQLMONEY4 0x7a -#define SQLDATETIM4 0x3a -// New types for SQL 6.0 and later servers -#define SQLDECIMAL 0x6a -#define SQLNUMERIC 0x6c -// New types for SQL 7.0 and later servers -#define SQLUNIQUEID 0x24 -#define SQLBIGCHAR 0xaf -#define SQLBIGVARCHAR 0xa7 -#define SQLBIGBINARY 0xad -#define SQLBIGVARBINARY 0xa5 -#define SQLBITN 0x68 -#define SQLNCHAR 0xef -#define SQLNVARCHAR 0xe7 -#define SQLNTEXT 0x63 -// New types for SQL 2000 and later servers -#define SQLINT8 0x7f -#define SQLVARIANT 0x62 -// New types for SQL 2005 and later servers -#define SQLUDT 0xf0 -#define SQLXML 0xf1 -// New types for SQL 2008 and later servers -#define SQLTABLE 0xf3 -#define SQLDATEN 0x28 -#define SQLTIMEN 0x29 -#define SQLDATETIME2N 0x2a -#define SQLDATETIMEOFFSETN 0x2b -// Define old names -#define SQLDECIMALN 0x6a -#define SQLNUMERICN 0x6c -#endif // SQLNCLI_NO_BCP - -// SQL_SS_LENGTH_UNLIMITED is used to describe the max length of -// VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns -#define SQL_SS_LENGTH_UNLIMITED 0 - -// User Data Type definitions. -// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE. -#define SQLudtBINARY 3 -#define SQLudtBIT 16 -#define SQLudtBITN 0 -#define SQLudtCHAR 1 -#define SQLudtDATETIM4 22 -#define SQLudtDATETIME 12 -#define SQLudtDATETIMN 15 -#define SQLudtDECML 24 -#define SQLudtDECMLN 26 -#define SQLudtFLT4 23 -#define SQLudtFLT8 8 -#define SQLudtFLTN 14 -#define SQLudtIMAGE 20 -#define SQLudtINT1 5 -#define SQLudtINT2 6 -#define SQLudtINT4 7 -#define SQLudtINTN 13 -#define SQLudtMONEY 11 -#define SQLudtMONEY4 21 -#define SQLudtMONEYN 17 -#define SQLudtNUM 10 -#define SQLudtNUMN 25 -#define SQLudtSYSNAME 18 -#define SQLudtTEXT 19 -#define SQLudtTIMESTAMP 80 -#define SQLudtUNIQUEIDENTIFIER 0 -#define SQLudtVARBINARY 4 -#define SQLudtVARCHAR 2 -#define MIN_USER_DATATYPE 256 -// Aggregate operator types. -// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP. -#define SQLAOPSTDEV 0x30 // Standard deviation -#define SQLAOPSTDEVP 0x31 // Standard deviation population -#define SQLAOPVAR 0x32 // Variance -#define SQLAOPVARP 0x33 // Variance population -#define SQLAOPCNT 0x4b // Count -#define SQLAOPSUM 0x4d // Sum -#define SQLAOPAVG 0x4f // Average -#define SQLAOPMIN 0x51 // Min -#define SQLAOPMAX 0x52 // Max -#define SQLAOPANY 0x53 // Any -#define SQLAOPNOOP 0x56 // None -// SQLGetInfo driver specific defines. -// Microsoft has 1151 thru 1200 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_INFO_SS_FIRST 1199 -#define SQL_INFO_SS_NETLIB_NAMEW (SQL_INFO_SS_FIRST+0) // dbprocinfo -#define SQL_INFO_SS_NETLIB_NAMEA (SQL_INFO_SS_FIRST+1) // dbprocinfo -#define SQL_INFO_SS_MAX_USED SQL_INFO_SS_NETLIB_NAMEA -#ifdef UNICODE -#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEW -#else -#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEA -#endif - -// SQLGetDiagField driver specific defines. -// Microsoft has -1150 thru -1199 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_DIAG_SS_BASE (-1150) -#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE) -#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1) -#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2) -#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3) -#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4) -// SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines. -// Microsoft has -200 thru -299 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_DIAG_DFC_SS_BASE (-200) -#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0) -#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1) -#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2) -#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3) -#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4) -#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5) -#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6) -#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7) -#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8) -#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9) -#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10) -#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11) -#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12) -#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13) -#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14) -#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15) -#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16) -#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17) -#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18) -#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19) -#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) -#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) -#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21) -#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) -#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) -#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23) -#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24) -#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25) -#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26) -#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26) -#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) -#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) -#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28) -#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) -#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) -#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30) -#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31) -#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32) -#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33) -#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34) -#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35) -#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36) -#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37) -#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38) -#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39) -#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40) -#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41) -#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42) -#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43) -#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44) -#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45) -#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46) -#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47) -#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48) -#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49) -#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50) -#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51) -#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52) -#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53) -#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54) -#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55) -#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56) - -// Severity codes for SQL_DIAG_SS_SEVERITY -#define EX_ANY 0 -#define EX_INFO 10 -#define EX_MAXISEVERITY EX_INFO -#define EX_MISSING 11 -#define EX_TYPE 12 -#define EX_DEADLOCK 13 -#define EX_PERMIT 14 -#define EX_SYNTAX 15 -#define EX_USER 16 -#define EX_RESOURCE 17 -#define EX_INTOK 18 -#define MAXUSEVERITY EX_INTOK -#define EX_LIMIT 19 -#define EX_CMDFATAL 20 -#define MINFATALERR EX_CMDFATAL -#define EX_DBFATAL 21 -#define EX_TABCORRUPT 22 -#define EX_DBCORRUPT 23 -#define EX_HARDWARE 24 -#define EX_CONTROL 25 -// Internal server datatypes - used when binding to SQL_C_BINARY -#ifndef MAXNUMERICLEN // Resolve ODS/DBLib conflicts -// DB-Library datatypes -#define DBMAXCHAR (8000+1) // Max length of DBVARBINARY and DBVARCHAR, etc. +1 for zero byte -#define MAXNAME (SQL_MAX_SQLSERVERNAME+1) // Max server identifier length including zero byte -#ifdef UNICODE -typedef wchar_t DBCHAR; -#else -typedef char DBCHAR; - -#endif -typedef short SQLSMALLINT; - -typedef unsigned short SQLUSMALLINT; - -typedef unsigned char DBBINARY; - -typedef unsigned char DBTINYINT; - -typedef short DBSMALLINT; - -typedef unsigned short DBUSMALLINT; - -typedef double DBFLT8; - -typedef unsigned char DBBIT; - -typedef unsigned char DBBOOL; - -typedef float DBFLT4; - -typedef DBFLT4 DBREAL; - -typedef UINT DBUBOOL; - -typedef struct dbmoney - { - LONG mnyhigh; - ULONG mnylow; - } DBMONEY; - -typedef struct dbdatetime - { - LONG dtdays; - ULONG dttime; - } DBDATETIME; - -typedef struct dbdatetime4 - { - USHORT numdays; - USHORT nummins; - } DBDATETIM4; - -typedef LONG DBMONEY4; - -#include // 8-byte structure packing - -// New Date Time Structures -// New Structure for TIME2 -typedef struct tagSS_TIME2_STRUCT -{ - SQLUSMALLINT hour; - SQLUSMALLINT minute; - SQLUSMALLINT second; - SQLUINTEGER fraction; -} SQL_SS_TIME2_STRUCT; -// New Structure for TIMESTAMPOFFSET -typedef struct tagSS_TIMESTAMPOFFSET_STRUCT -{ - SQLSMALLINT year; - SQLUSMALLINT month; - SQLUSMALLINT day; - SQLUSMALLINT hour; - SQLUSMALLINT minute; - SQLUSMALLINT second; - SQLUINTEGER fraction; - SQLSMALLINT timezone_hour; - SQLSMALLINT timezone_minute; -} SQL_SS_TIMESTAMPOFFSET_STRUCT; - -typedef struct tagDBTIME2 -{ - USHORT hour; - USHORT minute; - USHORT second; - ULONG fraction; -} DBTIME2; - -typedef struct tagDBTIMESTAMPOFFSET -{ - SHORT year; - USHORT month; - USHORT day; - USHORT hour; - USHORT minute; - USHORT second; - ULONG fraction; - SHORT timezone_hour; - SHORT timezone_minute; -} DBTIMESTAMPOFFSET; - -#include // restore original structure packing - -// Money value *10,000 -#define DBNUM_PREC_TYPE BYTE -#define DBNUM_SCALE_TYPE BYTE -#define DBNUM_VAL_TYPE BYTE - -#if (ODBCVER < 0x0300) -#define MAXNUMERICLEN 16 -typedef struct dbnumeric // Internal representation of NUMERIC data type -{ - DBNUM_PREC_TYPE precision; // Precision - DBNUM_SCALE_TYPE scale; // Scale - BYTE sign; // Sign (1 if positive, 0 if negative) - DBNUM_VAL_TYPE val[MAXNUMERICLEN];// Value -} DBNUMERIC; -typedef DBNUMERIC DBDECIMAL;// Internal representation of DECIMAL data type -#else // Use ODBC 3.0 definitions since same as DBLib -#define MAXNUMERICLEN SQL_MAX_NUMERIC_LEN -typedef SQL_NUMERIC_STRUCT DBNUMERIC; -typedef SQL_NUMERIC_STRUCT DBDECIMAL; -#endif // ODCBVER -#endif // MAXNUMERICLEN - -#ifndef INT -typedef int INT; -typedef LONG DBINT; -typedef DBINT * LPDBINT; -#ifndef _LPCBYTE_DEFINED -#define _LPCBYTE_DEFINED -typedef BYTE const* LPCBYTE; -#endif //_LPCBYTE_DEFINED -#endif // INT -/************************************************************************** -This struct is a global used for gathering statistical data on the driver. -Access to this structure is controlled via the pStatCrit; -***************************************************************************/ -typedef struct sqlperf -{ - // Application Profile Statistics - DWORD TimerResolution; - DWORD SQLidu; - DWORD SQLiduRows; - DWORD SQLSelects; - DWORD SQLSelectRows; - DWORD Transactions; - DWORD SQLPrepares; - DWORD ExecDirects; - DWORD SQLExecutes; - DWORD CursorOpens; - DWORD CursorSize; - DWORD CursorUsed; - LDOUBLE PercentCursorUsed; - LDOUBLE AvgFetchTime; - LDOUBLE AvgCursorSize; - LDOUBLE AvgCursorUsed; - DWORD SQLFetchTime; - DWORD SQLFetchCount; - DWORD CurrentStmtCount; - DWORD MaxOpenStmt; - DWORD SumOpenStmt; - // Connection Statistics - DWORD CurrentConnectionCount; - DWORD MaxConnectionsOpened; - DWORD SumConnectionsOpened; - DWORD SumConnectiontime; - LDOUBLE AvgTimeOpened; - // Network Statistics - DWORD ServerRndTrips; - DWORD BuffersSent; - DWORD BuffersRec; - DWORD BytesSent; - DWORD BytesRec; - // Time Statistics; - DWORD msExecutionTime; - DWORD msNetWorkServerTime; -} SQLPERF; -// The following are options for SQL_COPT_SS_PERF_DATA and SQL_COPT_SS_PERF_QUERY -#define SQL_PERF_START 1 // Starts the driver sampling performance data. -#define SQL_PERF_STOP 2 // Stops the counters from sampling performance data. -// The following are defines for SQL_COPT_SS_PERF_DATA_LOG -#define SQL_SS_DL_DEFAULT TEXT("STATS.LOG") -// The following are defines for SQL_COPT_SS_PERF_QUERY_LOG -#define SQL_SS_QL_DEFAULT TEXT("QUERY.LOG") -// The following are defines for SQL_COPT_SS_PERF_QUERY_INTERVAL -#define SQL_SS_QI_DEFAULT 30000 // 30,000 milliseconds - -#ifndef SQLNCLI_NO_BCP -// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application -// and you want to exclude the BCP-related definitions in this header file. - -// ODBC BCP prototypes and defines -// Return codes -#define SUCCEED 1 -#define FAIL 0 -#define SUCCEED_ABORT 2 -#define SUCCEED_ASYNC 3 -// Transfer directions -#define DB_IN 1 // Transfer from client to server -#define DB_OUT 2 // Transfer from server to client -// bcp_control option -#define BCPMAXERRS 1 // Sets max errors allowed -#define BCPFIRST 2 // Sets first row to be copied out -#define BCPLAST 3 // Sets number of rows to be copied out -#define BCPBATCH 4 // Sets input batch size -#define BCPKEEPNULLS 5 // Sets to insert NULLs for empty input values -#define BCPABORT 6 // Sets to have bcpexec return SUCCEED_ABORT -#define BCPODBC 7 // Sets ODBC canonical character output -#define BCPKEEPIDENTITY 8 // Sets IDENTITY_INSERT on -#if SQLNCLI_VER < 1000 -#define BCP6xFILEFMT 9 // DEPRECATED: Sets 6x file format on -#endif -#define BCPHINTSA 10 // Sets server BCP hints (ANSI string) -#define BCPHINTSW 11 // Sets server BCP hints (UNICODE string) -#define BCPFILECP 12 // Sets clients code page for the file -#define BCPUNICODEFILE 13 // Sets that the file contains unicode header -#define BCPTEXTFILE 14 // Sets BCP mode to expect a text file and to detect Unicode or ANSI automatically -#define BCPFILEFMT 15 // Sets file format version -#define BCPFMTXML 16 // Sets the format file type to xml -#define BCPFIRSTEX 17 // Starting Row for BCP operation (64 bit) -#define BCPLASTEX 18 // Ending Row for BCP operation (64 bit) -#define BCPROWCOUNT 19 // Total Number of Rows Copied (64 bit) -#define BCPDELAYREADFMT 20 // Delay reading format file unil bcp_exec -// BCPFILECP values -// Any valid code page that is installed on the client can be passed plus: -#define BCPFILECP_ACP 0 // Data in file is in Windows code page -#define BCPFILECP_OEMCP 1 // Data in file is in OEM code page (default) -#define BCPFILECP_RAW (-1)// Data in file is in Server code page (no conversion) -// bcp_collen definition -#define SQL_VARLEN_DATA (-10) // Use default length for column -// BCP column format properties -#define BCP_FMT_TYPE 0x01 -#define BCP_FMT_INDICATOR_LEN 0x02 -#define BCP_FMT_DATA_LEN 0x03 -#define BCP_FMT_TERMINATOR 0x04 -#define BCP_FMT_SERVER_COL 0x05 -#define BCP_FMT_COLLATION 0x06 -#define BCP_FMT_COLLATION_ID 0x07 -// bcp_setbulkmode properties -#define BCP_OUT_CHARACTER_MODE 0x01 -#define BCP_OUT_WIDE_CHARACTER_MODE 0x02 -#define BCP_OUT_NATIVE_TEXT_MODE 0x03 -#define BCP_OUT_NATIVE_MODE 0x04 - - - -// BCP functions -DBINT SQL_API bcp_batch (HDBC); -RETCODE SQL_API bcp_bind (HDBC, LPCBYTE, INT, DBINT, LPCBYTE, INT, INT, INT); -RETCODE SQL_API bcp_colfmt (HDBC, INT, BYTE, INT, DBINT, LPCBYTE, INT, INT); -RETCODE SQL_API bcp_collen (HDBC, DBINT, INT); -RETCODE SQL_API bcp_colptr (HDBC, LPCBYTE, INT); -RETCODE SQL_API bcp_columns (HDBC, INT); -RETCODE SQL_API bcp_control (HDBC, INT, void *); -DBINT SQL_API bcp_done (HDBC); -RETCODE SQL_API bcp_exec (HDBC, LPDBINT); -RETCODE SQL_API bcp_getcolfmt (HDBC, INT, INT, void *, INT, INT *); -RETCODE SQL_API bcp_initA (HDBC, LPCSTR, LPCSTR, LPCSTR, INT); -RETCODE SQL_API bcp_initW (HDBC, LPCWSTR, LPCWSTR, LPCWSTR, INT); -RETCODE SQL_API bcp_moretext (HDBC, DBINT, LPCBYTE); -RETCODE SQL_API bcp_readfmtA (HDBC, LPCSTR); -RETCODE SQL_API bcp_readfmtW (HDBC, LPCWSTR); -RETCODE SQL_API bcp_sendrow (HDBC); -RETCODE SQL_API bcp_setbulkmode (HDBC, INT, _In_reads_bytes_(cbField) void*, INT cbField, _In_reads_bytes_(cbRow) void *, INT cbRow); -RETCODE SQL_API bcp_setcolfmt (HDBC, INT, INT, void *, INT); -RETCODE SQL_API bcp_writefmtA (HDBC, LPCSTR); -RETCODE SQL_API bcp_writefmtW (HDBC, LPCWSTR); -CHAR* SQL_API dbprtypeA (INT); -WCHAR* SQL_API dbprtypeW (INT); -CHAR* SQL_API bcp_gettypenameA (INT, DBBOOL); -WCHAR* SQL_API bcp_gettypenameW (INT, DBBOOL); - -#ifdef UNICODE -#define bcp_init bcp_initW -#define bcp_readfmt bcp_readfmtW -#define bcp_writefmt bcp_writefmtW -#define dbprtype dbprtypeW -#define bcp_gettypename bcp_gettypenameW -#define BCPHINTS BCPHINTSW -#else -#define bcp_init bcp_initA -#define bcp_readfmt bcp_readfmtA -#define bcp_writefmt bcp_writefmtA -#define dbprtype dbprtypeA -#define bcp_gettypename bcp_gettypenameA -#define BCPHINTS BCPHINTSA -#endif // UNICODE - -#endif // SQLNCLI_NO_BCP - -// The following options have been deprecated -#define SQL_FAST_CONNECT (SQL_COPT_SS_BASE+0) -// Defines for use with SQL_FAST_CONNECT - only useable before connecting -#define SQL_FC_OFF 0L // Fast connect is off -#define SQL_FC_ON 1L // Fast connect is on -#define SQL_FC_DEFAULT SQL_FC_OFF -#define SQL_COPT_SS_ANSI_OEM (SQL_COPT_SS_BASE+6) -#define SQL_AO_OFF 0L -#define SQL_AO_ON 1L -#define SQL_AO_DEFAULT SQL_AO_OFF -#define SQL_CA_SS_BASE_COLUMN_NAME SQL_DESC_BASE_COLUMN_NAME - - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // ODBCVER - - - -#ifdef __cplusplus -extern "C" { -#endif -#include - -//The following facilitates opening a handle to a SQL filestream -typedef enum _SQL_FILESTREAM_DESIRED_ACCESS { - SQL_FILESTREAM_READ = 0, - SQL_FILESTREAM_WRITE = 1, - SQL_FILESTREAM_READWRITE = 2 -} SQL_FILESTREAM_DESIRED_ACCESS; -#define SQL_FILESTREAM_OPEN_FLAG_ASYNC 0x00000001L -#define SQL_FILESTREAM_OPEN_FLAG_NO_BUFFERING 0x00000002L -#define SQL_FILESTREAM_OPEN_FLAG_NO_WRITE_THROUGH 0x00000004L -#define SQL_FILESTREAM_OPEN_FLAG_SEQUENTIAL_SCAN 0x00000008L -#define SQL_FILESTREAM_OPEN_FLAG_RANDOM_ACCESS 0x00000010L - - -HANDLE __stdcall OpenSqlFilestream ( - LPCWSTR FilestreamPath, - SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess, - ULONG OpenOptions, - _In_reads_bytes_(FilestreamTransactionContextLength) - LPBYTE FilestreamTransactionContext, - SSIZE_T FilestreamTransactionContextLength, - PLARGE_INTEGER AllocationSize); -#define FSCTL_SQL_FILESTREAM_FETCH_OLD_CONTENT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 2392, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#ifdef __cplusplus -} // extern "C" -#endif - - -#endif //__sqlncli_h__ - -#define SQL_COPT_SS_CONNECT_RETRY_COUNT (SQL_COPT_SS_BASE+34) // Post connection attribute used to get ConnectRetryCount -#define SQL_COPT_SS_CONNECT_RETRY_INTERVAL (SQL_COPT_SS_BASE+35) // Post connection attribute used to get ConnectRetryInterval -#ifdef SQL_COPT_SS_MAX_USED -#undef SQL_COPT_SS_MAX_USED -#endif // SQL_COPT_SS_MAX_USED -#define SQL_COPT_SS_MAX_USED SQL_COPT_SS_CONNECT_RETRY_INTERVAL - - -#ifndef _SQLUSERINSTANCE_H_ -#define _SQLUSERINSTANCE_H_ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Recommended buffer size to store a LocalDB connection string -#define LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE 260 - -// type definition for LocalDBCreateInstance function -typedef HRESULT __cdecl FnLocalDBCreateInstance ( - // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - _In_z_ PCWSTR wszVersion, - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -); - -// type definition for pointer to LocalDBCreateInstance function -typedef FnLocalDBCreateInstance* PFnLocalDBCreateInstance; - -// type definition for LocalDBStartInstance function -typedef HRESULT __cdecl FnLocalDBStartInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags, - // O the buffer to store the connection string to the LocalDB instance - _Out_writes_opt_z_(*lpcchSqlConnection) LPWSTR wszSqlConnection, - // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. - _Inout_opt_ LPDWORD lpcchSqlConnection -); - -// type definition for pointer to LocalDBStartInstance function -typedef FnLocalDBStartInstance* PFnLocalDBStartInstance; - -// Flags for the LocalDBFormatMessage function -#define LOCALDB_TRUNCATE_ERR_MESSAGE 0x0001L - -// type definition for LocalDBFormatMessage function -typedef HRESULT __cdecl FnLocalDBFormatMessage( - // I the LocalDB error code - _In_ HRESULT hrLocalDB, - // I Available flags: - // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, - // the error message will be truncated to fit into the buffer - _In_ DWORD dwFlags, - // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - _In_ DWORD dwLanguageId, - // O the buffer to store the LocalDB error message - _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, - // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. If the function succeeds - // contains the number of characters in the message, excluding the trailing null - _Inout_ LPDWORD lpcchMessage -); - -// type definition for function pointer to LocalDBFormatMessage function -typedef FnLocalDBFormatMessage* PFnLocalDBFormatMessage; - - -// MessageId: LOCALDB_ERROR_NOT_INSTALLED -// -// MessageText: -// -// LocalDB is not installed. -// -#define LOCALDB_ERROR_NOT_INSTALLED ((HRESULT)0x89C50116L) - -//--------------------------------------------------------------------- -// Function: LocalDBCreateInstance -// -// Description: This function will create the new LocalDB instance. -// -// Available Flags: -// No flags available. Reserved for future use. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_VERSION, if the version parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INVALID_OPERATION, if the user tries to create a default instance -// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH -// LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED, if the specified service level is not installed -// LOCALDB_ERROR_INSTANCE_FOLDER_ALREADY_EXISTS, if the instance folder already exists and is not empty -// LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION, if the specified instance already exists but with lower version -// LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER, if a folder cannot be created under %userprofile% -// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified -// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created -// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed. -// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted -// -FnLocalDBCreateInstance LocalDBCreateInstance; - -//--------------------------------------------------------------------- -// Function: LocalDBStartInstance -// -// Description: This function will start the given LocalDB instance. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_CONNECTION, if the wszSqlConnection parameter is NULL -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the buffer wszSqlConnection is too small -// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH - -// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified -// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created -// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed. -// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted -// -FnLocalDBStartInstance LocalDBStartInstance; - -// type definition for LocalDBStopInstance function -typedef HRESULT __cdecl FnLocalDBStopInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I Available flags: - // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately - // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - _In_ DWORD dwFlags, - // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately - // without waiting for LocalDB instance to stop - _In_ ULONG ulTimeout -); - -// type definition for pointer to LocalDBStopInstance function -typedef FnLocalDBStopInstance* PFnLocalDBStopInstance; - -// Flags for the StopLocalDBInstance function -#define LOCALDB_SHUTDOWN_KILL_PROCESS 0x0001L -#define LOCALDB_SHUTDOWN_WITH_NOWAIT 0x0002L - -//--------------------------------------------------------------------- -// Function: LocalDBStopInstance -// -// Description: This function will shutdown the given LocalDB instance. -// If the flag LOCALDB_SHUTDOWN_KILL_PROCESS is set, the LocalDB instance will be killed immediately. -// IF the flag LOCALDB_SHUTDOWN_WITH_NOWAIT is set, the LocalDB instance will shutdown with NOWAIT option. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_WAIT_TIMEOUT - if this function has not finished in given time -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBStopInstance LocalDBStopInstance; - -// type definition for LocalDBDeleteInstance function -typedef HRESULT __cdecl FnLocalDBDeleteInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -); - -// type definition for pointer to LocalDBDeleteInstance function -typedef FnLocalDBDeleteInstance* PFnLocalDBDeleteInstance; - -//--------------------------------------------------------------------- -// Function: LocalDBDeleteInstance -// -// Description: This function will remove the given LocalDB instance. If the given instance is running this function will -// fail with the error code LOCALDB_ERROR_INSTANCE_BUSY. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INSTANCE_BUSY, if the given instance is running -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBDeleteInstance LocalDBDeleteInstance; - -// Function: LocalDBFormatMessage -// -// Description: This function will return the localized textual description for the given LocalDB error -// -// Available Flags: -// LOCALDB_TRUNCATE_ERR_MESSAGE - the error message should be truncated to fit into the provided buffer -// -// Return Value: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_UNKNOWN_HRESULT, if the given HRESULT is unknown -// LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID, if the given language id is unknown (0 is recommended for the // default language) -// LOCALDB_ERROR_UNKNOWN_ERROR_CODE, if the LocalDB error code is unknown -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the input buffer is too short and LOCALDB_TRUNCATE_ERR_MESSAGE flag -// is not set -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBFormatMessage LocalDBFormatMessage; - -#define MAX_LOCALDB_INSTANCE_NAME_LENGTH 128 -#define MAX_LOCALDB_PARENT_INSTANCE_LENGTH MAX_INSTANCE_NAME - -typedef WCHAR TLocalDBInstanceName[MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1]; -typedef TLocalDBInstanceName* PTLocalDBInstanceName; - -// type definition for LocalDBGetInstances function -typedef HRESULT __cdecl FnLocalDBGetInstances( - // O buffer for a LocalDB instance names - _Out_ PTLocalDBInstanceName pInstanceNames, - // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, - // has the number of existing LocalDB instances - _Inout_ LPDWORD lpdwNumberOfInstances -); - -// type definition for pointer to LocalDBGetInstances function -typedef FnLocalDBGetInstances* PFnLocalDBGetInstances; - -// Function: LocalDBGetInstances -// -// Description: This function returns names for all existing Local DB instances -// -// Usage Example: -// DWORD dwN = 0; -// LocalDBGetInstances(NULL, &dwN); - -// PTLocalDBInstanceName insts = (PTLocalDBInstanceName) malloc(dwN * sizeof(TLocalDBInstanceName)); -// LocalDBGetInstances(insts, &dwN); - -// for (int i = 0; i < dwN; i++) -// wprintf(L"%s\n", insts[i]); -// -// Return values: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBGetInstances LocalDBGetInstances; - -// SID string format: S - Revision(1B) - Authority ID (6B) {- Sub authority ID (4B)} * max 15 sub-authorities = 1 + 1 + 3 + 1 + 15 + (1 + 10) * 15 -#define MAX_STRING_SID_LENGTH 186 - -#pragma pack(push) -#pragma pack(8) - -// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetInstanceInfo in sqluserinstance.cpp file. -// -typedef struct _LocalDBInstanceInfo -{ - DWORD cbLocalDBInstanceInfoSize; - TLocalDBInstanceName wszInstanceName; - BOOL bExists; - BOOL bConfigurationCorrupted; - BOOL bIsRunning; - DWORD dwMajor; - DWORD dwMinor; - DWORD dwBuild; - DWORD dwRevision; - FILETIME ftLastStartDateUTC; - WCHAR wszConnection[LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE]; - BOOL bIsShared; - TLocalDBInstanceName wszSharedInstanceName; - WCHAR wszOwnerSID[MAX_STRING_SID_LENGTH + 1]; - BOOL bIsAutomatic; -} LocalDBInstanceInfo; - -#pragma pack(pop) - -typedef LocalDBInstanceInfo* PLocalDBInstanceInfo; - -// type definition for LocalDBGetInstanceInfo function -typedef HRESULT __cdecl FnLocalDBGetInstanceInfo( - // I the LocalDB instance name - _In_z_ PCWSTR wszInstanceName, - // O instance information - _Out_ PLocalDBInstanceInfo pInfo, - // I Size of LocalDBInstanceInfo structure in bytes - _In_ DWORD cbInfo); - -// type definition for pointer to LocalDBGetInstances function -typedef FnLocalDBGetInstanceInfo* PFnLocalDBGetInstanceInfo; - -// Function: LocalDBGetInstanceInfo -// -// Description: This function returns information about the given instance. -// -// Return values: -// S_OK, if the function succeeds -// -// ERROR_INVALID_PARAMETER, if some of the parameters is invalid -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBGetInstanceInfo LocalDBGetInstanceInfo; - -// Version has format: Major.Minor[.Build[.Revision]]. Each of components is 32bit integer which is at most 40 digits and 3 dots -// -#define MAX_LOCALDB_VERSION_LENGTH 43 - -typedef WCHAR TLocalDBVersion[MAX_LOCALDB_VERSION_LENGTH + 1]; -typedef TLocalDBVersion* PTLocalDBVersion; - -// type definition for LocalDBGetVersions function -typedef HRESULT __cdecl FnLocalDBGetVersions( - // O buffer for installed LocalDB versions - _Out_ PTLocalDBVersion pVersions, - // I/O on input has the number slots for versions in the pVersions buffer. On output, - // has the number of existing LocalDB versions - _Inout_ LPDWORD lpdwNumberOfVersions -); - -// type definition for pointer to LocalDBGetVersions function -typedef FnLocalDBGetVersions* PFnLocalDBGetVersions; - -// Function: LocalDBGetVersions -// -// Description: This function returns all installed LocalDB versions. Returned versions will be in format Major.Minor -// -// Usage Example: -// DWORD dwN = 0; -// LocalDBGetVersions(NULL, &dwN); - -// PTLocalDBVersion versions = (PTLocalDBVersion) malloc(dwN * sizeof(TLocalDBVersion)); -// LocalDBGetVersions(insts, &dwN); - -// for (int i = 0; i < dwN; i++) -// wprintf(L"%s\n", insts[i]); -// -// Return values: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurs. -// -FnLocalDBGetVersions LocalDBGetVersions; - -#pragma pack(push) -#pragma pack(8) - -// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetVersionInfo in sqluserinstance.cpp file. -// -typedef struct _LocalDBVersionInfo -{ - DWORD cbLocalDBVersionInfoSize; - TLocalDBVersion wszVersion; - BOOL bExists; - DWORD dwMajor; - DWORD dwMinor; - DWORD dwBuild; - DWORD dwRevision; -} LocalDBVersionInfo; - -#pragma pack(pop) - -typedef LocalDBVersionInfo* PLocalDBVersionInfo; - -// type definition for LocalDBGetVersionInfo function -typedef HRESULT __cdecl FnLocalDBGetVersionInfo( - // I LocalDB version string - _In_z_ PCWSTR wszVersion, - // O version information - _Out_ PLocalDBVersionInfo pVersionInfo, - // I Size of LocalDBVersionInfo structure in bytes - _In_ DWORD cbVersionInfo -); - -// type definition for pointer to LocalDBGetVersionInfo function -typedef FnLocalDBGetVersionInfo* PFnLocalDBGetVersionInfo; - -// Function: LocalDBGetVersionInfo -// -// Description: This function returns information about the given LocalDB version -// -// Return values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INTERNAL_ERROR, if some internal error occurred -// LOCALDB_ERROR_INVALID_PARAMETER, if a input parameter is invalid -// -FnLocalDBGetVersionInfo LocalDBGetVersionInfo; - -typedef HRESULT __cdecl FnLocalDBStartTracing(); -typedef FnLocalDBStartTracing* PFnLocalDBStartTracing; - -// Function: LocalDBStartTracing -// -// Description: This function will write in registry that Tracing sessions should be started for the current user. -// -// Return values: -// S_OK - on success -// Propper HRESULT in case of failure -// -FnLocalDBStartTracing LocalDBStartTracing; - -typedef HRESULT __cdecl FnLocalDBStopTracing(); -typedef FnLocalDBStopTracing* PFnFnLocalDBStopTracing; - -// Function: LocalDBStopTracing -// -// Description: This function will write in registry that Tracing sessions should be stopped for the current user. -// -// Return values: -// S_OK - on success -// Propper HRESULT in case of failure -// -FnLocalDBStopTracing LocalDBStopTracing; - -// type definition for LocalDBShareInstance function -typedef HRESULT __cdecl FnLocalDBShareInstance( - // I the SID of the LocalDB instance owner - _In_opt_ PSID pOwnerSID, - // I the private name of LocalDB instance which should be shared - _In_z_ PCWSTR wszPrivateLocalDBInstanceName, - // I the public shared name - _In_z_ PCWSTR wszSharedName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags); - -// type definition for pointer to LocalDBShareInstance function -typedef FnLocalDBShareInstance* PFnLocalDBShareInstance; - -// Function: LocalDBShareInstance -// -// Description: This function will share the given private instance of the given user with the given shared name. -// This function has to be executed elevated. -// -// Return values: -// HRESULT -// -FnLocalDBShareInstance LocalDBShareInstance; - -// type definition for LocalDBUnshareInstance function -typedef HRESULT __cdecl FnLocalDBUnshareInstance( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags); - -// type definition for pointer to LocalDBUnshareInstance function -typedef FnLocalDBUnshareInstance* PFnLocalDBUnshareInstance; - -// Function: LocalDBUnshareInstance -// -// Description: This function unshares the given LocalDB instance. -// If a shared name is given then that shared instance will be unshared. -// If a private name is given then we will check if the caller -// shares a private instance with the given private name and unshare it. -// -// Return values: -// HRESULT -// -FnLocalDBUnshareInstance LocalDBUnshareInstance; - -#ifdef __cplusplus -} // extern "C" -#endif - -#if defined(LOCALDB_DEFINE_PROXY_FUNCTIONS) -//--------------------------------------------------------------------- -// The following section is enabled only if the constant LOCALDB_DEFINE_PROXY_FUNCTIONS -// is defined. It provides an implementation of proxies for each of the LocalDB APIs. -// The proxy implementations use a common function to bind to entry points in the -// latest installed SqlUserInstance DLL, and then forward the requests. -// -// The current implementation loads the SqlUserInstance DLL on the first call into -// a proxy function. There is no provision for unloading the DLL. Note that if the -// process includes multiple binaries (EXE and one or more DLLs), each of them could -// load a separate instance of the SqlUserInstance DLL. -// -// For future consideration: allow the SqlUserInstance DLL to be unloaded dynamically. -// -// WARNING: these functions must not be called in DLL initialization, since a deadlock -// could result loading dependent DLLs. -//--------------------------------------------------------------------- - -// This macro provides the body for each proxy function. -// -#define LOCALDB_PROXY(LocalDBFn) static Fn##LocalDBFn* pfn##LocalDBFn = NULL; if (!pfn##LocalDBFn) {HRESULT hr = LocalDBGetPFn(#LocalDBFn, (FARPROC *)&pfn##LocalDBFn); if (FAILED(hr)) return hr;} return (*pfn##LocalDBFn) - -// Structure and function to parse the "Installed Versions" registry subkeys -// -typedef struct { - DWORD dwComponent[2]; - WCHAR wszKeyName[256]; -} Version; - -// The following algorithm is intended to match, in part, the .NET Version class. -// A maximum of two components are allowed, which must be separated with a period. -// Valid: "11", "11.0" -// Invalid: "", ".0", "11.", "11.0." -// -static BOOL ParseVersion(Version * pVersion) -{ - pVersion->dwComponent[0] = 0; - pVersion->dwComponent[1] = 0; - WCHAR * pwch = pVersion->wszKeyName; - - for (int i = 0; i<2; i++) - { - LONGLONG llVal = 0; - BOOL fHaveDigit = FALSE; - - while (*pwch >= L'0' && *pwch <= L'9') - { - llVal = llVal * 10 + (*pwch++ - L'0'); - fHaveDigit = TRUE; - - if (llVal > 0x7fffffff) - { - return FALSE; - } - } - - if (!fHaveDigit) - return FALSE; - - pVersion->dwComponent[i] = (DWORD) llVal; - - if (*pwch == L'\0') - return TRUE; - - if (*pwch != L'.') - return FALSE; - - pwch++; - } - // If we get here, the version string was terminated with L'.', which is not valid - // - return FALSE; -} - -#include - -// This function loads the correct LocalDB API DLL (if required) and returns a pointer to a procedure. -// Note that the first-loaded API DLL for the process will be used until process termination: installation of -// a new version of the API will not be recognized after first load. -// -static HRESULT LocalDBGetPFn(LPCSTR szLocalDBFn, FARPROC *pfnLocalDBFn) -{ - static volatile HMODULE hLocalDBDll = NULL; - - if (!hLocalDBDll) - { - LONG ec; - HKEY hkeyVersions = NULL; - HKEY hkeyVersion = NULL; - Version verHigh = {0}; - Version verCurrent; - DWORD cchKeyName; - DWORD dwValueType; - WCHAR wszLocalDBDll[MAX_PATH+1]; - DWORD cbLocalDBDll = sizeof(wszLocalDBDll) - sizeof(WCHAR); // to deal with RegQueryValueEx null-termination quirk - HMODULE hLocalDBDllTemp = NULL; - - if (ERROR_SUCCESS != (ec = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions", 0, KEY_READ, &hkeyVersions))) - { - goto Cleanup; - } - - for (int i = 0; ; i++) - { - cchKeyName = 256; - if (ERROR_SUCCESS != (ec = RegEnumKeyExW(hkeyVersions, i, verCurrent.wszKeyName, &cchKeyName, 0, NULL, NULL, NULL))) - { - if (ERROR_NO_MORE_ITEMS == ec) - { - break; - } - goto Cleanup; - } - - if (!ParseVersion(&verCurrent)) - { - continue; // invalid version syntax - } - - if (verCurrent.dwComponent[0] > verHigh.dwComponent[0] || - (verCurrent.dwComponent[0] == verHigh.dwComponent[0] && verCurrent.dwComponent[1] > verHigh.dwComponent[1])) - { - verHigh = verCurrent; - } - } - if (!verHigh.wszKeyName[0]) - { - // ec must be ERROR_NO_MORE_ITEMS here - // - assert(ec == ERROR_NO_MORE_ITEMS); - - // We will change the error code to ERROR_FILE_NOT_FOUND in order to indicate that - // LocalDB instalation is not found. Registry key "SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions" exists - // but it is empty. - // - ec = ERROR_FILE_NOT_FOUND; - goto Cleanup; - } - - if (ERROR_SUCCESS != (ec = RegOpenKeyExW(hkeyVersions, verHigh.wszKeyName, 0, KEY_READ, &hkeyVersion))) - { - goto Cleanup; - } - if (ERROR_SUCCESS != (ec = RegQueryValueExW(hkeyVersion, L"InstanceAPIPath", NULL, &dwValueType, (PBYTE) wszLocalDBDll, &cbLocalDBDll))) - { - goto Cleanup; - } - if (dwValueType != REG_SZ) - { - ec = ERROR_INVALID_DATA; - goto Cleanup; - } - // Ensure string value null-terminated - // Note that we left a spare character in the output buffer for RegQueryValueEx for this purpose - // - wszLocalDBDll[cbLocalDBDll/sizeof(WCHAR)] = L'\0'; - - hLocalDBDllTemp = LoadLibraryW(wszLocalDBDll); - if (NULL == hLocalDBDllTemp) - { - ec = GetLastError(); - goto Cleanup; - } - if (NULL == InterlockedCompareExchangePointer((volatile PVOID *)&hLocalDBDll, hLocalDBDllTemp, NULL)) - { - // We were the winner: we gave away our DLL handle - // - hLocalDBDllTemp = NULL; - } - ec = ERROR_SUCCESS; -Cleanup: - if (hLocalDBDllTemp) - FreeLibrary(hLocalDBDllTemp); - if (hkeyVersion) - RegCloseKey(hkeyVersion); - if (hkeyVersions) - RegCloseKey(hkeyVersions); - - // Error code ERROR_FILE_NOT_FOUND can occure if registry hive with installed LocalDB versions is missing. - // In that case we should return the LocalDB specific error code - // - if (ec == ERROR_FILE_NOT_FOUND) - return LOCALDB_ERROR_NOT_INSTALLED; - - if (ec != ERROR_SUCCESS) - return HRESULT_FROM_WIN32(ec); - } - - FARPROC pfn = GetProcAddress(hLocalDBDll, szLocalDBFn); - - if (!pfn) - { - return HRESULT_FROM_WIN32(GetLastError()); - } - *pfnLocalDBFn = pfn; - return S_OK; -} - -// The following proxy functions forward calls to the latest LocalDB API DLL. -// - -HRESULT __cdecl -LocalDBCreateInstance ( - // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - _In_z_ PCWSTR wszVersion, - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -) -{ - LOCALDB_PROXY(LocalDBCreateInstance)(wszVersion, pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBStartInstance( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags, - // O the buffer to store the connection string to the LocalDB instance - _Out_writes_z__opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, - // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. - _Inout_opt_ LPDWORD lpcchSqlConnection -) -{ - LOCALDB_PROXY(LocalDBStartInstance)(pInstanceName, dwFlags, wszSqlConnection, lpcchSqlConnection); -} - -HRESULT __cdecl -LocalDBStopInstance ( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I Available flags: - // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately - // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - _In_ DWORD dwFlags, - // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately - // without waiting for LocalDB instance to stop - _In_ ULONG ulTimeout -) -{ - LOCALDB_PROXY(LocalDBStopInstance)(pInstanceName, dwFlags, ulTimeout); -} - -HRESULT __cdecl -LocalDBDeleteInstance ( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -) -{ - LOCALDB_PROXY(LocalDBDeleteInstance)(pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBFormatMessage( - // I the LocalDB error code - _In_ HRESULT hrLocalDB, - // I Available flags: - // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, - // the error message will be truncated to fit into the buffer - _In_ DWORD dwFlags, - // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - _In_ DWORD dwLanguageId, - // O the buffer to store the LocalDB error message - _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, - // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. If the function succeeds - // contains the number of characters in the message, excluding the trailing null - _Inout_ LPDWORD lpcchMessage -) -{ - LOCALDB_PROXY(LocalDBFormatMessage)(hrLocalDB, dwFlags, dwLanguageId, wszMessage, lpcchMessage); -} - -HRESULT __cdecl -LocalDBGetInstances( - // O buffer with instance names - _Out_ PTLocalDBInstanceName pInstanceNames, - // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, - // has the number of existing LocalDB instances - _Inout_ LPDWORD lpdwNumberOfInstances -) -{ - LOCALDB_PROXY(LocalDBGetInstances)(pInstanceNames, lpdwNumberOfInstances); -} - -HRESULT __cdecl -LocalDBGetInstanceInfo( - // I the instance name - _In_z_ PCWSTR wszInstanceName, - // O instance information - _Out_ PLocalDBInstanceInfo pInfo, - // I Size of LocalDBInstanceInfo structure in bytes - _In_ DWORD cbInfo -) -{ - LOCALDB_PROXY(LocalDBGetInstanceInfo)(wszInstanceName, pInfo, cbInfo); -} - -HRESULT __cdecl -LocalDBStartTracing() -{ - LOCALDB_PROXY(LocalDBStartTracing)(); -} - -HRESULT __cdecl -LocalDBStopTracing() -{ - LOCALDB_PROXY(LocalDBStopTracing)(); -} - -HRESULT __cdecl -LocalDBShareInstance( - // I the SID of the LocalDB instance owner - _In_opt_ PSID pOwnerSID, - // I the private name of LocalDB instance which should be shared - _In_z_ PCWSTR wszLocalDBInstancePrivateName, - // I the public shared name - _In_z_ PCWSTR wszSharedName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags) -{ - LOCALDB_PROXY(LocalDBShareInstance)(pOwnerSID, wszLocalDBInstancePrivateName, wszSharedName, dwFlags); -} - -HRESULT __cdecl -LocalDBGetVersions( - // O buffer for installed LocalDB versions - _Out_ PTLocalDBVersion pVersions, - // I/O on input has the number slots for versions in the pVersions buffer. On output, - // has the number of existing LocalDB versions - _Inout_ LPDWORD lpdwNumberOfVersions -) -{ - LOCALDB_PROXY(LocalDBGetVersions)(pVersions, lpdwNumberOfVersions); -} - -HRESULT __cdecl -LocalDBUnshareInstance( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags) -{ - LOCALDB_PROXY(LocalDBUnshareInstance)(pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBGetVersionInfo( - // I LocalDB version string - _In_z_ PCWSTR wszVersion, - // O version information - _Out_ PLocalDBVersionInfo pVersionInfo, - // I Size of LocalDBVersionInfo structure in bytes - _In_ DWORD cbVersionInfo) -{ - LOCALDB_PROXY(LocalDBGetVersionInfo)(wszVersion, pVersionInfo, cbVersionInfo); -} - -#endif - -#endif // _SQLUSERINSTANCE_H_ - -//----------------------------------------------------------------------------- -// File: sqluserinstancemsgs.mc -// -// Copyright: Copyright (c) Microsoft Corporation -//----------------------------------------------------------------------------- -#ifndef _LOCALDB_MESSAGES_H_ -#define _LOCALDB_MESSAGES_H_ -// Header section -// -// Section with the LocalDB messages -// -// -// Values are 32 bit values laid out as follows: -// -// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 -// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 -// +-+-+-+-+-+---------------------+-------------------------------+ -// |S|R|C|N|r| Facility | Code | -// +-+-+-+-+-+---------------------+-------------------------------+ -// -// where -// -// S - Severity - indicates success/fail -// -// 0 - Success -// 1 - Fail (COERROR) -// -// R - reserved portion of the facility code, corresponds to NT's -// second severity bit. -// -// C - reserved portion of the facility code, corresponds to NT's -// C field. -// -// N - reserved portion of the facility code. Used to indicate a -// mapped NT status value. -// -// r - reserved portion of the facility code. Reserved for internal -// use. Used to indicate HRESULT values that are not status -// values, but are instead message ids for display strings. -// -// Facility - is the facility code -// -// Code - is the facility's status code -// -// -// Define the facility codes -// -#define FACILITY_LOCALDB 0x9C5 - - -// -// Define the severity codes -// -#define LOCALDB_SEVERITY_SUCCESS 0x0 -#define LOCALDB_SEVERITY_ERROR 0x2 - - -// -// MessageId: LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot create folder for the LocalDB instance at: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER ((HRESULT)0x89C50100L) - -// -// MessageId: LOCALDB_ERROR_INVALID_PARAMETER -// -// MessageText: -// -// The parameter for the LocalDB Instance API method is incorrect. Consult the API documentation. -// -#define LOCALDB_ERROR_INVALID_PARAMETER ((HRESULT)0x89C50101L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION -// -// MessageText: -// -// Unable to create the LocalDB instance with specified version. An instance with the same name already exists, but it has lower version than the specified version. -// -#define LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION ((HRESULT)0x89C50102L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER -// -// MessageText: -// -// Cannot access the user profile folder for local application data (%%LOCALAPPDATA%%). -// -#define LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER ((HRESULT)0x89C50103L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG -// -// MessageText: -// -// The full path length of the LocalDB instance folder is longer than MAX_PATH. The instance must be stored in folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG ((HRESULT)0x89C50104L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot access LocalDB instance folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50105L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY -// -// MessageText: -// -// Unexpected error occurred while trying to access the LocalDB instance registry configuration. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY ((HRESULT)0x89C50106L) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_INSTANCE -// -// MessageText: -// -// The specified LocalDB instance does not exist. -// -#define LOCALDB_ERROR_UNKNOWN_INSTANCE ((HRESULT)0x89C50107L) - -// -// MessageId: LOCALDB_ERROR_INTERNAL_ERROR -// -// MessageText: -// -// Unexpected error occurred inside a LocalDB instance API method call. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_INTERNAL_ERROR ((HRESULT)0x89C50108L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY -// -// MessageText: -// -// Unexpected error occurred while trying to modify the registry configuration for the LocalDB instance. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY ((HRESULT)0x89C50109L) - -// -// MessageId: LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED -// -// MessageText: -// -// Error occurred during LocalDB instance startup: SQL Server process failed to start. -// -#define LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED ((HRESULT)0x89C5010AL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT -// -// MessageText: -// -// LocalDB instance is corrupted. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT ((HRESULT)0x89C5010BL) - -// -// MessageId: LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS -// -// MessageText: -// -// Error occurred during LocalDB instance startup: unable to create the SQL Server process. -// -#define LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS ((HRESULT)0x89C5010CL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_VERSION -// -// MessageText: -// -// The specified LocalDB version is not available on this computer. -// -#define LOCALDB_ERROR_UNKNOWN_VERSION ((HRESULT)0x89C5010DL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID -// -// MessageText: -// -// Error getting the localized error message. The language specified by 'Language ID' parameter is unknown. -// -#define LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID ((HRESULT)0x89C5010EL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_STOP_FAILED -// -// MessageText: -// -// Stop operation for LocalDB instance failed to complete within the specified time. -// -#define LOCALDB_ERROR_INSTANCE_STOP_FAILED ((HRESULT)0x89C5010FL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_ERROR_CODE -// -// MessageText: -// -// Error getting the localized error message. The specified error code is unknown. -// -#define LOCALDB_ERROR_UNKNOWN_ERROR_CODE ((HRESULT)0x89C50110L) - -// -// MessageId: LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED -// -// MessageText: -// -// The LocalDB version available on this workstation is lower than the requested LocalDB version. -// -#define LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED ((HRESULT)0x89C50111L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_BUSY -// -// MessageText: -// -// Requested operation on LocalDB instance cannot be performed because specified instance is currently in use. Stop the instance and try again. -// -#define LOCALDB_ERROR_INSTANCE_BUSY ((HRESULT)0x89C50112L) - -// -// MessageId: LOCALDB_ERROR_INVALID_OPERATION -// -// MessageText: -// -// Default LocalDB instances cannot be created, stopped or deleted manually. -// -#define LOCALDB_ERROR_INVALID_OPERATION ((HRESULT)0x89C50113L) - -// -// MessageId: LOCALDB_ERROR_INSUFFICIENT_BUFFER -// -// MessageText: -// -// The buffer passed to the LocalDB instance API method has insufficient size. -// -#define LOCALDB_ERROR_INSUFFICIENT_BUFFER ((HRESULT)0x89C50114L) - -// -// MessageId: LOCALDB_ERROR_WAIT_TIMEOUT -// -// MessageText: -// -// Timeout occurred inside the LocalDB instance API method. -// -#define LOCALDB_ERROR_WAIT_TIMEOUT ((HRESULT)0x89C50115L) - -// MessageId=0x0116 message id is reserved. This message ID will be used for error LOCALDB_ERROR_NOT_INSTALLED. -// This message is specific since it has to be present in SqlUserIntsnace.h because it can be returned by discovery API. -// -// -// MessageId: LOCALDB_ERROR_XEVENT_FAILED -// -// MessageText: -// -// Failed to start XEvent engine within the LocalDB Instance API. -// -#define LOCALDB_ERROR_XEVENT_FAILED ((HRESULT)0x89C50117L) - -// -// MessageId: LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED -// -// MessageText: -// -// Cannot create an automatic instance. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED ((HRESULT)0x89C50118L) - -// -// MessageId: LOCALDB_ERROR_SHARED_NAME_TAKEN -// -// MessageText: -// -// Cannot create a shared instance. The specified shared instance name is already in use. -// -#define LOCALDB_ERROR_SHARED_NAME_TAKEN ((HRESULT)0x89C50119L) - -// -// MessageId: LOCALDB_ERROR_CALLER_IS_NOT_OWNER -// -// MessageText: -// -// API caller is not LocalDB instance owner. -// -#define LOCALDB_ERROR_CALLER_IS_NOT_OWNER ((HRESULT)0x89C5011AL) - -// -// MessageId: LOCALDB_ERROR_INVALID_INSTANCE_NAME -// -// MessageText: -// -// Specified LocalDB instance name is invalid. -// -#define LOCALDB_ERROR_INVALID_INSTANCE_NAME ((HRESULT)0x89C5011BL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_ALREADY_SHARED -// -// MessageText: -// -// The specified LocalDB instance is already shared with different shared name. -// -#define LOCALDB_ERROR_INSTANCE_ALREADY_SHARED ((HRESULT)0x89C5011CL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_NOT_SHARED -// -// MessageText: -// -// The specified LocalDB instance is not shared. -// -#define LOCALDB_ERROR_INSTANCE_NOT_SHARED ((HRESULT)0x89C5011DL) - -// -// MessageId: LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED -// -// MessageText: -// -// Administrator privileges are required in order to execute this operation. -// -#define LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED ((HRESULT)0x89C5011EL) - -// -// MessageId: LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES -// -// MessageText: -// -// There are too many shared instance and we cannot generate unique User Instance Name. Unshare some of the existing shared instances. -// -#define LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES ((HRESULT)0x89C5011FL) - -// -// MessageId: LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH -// -// MessageText: -// -// Cannot get a local application data path. Most probably a user profile is not loaded. If LocalDB is executed under IIS, make sure that profile loading is enabled for the current user. -// -#define LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH ((HRESULT)0x89C50120L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_LOAD_RESOURCES -// -// MessageText: -// -// Cannot load resources for this DLL. Resources for this DLL should be stored in a subfolder Resources, with the same file name as this DLL and the extension ".RLL". -// -#define LOCALDB_ERROR_CANNOT_LOAD_RESOURCES ((HRESULT)0x89C50121L) - - // Detailed error descriptions -// -// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING -// -// MessageText: -// -// The "DataDirectory" registry value is missing in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING ((HRESULT)0x89C50200L) - -// -// MessageId: LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot access LocalDB instance folder: %1 -// -#define LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50201L) - -// -// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG -// -// MessageText: -// -// The "DataDirectory" registry value is too long in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG ((HRESULT)0x89C50202L) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING -// -// MessageText: -// -// The "Parent Instance" registry value is missing in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING ((HRESULT)0x89C50203L) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG -// -// MessageText: -// -// The "Parent Instance" registry value is too long in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG ((HRESULT)0x89C50204L) - -// -// MessageId: LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID -// -// MessageText: -// -// Data directory for LocalDB instance is invalid: %1 -// -#define LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID ((HRESULT)0x89C50205L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_ASSERT -// -// MessageText: -// -// LocalDB instance API: XEvent engine assert: %1 in %2:%3 (%4) -// -#define LOCALDB_EDETAIL_XEVENT_ASSERT ((HRESULT)0x89C50206L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_ERROR -// -// MessageText: -// -// LocalDB instance API: XEvent error: %1 -// -#define LOCALDB_EDETAIL_XEVENT_ERROR ((HRESULT)0x89C50207L) - -// -// MessageId: LOCALDB_EDETAIL_INSTALLATION_CORRUPTED -// -// MessageText: -// -// LocalDB installation is corrupted. Reinstall the LocalDB. -// -#define LOCALDB_EDETAIL_INSTALLATION_CORRUPTED ((HRESULT)0x89C50208L) - -// -// MessageId: LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION -// -// MessageText: -// -// LocalDB XEvent error: cannot determine %ProgramFiles% folder location. -// -#define LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION ((HRESULT)0x89C50209L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE -// -// MessageText: -// -// LocalDB XEvent error: Cannot initialize XEvent engine. -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE ((HRESULT)0x89C5020AL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE -// -// MessageText: -// -// LocalDB XEvent error: Cannot find XEvents configuration file: %1 -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE ((HRESULT)0x89C5020BL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE -// -// MessageText: -// -// LocalDB XEvent error: Cannot configure XEvents engine with the configuration file: %1 -// HRESULT returned: %2 -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE ((HRESULT)0x89C5020CL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG -// -// MessageText: -// -// LocalDB XEvent error: XEvents engine configuration file too long -// -#define LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG ((HRESULT)0x89C5020DL) - -// -// MessageId: LOCALDB_EDETAIL_COINITIALIZEEX_FAILED -// -// MessageText: -// -// CoInitializeEx API failed. HRESULT returned: %1 -// -#define LOCALDB_EDETAIL_COINITIALIZEEX_FAILED ((HRESULT)0x89C5020EL) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID -// -// MessageText: -// -// LocalDB parent instance version is invalid: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID ((HRESULT)0x89C5020FL) - -// -// MessageId: LOCALDB_EDETAIL_WINAPI_ERROR -// -// MessageText: -// -// Windows API call %1 returned error code: %2. Windows system error message is: %3Reported at line: %4. %5 -// -#define LOCALDB_EDETAIL_WINAPI_ERROR ((HRESULT)0xC9C50210L) - -// -// MessageId: LOCALDB_EDETAIL_UNEXPECTED_RESULT -// -// MessageText: -// -// Function %1 returned %2 at line %3. -// -#define LOCALDB_EDETAIL_UNEXPECTED_RESULT ((HRESULT)0x89C50211L) - -// -#endif // _LOCALDB_MESSAGES_H_ - -#endif //__msodbcsql_h__ diff --git a/source/packagize.sh b/source/packagize.sh new file mode 100644 index 000000000..dc9051b3c --- /dev/null +++ b/source/packagize.sh @@ -0,0 +1,11 @@ +#!/bin/bash +if [ "$1" == "" ]; then + cp -rf $PWD/shared $PWD/sqlsrv + cp -rf $PWD/shared $PWD/pdo_sqlsrv +else + [[ -d $1 ]] || { echo "No such path!"; exit 1; } + cp -rf $PWD/sqlsrv $1 + cp -rf $PWD/pdo_sqlsrv $1 + cp -rf $PWD/shared $1/sqlsrv + cp -rf $PWD/shared $1/pdo_sqlsrv +fi diff --git a/source/pdo_sqlsrv/CREDITS b/source/pdo_sqlsrv/CREDITS new file mode 100644 index 000000000..b0c7112c1 --- /dev/null +++ b/source/pdo_sqlsrv/CREDITS @@ -0,0 +1 @@ +Microsoft Drivers for PHP for SQL Server (PDO driver) diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 new file mode 100644 index 000000000..411d405a1 --- /dev/null +++ b/source/pdo_sqlsrv/config.m4 @@ -0,0 +1,68 @@ +PHP_ARG_WITH(pdo_sqlsrv, for pdo_sqlsrv support, +[ --with-pdo_sqlsrv Include pdo_sqlsrv support]) + +if test "$PHP_PDO_SQLSRV" != "no"; then + if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then + AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) + fi + + ifdef([PHP_CHECK_PDO_INCLUDES], + [ + PHP_CHECK_PDO_INCLUDES + ],[ + AC_MSG_CHECKING([for PDO includes]) + if test -f $abs_srcdir/include/php/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$abs_srcdir/ext + elif test -f $abs_srcdir/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$abs_srcdir/ext + elif test -f $phpincludedir/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$phpincludedir/ext + else + AC_MSG_ERROR([Cannot find php_pdo_driver.h.]) + fi + AC_MSG_RESULT($pdo_cv_inc_path) + ]) + + pdo_sqlsrv_src_class="\ + pdo_dbh.cpp \ + pdo_parser.cpp \ + pdo_util.cpp \ + pdo_init.cpp \ + pdo_stmt.cpp \ + " + + shared_src_class="\ + shared/core_conn.cpp \ + shared/core_results.cpp \ + shared/core_stream.cpp \ + shared/core_init.cpp \ + shared/core_stmt.cpp \ + shared/core_util.cpp \ + shared/FormattedPrint.cpp \ + shared/localizationimpl.cpp \ + shared/StringFunctions.cpp \ + " + AC_MSG_CHECKING([for PDO_SQLSRV headers]) + if test -f $srcdir/ext/pdo_sqlsrv/shared/core_sqlsrv.h; then + pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/ + elif test -f $srcdir/shared/core_sqlsrv.h; then + pdo_sqlsrv_inc_path=$srcdir/shared/ + else + AC_MSG_ERROR([Cannot find PDO_SQLSRV headers]) + fi + AC_MSG_RESULT($pdo_sqlsrv_inc_path) + + + CXXFLAGS="$CXXFLAGS -std=c++11" + PHP_REQUIRE_CXX() + PHP_ADD_LIBRARY(stdc++, 1, PDO_SQLSRV_SHARED_LIBADD) + PHP_ADD_LIBRARY(odbc, 1, PDO_SQLSRV_SHARED_LIBADD) + PHP_ADD_LIBRARY(odbcinst, 1, PDO_SQLSRV_SHARED_LIBADD) + AC_DEFINE(HAVE_PDO_SQLSRV, 1, [ ]) + PHP_ADD_INCLUDE([$pdo_sqlsrv_inc_path]) + PHP_NEW_EXTENSION(pdo_sqlsrv, $pdo_sqlsrv_src_class $shared_src_class, $ext_shared,,-I$pdo_cv_inc_path -std=c++11) + PHP_SUBST(PDO_SQLSRV_SHARED_LIBADD) + PHP_ADD_EXTENSION_DEP(pdo_sqlsrv, pdo) + PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1) +fi + diff --git a/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 similarity index 57% rename from pdo_sqlsrv/config.w32 rename to source/pdo_sqlsrv/config.w32 index 0a598a51d..f73b5be40 100644 --- a/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -1,41 +1,43 @@ -//---------------------------------------------------------------------------------------------------------------------------------- -// File: config.w32 -// -// Contents: JScript build configuration used by buildconf.bat -// -// Microsoft Drivers 4.0 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -ARG_WITH("pdo-sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (PDO driver)", "no"); - -if( PHP_PDO_SQLSRV != "no" ) { - - pdo_sqlsrv_src = "pdo_dbh.cpp pdo_init.cpp pdo_stmt.cpp pdo_util.cpp pdo_parser.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp"; - - if (CHECK_LIB("odbc32.lib", "pdo_sqlsrv") && CHECK_LIB("odbccp32.lib", "pdo_sqlsrv") && - CHECK_LIB("version.lib", "pdo_sqlsrv") && CHECK_LIB("psapi.lib", "pdo_sqlsrv")) { - - EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1") - - CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_PDO_SQLSRV_ODBC'); - CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_PDO_SQLSRV_ODBC'); - ADD_FLAG( 'LDFLAGS_PDO_SQLSRV', '/NXCOMPAT /DYNAMICBASE /debug' ); - ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/EHsc' ); - ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/GS' ); - ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/Zi' ); - ADD_FLAG( 'CFLAGS_PDO_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' ); - ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo'); - } - -} +//---------------------------------------------------------------------------------------------------------------------------------- +// File: config.w32 +// +// Contents: JScript build configuration used by buildconf.bat +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +ARG_WITH("pdo-sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (PDO driver)", "no"); + +if( PHP_PDO_SQLSRV != "no" ) { + + pdo_sqlsrv_src_class = " pdo_dbh.cpp pdo_init.cpp pdo_stmt.cpp pdo_util.cpp pdo_parser.cpp "; + shared_src_class = " core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp "; + + if (CHECK_LIB("odbc32.lib", "pdo_sqlsrv") && CHECK_LIB("odbccp32.lib", "pdo_sqlsrv") && + CHECK_LIB("version.lib", "pdo_sqlsrv") && CHECK_LIB("psapi.lib", "pdo_sqlsrv")&& + CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_PDO_SQLSRV", configure_module_dirname + "\\shared")) { + CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_PDO_SQLSRV_ODBC"); + CHECK_HEADER_ADD_INCLUDE("sqlext.h", "CFLAGS_PDO_SQLSRV_ODBC"); + ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "pdo_sqlsrv" ); + ADD_FLAG( "LDFLAGS_PDO_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug" ); + ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/EHsc" ); + ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/GS" ); + ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/Zi" ); + ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" ); + ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo'); + EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src_class, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); + } else { + WARNING("pdo-sqlsrv not enabled; libraries and headers not found"); + } +} diff --git a/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp similarity index 85% rename from pdo_sqlsrv/pdo_dbh.cpp rename to source/pdo_sqlsrv/pdo_dbh.cpp index 27e2cc5b2..0d8e18be4 100644 --- a/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -1,1474 +1,1496 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// file: pdo_dbh.cpp -// -// Contents: Implements the PDO object for PDO_SQLSRV -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "pdo_sqlsrv.h" -#include -#include -#include - -#include -#include - - -typedef const zend_function_entry pdo_sqlsrv_function_entry; - -// *** internal variables and constants *** - -namespace { - -const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;"; -const size_t LAST_INSERT_ID_BUFF_LEN = 10; // size of the buffer to hold the string value of the last insert id integer -const char TABLE_LAST_INSERT_ID_QUERY[] = "SELECT IDENT_CURRENT(%s)"; -const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( TABLE_LAST_INSERT_ID_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes - -// List of PDO supported connection options. -namespace PDOConnOptionNames { - -const char Server[] = "Server"; -const char APP[] = "APP"; -const char ApplicationIntent[] = "ApplicationIntent"; -const char AttachDBFileName[] = "AttachDbFileName"; -const char ConnectionPooling[] = "ConnectionPooling"; -const char Database[] = "Database"; -const char Encrypt[] = "Encrypt"; -const char Failover_Partner[] = "Failover_Partner"; -const char LoginTimeout[] = "LoginTimeout"; -const char MARS_Option[] = "MultipleActiveResultSets"; -const char MultiSubnetFailover[] = "MultiSubnetFailover"; -const char QuotedId[] = "QuotedId"; -const char TraceFile[] = "TraceFile"; -const char TraceOn[] = "TraceOn"; -const char TrustServerCertificate[] = "TrustServerCertificate"; -const char TransactionIsolation[] = "TransactionIsolation"; -const char WSID[] = "WSID"; - -} - -enum PDO_CONN_OPTIONS { - - PDO_CONN_OPTION_SERVER = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC, - -}; - -enum PDO_STMT_OPTIONS { - - PDO_STMT_OPTION_ENCODING = SQLSRV_STMT_OPTION_DRIVER_SPECIFIC, - PDO_STMT_OPTION_DIRECT_QUERY, - PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, - PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, - PDO_STMT_OPTION_EMULATE_PREPARES, - PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, -}; - -// List of all the statement options supported by this driver. -const stmt_option PDO_STMT_OPTS[] = { - - { NULL, 0, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, std::unique_ptr( new stmt_option_query_timeout ) }, - { NULL, 0, SQLSRV_STMT_OPTION_SCROLLABLE, std::unique_ptr( new stmt_option_scrollable ) }, - { NULL, 0, PDO_STMT_OPTION_ENCODING, std::unique_ptr( new stmt_option_encoding ) }, - { NULL, 0, PDO_STMT_OPTION_DIRECT_QUERY, std::unique_ptr( new stmt_option_direct_query ) }, - { NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, std::unique_ptr( new stmt_option_cursor_scroll_type ) }, - { NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, std::unique_ptr( new stmt_option_buffered_query_limit ) }, - { NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr( new stmt_option_emulate_prepares ) }, - { NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr( new stmt_option_fetch_numeric ) }, - - { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, -}; - -// boolean connection string -struct pdo_bool_conn_str_func -{ - static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); -}; - -struct pdo_txn_isolation_conn_attr_func -{ - static void func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ); -}; - -template -struct pdo_int_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - - SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "pdo_int_conn_attr_func: Unexpected zval type." ); - - size_t val = static_cast( atoi( Z_STRVAL_P( value )) ); - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( val ), SQL_IS_UINTEGER TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } - } -}; - -template -struct pdo_bool_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( core_str_zval_is_true( value )), - SQL_IS_UINTEGER TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } - } -}; - -// statement options related functions -void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, - zval** data TSRMLS_DC ); -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ); - -} // namespace - - -// List of all connection options supported by this driver. -const connection_option PDO_CONN_OPTS[] = { - { - PDOConnOptionNames::Server, - sizeof( PDOConnOptionNames::Server ), - PDO_CONN_OPTION_SERVER, - NULL, - 0, - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::APP, - sizeof( PDOConnOptionNames::APP ), - SQLSRV_CONN_OPTION_APP, - ODBCConnOptions::APP, - sizeof( ODBCConnOptions::APP ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::ApplicationIntent, - sizeof( PDOConnOptionNames::ApplicationIntent ), - SQLSRV_CONN_OPTION_APPLICATION_INTENT, - ODBCConnOptions::ApplicationIntent, - sizeof( ODBCConnOptions::ApplicationIntent ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::AttachDBFileName, - sizeof( PDOConnOptionNames::AttachDBFileName ), - SQLSRV_CONN_OPTION_ATTACHDBFILENAME, - ODBCConnOptions::AttachDBFileName, - sizeof( ODBCConnOptions::AttachDBFileName ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::ConnectionPooling, - sizeof( PDOConnOptionNames::ConnectionPooling ), - SQLSRV_CONN_OPTION_CONN_POOLING, - ODBCConnOptions::ConnectionPooling, - sizeof( ODBCConnOptions::ConnectionPooling ), - CONN_ATTR_BOOL, - conn_null_func::func - }, - { - PDOConnOptionNames::Database, - sizeof( PDOConnOptionNames::Database ), - SQLSRV_CONN_OPTION_DATABASE, - ODBCConnOptions::Database, - sizeof( ODBCConnOptions::Database ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::Encrypt, - sizeof( PDOConnOptionNames::Encrypt ), - SQLSRV_CONN_OPTION_ENCRYPT, - ODBCConnOptions::Encrypt, - sizeof( ODBCConnOptions::Encrypt ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func - }, - { - PDOConnOptionNames::Failover_Partner, - sizeof( PDOConnOptionNames::Failover_Partner ), - SQLSRV_CONN_OPTION_FAILOVER_PARTNER, - ODBCConnOptions::Failover_Partner, - sizeof( ODBCConnOptions::Failover_Partner ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - PDOConnOptionNames::LoginTimeout, - sizeof( PDOConnOptionNames::LoginTimeout ), - SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, - ODBCConnOptions::LoginTimeout, - sizeof( ODBCConnOptions::LoginTimeout ), - CONN_ATTR_INT, - pdo_int_conn_attr_func::func - }, - { - PDOConnOptionNames::MARS_Option, - sizeof( PDOConnOptionNames::MARS_Option ), - SQLSRV_CONN_OPTION_MARS, - ODBCConnOptions::MARS_ODBC, - sizeof( ODBCConnOptions::MARS_ODBC ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func - }, - { - PDOConnOptionNames::MultiSubnetFailover, - sizeof( PDOConnOptionNames::MultiSubnetFailover ), - SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, - ODBCConnOptions::MultiSubnetFailover, - sizeof( ODBCConnOptions::MultiSubnetFailover ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func - }, - { - PDOConnOptionNames::QuotedId, - sizeof( PDOConnOptionNames::QuotedId ), - SQLSRV_CONN_OPTION_QUOTED_ID, - ODBCConnOptions::QuotedId, - sizeof( ODBCConnOptions::QuotedId ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func - }, - { - PDOConnOptionNames::TraceFile, - sizeof( PDOConnOptionNames::TraceFile ), - SQLSRV_CONN_OPTION_TRACE_FILE, - ODBCConnOptions::TraceFile, - sizeof( ODBCConnOptions::TraceFile ), - CONN_ATTR_STRING, - str_conn_attr_func::func - }, - { - PDOConnOptionNames::TraceOn, - sizeof( PDOConnOptionNames::TraceOn ), - SQLSRV_CONN_OPTION_TRACE_ON, - ODBCConnOptions::TraceOn, - sizeof( ODBCConnOptions::TraceOn ), - CONN_ATTR_BOOL, - pdo_bool_conn_attr_func::func - }, - { - PDOConnOptionNames::TransactionIsolation, - sizeof( PDOConnOptionNames::TransactionIsolation ), - SQLSRV_CONN_OPTION_TRANS_ISOLATION, - ODBCConnOptions::TransactionIsolation, - sizeof( ODBCConnOptions::TransactionIsolation ), - CONN_ATTR_INT, - pdo_txn_isolation_conn_attr_func::func - }, - { - PDOConnOptionNames::TrustServerCertificate, - sizeof( PDOConnOptionNames::TrustServerCertificate ), - SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, - ODBCConnOptions::TrustServerCertificate, - sizeof( ODBCConnOptions::TrustServerCertificate ), - CONN_ATTR_BOOL, - pdo_bool_conn_str_func::func - }, - { - PDOConnOptionNames::WSID, - sizeof( PDOConnOptionNames::WSID ), - SQLSRV_CONN_OPTION_WSID, - ODBCConnOptions::WSID, - sizeof( ODBCConnOptions::WSID ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table -}; - - -// close the connection -int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ); - -// execute queries -int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, - size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ); -zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ); - -// transaction support functions -int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ); -int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ); -int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ); - -// attribute functions -int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ); -int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ); - -// return more information -int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, - zval *info TSRMLS_DC); - -// return the last id generated by an executed SQL statement -char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, size_t* len TSRMLS_DC ); - -// additional methods are supported in this function -pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ); - -// quote a string, meaning put quotes around it and escape any quotes within it -int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquotedlen, char **quoted, size_t* quotedlen, - enum pdo_param_type paramtype TSRMLS_DC ); - -struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { - - pdo_sqlsrv_dbh_close, - pdo_sqlsrv_dbh_prepare, - pdo_sqlsrv_dbh_do, - pdo_sqlsrv_dbh_quote, - pdo_sqlsrv_dbh_begin, - pdo_sqlsrv_dbh_commit, - pdo_sqlsrv_dbh_rollback, - pdo_sqlsrv_dbh_set_attr, - pdo_sqlsrv_dbh_last_id, - pdo_sqlsrv_dbh_return_error, - pdo_sqlsrv_dbh_get_attr, - NULL, // check liveness not implemented - pdo_sqlsrv_get_driver_methods, - NULL, // request shutdown not implemented - NULL // in transaction not implemented -}; - - -// log a function entry point -#define PDO_LOG_DBH_ENTRY \ -{ \ - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ - driver_dbh->set_func( __FUNCTION__ ); \ - LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ -} - -// constructor for the internal object for connections -pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) : - sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ), - stmts( NULL ), - direct_query( false ), - query_timeout( QUERY_TIMEOUT_INVALID ), - client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )), - bind_param_encoding( SQLSRV_ENCODING_CHAR ), - fetch_numeric( false ) -{ - if( client_buffer_max_size < 0 ) { - client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; - LOG( SEV_WARNING, INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE " set to a invalid value. Resetting to default value." ); - } -} - -// pdo_sqlsrv_db_handle_factory -// Maps to PDO::__construct. -// Factory method called by the PDO driver manager to create a SQLSRV PDO connection. -// Does the following things: -// 1.Sets the error handling temporarily to PDO_ERRMODE_EXCEPTION. -// (If an error occurs in this function, the PDO specification mandates that -// an exception be thrown, regardless of the error mode setting.) -// 2. Processes the driver options. -// 3. Creates a core_conn object by calling core_sqlsrv_connect. -// 4. Restores the previous error mode on success. -// alloc_own_columns is set to 1 to tell the PDO driver manager that we manage memory -// Parameters: -// dbh - The PDO managed structure for the connection. -// driver_options - A HashTable (within the zval) of options to use when creating the connection. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) -{ - LOG( SEV_NOTICE, "pdo_sqlsrv_db_handle_factory: entering" ); - - hash_auto_ptr pdo_conn_options_ht; - pdo_error_mode prev_err_mode = dbh->error_mode; - - // must be done in all cases so that even a failed connection can query the - // object for errors. - dbh->methods = &pdo_sqlsrv_dbh_methods; - dbh->driver_data = NULL; - zval* temp_server_z = NULL; - sqlsrv_malloc_auto_ptr dsn_parser; - zval server_z; - ZVAL_UNDEF( &server_z ); - - try { - - // no matter what the error mode, we want exceptions thrown if the connection fails - // to happen (per the PDO spec) - dbh->error_mode = PDO_ERRMODE_EXCEPTION; - - g_henv_cp->set_driver( dbh ); - g_henv_ncp->set_driver( dbh ); - - CHECK_CUSTOM_ERROR( driver_options && Z_TYPE_P( driver_options ) != IS_ARRAY, *g_henv_cp, SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE ) { - throw core::CoreException(); - } - - // Initialize the options array to be passed to the core layer - ALLOC_HASHTABLE( pdo_conn_options_ht ); - - core::sqlsrv_zend_hash_init( *g_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, - ZVAL_INTERNAL_DTOR, 0 /*persistent*/ TSRMLS_CC ); - - // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. - dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_henv_cp, dbh->data_source, - static_cast( dbh->data_source_len ), pdo_conn_options_ht ); - dsn_parser->parse_conn_string( TSRMLS_C ); - - // Extract the server name - temp_server_z = zend_hash_index_find( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER); - - CHECK_CUSTOM_ERROR(( temp_server_z == NULL ), g_henv_cp, PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED ) { - - throw pdo::PDOException(); - } - - server_z = *temp_server_z; - - // Add a reference to the option value since we are deleting it from the hashtable - zval_add_ref( &server_z ); - zend_hash_index_del( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER ); - - sqlsrv_conn* conn = core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, core::allocate_conn, Z_STRVAL( server_z ), - dbh->username, dbh->password, pdo_conn_options_ht, pdo_sqlsrv_handle_dbh_error, - PDO_CONN_OPTS, dbh, "pdo_sqlsrv_db_handle_factory" TSRMLS_CC ); - - // Free the string in server_z after being used - zend_string_release( Z_STR( server_z )); - - SQLSRV_ASSERT( conn != NULL, "Invalid connection returned. Exception should have been thrown." ); - - // set the driver_data and methods to complete creation of the PDO object - dbh->driver_data = conn; - dbh->error_mode = prev_err_mode; // reset the error mode - dbh->alloc_own_columns = 1; // we do our own memory management for columns - dbh->native_case = PDO_CASE_NATURAL;// SQL Server supports mixed case types - - } - catch( core::CoreException& ) { - if ( Z_TYPE( server_z ) == IS_STRING ) { - zend_string_release( Z_STR( server_z )); - } - dbh->error_mode = prev_err_mode; // reset the error mode - g_henv_cp->last_error().reset(); // reset the last error; callee will check if last_error exist before freeing it and setting it to NULL - return 0; - } - catch( ... ) { - - DIE( "pdo_sqlsrv_db_handle_factory: Unknown exception caught" ); - } - - return 1; -} - -// pdo_sqlsrv_dbh_close -// Maps to PDO::__destruct. -// Called when a PDO object is to be destroyed. -// By the time this function is called, PDO has already made sure that -// all statements are disposed and the PDO object is the last item destroyed. -// Parameters: -// dbh - The PDO managed connection object. -// Return: -// Always returns 1 for success. -int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ) -{ - LOG( SEV_NOTICE, "pdo_sqlsrv_dbh_close: entering" ); - - // if the connection didn't complete properly, driver_data isn't initialized. - if( dbh->driver_data == NULL ) { - - return 1; - } - - PDO_RESET_DBH_ERROR; - - // call the core layer close - core_sqlsrv_close( reinterpret_cast( dbh->driver_data ) TSRMLS_CC ); - dbh->driver_data = NULL; - - // always return success that the connection is closed - return 1; -} - -// pdo_sqlsrv_dbh_prepare -// Called by PDO::prepare and PDOStatement::__construct. -// Creates a statement and prepares it for execution by PDO -// Paramters: -// dbh - The PDO managed connection object. -// sql - SQL query to be prepared. -// sql_len - Length of the sql query -// stmt - The PDO managed statement object. -// driver_options - User provided list of statement options. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, - size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - hash_auto_ptr pdo_stmt_options_ht; - sqlsrv_malloc_auto_ptr sql_rewrite; - size_t sql_rewrite_len = 0; - sqlsrv_malloc_auto_ptr driver_stmt; - - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); - SQLSRV_ASSERT(( driver_dbh != NULL ), "pdo_sqlsrv_dbh_prepare: dbh->driver_data was null"); - - try { - - // assign the methods for the statement object. This is necessary even if the - // statement fails so the user can retrieve the error information. - stmt->methods = &pdo_sqlsrv_stmt_methods; - stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; // we support parameterized queries with ?, not names - - // Initialize the options array to be passed to the core layer - ALLOC_HASHTABLE( pdo_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); - - // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. - validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht TSRMLS_CC ); - - driver_stmt = static_cast( core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, - pdo_stmt_options_ht, PDO_STMT_OPTS, - pdo_sqlsrv_handle_stmt_error, stmt TSRMLS_CC )); - - // if the user didn't set anything in the prepare options, then set the buffer limit - // to the value set on the connection. - if( driver_stmt->buffered_query_limit== sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) { - - driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size; - } - - // if the user didn't set anything in the prepare options, then set the query timeout - // to the value set on the connection. - if(( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ) && ( driver_dbh->query_timeout != QUERY_TIMEOUT_INVALID )) { - - core_sqlsrv_set_query_timeout( driver_stmt, driver_dbh->query_timeout TSRMLS_CC ); - } - - // rewrite named parameters in the query to positional parameters if we aren't letting PDO do the - // parameter substitution for us - if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { - - // rewrite the query to map named parameters to positional parameters. We do this rather than use the ODBC named - // parameters for consistency with the PDO MySQL and PDO ODBC drivers. - int zr = pdo_subst_named_params( stmt, const_cast( sql ), sql_len, &sql_rewrite, &sql_rewrite_len TSRMLS_CC ); - CHECK_ZEND_ERROR( zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE ) { - throw core::CoreException(); - } - // if parameter substitution happened, use that query instead of the original - if( sql_rewrite != NULL ) { - sql = sql_rewrite; - sql_len = sql_rewrite_len; - } - } - - if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { - - core_sqlsrv_prepare( driver_stmt, sql, sql_len TSRMLS_CC ); - } - else if( driver_stmt->direct_query ) { - - if( driver_stmt->direct_query_subst_string ) { - // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme - // and we use estrdup below to allocate the new string, which uses emalloc - efree( reinterpret_cast( const_cast( driver_stmt->direct_query_subst_string ))); - } - driver_stmt->direct_query_subst_string = estrdup( sql ); - driver_stmt->direct_query_subst_string_len = sql_len; - } - // else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be - // set to the substituted query - - - stmt->driver_data = driver_stmt; - driver_stmt.transferred(); - } - // everything is cleaned up by this point - // catch everything so the exception doesn't spill into the calling PDO code - catch( core::CoreException& ) { - - if( driver_stmt ) { - - driver_stmt->~pdo_sqlsrv_stmt(); - } - - // in the event that the statement caused an error that was copied to the connection, update the - // connection with the error's SQLSTATE. - if( driver_dbh->last_error() ) { - - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), - reinterpret_cast( driver_dbh->last_error()->sqlstate )); - } - - return 0; - } - - // catch any errant exception and die - catch(...) { - - DIE( "pdo_sqlsrv_dbh_prepare: Unknown exception caught." ); - } - - return 1; -} - - -// pdo_sqlsrv_dbh_do -// Maps to PDO::exec. -// Execute a SQL statement, such as an insert, update or delete, and return -// the number of rows affected. -// Parameters: -// dbh - the PDO connection object, which contains the ODBC handle -// sql - the query to execute -// sql_len - length of sql query -// Return -// # of rows affected, -1 for an error. -zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); - - sqlsrv_malloc_auto_ptr driver_stmt; - SQLLEN rows = 0; - - // verify that the data type sizes are the same. If we ever upgrade to 64 bit we don't want the wrong - // thing to happen here. - SQLSRV_STATIC_ASSERT( sizeof( rows ) == sizeof( SQLLEN )); - - try { - - SQLSRV_ASSERT( sql != NULL, "NULL or empty SQL string passed." ); - - // temp PDO statement used for error handling if something happens - pdo_stmt_t temp_stmt; - temp_stmt.dbh = dbh; - // allocate a full driver statement to take advantage of the error handling - driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, - NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); - driver_stmt->set_func( __FUNCTION__ ); - - core_sqlsrv_execute( driver_stmt TSRMLS_CC, sql, static_cast( sql_len ) ); - - // since the user can give us a compound statement, we return the row count for the last set, and since the row count - // isn't guaranteed to be valid until all the results have been fetched, we fetch them all first. - if( core_sqlsrv_has_any_result( driver_stmt TSRMLS_CC )) { - - SQLRETURN r = SQL_SUCCESS; - - do { - - rows = core::SQLRowCount( driver_stmt TSRMLS_CC ); - - r = core::SQLMoreResults( driver_stmt TSRMLS_CC ); - - } while( r != SQL_NO_DATA ); - } - - // returning -1 forces PDO to return false, which signals an error occurred. SQLRowCount returns -1 for a number of cases - // naturally, so we override that here with no rows returned. - if( rows == -1 ) { - rows = 0; - } - } - catch( core::CoreException& ) { - - // copy any errors on the statement to the connection so that the user sees them, since the statement is released - // before this method returns - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), - reinterpret_cast( driver_stmt->last_error()->sqlstate )); - driver_dbh->set_last_error( driver_stmt->last_error() ); - - if( driver_stmt ) { - driver_stmt->~sqlsrv_stmt(); - } - - return -1; - } - catch( ... ) { - - DIE( "pdo_sqlsrv_dbh_do: Unknown exception caught." ); - } - - if( driver_stmt ) { - driver_stmt->~sqlsrv_stmt(); - } - - return rows; -} - - -// transaction support functions - -// pdo_sqlsrv_dbh_begin -// Maps to PDO::beginTransaction. -// Begins a transaction. Turns off auto-commit mode. The pdo_dbh_t::in_txn -// flag is maintained by PDO so we dont have to worry about it. -// Parameters: -// dbh - The PDO managed connection object. -// Return: -// 0 for failure and 1 for success. -int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - try { - - SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_begin: pdo_dbh_t object was null" ); - - sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); - - SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_begin: driver_data object was null" ); - - DEBUG_SQLSRV_ASSERT( !dbh->in_txn, "pdo_sqlsrv_dbh_begin: Already in transaction" ); - - core_sqlsrv_begin_transaction( driver_conn TSRMLS_CC ); - - return 1; - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE ("pdo_sqlsrv_dbh_begin: Uncaught exception occurred."); - } -} - - - -// pdo_sqlsrv_dbh_commit -// Maps to PDO::commit. -// Commits a transaction. Returns the connection to auto-commit mode. -// PDO throws error if PDO::commit is called on a connection that is not in an active -// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have -// to worry about it here. -// Parameters: -// dbh - The PDO managed connection object. -// Return: -// 0 for failure and 1 for success. -int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - try { - - SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_commit: pdo_dbh_t object was null" ); - - sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); - - SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_commit: driver_data object was null" ); - - DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_commit: Not in transaction" ); - - core_sqlsrv_commit( driver_conn TSRMLS_CC ); - - return 1; - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE ("pdo_sqlsrv_dbh_commit: Uncaught exception occurred."); - } -} - -// pdo_sqlsrv_dbh_rollback -// Maps to PDO::rollback. -// Rolls back a transaction. Returns the connection in auto-commit mode. -// PDO throws error if PDO::rollBack is called on a connection that is not in an active -// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have -// to worry about it here. -// Parameters: -// dbh - The PDO managed connection object. -// Return: -// 0 for failure and 1 for success. -int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - try { - SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_rollback: pdo_dbh_t object was null" ); - - sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); - - SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_rollback: driver_data object was null" ); - - DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_rollback: Not in transaction" ); - - core_sqlsrv_rollback( driver_conn TSRMLS_CC ); - - return 1; - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE ("pdo_sqlsrv_dbh_rollback: Uncaught exception occurred."); - } -} - -// pdo_sqlsrv_dbh_set_attr -// Maps to PDO::setAttribute. Sets an attribute on the PDO connection object. -// PDO driver manager calls this function directly after calling the factory -// method for PDO, for any attribute which is specified in the PDO constructor. -// Parameters: -// dbh - The PDO connection object maintained by PDO. -// attr - The attribute to be set. -// val - The value of the attribute to be set. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); - - try { - - switch( attr ) { - - case SQLSRV_ATTR_ENCODING: - { - zend_long attr_value; - if( Z_TYPE_P( val ) != IS_LONG ) { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); - } - attr_value = Z_LVAL_P( val ); - switch( attr_value ) { - - case SQLSRV_ENCODING_DEFAULT: - // when default is applied to a connection, that means use UTF-8 encoding - driver_dbh->set_encoding( SQLSRV_ENCODING_UTF8 ); - break; - case SQLSRV_ENCODING_SYSTEM: - case SQLSRV_ENCODING_UTF8: - driver_dbh->set_encoding( static_cast( attr_value )); - break; - default: - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); - break; - } - } - break; - - case SQLSRV_ATTR_DIRECT_QUERY: - driver_dbh->direct_query = ( zend_is_true( val ) ) ? true : false; - break; - - case SQLSRV_ATTR_QUERY_TIMEOUT: - if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) < 0 ) { - convert_to_string( val ); - THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( val )); - } - driver_dbh->query_timeout = static_cast( Z_LVAL_P( val ) ); - break; - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) <= 0 ) { - convert_to_string( val ); - THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, Z_STRVAL_P( val )); - } - driver_dbh->client_buffer_max_size = Z_LVAL_P( val ); - break; - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - driver_dbh->fetch_numeric = (zend_is_true(val)) ? true : false; - break; - - // Not supported - case PDO_ATTR_FETCH_TABLE_NAMES: - case PDO_ATTR_FETCH_CATALOG_NAMES: - case PDO_ATTR_PREFETCH: - case PDO_ATTR_MAX_COLUMN_LEN: - case PDO_ATTR_CURSOR_NAME: - case PDO_ATTR_AUTOCOMMIT: - case PDO_ATTR_PERSISTENT: - case PDO_ATTR_TIMEOUT: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ); - } - - // Read-only - case PDO_ATTR_SERVER_VERSION: - case PDO_ATTR_SERVER_INFO: - case PDO_ATTR_CLIENT_VERSION: - case PDO_ATTR_DRIVER_NAME: - case PDO_ATTR_CONNECTION_STATUS: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR ); - } - - // Statement level only - case PDO_ATTR_EMULATE_PREPARES: - case PDO_ATTR_CURSOR: - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); - } - - default: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); - break; - } - } - } - catch( pdo::PDOException& ) { - - return 0; - } - - return 1; -} - - -// pdo_sqlsrv_dbh_get_attr -// Maps to PDO::getAttribute. Gets an attribute on the PDO connection object. -// Parameters: -// dbh - The PDO connection object maintained by PDO. -// attr - The attribute to get. -// return_value - zval in which to return the attribute value. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); - - try { - - switch( attr ) { - - // Not supported - case PDO_ATTR_FETCH_TABLE_NAMES: - case PDO_ATTR_FETCH_CATALOG_NAMES: - case PDO_ATTR_PREFETCH: - case PDO_ATTR_MAX_COLUMN_LEN: - case PDO_ATTR_CURSOR_NAME: - case PDO_ATTR_AUTOCOMMIT: - case PDO_ATTR_TIMEOUT: - { - // PDO does not throw "not supported" error message for these attributes. - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ); - } - - // Statement level only - case PDO_ATTR_EMULATE_PREPARES: - case PDO_ATTR_CURSOR: - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); - } - - case PDO_ATTR_STRINGIFY_FETCHES: - { - // For this attribute, if we dont set the return_value than PDO returns NULL. - ZVAL_BOOL(return_value, ( dbh->stringify ? 1 : 0 ) ); - break; - } - - case PDO_ATTR_SERVER_INFO: - { - core_sqlsrv_get_server_info( driver_dbh, return_value TSRMLS_CC ); - break; - } - - case PDO_ATTR_SERVER_VERSION: - { - core_sqlsrv_get_server_version( driver_dbh, return_value TSRMLS_CC ); - break; - } - - case PDO_ATTR_CLIENT_VERSION: - { - core_sqlsrv_get_client_info( driver_dbh, return_value TSRMLS_CC ); - - //Add the PDO SQLSRV driver's file version - core::sqlsrv_add_assoc_string( *driver_dbh, return_value, "ExtensionVer", VER_FILEVERSION_STR, 1 /*duplicate*/ - TSRMLS_CC ); - break; - } - - case SQLSRV_ATTR_ENCODING: - { - ZVAL_LONG( return_value, driver_dbh->encoding() ); - break; - } - - case SQLSRV_ATTR_QUERY_TIMEOUT: - { - ZVAL_LONG( return_value, ( driver_dbh->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_dbh->query_timeout )); - break; - } - - case SQLSRV_ATTR_DIRECT_QUERY: - { - ZVAL_BOOL( return_value, driver_dbh->direct_query ); - break; - } - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - { - ZVAL_LONG( return_value, driver_dbh->client_buffer_max_size ); - break; - } - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - { - ZVAL_BOOL( return_value, driver_dbh->fetch_numeric ); - break; - } - - default: - { - THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); - break; - } - } - - return 1; - } - catch( core::CoreException& ) { - return 0; - } -} - -// Called by PDO::errorInfo and PDOStatement::errorInfo. -// Returns the error info. -// Parameters: -// dbh - The PDO managed connection object. -// stmt - The PDO managed statement object. -// info - zval in which to return the error info. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, - zval *info TSRMLS_DC) -{ - SQLSRV_ASSERT( dbh != NULL || stmt != NULL, "Either dbh or stmt must not be NULL to dereference the error." ); - - sqlsrv_error* ctx_error = NULL; - if( stmt ) { - ctx_error = static_cast( stmt->driver_data )->last_error(); - } - else { - ctx_error = static_cast( dbh->driver_data )->last_error(); - } - - pdo_sqlsrv_retrieve_context_error( ctx_error, info ); - - return 1; -} - -// pdo_sqlsrv_dbh_last_id -// Maps to PDO::lastInsertId. -// Returns the last id generated by an executed SQL statement -// Parameters: -// dbh - The PDO managed connection object. -// name - Table name. -// len - Length of the name. -// Return: -// Returns the last insert id as a string. -char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, _Out_ size_t* len TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - // turn off any error handling for last_id - pdo_error_mode prev_err_mode = dbh->error_mode; - dbh->error_mode = PDO_ERRMODE_SILENT; - - sqlsrv_malloc_auto_ptr driver_stmt; - - pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); - - sqlsrv_malloc_auto_ptr id_str; - id_str = reinterpret_cast( sqlsrv_malloc( LAST_INSERT_ID_BUFF_LEN )); - - try { - - char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ]; - if( name == NULL ) { - strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY ); - } - else { - char* quoted_table = NULL; - size_t quoted_len = 0; - int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strlen( name ), "ed_table, "ed_len, PDO_PARAM_NULL TSRMLS_CC ); - SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name." ); - sprintf_s( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, TABLE_LAST_INSERT_ID_QUERY, quoted_table ); - sqlsrv_free( quoted_table ); - } - - // temp PDO statement used for error handling if something happens - pdo_stmt_t temp_stmt; - temp_stmt.dbh = dbh; - - // allocate a full driver statement to take advantage of the error handling - driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, - NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); - driver_stmt->set_func( __FUNCTION__ ); - - // execute the last insert id query - core::SQLExecDirect( driver_stmt, last_insert_id_query TSRMLS_CC ); - - core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); - SQLRETURN r = core::SQLGetData( driver_stmt, 1, SQL_C_CHAR, id_str, LAST_INSERT_ID_BUFF_LEN, - reinterpret_cast( len ), false TSRMLS_CC ); - - CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt, - PDO_SQLSRV_ERROR_LAST_INSERT_ID ) { - throw core::CoreException(); - } - - driver_stmt->~sqlsrv_stmt(); - } - catch( core::CoreException& ) { - - // copy any errors on the statement to the connection so that the user sees them, since the statement is released - // before this method returns - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), - reinterpret_cast( driver_stmt->last_error()->sqlstate )); - driver_dbh->set_last_error( driver_stmt->last_error() ); - - if( driver_stmt ) { - driver_stmt->~sqlsrv_stmt(); - } - - strcpy_s( id_str.get(), 1, "" ); - *len = 0; - } - - char* ret_id_str = id_str.get(); - id_str.transferred(); - - // restore error handling to its previous mode - dbh->error_mode = prev_err_mode; - - return ret_id_str; -} - -// pdo_sqlsrv_dbh_quote -// Maps to PDO::quote. As the name says, this function quotes a string. -// Always returns a valid string unless memory allocation fails. -// Parameters: -// dbh - The PDO managed connection object. -// unquoted - The unquoted string to be quoted. -// unquoted_len - Length of the unquoted string. -// quoted - Buffer for output string. -// quoted_len - Length of the output string. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquoted_len, char **quoted, size_t* quoted_len, - enum pdo_param_type /*paramtype*/ TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); - SQLSRV_ENCODING encoding = driver_dbh->bind_param_encoding; - - if ( encoding == SQLSRV_ENCODING_BINARY ) { - // convert from char* to hex digits using os - std::basic_ostringstream os; - for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { - os << std::hex << ( int )unquoted[index]; - } - std::basic_string str_hex = os.str(); - // each character is represented by 2 digits of hex - size_t unquoted_str_len = unquoted_len * 2; // length returned should not account for null terminator - char* unquoted_str = reinterpret_cast( sqlsrv_malloc( unquoted_str_len, sizeof( char ), 1 )); // include space for null terminator - strcpy_s( unquoted_str, unquoted_str_len + 1 /* include null terminator*/, str_hex.c_str()); - // include length of '0x' in the binary string - *quoted_len = unquoted_str_len + 2; - *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); - unsigned int out_current = 0; - // insert '0x' - ( *quoted )[out_current++] = '0'; - ( *quoted )[out_current++] = 'x'; - for ( size_t index = 0; index < unquoted_str_len && unquoted_str[index] != '\0'; ++index ) { - ( *quoted )[out_current++] = unquoted_str[index]; - } - // null terminator - ( *quoted )[out_current] = '\0'; - sqlsrv_free( unquoted_str ); - return 1; - } - else { - // count the number of quotes needed - unsigned int quotes_needed = 2; // the initial start and end quotes of course - // include the N proceeding the initial quote if encoding is UTF8 - if ( encoding == SQLSRV_ENCODING_UTF8 ) { - quotes_needed = 3; - } - - for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { - if ( unquoted[index] == '\'' ) { - ++quotes_needed; - } - } - - *quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator. - *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); // include space for null terminator. - unsigned int out_current = 0; - - // insert N if the encoding is UTF8 - if ( encoding == SQLSRV_ENCODING_UTF8 ) { - ( *quoted )[out_current++] = 'N'; - } - // insert initial quote - ( *quoted )[out_current++] = '\''; - - for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { - - if ( unquoted[index] == '\'' ) { - ( *quoted )[out_current++] = '\''; - ( *quoted )[out_current++] = '\''; - } - else { - ( *quoted )[out_current++] = unquoted[index]; - } - } - - // trailing quote and null terminator - ( *quoted )[out_current++] = '\''; - ( *quoted )[out_current] = '\0'; - - return 1; - } -} - -// This method is not implemented by this driver. -pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ) -{ - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; - PDO_LOG_DBH_ENTRY; - - sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); - CHECK_CUSTOM_ERROR( true, driver_conn, PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED ) { - return NULL; - } - - return NULL; // to avoid a compiler warning -} - -namespace { - -// Maps the PDO driver specific statement option/attribute constants to the core layer -// statement option/attribute constants. -void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, - zval* data TSRMLS_DC ) -{ - zend_ulong option_key = -1; - switch( key ) { - - case PDO_ATTR_CURSOR: - option_key = SQLSRV_STMT_OPTION_SCROLLABLE; - break; - - case SQLSRV_ATTR_ENCODING: - option_key = PDO_STMT_OPTION_ENCODING; - break; - - case SQLSRV_ATTR_QUERY_TIMEOUT: - option_key = SQLSRV_STMT_OPTION_QUERY_TIMEOUT; - break; - - case PDO_ATTR_STATEMENT_CLASS: - break; - - case SQLSRV_ATTR_DIRECT_QUERY: - option_key = PDO_STMT_OPTION_DIRECT_QUERY; - break; - - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - option_key = PDO_STMT_OPTION_CURSOR_SCROLL_TYPE; - break; - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - option_key = PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE; - break; - - case PDO_ATTR_EMULATE_PREPARES: - option_key = PDO_STMT_OPTION_EMULATE_PREPARES; - break; - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; - - default: - CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { - throw core::CoreException(); - } - break; - } - - // if a PDO handled option makes it through (such as PDO_ATTR_STATEMENT_CLASS, just skip it - if( option_key != -1 ) { - zval_add_ref( data ); - core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data TSRMLS_CC ); - } -} - - -// validate_stmt_options -// Iterates through the list of statement options provided by the user and validates them -// against the list of statement options provided by this driver. After validation -// creates a Hashtable of statement options to be sent to the core layer for processing. -// Parameters: -// ctx - The current context. -// stmt_options - The user provided list of statement options. -// pdo_stmt_options_ht - Output hashtable of statement options. -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ) -{ - try { - - if( stmt_options ) { - - HashTable* options_ht = Z_ARRVAL_P( stmt_options ); - size_t int_key = -1; - zend_string *key = NULL; - zval* data = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - int result = 0; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_LONG ), ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { - throw core::CoreException(); - } - - add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - } - } - catch( core::CoreException& ) { - - throw; - } -} - - -void pdo_bool_conn_str_func::func(connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ) -{ - TSRMLS_C; - char const* val_str = "no"; - - if( core_str_zval_is_true( value ) ) { - - val_str = "yes"; - } - - conn_str += option->odbc_name; - conn_str += "={"; - conn_str += val_str; - conn_str += "};"; -} - -void pdo_txn_isolation_conn_attr_func::func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, - std::string& /*conn_str*/ TSRMLS_DC ) -{ - try { - - SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "pdo_txn_isolation_conn_attr_func: Unexpected zval type." ); - const char* val = Z_STRVAL_P( value_z ); - size_t val_len = Z_STRLEN_P( value_z ); - zend_long out_val = SQL_TXN_READ_COMMITTED; - - // READ_COMMITTED - if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_COMMITTED ) - 1 ) - && !_stricmp( val, PDOTxnIsolationValues::READ_COMMITTED ))) { - - out_val = SQL_TXN_READ_COMMITTED; - } - - // READ_UNCOMMITTED - else if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_UNCOMMITTED ) - 1 ) - && !_stricmp( val, PDOTxnIsolationValues::READ_UNCOMMITTED ))) { - - out_val = SQL_TXN_READ_UNCOMMITTED; - } - - // REPEATABLE_READ - else if(( val_len == ( sizeof( PDOTxnIsolationValues::REPEATABLE_READ ) - 1 ) - && !_stricmp( val, PDOTxnIsolationValues::REPEATABLE_READ ))) { - - out_val = SQL_TXN_REPEATABLE_READ; - } - - // SERIALIZABLE - else if(( val_len == ( sizeof( PDOTxnIsolationValues::SERIALIZABLE ) - 1 ) - && !_stricmp( val, PDOTxnIsolationValues::SERIALIZABLE ))) { - - out_val = SQL_TXN_SERIALIZABLE; - } - - // SNAPSHOT - else if(( val_len == ( sizeof( PDOTxnIsolationValues::SNAPSHOT ) - 1 ) - && !_stricmp( val, PDOTxnIsolationValues::SNAPSHOT ))) { - - out_val = SQL_TXN_SS_SNAPSHOT; - } - - else { - - CHECK_CUSTOM_ERROR( true, conn, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, PDOConnOptionNames::TransactionIsolation ) { - - throw core::CoreException(); - } - } - - core::SQLSetConnectAttr( conn, SQL_COPT_SS_TXN_ISOLATION, reinterpret_cast( out_val ), SQL_IS_UINTEGER TSRMLS_CC ); - - } - catch( core::CoreException& ) { - - throw; - } -} - -} // namespace +//--------------------------------------------------------------------------------------------------------------------------------- +// file: pdo_dbh.cpp +// +// Contents: Implements the PDO object for PDO_SQLSRV +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_pdo_sqlsrv.h" + +#include +#include + +typedef const zend_function_entry pdo_sqlsrv_function_entry; + +// *** internal variables and constants *** + +namespace { + +const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;"; +const size_t LAST_INSERT_ID_BUFF_LEN = 10; // size of the buffer to hold the string value of the last insert id integer +const char TABLE_LAST_INSERT_ID_QUERY[] = "SELECT IDENT_CURRENT(%s)"; +const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( TABLE_LAST_INSERT_ID_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes + +// List of PDO supported connection options. +namespace PDOConnOptionNames { + +const char Server[] = "Server"; +const char APP[] = "APP"; +const char ApplicationIntent[] = "ApplicationIntent"; +const char AttachDBFileName[] = "AttachDbFileName"; +const char ConnectionPooling[] = "ConnectionPooling"; +const char Database[] = "Database"; +const char Encrypt[] = "Encrypt"; +const char Failover_Partner[] = "Failover_Partner"; +const char LoginTimeout[] = "LoginTimeout"; +const char MARS_Option[] = "MultipleActiveResultSets"; +const char MultiSubnetFailover[] = "MultiSubnetFailover"; +const char QuotedId[] = "QuotedId"; +const char TraceFile[] = "TraceFile"; +const char TraceOn[] = "TraceOn"; +const char TrustServerCertificate[] = "TrustServerCertificate"; +const char TransactionIsolation[] = "TransactionIsolation"; +const char WSID[] = "WSID"; + +} + +enum PDO_CONN_OPTIONS { + + PDO_CONN_OPTION_SERVER = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC, + +}; + +enum PDO_STMT_OPTIONS { + + PDO_STMT_OPTION_ENCODING = SQLSRV_STMT_OPTION_DRIVER_SPECIFIC, + PDO_STMT_OPTION_DIRECT_QUERY, + PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, + PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, + PDO_STMT_OPTION_EMULATE_PREPARES, + PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, +}; + +// List of all the statement options supported by this driver. +const stmt_option PDO_STMT_OPTS[] = { + + { NULL, 0, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, std::unique_ptr( new stmt_option_query_timeout ) }, + { NULL, 0, SQLSRV_STMT_OPTION_SCROLLABLE, std::unique_ptr( new stmt_option_scrollable ) }, + { NULL, 0, PDO_STMT_OPTION_ENCODING, std::unique_ptr( new stmt_option_encoding ) }, + { NULL, 0, PDO_STMT_OPTION_DIRECT_QUERY, std::unique_ptr( new stmt_option_direct_query ) }, + { NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, std::unique_ptr( new stmt_option_cursor_scroll_type ) }, + { NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, std::unique_ptr( new stmt_option_buffered_query_limit ) }, + { NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr( new stmt_option_emulate_prepares ) }, + { NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr( new stmt_option_fetch_numeric ) }, + + { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, +}; + +// boolean connection string +struct pdo_bool_conn_str_func +{ + static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); +}; + +struct pdo_txn_isolation_conn_attr_func +{ + static void func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ); +}; + +template +struct pdo_int_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + + SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "pdo_int_conn_attr_func: Unexpected zval type." ); + + size_t val = static_cast( atoi( Z_STRVAL_P( value )) ); + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( val ), SQL_IS_UINTEGER TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +template +struct pdo_bool_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( core_str_zval_is_true( value )), + SQL_IS_UINTEGER TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +// statement options related functions +void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, + zval** data TSRMLS_DC ); +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ); + +} // namespace + + +// List of all connection options supported by this driver. +const connection_option PDO_CONN_OPTS[] = { + { + PDOConnOptionNames::Server, + sizeof( PDOConnOptionNames::Server ), + PDO_CONN_OPTION_SERVER, + NULL, + 0, + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::APP, + sizeof( PDOConnOptionNames::APP ), + SQLSRV_CONN_OPTION_APP, + ODBCConnOptions::APP, + sizeof( ODBCConnOptions::APP ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::ApplicationIntent, + sizeof( PDOConnOptionNames::ApplicationIntent ), + SQLSRV_CONN_OPTION_APPLICATION_INTENT, + ODBCConnOptions::ApplicationIntent, + sizeof( ODBCConnOptions::ApplicationIntent ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::AttachDBFileName, + sizeof( PDOConnOptionNames::AttachDBFileName ), + SQLSRV_CONN_OPTION_ATTACHDBFILENAME, + ODBCConnOptions::AttachDBFileName, + sizeof( ODBCConnOptions::AttachDBFileName ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::ConnectionPooling, + sizeof( PDOConnOptionNames::ConnectionPooling ), + SQLSRV_CONN_OPTION_CONN_POOLING, + ODBCConnOptions::ConnectionPooling, + sizeof( ODBCConnOptions::ConnectionPooling ), + CONN_ATTR_BOOL, + conn_null_func::func + }, + { + PDOConnOptionNames::Database, + sizeof( PDOConnOptionNames::Database ), + SQLSRV_CONN_OPTION_DATABASE, + ODBCConnOptions::Database, + sizeof( ODBCConnOptions::Database ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::Encrypt, + sizeof( PDOConnOptionNames::Encrypt ), + SQLSRV_CONN_OPTION_ENCRYPT, + ODBCConnOptions::Encrypt, + sizeof( ODBCConnOptions::Encrypt ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::Failover_Partner, + sizeof( PDOConnOptionNames::Failover_Partner ), + SQLSRV_CONN_OPTION_FAILOVER_PARTNER, + ODBCConnOptions::Failover_Partner, + sizeof( ODBCConnOptions::Failover_Partner ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + PDOConnOptionNames::LoginTimeout, + sizeof( PDOConnOptionNames::LoginTimeout ), + SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, + ODBCConnOptions::LoginTimeout, + sizeof( ODBCConnOptions::LoginTimeout ), + CONN_ATTR_INT, + pdo_int_conn_attr_func::func + }, + { + PDOConnOptionNames::MARS_Option, + sizeof( PDOConnOptionNames::MARS_Option ), + SQLSRV_CONN_OPTION_MARS, + ODBCConnOptions::MARS_ODBC, + sizeof( ODBCConnOptions::MARS_ODBC ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::MultiSubnetFailover, + sizeof( PDOConnOptionNames::MultiSubnetFailover ), + SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, + ODBCConnOptions::MultiSubnetFailover, + sizeof( ODBCConnOptions::MultiSubnetFailover ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::QuotedId, + sizeof( PDOConnOptionNames::QuotedId ), + SQLSRV_CONN_OPTION_QUOTED_ID, + ODBCConnOptions::QuotedId, + sizeof( ODBCConnOptions::QuotedId ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::TraceFile, + sizeof( PDOConnOptionNames::TraceFile ), + SQLSRV_CONN_OPTION_TRACE_FILE, + ODBCConnOptions::TraceFile, + sizeof( ODBCConnOptions::TraceFile ), + CONN_ATTR_STRING, + str_conn_attr_func::func + }, + { + PDOConnOptionNames::TraceOn, + sizeof( PDOConnOptionNames::TraceOn ), + SQLSRV_CONN_OPTION_TRACE_ON, + ODBCConnOptions::TraceOn, + sizeof( ODBCConnOptions::TraceOn ), + CONN_ATTR_BOOL, + pdo_bool_conn_attr_func::func + }, + { + PDOConnOptionNames::TransactionIsolation, + sizeof( PDOConnOptionNames::TransactionIsolation ), + SQLSRV_CONN_OPTION_TRANS_ISOLATION, + ODBCConnOptions::TransactionIsolation, + sizeof( ODBCConnOptions::TransactionIsolation ), + CONN_ATTR_INT, + pdo_txn_isolation_conn_attr_func::func + }, + { + PDOConnOptionNames::TrustServerCertificate, + sizeof( PDOConnOptionNames::TrustServerCertificate ), + SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, + ODBCConnOptions::TrustServerCertificate, + sizeof( ODBCConnOptions::TrustServerCertificate ), + CONN_ATTR_BOOL, + pdo_bool_conn_str_func::func + }, + { + PDOConnOptionNames::WSID, + sizeof( PDOConnOptionNames::WSID ), + SQLSRV_CONN_OPTION_WSID, + ODBCConnOptions::WSID, + sizeof( ODBCConnOptions::WSID ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table +}; + + +// close the connection +int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ); + +// execute queries +int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, + size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ); +zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ); + +// transaction support functions +int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ); +int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ); +int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ); + +// attribute functions +int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ); +int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ); + +// return more information +int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, + zval *info TSRMLS_DC); + +// return the last id generated by an executed SQL statement +char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, size_t* len TSRMLS_DC ); + +// additional methods are supported in this function +pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ); + +// quote a string, meaning put quotes around it and escape any quotes within it +int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquotedlen, char **quoted, size_t* quotedlen, + enum pdo_param_type paramtype TSRMLS_DC ); + +struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { + + pdo_sqlsrv_dbh_close, + pdo_sqlsrv_dbh_prepare, + pdo_sqlsrv_dbh_do, + pdo_sqlsrv_dbh_quote, + pdo_sqlsrv_dbh_begin, + pdo_sqlsrv_dbh_commit, + pdo_sqlsrv_dbh_rollback, + pdo_sqlsrv_dbh_set_attr, + pdo_sqlsrv_dbh_last_id, + pdo_sqlsrv_dbh_return_error, + pdo_sqlsrv_dbh_get_attr, + NULL, // check liveness not implemented + pdo_sqlsrv_get_driver_methods, + NULL, // request shutdown not implemented + NULL // in transaction not implemented +}; + + +// log a function entry point +#ifndef _WIN32 +#define PDO_LOG_DBH_ENTRY \ +{ \ + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ + driver_dbh->set_func( __FUNCTION__ ); \ + int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ + char func[length+1]; \ + strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ + strcat_s( func, length+1, ": entering" ); \ + LOG( SEV_NOTICE, func ); \ +} +#else +#define PDO_LOG_DBH_ENTRY \ +{ \ + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ + driver_dbh->set_func( __FUNCTION__ ); \ + LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ +} +#endif + +// constructor for the internal object for connections +pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) : + sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ), + stmts( NULL ), + direct_query( false ), + query_timeout( QUERY_TIMEOUT_INVALID ), + client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )), + bind_param_encoding( SQLSRV_ENCODING_CHAR ), + fetch_numeric( false ) +{ + if( client_buffer_max_size < 0 ) { + client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; + LOG( SEV_WARNING, INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE " set to a invalid value. Resetting to default value." ); + } +} + +// pdo_sqlsrv_db_handle_factory +// Maps to PDO::__construct. +// Factory method called by the PDO driver manager to create a SQLSRV PDO connection. +// Does the following things: +// 1.Sets the error handling temporarily to PDO_ERRMODE_EXCEPTION. +// (If an error occurs in this function, the PDO specification mandates that +// an exception be thrown, regardless of the error mode setting.) +// 2. Processes the driver options. +// 3. Creates a core_conn object by calling core_sqlsrv_connect. +// 4. Restores the previous error mode on success. +// alloc_own_columns is set to 1 to tell the PDO driver manager that we manage memory +// Parameters: +// dbh - The PDO managed structure for the connection. +// driver_options - A HashTable (within the zval) of options to use when creating the connection. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) +{ + LOG( SEV_NOTICE, "pdo_sqlsrv_db_handle_factory: entering" ); + + hash_auto_ptr pdo_conn_options_ht; + pdo_error_mode prev_err_mode = dbh->error_mode; + + // must be done in all cases so that even a failed connection can query the + // object for errors. + dbh->methods = &pdo_sqlsrv_dbh_methods; + dbh->driver_data = NULL; + zval* temp_server_z = NULL; + sqlsrv_malloc_auto_ptr dsn_parser; + zval server_z; + ZVAL_UNDEF( &server_z ); + + try { + + // no matter what the error mode, we want exceptions thrown if the connection fails + // to happen (per the PDO spec) + dbh->error_mode = PDO_ERRMODE_EXCEPTION; + + g_henv_cp->set_driver( dbh ); + g_henv_ncp->set_driver( dbh ); + + CHECK_CUSTOM_ERROR( driver_options && Z_TYPE_P( driver_options ) != IS_ARRAY, *g_henv_cp, SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE ) { + throw core::CoreException(); + } + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( pdo_conn_options_ht ); + + core::sqlsrv_zend_hash_init( *g_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, + ZVAL_INTERNAL_DTOR, 0 /*persistent*/ TSRMLS_CC ); + + // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. + dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_henv_cp, dbh->data_source, + static_cast( dbh->data_source_len ), pdo_conn_options_ht ); + dsn_parser->parse_conn_string( TSRMLS_C ); + + // Extract the server name + temp_server_z = zend_hash_index_find( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER); + + CHECK_CUSTOM_ERROR(( temp_server_z == NULL ), g_henv_cp, PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED ) { + + throw pdo::PDOException(); + } + + server_z = *temp_server_z; + + // Add a reference to the option value since we are deleting it from the hashtable + zval_add_ref( &server_z ); + zend_hash_index_del( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER ); + + sqlsrv_conn* conn = core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, core::allocate_conn, Z_STRVAL( server_z ), + dbh->username, dbh->password, pdo_conn_options_ht, pdo_sqlsrv_handle_dbh_error, + PDO_CONN_OPTS, dbh, "pdo_sqlsrv_db_handle_factory" TSRMLS_CC ); + + // Free the string in server_z after being used + zend_string_release( Z_STR( server_z )); + + SQLSRV_ASSERT( conn != NULL, "Invalid connection returned. Exception should have been thrown." ); + + // set the driver_data and methods to complete creation of the PDO object + dbh->driver_data = conn; + dbh->error_mode = prev_err_mode; // reset the error mode + dbh->alloc_own_columns = 1; // we do our own memory management for columns + dbh->native_case = PDO_CASE_NATURAL;// SQL Server supports mixed case types + + } + catch( core::CoreException& ) { + + if ( Z_TYPE( server_z ) == IS_STRING ) { + zend_string_release( Z_STR( server_z )); + } + dbh->error_mode = prev_err_mode; // reset the error mode + g_henv_cp->last_error().reset(); // reset the last error; callee will check if last_error exist before freeing it and setting it to NULL + + return 0; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_db_handle_factory: Unknown exception caught" ); + } + + return 1; +} + +// pdo_sqlsrv_dbh_close +// Maps to PDO::__destruct. +// Called when a PDO object is to be destroyed. +// By the time this function is called, PDO has already made sure that +// all statements are disposed and the PDO object is the last item destroyed. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// Always returns 1 for success. +int pdo_sqlsrv_dbh_close( pdo_dbh_t *dbh TSRMLS_DC ) +{ + LOG( SEV_NOTICE, "pdo_sqlsrv_dbh_close: entering" ); + + // if the connection didn't complete properly, driver_data isn't initialized. + if( dbh->driver_data == NULL ) { + + return 1; + } + + PDO_RESET_DBH_ERROR; + + // call the core layer close + core_sqlsrv_close( reinterpret_cast( dbh->driver_data ) TSRMLS_CC ); + dbh->driver_data = NULL; + + // always return success that the connection is closed + return 1; +} + +// pdo_sqlsrv_dbh_prepare +// Called by PDO::prepare and PDOStatement::__construct. +// Creates a statement and prepares it for execution by PDO +// Paramters: +// dbh - The PDO managed connection object. +// sql - SQL query to be prepared. +// sql_len - Length of the sql query +// stmt - The PDO managed statement object. +// driver_options - User provided list of statement options. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, + size_t sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + hash_auto_ptr pdo_stmt_options_ht; + sqlsrv_malloc_auto_ptr sql_rewrite; + size_t sql_rewrite_len = 0; + sqlsrv_malloc_auto_ptr driver_stmt; + + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); + SQLSRV_ASSERT(( driver_dbh != NULL ), "pdo_sqlsrv_dbh_prepare: dbh->driver_data was null"); + + try { + + // assign the methods for the statement object. This is necessary even if the + // statement fails so the user can retrieve the error information. + stmt->methods = &pdo_sqlsrv_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; // we support parameterized queries with ?, not names + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( pdo_stmt_options_ht ); + core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */, + ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + + // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. + validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht TSRMLS_CC ); + + driver_stmt = static_cast( core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, + pdo_stmt_options_ht, PDO_STMT_OPTS, + pdo_sqlsrv_handle_stmt_error, stmt TSRMLS_CC )); + + // if the user didn't set anything in the prepare options, then set the buffer limit + // to the value set on the connection. + if( driver_stmt->buffered_query_limit== sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) { + + driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size; + } + + // if the user didn't set anything in the prepare options, then set the query timeout + // to the value set on the connection. + if(( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ) && ( driver_dbh->query_timeout != QUERY_TIMEOUT_INVALID )) { + + core_sqlsrv_set_query_timeout( driver_stmt, driver_dbh->query_timeout TSRMLS_CC ); + } + + // rewrite named parameters in the query to positional parameters if we aren't letting PDO do the + // parameter substitution for us + if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { + + // rewrite the query to map named parameters to positional parameters. We do this rather than use the ODBC named + // parameters for consistency with the PDO MySQL and PDO ODBC drivers. + int zr = pdo_parse_params( stmt, const_cast( sql ), sql_len, &sql_rewrite, &sql_rewrite_len TSRMLS_CC ); + + CHECK_ZEND_ERROR( zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE) { + throw core::CoreException(); + } + // if parameter substitution happened, use that query instead of the original + if( sql_rewrite != 0) { + sql = sql_rewrite; + sql_len = sql_rewrite_len; + } + } + + if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { + + core_sqlsrv_prepare( driver_stmt, sql, sql_len TSRMLS_CC ); + } + else if( driver_stmt->direct_query ) { + + if( driver_stmt->direct_query_subst_string ) { + // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme + // and we use estrdup below to allocate the new string, which uses emalloc + efree( reinterpret_cast( const_cast( driver_stmt->direct_query_subst_string ))); + } + driver_stmt->direct_query_subst_string = estrdup( sql ); + driver_stmt->direct_query_subst_string_len = sql_len; + } + // else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be + // set to the substituted query + + + stmt->driver_data = driver_stmt; + driver_stmt.transferred(); + } + // everything is cleaned up by this point + // catch everything so the exception doesn't spill into the calling PDO code + catch( core::CoreException& ) { + + if( driver_stmt ) { + + driver_stmt->~pdo_sqlsrv_stmt(); + } + + // in the event that the statement caused an error that was copied to the connection, update the + // connection with the error's SQLSTATE. + if( driver_dbh->last_error() ) { + + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), + reinterpret_cast( driver_dbh->last_error()->sqlstate )); + } + + return 0; + } + + // catch any errant exception and die + catch(...) { + + DIE( "pdo_sqlsrv_dbh_prepare: Unknown exception caught." ); + } + + return 1; +} + + +// pdo_sqlsrv_dbh_do +// Maps to PDO::exec. +// Execute a SQL statement, such as an insert, update or delete, and return +// the number of rows affected. +// Parameters: +// dbh - the PDO connection object, which contains the ODBC handle +// sql - the query to execute +// sql_len - length of sql query +// Return +// # of rows affected, -1 for an error. +zend_long pdo_sqlsrv_dbh_do( pdo_dbh_t *dbh, const char *sql, size_t sql_len TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + sqlsrv_malloc_auto_ptr driver_stmt; + SQLLEN rows = 0; + + // verify that the data type sizes are the same. If we ever upgrade to 64 bit we don't want the wrong + // thing to happen here. + SQLSRV_STATIC_ASSERT( sizeof( rows ) == sizeof( SQLLEN )); + + try { + + SQLSRV_ASSERT( sql != NULL, "NULL or empty SQL string passed." ); + + // temp PDO statement used for error handling if something happens + pdo_stmt_t temp_stmt; + temp_stmt.dbh = dbh; + // allocate a full driver statement to take advantage of the error handling + driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, + NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); + driver_stmt->set_func( __FUNCTION__ ); + + core_sqlsrv_execute( driver_stmt TSRMLS_CC, sql, static_cast( sql_len ) ); + + // since the user can give us a compound statement, we return the row count for the last set, and since the row count + // isn't guaranteed to be valid until all the results have been fetched, we fetch them all first. + if( core_sqlsrv_has_any_result( driver_stmt TSRMLS_CC )) { + + SQLRETURN r = SQL_SUCCESS; + + do { + + rows = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + r = core::SQLMoreResults( driver_stmt TSRMLS_CC ); + + } while( r != SQL_NO_DATA ); + } + + // returning -1 forces PDO to return false, which signals an error occurred. SQLRowCount returns -1 for a number of cases + // naturally, so we override that here with no rows returned. + if( rows == -1 ) { + rows = 0; + } + } + catch( core::CoreException& ) { + + // copy any errors on the statement to the connection so that the user sees them, since the statement is released + // before this method returns + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), + reinterpret_cast( driver_stmt->last_error()->sqlstate )); + driver_dbh->set_last_error( driver_stmt->last_error() ); + + if( driver_stmt ) { + driver_stmt->~sqlsrv_stmt(); + } + + return -1; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_dbh_do: Unknown exception caught." ); + } + + if( driver_stmt ) { + driver_stmt->~sqlsrv_stmt(); + } + + return rows; +} + + +// transaction support functions + +// pdo_sqlsrv_dbh_begin +// Maps to PDO::beginTransaction. +// Begins a transaction. Turns off auto-commit mode. The pdo_dbh_t::in_txn +// flag is maintained by PDO so we dont have to worry about it. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// 0 for failure and 1 for success. +int pdo_sqlsrv_dbh_begin( pdo_dbh_t *dbh TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + try { + + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_begin: pdo_dbh_t object was null" ); + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + + SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_begin: driver_data object was null" ); + + DEBUG_SQLSRV_ASSERT( !dbh->in_txn, "pdo_sqlsrv_dbh_begin: Already in transaction" ); + + core_sqlsrv_begin_transaction( driver_conn TSRMLS_CC ); + + return 1; + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_dbh_begin: Uncaught exception occurred."); + } +} + + + +// pdo_sqlsrv_dbh_commit +// Maps to PDO::commit. +// Commits a transaction. Returns the connection to auto-commit mode. +// PDO throws error if PDO::commit is called on a connection that is not in an active +// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have +// to worry about it here. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// 0 for failure and 1 for success. +int pdo_sqlsrv_dbh_commit( pdo_dbh_t *dbh TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + try { + + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_commit: pdo_dbh_t object was null" ); + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + + SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_commit: driver_data object was null" ); + + DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_commit: Not in transaction" ); + + core_sqlsrv_commit( driver_conn TSRMLS_CC ); + + return 1; + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_dbh_commit: Uncaught exception occurred."); + } +} + +// pdo_sqlsrv_dbh_rollback +// Maps to PDO::rollback. +// Rolls back a transaction. Returns the connection in auto-commit mode. +// PDO throws error if PDO::rollBack is called on a connection that is not in an active +// transaction. The pdo_dbh_t::in_txn flag is maintained by PDO so we dont have +// to worry about it here. +// Parameters: +// dbh - The PDO managed connection object. +// Return: +// 0 for failure and 1 for success. +int pdo_sqlsrv_dbh_rollback( pdo_dbh_t *dbh TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + try { + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_dbh_rollback: pdo_dbh_t object was null" ); + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + + SQLSRV_ASSERT( driver_conn != NULL, "pdo_sqlsrv_dbh_rollback: driver_data object was null" ); + + DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_rollback: Not in transaction" ); + + core_sqlsrv_rollback( driver_conn TSRMLS_CC ); + + return 1; + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_dbh_rollback: Uncaught exception occurred."); + } +} + +// pdo_sqlsrv_dbh_set_attr +// Maps to PDO::setAttribute. Sets an attribute on the PDO connection object. +// PDO driver manager calls this function directly after calling the factory +// method for PDO, for any attribute which is specified in the PDO constructor. +// Parameters: +// dbh - The PDO connection object maintained by PDO. +// attr - The attribute to be set. +// val - The value of the attribute to be set. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + try { + + switch( attr ) { + + case SQLSRV_ATTR_ENCODING: + { + zend_long attr_value; + if( Z_TYPE_P( val ) != IS_LONG ) { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + } + attr_value = Z_LVAL_P( val ); + switch( attr_value ) { + + case SQLSRV_ENCODING_DEFAULT: + // when default is applied to a connection, that means use UTF-8 encoding + driver_dbh->set_encoding( SQLSRV_ENCODING_UTF8 ); + break; + case SQLSRV_ENCODING_SYSTEM: + case SQLSRV_ENCODING_UTF8: + driver_dbh->set_encoding( static_cast( attr_value )); + break; + default: + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + break; + } + } + break; + + case SQLSRV_ATTR_DIRECT_QUERY: + driver_dbh->direct_query = ( zend_is_true( val ) ) ? true : false; + break; + + case SQLSRV_ATTR_QUERY_TIMEOUT: + if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) < 0 ) { + convert_to_string( val ); + THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( val )); + } + driver_dbh->query_timeout = static_cast( Z_LVAL_P( val ) ); + break; + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + if( Z_TYPE_P( val ) != IS_LONG || Z_LVAL_P( val ) <= 0 ) { + convert_to_string( val ); + THROW_PDO_ERROR( driver_dbh, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, Z_STRVAL_P( val )); + } + driver_dbh->client_buffer_max_size = Z_LVAL_P( val ); + break; + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + driver_dbh->fetch_numeric = (zend_is_true(val)) ? true : false; + break; + + // Not supported + case PDO_ATTR_FETCH_TABLE_NAMES: + case PDO_ATTR_FETCH_CATALOG_NAMES: + case PDO_ATTR_PREFETCH: + case PDO_ATTR_MAX_COLUMN_LEN: + case PDO_ATTR_CURSOR_NAME: + case PDO_ATTR_AUTOCOMMIT: + case PDO_ATTR_PERSISTENT: + case PDO_ATTR_TIMEOUT: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ); + } + + // Read-only + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: + case PDO_ATTR_CLIENT_VERSION: + case PDO_ATTR_DRIVER_NAME: + case PDO_ATTR_CONNECTION_STATUS: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR ); + } + + // Statement level only + case PDO_ATTR_EMULATE_PREPARES: + case PDO_ATTR_CURSOR: + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); + } + + default: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); + break; + } + } + } + catch( pdo::PDOException& ) { + + return 0; + } + + return 1; +} + + +// pdo_sqlsrv_dbh_get_attr +// Maps to PDO::getAttribute. Gets an attribute on the PDO connection object. +// Parameters: +// dbh - The PDO connection object maintained by PDO. +// attr - The attribute to get. +// return_value - zval in which to return the attribute value. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + try { + + switch( attr ) { + + // Not supported + case PDO_ATTR_FETCH_TABLE_NAMES: + case PDO_ATTR_FETCH_CATALOG_NAMES: + case PDO_ATTR_PREFETCH: + case PDO_ATTR_MAX_COLUMN_LEN: + case PDO_ATTR_CURSOR_NAME: + case PDO_ATTR_AUTOCOMMIT: + case PDO_ATTR_TIMEOUT: + { + // PDO does not throw "not supported" error message for these attributes. + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ); + } + + // Statement level only + case PDO_ATTR_EMULATE_PREPARES: + case PDO_ATTR_CURSOR: + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); + } + + case PDO_ATTR_STRINGIFY_FETCHES: + { + // For this attribute, if we dont set the return_value than PDO returns NULL. + ZVAL_BOOL(return_value, ( dbh->stringify ? 1 : 0 ) ); + break; + } + + case PDO_ATTR_SERVER_INFO: + { + core_sqlsrv_get_server_info( driver_dbh, return_value TSRMLS_CC ); + break; + } + + case PDO_ATTR_SERVER_VERSION: + { + core_sqlsrv_get_server_version( driver_dbh, return_value TSRMLS_CC ); + break; + } + + case PDO_ATTR_CLIENT_VERSION: + { + core_sqlsrv_get_client_info( driver_dbh, return_value TSRMLS_CC ); + + //Add the PDO SQLSRV driver's file version + //Declarations below eliminate compiler warnings about string constant to char* conversions + const char* extver = "ExtensionVer"; + std::string filever = VER_FILEVERSION_STR; + core::sqlsrv_add_assoc_string( *driver_dbh, return_value, extver, &filever[0], 1 /*duplicate*/ + TSRMLS_CC ); + break; + } + + case SQLSRV_ATTR_ENCODING: + { + ZVAL_LONG( return_value, driver_dbh->encoding() ); + break; + } + + case SQLSRV_ATTR_QUERY_TIMEOUT: + { + ZVAL_LONG( return_value, ( driver_dbh->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_dbh->query_timeout )); + break; + } + + case SQLSRV_ATTR_DIRECT_QUERY: + { + ZVAL_BOOL( return_value, driver_dbh->direct_query ); + break; + } + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + { + ZVAL_LONG( return_value, driver_dbh->client_buffer_max_size ); + break; + } + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + { + ZVAL_BOOL( return_value, driver_dbh->fetch_numeric ); + break; + } + + default: + { + THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); + break; + } + } + + return 1; + } + catch( core::CoreException& ) { + return 0; + } +} + +// Called by PDO::errorInfo and PDOStatement::errorInfo. +// Returns the error info. +// Parameters: +// dbh - The PDO managed connection object. +// stmt - The PDO managed statement object. +// info - zval in which to return the error info. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, + zval *info TSRMLS_DC) +{ + SQLSRV_ASSERT( dbh != NULL || stmt != NULL, "Either dbh or stmt must not be NULL to dereference the error." ); + + sqlsrv_error* ctx_error = NULL; + if( stmt ) { + ctx_error = static_cast( stmt->driver_data )->last_error(); + } + else { + ctx_error = static_cast( dbh->driver_data )->last_error(); + } + + pdo_sqlsrv_retrieve_context_error( ctx_error, info ); + + return 1; +} + +// pdo_sqlsrv_dbh_last_id +// Maps to PDO::lastInsertId. +// Returns the last id generated by an executed SQL statement +// Parameters: +// dbh - The PDO managed connection object. +// name - Table name. +// len - Length of the name. +// Return: +// Returns the last insert id as a string. +char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, _Out_ size_t* len TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + // turn off any error handling for last_id + pdo_error_mode prev_err_mode = dbh->error_mode; + dbh->error_mode = PDO_ERRMODE_SILENT; + + sqlsrv_malloc_auto_ptr driver_stmt; + + pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); + + sqlsrv_malloc_auto_ptr id_str; + id_str = reinterpret_cast( sqlsrv_malloc( LAST_INSERT_ID_BUFF_LEN )); + + try { + + char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ]; + if( name == NULL ) { + strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY ); + } + else { + char* quoted_table = NULL; + size_t quoted_len = 0; + int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strlen( name ), "ed_table, "ed_len, PDO_PARAM_NULL TSRMLS_CC ); + SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name."); + snprintf( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, TABLE_LAST_INSERT_ID_QUERY, quoted_table ); + sqlsrv_free( quoted_table ); + } + + // temp PDO statement used for error handling if something happens + pdo_stmt_t temp_stmt; + temp_stmt.dbh = dbh; + + // allocate a full driver statement to take advantage of the error handling + driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); + driver_stmt->set_func( __FUNCTION__ ); + + + sqlsrv_malloc_auto_ptr wsql_string; + unsigned int wsql_len; + wsql_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_CHAR, reinterpret_cast( last_insert_id_query ), strlen(last_insert_id_query), &wsql_len ); + + CHECK_CUSTOM_ERROR( wsql_string == 0, driver_stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) { + throw core::CoreException(); + } + + // execute the last insert id query + core::SQLExecDirectW( driver_stmt, wsql_string TSRMLS_CC ); + + core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); + SQLRETURN r = core::SQLGetData( driver_stmt, 1, SQL_C_CHAR, id_str, LAST_INSERT_ID_BUFF_LEN, + reinterpret_cast( len ), false TSRMLS_CC ); + + CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt, + PDO_SQLSRV_ERROR_LAST_INSERT_ID ) { + throw core::CoreException(); + } + + driver_stmt->~sqlsrv_stmt(); + } + catch( core::CoreException& ) { + + // copy any errors on the statement to the connection so that the user sees them, since the statement is released + // before this method returns + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), + reinterpret_cast( driver_stmt->last_error()->sqlstate )); + driver_dbh->set_last_error( driver_stmt->last_error() ); + + if( driver_stmt ) { + driver_stmt->~sqlsrv_stmt(); + } + + strcpy_s( id_str.get(), 1, "" ); + *len = 0; + } + + char* ret_id_str = id_str.get(); + id_str.transferred(); + + // restore error handling to its previous mode + dbh->error_mode = prev_err_mode; + + return ret_id_str; +} + +// pdo_sqlsrv_dbh_quote +// Maps to PDO::quote. As the name says, this function quotes a string. +// Always returns a valid string unless memory allocation fails. +// Parameters: +// dbh - The PDO managed connection object. +// unquoted - The unquoted string to be quoted. +// unquoted_len - Length of the unquoted string. +// quoted - Buffer for output string. +// quoted_len - Length of the output string. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquoted_len, char **quoted, size_t* quoted_len, + enum pdo_param_type /*paramtype*/ TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); + SQLSRV_ENCODING encoding = driver_dbh->bind_param_encoding; + + if ( encoding == SQLSRV_ENCODING_BINARY ) { + // convert from char* to hex digits using os + std::basic_ostringstream os; + for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + os << std::hex << ( int )unquoted[ index ]; + } + std::basic_string str_hex = os.str(); + // each character is represented by 2 digits of hex + size_t unquoted_str_len = unquoted_len * 2; // length returned should not account for null terminator + char* unquoted_str = reinterpret_cast( sqlsrv_malloc( unquoted_str_len, sizeof( char ), 1 )); // include space for null terminator + strcpy_s( unquoted_str, unquoted_str_len + 1 /* include null terminator*/, str_hex.c_str() ); + // include length of '0x' in the binary string + *quoted_len = unquoted_str_len + 2; + *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); + unsigned int out_current = 0; + // insert '0x' + ( *quoted )[ out_current++ ] = '0'; + ( *quoted )[ out_current++ ] = 'x'; + for ( size_t index = 0; index < unquoted_str_len && unquoted_str[ index ] != '\0'; ++index ) { + ( *quoted )[ out_current++ ] = unquoted_str[ index ]; + } + // null terminator + ( *quoted )[ out_current ] = '\0'; + sqlsrv_free( unquoted_str ); + return 1; + } + else { + // count the number of quotes needed + unsigned int quotes_needed = 2; // the initial start and end quotes of course + // include the N proceeding the initial quote if encoding is UTF8 + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + quotes_needed = 3; + } + for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + if ( unquoted[ index ] == '\'' ) { + ++quotes_needed; + } + } + + *quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator. + *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); // include space for null terminator. + unsigned int out_current = 0; + + // insert N if the encoding is UTF8 + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + ( *quoted )[ out_current++ ] = 'N'; + } + // insert initial quote + ( *quoted )[ out_current++ ] = '\''; + + for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + if ( unquoted[ index ] == '\'' ) { + ( *quoted )[ out_current++ ] = '\''; + ( *quoted )[ out_current++ ] = '\''; + } + else { + ( *quoted )[ out_current++ ] = unquoted[ index ]; + } + } + + // trailing quote and null terminator + ( *quoted )[ out_current++ ] = '\''; + ( *quoted )[ out_current ] = '\0'; + + return 1; + } +} + +// This method is not implemented by this driver. +pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( pdo_dbh_t *dbh, int kind TSRMLS_DC ) +{ + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; + PDO_LOG_DBH_ENTRY; + + sqlsrv_conn* driver_conn = reinterpret_cast( dbh->driver_data ); + CHECK_CUSTOM_ERROR( true, driver_conn, PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED ) { + return NULL; + } + + return NULL; // to avoid a compiler warning +} + +namespace { + +// Maps the PDO driver specific statement option/attribute constants to the core layer +// statement option/attribute constants. +void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht, + zval* data TSRMLS_DC ) +{ + zend_ulong option_key = -1; + switch( key ) { + + case PDO_ATTR_CURSOR: + option_key = SQLSRV_STMT_OPTION_SCROLLABLE; + break; + + case SQLSRV_ATTR_ENCODING: + option_key = PDO_STMT_OPTION_ENCODING; + break; + + case SQLSRV_ATTR_QUERY_TIMEOUT: + option_key = SQLSRV_STMT_OPTION_QUERY_TIMEOUT; + break; + + case PDO_ATTR_STATEMENT_CLASS: + break; + + case SQLSRV_ATTR_DIRECT_QUERY: + option_key = PDO_STMT_OPTION_DIRECT_QUERY; + break; + + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + option_key = PDO_STMT_OPTION_CURSOR_SCROLL_TYPE; + break; + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + option_key = PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE; + break; + + case PDO_ATTR_EMULATE_PREPARES: + option_key = PDO_STMT_OPTION_EMULATE_PREPARES; + break; + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; + break; + + default: + CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { + throw core::CoreException(); + } + break; + } + + // if a PDO handled option makes it through (such as PDO_ATTR_STATEMENT_CLASS, just skip it + if( option_key != -1 ) { + zval_add_ref( data ); + core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data TSRMLS_CC ); + } +} + + +// validate_stmt_options +// Iterates through the list of statement options provided by the user and validates them +// against the list of statement options provided by this driver. After validation +// creates a Hashtable of statement options to be sent to the core layer for processing. +// Parameters: +// ctx - The current context. +// stmt_options - The user provided list of statement options. +// pdo_stmt_options_ht - Output hashtable of statement options. +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ) +{ + try { + + if( stmt_options ) { + + HashTable* options_ht = Z_ARRVAL_P( stmt_options ); + size_t int_key = -1; + zend_string *key = NULL; + zval* data = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + int result = 0; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_LONG ), ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { + throw core::CoreException(); + } + + add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); + } + } + catch( core::CoreException& ) { + + throw; + } +} + + +void pdo_bool_conn_str_func::func(connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ) +{ + TSRMLS_C; + char const* val_str = "no"; + + if( core_str_zval_is_true( value ) ) { + + val_str = "yes"; + } + + conn_str += option->odbc_name; + conn_str += "={"; + conn_str += val_str; + conn_str += "};"; +} + +void pdo_txn_isolation_conn_attr_func::func( connection_option const* /*option*/, zval* value_z, sqlsrv_conn* conn, + std::string& /*conn_str*/ TSRMLS_DC ) +{ + try { + + SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "pdo_txn_isolation_conn_attr_func: Unexpected zval type." ); + const char* val = Z_STRVAL_P( value_z ); + size_t val_len = Z_STRLEN_P( value_z ); + zend_long out_val = SQL_TXN_READ_COMMITTED; + + // READ_COMMITTED + if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_COMMITTED ) - 1 ) + && !strcasecmp( val, PDOTxnIsolationValues::READ_COMMITTED ))) { + + out_val = SQL_TXN_READ_COMMITTED; + } + + // READ_UNCOMMITTED + else if(( val_len == ( sizeof( PDOTxnIsolationValues::READ_UNCOMMITTED ) - 1 ) + && !strcasecmp( val, PDOTxnIsolationValues::READ_UNCOMMITTED ))) { + + out_val = SQL_TXN_READ_UNCOMMITTED; + } + + // REPEATABLE_READ + else if(( val_len == ( sizeof( PDOTxnIsolationValues::REPEATABLE_READ ) - 1 ) + && !strcasecmp( val, PDOTxnIsolationValues::REPEATABLE_READ ))) { + + out_val = SQL_TXN_REPEATABLE_READ; + } + + // SERIALIZABLE + else if(( val_len == ( sizeof( PDOTxnIsolationValues::SERIALIZABLE ) - 1 ) + && !strcasecmp( val, PDOTxnIsolationValues::SERIALIZABLE ))) { + + out_val = SQL_TXN_SERIALIZABLE; + } + + // SNAPSHOT + else if(( val_len == ( sizeof( PDOTxnIsolationValues::SNAPSHOT ) - 1 ) + && !strcasecmp( val, PDOTxnIsolationValues::SNAPSHOT ))) { + + out_val = SQL_TXN_SS_SNAPSHOT; + } + + else { + + CHECK_CUSTOM_ERROR( true, conn, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, PDOConnOptionNames::TransactionIsolation ) { + + throw core::CoreException(); + } + } + + core::SQLSetConnectAttr( conn, SQL_COPT_SS_TXN_ISOLATION, reinterpret_cast( out_val ), SQL_IS_UINTEGER TSRMLS_CC ); + + } + catch( core::CoreException& ) { + + throw; + } +} + +} // namespace diff --git a/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp similarity index 63% rename from pdo_sqlsrv/pdo_init.cpp rename to source/pdo_sqlsrv/pdo_init.cpp index 7db1ab9ac..b20388ed2 100644 --- a/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -1,403 +1,307 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: pdo_init.cpp -// -// Contents: initialization routines for PDO_SQLSRV -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "pdo_sqlsrv.h" -#include - -#ifdef ZTS -ZEND_TSRMLS_CACHE_DEFINE(); -#endif -ZEND_GET_MODULE(g_pdo_sqlsrv) - -extern "C" { - -ZEND_DECLARE_MODULE_GLOBALS(pdo_sqlsrv); - -} - -// module global variables (initialized in minit and freed in mshutdown) -HashTable* g_pdo_errors_ht = NULL; - -// henv context for creating connections -sqlsrv_context* g_henv_cp; -sqlsrv_context* g_henv_ncp; - -namespace { - -pdo_driver_t pdo_sqlsrv_driver = { - - PDO_DRIVER_HEADER(sqlsrv), - pdo_sqlsrv_db_handle_factory -}; - -const char PDO_DLL_NAME[] = "php_pdo.dll"; - -// PHP_DEBUG is always defined as either 0 or 1 -#if PHP_DEBUG == 1 && !defined(ZTS) - -const char PHP_DLL_NAME[] = "php7_debug.dll"; - -#elif PHP_DEBUG == 1 && defined(ZTS) - -const char PHP_DLL_NAME[] = "php7ts_debug.dll"; - -#elif PHP_DEBUG == 0 && !defined(ZTS) - -const char PHP_DLL_NAME[] = "php7.dll"; - -#elif PHP_DEBUG == 0 && defined(ZTS) - -const char PHP_DLL_NAME[] = "php7ts.dll"; - -#else - -#error Invalid combination of PHP_DEBUG and ZTS macros - -#endif - -typedef PDO_API int (*pdo_register_func)(pdo_driver_t *); - -// function pointers to register and unregister this driver -pdo_register_func pdo_register_driver; -pdo_register_func pdo_unregister_driver; - -// functions to register SQLSRV constants with the PDO class -// (It's in all CAPS so it looks like the Zend macros that do similar work) -void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC ); -void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC ); - -// return the Zend class entry for the PDO dbh (connection) class -zend_class_entry* (*pdo_get_dbh_class)( void ); - -struct sqlsrv_attr_pdo_constant { - const char *name; - int value; -}; - -// forward decl for table -extern sqlsrv_attr_pdo_constant pdo_attr_constants[]; - -} - -static zend_module_dep pdo_sqlsrv_depends[] = { - ZEND_MOD_REQUIRED("pdo") - {NULL, NULL, NULL} -}; - - -// argument info structures for functions, arranged alphabetically. -// see zend_API.h in the PHP sources for more information about these macros - -// function table with associated arginfo structures -zend_function_entry pdo_sqlsrv_functions[] = { - {NULL, NULL, NULL} // no functions directly defined by this driver -}; - -// the structure returned to Zend that exposes the extension to the Zend engine. -// this structure is defined in zend_modules.h in the PHP sources - -zend_module_entry g_pdo_sqlsrv_module_entry = -{ - STANDARD_MODULE_HEADER_EX, - NULL, - pdo_sqlsrv_depends, - "pdo_sqlsrv", - pdo_sqlsrv_functions, // exported function table - // initialization and shutdown functions - PHP_MINIT(pdo_sqlsrv), - PHP_MSHUTDOWN(pdo_sqlsrv), - PHP_RINIT(pdo_sqlsrv), - PHP_RSHUTDOWN(pdo_sqlsrv), - PHP_MINFO(pdo_sqlsrv), - // version of the extension. Matches the version resource of the extension dll - VER_FILEVERSION_STR, - PHP_MODULE_GLOBALS(pdo_sqlsrv), - NULL, - NULL, - NULL, - STANDARD_MODULE_PROPERTIES_EX -}; - -// functions dynamically linked from the PDO (or PHP) dll and called by other parts of the driver -zend_class_entry* (*pdo_get_exception_class)( void ); -int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, size_t inquery_len, - char **outquery, size_t *outquery_len TSRMLS_DC); - -// called by Zend for each parameter in the g_pdo_errors_ht hash table when it is destroyed -void pdo_error_dtor(zval* elem) { - pdo_error* error_to_ignore = reinterpret_cast(Z_PTR_P(elem)); - pefree(error_to_ignore, 1); -} - -// Module initialization -// This function is called once per execution of the Zend engine - -PHP_MINIT_FUNCTION(pdo_sqlsrv) -{ - SQLSRV_UNUSED( type ); - - // our global variables are initialized in the RINIT function -#if defined(ZTS) - if( ts_allocate_id( &pdo_sqlsrv_globals_id, - sizeof( zend_pdo_sqlsrv_globals ), - (ts_allocate_ctor) NULL, - (ts_allocate_dtor) NULL ) == 0 ) - return FAILURE; - ZEND_TSRMLS_CACHE_UPDATE(); -#endif - - core_sqlsrv_register_logger( pdo_sqlsrv_log ); - - REGISTER_INI_ENTRIES(); - - LOG( SEV_NOTICE, "pdo_sqlsrv: entering minit" ); - - // PHP extensions may be either external DLLs loaded by PHP or statically compiled into the PHP dll - // This becomes an issue when we are dependent on other extensions, e.g. PDO. Normally this is solved - // by the build process by linking our extension to the appropriate import library (either php*.dll or php_pdo.dll) - // However, this leaves us with a problem that the extension has a dependency on that build type. - // Since we don't distribute our extension with PHP directly (yet), it would mean that we would have to have SKUs - // for both types of PDO builds, internal and external. Rather than this, we just dynamically link the PDO routines we call - // against either the PDO dll if it exists and is loaded, otherwise against the PHP dll directly. - - DWORD needed = 0; - HANDLE hprocess = GetCurrentProcess(); - HMODULE pdo_hmodule; - - pdo_hmodule = GetModuleHandle( PDO_DLL_NAME ); - if( pdo_hmodule == 0 ) { - - pdo_hmodule = GetModuleHandle( PHP_DLL_NAME ); - if( pdo_hmodule == NULL ) { - LOG( SEV_ERROR, "Failed to get PHP module handle." ); - return FAILURE; - } - } - - pdo_register_driver = reinterpret_cast( GetProcAddress( pdo_hmodule, "php_pdo_register_driver" )); - if( pdo_register_driver == NULL ) { - LOG( SEV_ERROR, "Failed to register driver." ); - return FAILURE; - } - - pdo_unregister_driver = reinterpret_cast( GetProcAddress( pdo_hmodule, "php_pdo_unregister_driver" )); - if( pdo_unregister_driver == NULL ) { - LOG( SEV_ERROR, "Failed to register driver." ); - return FAILURE; - } - - pdo_get_exception_class = reinterpret_cast( GetProcAddress( pdo_hmodule, - "php_pdo_get_exception" )); - if( pdo_get_exception_class == NULL ) { - LOG( SEV_ERROR, "Failed to register driver." ); - return FAILURE; - } - - pdo_get_dbh_class = reinterpret_cast( GetProcAddress( pdo_hmodule, "php_pdo_get_dbh_ce" )); - if( pdo_get_dbh_class == NULL ) { - LOG( SEV_ERROR, "Failed to register driver." ); - return FAILURE; - } - - pdo_subst_named_params = - reinterpret_cast( - GetProcAddress( pdo_hmodule, "pdo_parse_params" )); - if( pdo_subst_named_params == NULL ) { - LOG( SEV_ERROR, "Failed to register driver." ); - return FAILURE; - } - - // initialize list of pdo errors - g_pdo_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - ::zend_hash_init( g_pdo_errors_ht, 50, NULL, pdo_error_dtor /*pDestructor*/, 1 ); - - for( int i = 0; PDO_ERRORS[ i ].error_code != -1; ++i ) { - - void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[ i ].error_code, - &( PDO_ERRORS[ i ].sqlsrv_error ), sizeof( PDO_ERRORS[ i ].sqlsrv_error ) ); - if( zr == NULL ) { - - LOG( SEV_ERROR, "Failed to insert data into PDO errors hashtable." ); - return FAILURE; - } - } - - try { - - // register all attributes supported by this driver. - for( int i= 0; pdo_attr_constants[i].name != NULL; ++i ) { - - REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( pdo_attr_constants[i].name, pdo_attr_constants[i].value TSRMLS_CC ); - - } - - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_UNCOMMITTED", PDOTxnIsolationValues::READ_UNCOMMITTED TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_COMMITTED", PDOTxnIsolationValues::READ_COMMITTED TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_REPEATABLE_READ", PDOTxnIsolationValues::REPEATABLE_READ TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SERIALIZABLE", PDOTxnIsolationValues::SERIALIZABLE TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SNAPSHOT", PDOTxnIsolationValues::SNAPSHOT TSRMLS_CC ); - - // retrieve the handles for the environments - core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, pdo_sqlsrv_handle_env_error, "PHP_MINIT_FUNCTION for pdo_sqlsrv" TSRMLS_CC ); - - } - catch( ... ) { - - return FAILURE; - } - - pdo_register_driver( &pdo_sqlsrv_driver ); - - return SUCCESS; -} - -// Module shutdown function - -// Module shutdown function -// This function is called once per execution of the Zend engine - -PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv) -{ - try { - - SQLSRV_UNUSED( type ); - - UNREGISTER_INI_ENTRIES(); - - pdo_unregister_driver( &pdo_sqlsrv_driver ); - - // clean up the list of pdo errors - zend_hash_destroy( g_pdo_errors_ht ); - pefree( g_pdo_errors_ht, 1 /*persistent*/ ); - - core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp ); - - } - catch( ... ) { - - LOG( SEV_NOTICE, "Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)" ); - return FAILURE; - } - - return SUCCESS; -} - - -// Request initialization function -// This function is called once per PHP script execution - -PHP_RINIT_FUNCTION(pdo_sqlsrv) -{ - SQLSRV_UNUSED( module_number ); - SQLSRV_UNUSED( type ); - -#if defined(ZTS) - ZEND_TSRMLS_CACHE_UPDATE(); -#endif - - LOG( SEV_NOTICE, "pdo_sqlsrv: entering rinit" ); - - return SUCCESS; -} - - -// Request shutdown -// Called at the end of a script's execution - -PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv) -{ - SQLSRV_UNUSED( module_number ); - SQLSRV_UNUSED( type ); - - LOG( SEV_NOTICE, "pdo_sqlsrv: entering rshutdown" ); - - return SUCCESS; -} - -// Called for php_info(); -// Displays the INI settings registered and their current values - -PHP_MINFO_FUNCTION(pdo_sqlsrv) -{ - php_info_print_table_start(); - php_info_print_table_header(2, "pdo_sqlsrv support", "enabled"); - php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); - php_info_print_table_end(); - DISPLAY_INI_ENTRIES(); -} - -// *** internal init functions *** - -namespace { - - // mimic the functionality of the REGISTER_PDO_CLASS_CONST_LONG. We use this instead of the macro because - // we dynamically link the pdo_get_dbh_class function rather than use the static php_pdo_get_dbh_ce (see MINIT) - - void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC ) - { - zend_class_entry* zend_class = pdo_get_dbh_class(); - SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_LONG: pdo_get_dbh_class failed" ); - int zr = zend_declare_class_constant_long( zend_class, const_cast( name ), strlen( name ), value TSRMLS_CC ); - if( zr == FAILURE ) { - throw core::CoreException(); - } - } - - void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC ) - { - zend_class_entry* zend_class = pdo_get_dbh_class(); - SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_STRING: pdo_get_dbh_class failed" ); - int zr = zend_declare_class_constant_string( zend_class, const_cast( name ), strlen( name ), const_cast( value ) TSRMLS_CC ); - if( zr == FAILURE ) { - - throw core::CoreException(); - } - } - - // array of pdo constants. - sqlsrv_attr_pdo_constant pdo_attr_constants[] = { - - // driver specific attributes - { "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING }, - { "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT }, - { "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY }, - { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, - { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, - { "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE }, - - // used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int, - // PDO::PARAM_STR uses the size of the string in the variable - { "SQLSRV_PARAM_OUT_DEFAULT_SIZE" , -1 }, - - // encoding attributes - { "SQLSRV_ENCODING_DEFAULT" , SQLSRV_ENCODING_DEFAULT }, - { "SQLSRV_ENCODING_SYSTEM" , SQLSRV_ENCODING_SYSTEM }, - { "SQLSRV_ENCODING_BINARY" , SQLSRV_ENCODING_BINARY }, - { "SQLSRV_ENCODING_UTF8" , SQLSRV_ENCODING_UTF8 }, - - // cursor types (can be assigned to SQLSRV_ATTR_CURSOR_SCROLL_TYPE - { "SQLSRV_CURSOR_STATIC" , SQL_CURSOR_STATIC }, - { "SQLSRV_CURSOR_DYNAMIC" , SQL_CURSOR_DYNAMIC }, - { "SQLSRV_CURSOR_KEYSET" , SQL_CURSOR_KEYSET_DRIVEN }, - { "SQLSRV_CURSOR_BUFFERED" , static_cast(SQLSRV_CURSOR_BUFFERED) }, - - { NULL , 0 } // terminate the table - }; -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_init.cpp +// +// Contents: initialization routines for PDO_SQLSRV +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_pdo_sqlsrv.h" + +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE(); +#endif +ZEND_GET_MODULE(g_pdo_sqlsrv) + +extern "C" { + +ZEND_DECLARE_MODULE_GLOBALS(pdo_sqlsrv); + +} + +// module global variables (initialized in minit and freed in mshutdown) +HashTable* g_pdo_errors_ht = NULL; + +// henv context for creating connections +sqlsrv_context* g_henv_cp; +sqlsrv_context* g_henv_ncp; + +namespace { + +pdo_driver_t pdo_sqlsrv_driver = { + + PDO_DRIVER_HEADER(sqlsrv), + pdo_sqlsrv_db_handle_factory +}; + +// functions to register SQLSRV constants with the PDO class +// (It's in all CAPS so it looks like the Zend macros that do similar work) +void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC ); +void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC ); + +struct sqlsrv_attr_pdo_constant { + const char *name; + int value; +}; + +// forward decl for table +extern sqlsrv_attr_pdo_constant pdo_attr_constants[]; + +} + +static zend_module_dep pdo_sqlsrv_depends[] = { + ZEND_MOD_REQUIRED("pdo") + {NULL, NULL, NULL} +}; + + +// argument info structures for functions, arranged alphabetically. +// see zend_API.h in the PHP sources for more information about these macros + +// function table with associated arginfo structures +zend_function_entry pdo_sqlsrv_functions[] = { + {NULL, NULL, NULL} // no functions directly defined by this driver +}; + +// the structure returned to Zend that exposes the extension to the Zend engine. +// this structure is defined in zend_modules.h in the PHP sources + +zend_module_entry g_pdo_sqlsrv_module_entry = +{ + STANDARD_MODULE_HEADER_EX, + NULL, + pdo_sqlsrv_depends, + "pdo_sqlsrv", + pdo_sqlsrv_functions, // exported function table + // initialization and shutdown functions + PHP_MINIT(pdo_sqlsrv), + PHP_MSHUTDOWN(pdo_sqlsrv), + PHP_RINIT(pdo_sqlsrv), + PHP_RSHUTDOWN(pdo_sqlsrv), + PHP_MINFO(pdo_sqlsrv), + // version of the extension. Matches the version resource of the extension dll + VER_FILEVERSION_STR, + PHP_MODULE_GLOBALS(pdo_sqlsrv), + NULL, + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +// called by Zend for each parameter in the g_pdo_errors_ht hash table when it is destroyed +void pdo_error_dtor( zval* elem ) { + pdo_error* error_to_ignore = reinterpret_cast( Z_PTR_P( elem ) ); + pefree( error_to_ignore, 1 ); +} + +// Module initialization +// This function is called once per execution of the Zend engine + +PHP_MINIT_FUNCTION(pdo_sqlsrv) +{ + SQLSRV_UNUSED( type ); + + // our global variables are initialized in the RINIT function +#if defined(ZTS) + if( ts_allocate_id( &pdo_sqlsrv_globals_id, + sizeof( zend_pdo_sqlsrv_globals ), + (ts_allocate_ctor) NULL, + (ts_allocate_dtor) NULL ) == 0 ) + return FAILURE; + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + core_sqlsrv_register_logger( pdo_sqlsrv_log ); + + REGISTER_INI_ENTRIES(); + + LOG( SEV_NOTICE, "pdo_sqlsrv: entering minit" ); + + // initialize list of pdo errors + g_pdo_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); + ::zend_hash_init( g_pdo_errors_ht, 50, NULL, pdo_error_dtor /*pDestructor*/, 1 ); + + for( int i = 0; PDO_ERRORS[ i ].error_code != -1; ++i ) { + + void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[ i ].error_code, + &( PDO_ERRORS[ i ].sqlsrv_error ), sizeof( PDO_ERRORS[ i ].sqlsrv_error ) ); + if( zr == NULL ) { + + LOG( SEV_ERROR, "Failed to insert data into PDO errors hashtable." ); + return FAILURE; + } + } + + try { + + // register all attributes supported by this driver. + for( int i= 0; pdo_attr_constants[i].name != NULL; ++i ) { + + REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( pdo_attr_constants[i].name, pdo_attr_constants[i].value TSRMLS_CC ); + + } + + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_UNCOMMITTED", PDOTxnIsolationValues::READ_UNCOMMITTED TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_COMMITTED", PDOTxnIsolationValues::READ_COMMITTED TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_REPEATABLE_READ", PDOTxnIsolationValues::REPEATABLE_READ TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SERIALIZABLE", PDOTxnIsolationValues::SERIALIZABLE TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SNAPSHOT", PDOTxnIsolationValues::SNAPSHOT TSRMLS_CC ); + + // retrieve the handles for the environments + core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, pdo_sqlsrv_handle_env_error, "PHP_MINIT_FUNCTION for pdo_sqlsrv" TSRMLS_CC ); + + } + catch( ... ) { + + return FAILURE; + } + + php_pdo_register_driver( &pdo_sqlsrv_driver ); + + return SUCCESS; +} + +// Module shutdown function +// This function is called once per execution of the Zend engine + +PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv) +{ + try { + + SQLSRV_UNUSED( type ); + + UNREGISTER_INI_ENTRIES(); + + php_pdo_unregister_driver( &pdo_sqlsrv_driver ); + + // clean up the list of pdo errors + zend_hash_destroy( g_pdo_errors_ht ); + pefree( g_pdo_errors_ht, 1 /*persistent*/ ); + + core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp ); + + } + catch( ... ) { + + LOG( SEV_NOTICE, "Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)" ); + return FAILURE; + } + + return SUCCESS; +} + + +// Request initialization function +// This function is called once per PHP script execution + +PHP_RINIT_FUNCTION(pdo_sqlsrv) +{ + SQLSRV_UNUSED( module_number ); + SQLSRV_UNUSED( type ); + +#if defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + LOG( SEV_NOTICE, "pdo_sqlsrv: entering rinit" ); + + return SUCCESS; +} + + +// Request shutdown +// Called at the end of a script's execution + +PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv) +{ + SQLSRV_UNUSED( module_number ); + SQLSRV_UNUSED( type ); + + LOG( SEV_NOTICE, "pdo_sqlsrv: entering rshutdown" ); + + return SUCCESS; +} + +// Called for php_info(); +// Displays the INI settings registered and their current values + +PHP_MINFO_FUNCTION(pdo_sqlsrv) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "pdo_sqlsrv support", "enabled"); + php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} + +// *** internal init functions *** + +namespace { + + // mimic the functionality of the REGISTER_PDO_CLASS_CONST_LONG. We use this instead of the macro because + // we dynamically link the pdo_get_dbh_class function rather than use the static php_pdo_get_dbh_ce (see MINIT) + + void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( char const* name, long value TSRMLS_DC ) + { + zend_class_entry* zend_class = php_pdo_get_dbh_ce(); + + SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_LONG: php_pdo_get_dbh_ce failed"); + int zr = zend_declare_class_constant_long( zend_class, const_cast( name ), strlen( name ), value TSRMLS_CC ); + if( zr == FAILURE ) { + throw core::CoreException(); + } + } + + void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( char const* name, char const* value TSRMLS_DC ) + { + zend_class_entry* zend_class = php_pdo_get_dbh_ce(); + + SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_STRING: php_pdo_get_dbh_ce failed"); + int zr = zend_declare_class_constant_string( zend_class, const_cast( name ), strlen( name ), const_cast( value ) TSRMLS_CC ); + if( zr == FAILURE ) { + + throw core::CoreException(); + } + } + + // array of pdo constants. + sqlsrv_attr_pdo_constant pdo_attr_constants[] = { + + // driver specific attributes + { "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING }, + { "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT }, + { "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY }, + { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, + { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, + { "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE }, + + // used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int, + // PDO::PARAM_STR uses the size of the string in the variable + { "SQLSRV_PARAM_OUT_DEFAULT_SIZE" , -1 }, + + // encoding attributes + { "SQLSRV_ENCODING_DEFAULT" , SQLSRV_ENCODING_DEFAULT }, + { "SQLSRV_ENCODING_SYSTEM" , SQLSRV_ENCODING_SYSTEM }, + { "SQLSRV_ENCODING_BINARY" , SQLSRV_ENCODING_BINARY }, + { "SQLSRV_ENCODING_UTF8" , SQLSRV_ENCODING_UTF8 }, + + // cursor types (can be assigned to SQLSRV_ATTR_CURSOR_SCROLL_TYPE + { "SQLSRV_CURSOR_STATIC" , SQL_CURSOR_STATIC }, + { "SQLSRV_CURSOR_DYNAMIC" , SQL_CURSOR_DYNAMIC }, + { "SQLSRV_CURSOR_KEYSET" , SQL_CURSOR_KEYSET_DRIVEN }, + { "SQLSRV_CURSOR_BUFFERED" , static_cast(SQLSRV_CURSOR_BUFFERED) }, + + { NULL , 0 } // terminate the table + }; +} diff --git a/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp similarity index 96% rename from pdo_sqlsrv/pdo_parser.cpp rename to source/pdo_sqlsrv/pdo_parser.cpp index f055ea8e1..74c1a222b 100644 --- a/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -1,359 +1,362 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: pdo_parser.cpp -// -// Contents: Implements a parser to parse the PDO DSN. -// -// Copyright Microsoft Corporation -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "pdo_sqlsrv.h" - -// Constructor -conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ) -{ - this->conn_str = dsn; - this->len = len; - this->conn_options_ht = conn_options_ht; - this->pos = -1; - this->ctx = &ctx; -} - -// Move to the next character -inline bool conn_string_parser::next( void ) -{ - // if already at the end then return false - if( this->is_eos() ) { - - return false; - } - - SQLSRV_ASSERT( this->pos < len, "Unexpected cursor position in conn_string_parser::next" ); - - this->pos++; - - if ( this->is_eos() ) { - - return false; - } - - return true; -} - -// Check for end of string. -inline bool conn_string_parser::is_eos( void ) -{ - if( this->pos == len ) - { - return true; // EOS - } - - SQLSRV_ASSERT(this->pos < len, "Unexpected cursor position in conn_string_parser::is_eos" ); - - return false; -} - -// Check for white space. -inline bool conn_string_parser::is_white_space( char c ) -{ - if( c == ' ' || c == '\r' || c == '\n' || c == '\t' ) { - return true; - } - return false; -} - -// Discard any trailing white spaces. -int conn_string_parser::discard_trailing_white_spaces( const char* str, int len ) -{ - const char* end = str + ( len - 1 ); - - while(( this->is_white_space( *end ) ) && (len > 0) ) { - - len--; - end--; - } - - return len; -} - -// Discard white spaces. -bool conn_string_parser::discard_white_spaces() -{ - if( this->is_eos() ) { - - return false; - } - - while( this->is_white_space( this->conn_str[ pos ] )) { - - if( !next() ) - return false; - } - - return true; -} - -// Add a key-value pair to the hashtable of connection options. -void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC ) -{ - zval value_z; - ZVAL_UNDEF( &value_z ); - - if( len == 0 ) { - - ZVAL_STRINGL( &value_z, "", 0); - } - else { - - ZVAL_STRINGL( &value_z, const_cast( value ), len ); - } - - core::sqlsrv_zend_hash_index_update( *ctx, this->conn_options_ht, this->current_key, &value_z TSRMLS_CC ); -} - -// Validate a given DSN keyword. -void conn_string_parser::validate_key(const char *key, int key_len TSRMLS_DC ) -{ - int new_len = discard_trailing_white_spaces( key, key_len ); - - for( int i=0; PDO_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) - { - // discard the null terminator. - if( new_len == ( PDO_CONN_OPTS[ i ].sqlsrv_len - 1 ) && !_strnicmp( key, PDO_CONN_OPTS[ i ].sqlsrv_name, new_len )) { - - this->current_key = PDO_CONN_OPTS[ i ].conn_option_key; - this->current_key_name = PDO_CONN_OPTS[ i ].sqlsrv_name; - return; - } - } - - // encountered an invalid key, throw error. - sqlsrv_malloc_auto_ptr key_name; - key_name = static_cast( sqlsrv_malloc( new_len + 1 )); - memcpy_s( key_name, new_len + 1 ,key, new_len ); - key_name[ new_len ] = '\0'; - - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, key_name ); -} - -// Primary function which parses the connection string/DSN. -void conn_string_parser:: parse_conn_string( TSRMLS_D ) -{ - States state = FirstKeyValuePair; // starting state - int start_pos = -1; - - try { - - while( !this->is_eos() ) { - - switch( state ) { - - case FirstKeyValuePair: - { - // discard leading spaces - if( !next() || !discard_white_spaces() ) { - - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING ); //EOS - } - - state = Key; - break; - } - - case Key: - { - start_pos = this->pos; - - // read the key name - while( this->conn_str[ pos ] != '=' ) { - - if( !next() ) { - - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY ); //EOS - } - } - - this->validate_key( &( this->conn_str[ start_pos ] ), ( pos - start_pos ) TSRMLS_CC ); - - state = Value; - - break; - } - - case Value: - { - SQLSRV_ASSERT(( this->conn_str[ pos ] == '=' ), "conn_string_parser:: parse_conn_string: " - "Equal was expected" ); - - next(); // skip "=" - - // if EOS encountered after 0 or more spaces OR semi-colon encountered. - if( !discard_white_spaces() || this->conn_str[ pos ] == ';' ) { - - add_key_value_pair( NULL, 0 TSRMLS_CC ); - - if( this->is_eos() ) { - - break; // EOS - } - else { - - // this->conn_str[ pos ] == ';' - state = NextKeyValuePair; - } - } - - // if LCB - else if( this->conn_str[ pos ] == '{' ) { - - start_pos = this->pos; // starting character is LCB - state = ValueContent1; - } - - // If NonSP-LCB-SC - else { - - start_pos = this->pos; - state = ValueContent2; - } - - break; - } - - case ValueContent1: - { - while ( this->conn_str[ pos ] != '}' ) { - - if ( ! next() ) { - - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, this->current_key_name ); - } - } - - // If we reached here than RCB encountered - state = RCBEncountered; - - break; - } - - case ValueContent2: - { - while( this->conn_str[ pos ] != ';' ) { - - if( ! next() ) { - - break; //EOS - } - } - - if( !this->is_eos() && this->conn_str[ pos ] == ';' ) { - - // semi-colon encountered, so go to next key-value pair - state = NextKeyValuePair; - } - - add_key_value_pair( &( this->conn_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC ); - - SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )), - "conn_string_parser::parse_conn_string: Invalid state encountered " ); - - break; - } - - case RCBEncountered: - { - - // Read the next character after RCB. - if( !next() ) { - - // EOS - add_key_value_pair( &( this->conn_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC ); - break; - } - - SQLSRV_ASSERT( !this->is_eos(), "conn_string_parser::parse_conn_string: Unexpected EOS encountered" ); - - // if second RCB encountered than go back to ValueContent1 - if( this->conn_str[ pos ] == '}' ) { - - if( !next() ) { - - // EOS after a second RCB is error - THROW_PDO_ERROR( this->ctx, SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, this->current_key_name ); - } - - state = ValueContent1; - break; - } - - int end_pos = this->pos; - - // discard any trailing white-spaces. - if( this->is_white_space( this->conn_str[ pos ] )) { - - if( ! this->discard_white_spaces() ) { - - //EOS - add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); - break; - } - } - - // if semi-colon than go to next key-value pair - if ( this->conn_str[ pos ] == ';' ) { - - add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); - state = NextKeyValuePair; - break; - } - - // Non - (RCB, SP*, SC, EOS) character. Any other character after an RCB is an error. - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, this->current_key_name ); - break; - } - case NextKeyValuePair: - { - SQLSRV_ASSERT(( this->conn_str[ pos ] == ';' ), - "conn_string_parser::parse_conn_string: semi-colon was expected." ); - - // Call next() to skip the semi-colon. - if( !next() || !this->discard_white_spaces() ) { - - // EOS - break; - } - - if( this->conn_str[ pos ] == ';' ) { - - // a second semi-colon is error case. - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos ); - } - - else { - - // any other character leads to the next key - state = Key; - break; - } - } //case NextKeyValuePair - } // switch - } //while - } - catch( pdo::PDOException& ) { - - throw; - } -} - +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_parser.cpp +// +// Contents: Implements a parser to parse the PDO DSN. +// +// Copyright Microsoft Corporation +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_pdo_sqlsrv.h" + +// Constructor +conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ) +{ + this->conn_str = dsn; + this->len = len; + this->conn_options_ht = conn_options_ht; + this->pos = -1; + this->ctx = &ctx; + this->current_key = 0; + this->current_key_name = NULL; +} + +// Move to the next character +inline bool conn_string_parser::next( void ) +{ + // if already at the end then return false + if( this->is_eos() ) { + + return false; + } + + SQLSRV_ASSERT( this->pos < len, "Unexpected cursor position in conn_string_parser::next" ); + + this->pos++; + + if ( this->is_eos() ) { + + return false; + } + + return true; +} + +// Check for end of string. +inline bool conn_string_parser::is_eos( void ) +{ + if( this->pos == len ) + { + return true; // EOS + } + + SQLSRV_ASSERT(this->pos < len, "Unexpected cursor position in conn_string_parser::is_eos" ); + + return false; +} + +// Check for white space. +inline bool conn_string_parser::is_white_space( char c ) +{ + if( c == ' ' || c == '\r' || c == '\n' || c == '\t' ) { + return true; + } + return false; +} + +// Discard any trailing white spaces. +int conn_string_parser::discard_trailing_white_spaces( const char* str, int len ) +{ + const char* end = str + ( len - 1 ); + + while(( this->is_white_space( *end ) ) && (len > 0) ) { + + len--; + end--; + } + + return len; +} + +// Discard white spaces. +bool conn_string_parser::discard_white_spaces() +{ + if( this->is_eos() ) { + + return false; + } + + while( this->is_white_space( this->conn_str[ pos ] )) { + + if( !next() ) + return false; + } + + return true; +} + +// Add a key-value pair to the hashtable of connection options. +void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC ) +{ + zval value_z; + ZVAL_UNDEF( &value_z ); + + if( len == 0 ) { + + ZVAL_STRINGL( &value_z, "", 0); + } + else { + + ZVAL_STRINGL( &value_z, const_cast( value ), len ); + } + + core::sqlsrv_zend_hash_index_update( *ctx, this->conn_options_ht, this->current_key, &value_z TSRMLS_CC ); +} + +// Validate a given DSN keyword. +void conn_string_parser::validate_key(const char *key, int key_len TSRMLS_DC ) +{ + int new_len = discard_trailing_white_spaces( key, key_len ); + + for( int i=0; PDO_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) + { + // discard the null terminator. + if( new_len == ( PDO_CONN_OPTS[ i ].sqlsrv_len - 1 ) && !strncasecmp( key, PDO_CONN_OPTS[ i ].sqlsrv_name, new_len )) { + + this->current_key = PDO_CONN_OPTS[ i ].conn_option_key; + this->current_key_name = PDO_CONN_OPTS[ i ].sqlsrv_name; + return; + } + } + + // encountered an invalid key, throw error. + sqlsrv_malloc_auto_ptr key_name; + key_name = static_cast( sqlsrv_malloc( new_len + 1 )); + memcpy_s( key_name, new_len + 1 ,key, new_len ); + + key_name[ new_len ] = '\0'; + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast( key_name ) ); +} + +// Primary function which parses the connection string/DSN. +void conn_string_parser:: parse_conn_string( TSRMLS_D ) +{ + States state = FirstKeyValuePair; // starting state + int start_pos = -1; + + try { + + while( !this->is_eos() ) { + + switch( state ) { + + case FirstKeyValuePair: + { + // discard leading spaces + if( !next() || !discard_white_spaces() ) { + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING ); //EOS + } + + state = Key; + break; + } + + case Key: + { + start_pos = this->pos; + + // read the key name + while( this->conn_str[ pos ] != '=' ) { + + if( !next() ) { + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY ); //EOS + } + } + + this->validate_key( &( this->conn_str[ start_pos ] ), ( pos - start_pos ) TSRMLS_CC ); + + state = Value; + + break; + } + + case Value: + { + SQLSRV_ASSERT(( this->conn_str[ pos ] == '=' ), "conn_string_parser:: parse_conn_string: " + "Equal was expected" ); + + next(); // skip "=" + + // if EOS encountered after 0 or more spaces OR semi-colon encountered. + if( !discard_white_spaces() || this->conn_str[ pos ] == ';' ) { + + add_key_value_pair( NULL, 0 TSRMLS_CC ); + + if( this->is_eos() ) { + + break; // EOS + } + else { + + // this->conn_str[ pos ] == ';' + state = NextKeyValuePair; + } + } + + // if LCB + else if( this->conn_str[ pos ] == '{' ) { + + start_pos = this->pos; // starting character is LCB + state = ValueContent1; + } + + // If NonSP-LCB-SC + else { + + start_pos = this->pos; + state = ValueContent2; + } + + break; + } + + case ValueContent1: + { + while ( this->conn_str[ pos ] != '}' ) { + + if ( ! next() ) { + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, this->current_key_name ); + } + } + + // If we reached here than RCB encountered + state = RCBEncountered; + + break; + } + + case ValueContent2: + { + while( this->conn_str[ pos ] != ';' ) { + + if( ! next() ) { + + break; //EOS + } + } + + if( !this->is_eos() && this->conn_str[ pos ] == ';' ) { + + // semi-colon encountered, so go to next key-value pair + state = NextKeyValuePair; + } + + add_key_value_pair( &( this->conn_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC ); + + SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )), + "conn_string_parser::parse_conn_string: Invalid state encountered " ); + + break; + } + + case RCBEncountered: + { + + // Read the next character after RCB. + if( !next() ) { + + // EOS + add_key_value_pair( &( this->conn_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC ); + break; + } + + SQLSRV_ASSERT( !this->is_eos(), "conn_string_parser::parse_conn_string: Unexpected EOS encountered" ); + + // if second RCB encountered than go back to ValueContent1 + if( this->conn_str[ pos ] == '}' ) { + + if( !next() ) { + + // EOS after a second RCB is error + THROW_PDO_ERROR( this->ctx, SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, this->current_key_name ); + } + + state = ValueContent1; + break; + } + + int end_pos = this->pos; + + // discard any trailing white-spaces. + if( this->is_white_space( this->conn_str[ pos ] )) { + + if( ! this->discard_white_spaces() ) { + + //EOS + add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); + break; + } + } + + // if semi-colon than go to next key-value pair + if ( this->conn_str[ pos ] == ';' ) { + + add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); + state = NextKeyValuePair; + break; + } + + // Non - (RCB, SP*, SC, EOS) character. Any other character after an RCB is an error. + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, this->current_key_name ); + break; + } + case NextKeyValuePair: + { + SQLSRV_ASSERT(( this->conn_str[ pos ] == ';' ), + "conn_string_parser::parse_conn_string: semi-colon was expected." ); + + // Call next() to skip the semi-colon. + if( !next() || !this->discard_white_spaces() ) { + + // EOS + break; + } + + if( this->conn_str[ pos ] == ';' ) { + + // a second semi-colon is error case. + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos ); + } + + else { + + // any other character leads to the next key + state = Key; + break; + } + } //case NextKeyValuePair + } // switch + } //while + } + catch( pdo::PDOException& ) { + + throw; + } +} + diff --git a/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp similarity index 93% rename from pdo_sqlsrv/pdo_stmt.cpp rename to source/pdo_sqlsrv/pdo_stmt.cpp index 324afebca..ff92c6d47 100644 --- a/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1,1340 +1,1358 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: pdo_stmt.cpp -// -// Contents: Implements the PDOStatement object for the PDO_SQLSRV -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "pdo_sqlsrv.h" - -// *** internal variables and constants *** - -namespace { - -// Maps to the list of PDO::FETCH_ORI_* constants -SQLSMALLINT odbc_fetch_orientation[] = -{ - SQL_FETCH_NEXT, // PDO_FETCH_ORI_NEXT - SQL_FETCH_PRIOR, // PDO_FETCH_ORI_PRIOR - SQL_FETCH_FIRST, // PDO_FETCH_ORI_FIRST - SQL_FETCH_LAST, // PDO_FETCH_ORI_LAST - SQL_FETCH_ABSOLUTE, // PDO_FETCH_ORI_ABS - SQL_FETCH_RELATIVE // PDO_FETCH_ORI_REL -}; - -// max length of a field type -const int SQL_SERVER_IDENT_SIZE_MAX = 128; - -inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori (enum pdo_fetch_orientation ori) -{ - SQLSRV_ASSERT( ori >= PDO_FETCH_ORI_NEXT && ori <= PDO_FETCH_ORI_REL, "Fetch orientation out of range." ); - OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); - OACR_WARNING_SUPPRESS( 26000, "Buffer length verified above" ); - return odbc_fetch_orientation[ori]; -} - -// Returns SQLSRV data type for a given PDO type. See pdo_param_type -// for list of supported pdo types. -SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( sqlsrv_stmt* driver_stmt, enum pdo_param_type pdo_type TSRMLS_DC ) -{ - switch( pdo_type ) { - - case PDO_PARAM_BOOL: - case PDO_PARAM_INT: - return SQLSRV_PHPTYPE_INT; - - case PDO_PARAM_STR: - return SQLSRV_PHPTYPE_STRING; - - case PDO_PARAM_NULL: - return SQLSRV_PHPTYPE_NULL; - - case PDO_PARAM_LOB: - // TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented. - return SQLSRV_PHPTYPE_STRING; - - case PDO_PARAM_STMT: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED ); - break; - - default: - DIE( "pdo_type_to_sqlsrv_php_type: Unexpected pdo_param_type encountered" ); - } - - return SQLSRV_PHPTYPE_INVALID; // to prevent compiler warning -} - -// Returns a pdo type for a given SQL type. See pdo_param_type -// for list of supported pdo types. -inline pdo_param_type sql_type_to_pdo_type( SQLSMALLINT sql_type ) -{ - pdo_param_type return_type = PDO_PARAM_STR; - - switch( sql_type ) { - - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - case SQL_BIGINT: - case SQL_BINARY: - case SQL_CHAR: - case SQL_DECIMAL: - case SQL_DOUBLE: - case SQL_FLOAT: - case SQL_GUID: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_NUMERIC: - case SQL_REAL: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_UDT: - case SQL_SS_VARIANT: - case SQL_SS_XML: - case SQL_TYPE_DATE: - case SQL_TYPE_TIMESTAMP: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_WCHAR: - case SQL_WLONGVARCHAR: - case SQL_WVARCHAR: - return_type = PDO_PARAM_STR; - break; - - default: { - DIE( "sql_type_to_pdo_type: Invalid SQL type provided." ); - break; - } - } - - return return_type; -} - -// Calls core_sqlsrv_set_scrollable function to set cursor. -// PDO supports two cursor types: PDO_CURSOR_FWDONLY, PDO_CURSOR_SCROLL. -void set_stmt_cursors( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - if( Z_TYPE_P( value_z ) != IS_LONG ) { - - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); - } - - zend_long pdo_cursor_type = Z_LVAL_P( value_z ); - long odbc_cursor_type = -1; - - switch( pdo_cursor_type ) { - - case PDO_CURSOR_FWDONLY: - odbc_cursor_type = SQL_CURSOR_FORWARD_ONLY; - break; - - case PDO_CURSOR_SCROLL: - odbc_cursor_type = SQL_CURSOR_STATIC; - break; - - default: - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); - } - - core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); -} - -void set_stmt_cursor_scroll_type( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - if( Z_TYPE_P( value_z ) != IS_LONG ) { - - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); - } - - if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { - - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE ); - } - - long odbc_cursor_type = static_cast( Z_LVAL_P( value_z ) ); - - core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); - - return; -} - -// Sets the statement encoding. Default encoding on the statement -// implies use the connection's encoding. -void set_stmt_encoding( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - // validate the value - if( Z_TYPE_P( value_z ) != IS_LONG ) { - - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING ); - } - - zend_long attr_value = Z_LVAL_P( value_z ); - - switch( attr_value ) { - - // when the default encoding is applied to a statement, it means use the creating connection's encoding - case SQLSRV_ENCODING_DEFAULT: - case SQLSRV_ENCODING_BINARY: - case SQLSRV_ENCODING_SYSTEM: - case SQLSRV_ENCODING_UTF8: - stmt->set_encoding( static_cast( attr_value )); - break; - - default: - THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING ); - break; - } -} - -// internal helper function to free meta data structures allocated -void meta_data_free( field_meta_data* meta ) -{ - if( meta->field_name ) { - meta->field_name.reset(); - } - sqlsrv_free( meta ); -} - -zval convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len ) -{ - zval out_zval; - - switch( sqlsrv_php_type ) { - - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - { - if( *in_val == NULL ) { - ZVAL_NULL( &out_zval ); - } - else { - - if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) { - ZVAL_LONG( &out_zval, **( reinterpret_cast( in_val ))); - } - else { - ZVAL_DOUBLE( &out_zval, **( reinterpret_cast( in_val ))); - } - } - - if( *in_val ) { - sqlsrv_free( *in_val ); - } - - break; - } - - case SQLSRV_PHPTYPE_STRING: - case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented - { - - if( *in_val == NULL ) { - - ZVAL_NULL( &out_zval ); - } - else { - - ZVAL_STRINGL( &out_zval, reinterpret_cast( *in_val ), field_len ); - sqlsrv_free( *in_val ); - } - break; - } - - case SQLSRV_PHPTYPE_DATETIME: - DIE( "Unsupported php type" ); - out_zval = *( reinterpret_cast( *in_val )); - break; - - case SQLSRV_PHPTYPE_NULL: - ZVAL_NULL( &out_zval ); - break; - - default: - DIE( "Unknown php type" ); - break; - } - - return out_zval; -} - -} // namespace - -int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC); -int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC); -int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, - zend_long offset TSRMLS_DC); -int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, - struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC); -int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC); -int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, - char **ptr, size_t *len, int *caller_frees TSRMLS_DC); -int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC); -int pdo_sqlsrv_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC); -int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC); -int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC); -int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC); - -struct pdo_stmt_methods pdo_sqlsrv_stmt_methods = { - - pdo_sqlsrv_stmt_dtor, - pdo_sqlsrv_stmt_execute, - pdo_sqlsrv_stmt_fetch, - pdo_sqlsrv_stmt_describe_col, - pdo_sqlsrv_stmt_get_col_data, - pdo_sqlsrv_stmt_param_hook, - pdo_sqlsrv_stmt_set_attr, - pdo_sqlsrv_stmt_get_attr, - pdo_sqlsrv_stmt_get_col_meta, - pdo_sqlsrv_stmt_next_rowset, - pdo_sqlsrv_stmt_close_cursor - -}; - -void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - set_stmt_cursors( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_encoding:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - set_stmt_encoding( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_direct_query:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); - pdo_stmt->direct_query = ( zend_is_true( value_z )) ? true : false; -} - -void stmt_option_cursor_scroll_type:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - set_stmt_cursor_scroll_type( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_emulate_prepares:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - pdo_stmt_t *pdo_stmt = static_cast( stmt->driver() ); - pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; -} - -void stmt_option_fetch_numeric:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); - pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false; -} - - -// log a function entry point -#define PDO_LOG_STMT_ENTRY \ -{ \ - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ - driver_stmt->set_func( __FUNCTION__ ); \ - LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ -} - -// PDO SQLSRV statement destructor -pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void ) -{ - std::for_each( current_meta_data.begin(), current_meta_data.end(), meta_data_free ); - current_meta_data.clear(); - - if( bound_column_param_types ) { - sqlsrv_free( bound_column_param_types ); - bound_column_param_types = NULL; - } - - if( direct_query_subst_string ) { - // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme - // and we use estrdup to allocate this string, which uses emalloc - efree( reinterpret_cast( const_cast( direct_query_subst_string ))); - } -} - - -// pdo_sqlsrv_stmt_close_cursor -// Close any open cursors on the statement. Maps to PDO function PDOStatement::closeCursor. -// Parameters: -// *stmt - Pointer to current statement -// Return: -// Returns 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" ); - - sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); - - // to "close the cursor" means we make the statement ready for execution again. To do this, we - // skip all the result sets on the current statement. - while( driver_stmt->past_next_result_end == false ) { - - core_sqlsrv_next_result( driver_stmt TSRMLS_CC ); - } - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advanding to the next result set." ); - } - - return 1; -} - -// pdo_sqlsrv_stmt_describe_col -// Gets the metadata for a column based on the column number. -// Calls the core_sqlsrv_field_metadata function present in the core layer. -// Parameters: -// *stmt - pointer to current statement -// colno - Index of the column which requires description. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - SQLSRV_ASSERT(( colno >= 0 ), "pdo_sqlsrv_stmt_describe_col: Column number should be >= 0." ); - - sqlsrv_malloc_auto_ptr core_meta_data; - - try { - - core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast( stmt->driver_data ), colno TSRMLS_CC ); - } - - catch( core::CoreException& ) { - return 0; - } - - catch(...) { - DIE( "pdo_sqlsrv_stmt_describe_col: Unexpected exception occurred." ); - } - - pdo_column_data* column_data = &(stmt->columns[colno]); - - SQLSRV_ASSERT( column_data != NULL, "pdo_sqsrv_stmt_describe_col: pdo_column_data was null" ); - - // Set the name - column_data->name = zend_string_init( (const char*)core_meta_data->field_name.get(), core_meta_data->field_name_len, 0 ); - core_meta_data->field_name.reset(); - - // Set the maxlen - column_data->maxlen = ( core_meta_data->field_precision > 0 ) ? core_meta_data->field_precision : core_meta_data->field_size; - - // Set the precision - column_data->precision = core_meta_data->field_scale; - - // Set the param_type - column_data->param_type = PDO_PARAM_ZVAL; - - // store the field data for use by pdo_sqlsrv_stmt_get_col_data - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - SQLSRV_ASSERT( driver_stmt != NULL, "Invalid driver statement in pdo_sqlsrv_stmt_describe_col" ); - driver_stmt->current_meta_data.push_back( core_meta_data.get() ); - SQLSRV_ASSERT( driver_stmt->current_meta_data.size() == colno + 1, "Meta data vector out of sync with column numbers" ); - core_meta_data.transferred(); - - return 1; -} - - -// pdo_sqlsrv_stmt_dtor -// Maps to PDOStatement::__destruct. Destructor for the PDO Statement. -// Parameters: -// *stmt - pointer to current statement -// Return: -// 1 for success. -int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) -{ - sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - - LOG( SEV_NOTICE, "pdo_sqlsrv_stmt_dtor: entering" ); - - // if a PDO statement didn't complete preparation, its driver_data can be NULL - if( driver_stmt == NULL ) { - - return 1; - } - - driver_stmt->~sqlsrv_stmt(); - - sqlsrv_free( driver_stmt ); - - stmt->driver_data = NULL; - - return 1; -} - -// pdo_sqlsrv_stmt_execute -// Maps to PDOStatement::Execute. Executes the prepared statement. -// Parameters: -// *stmt - pointer to the current statement. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_execute: driver_data object was null" ); - - // prepare for execution by flushing anything remaining in the result set if it wasn't already - // done before binding parameters - if( driver_stmt->executed && !driver_stmt->past_next_result_end ) { - - while( driver_stmt->past_next_result_end == false ) { - - core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false ); - } - } - - const char* query = NULL; - unsigned int query_len = 0; - - // if the user is doing a direct query (PDO::SQLSRV_ATTR_DIRECT_QUERY), set the query here - if( driver_stmt->direct_query ) { - - query = driver_stmt->direct_query_subst_string; - query_len = static_cast( driver_stmt->direct_query_subst_string_len ); - } - - // if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the - // subtituted query provided by PDO - if( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { - - query = stmt->active_query_string; - query_len = static_cast( stmt->active_query_stringlen ); - } - - core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len ); - - stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); - - // return the row count regardless if there are any rows or not - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); - - // workaround for a bug in the PDO driver manager. It is fairly simple to crash the PDO driver manager with - // the following sequence: - // 1) Prepare and execute a statement (that has some results with it) - // 2) call PDOStatement::nextRowset until there are no more results - // 3) execute the statement again - // 4) call PDOStatement::getColumnMeta - // It crashes from what I can tell because there is no metadata because there was no call to - // pdo_stmt_sqlsrv_describe_col and stmt->columns is NULL on the second call to - // PDO::execute. My guess is that because stmt->executed is true, it optimizes away a necessary call to - // pdo_sqlsrv_stmt_describe_col. By setting the stmt->executed flag to 0, this call is not optimized away - // and the crash disappears. - if( stmt->columns == NULL ) { - stmt->executed = 0; - } - } - catch( core::CoreException& /*e*/ ) { - - return 0; - - } - catch( ... ) { - - DIE( "pdo_sqlsrv_stmt_execute: Unexpected exception occurred." ); - - } - - // success - return 1; -} - - - -// pdo_sqlsrv_stmt_fetch -// Maps to PDOStatement::fetch -// Move the cursor to the record indicated. If the cursor is moved off the end, -// or before the beginning if a scrollable cursor is created, then FAILURE is returned. -// Parameters: -// *stmt - pointer to current statement for which the cursor should be moved. -// ori - cursor orientation. Maps to the list of PDO::FETCH_ORI_* constants -// offset - For orientations that use it, offset to move to. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, - zend_long offset TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_fetch: pdo_stmt object was null" ); - - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_fetch: driver_data object was null" ); - - // set the types for bound columns to zval so that PDO does no conversion when the value - // is returned by pdo_sqlsrv_get_col_data. Remember the types that were bound by the user - // and use it to manually convert data types - if( stmt->bound_columns ) { - - pdo_bound_param_data* bind_data = NULL; - - if( !driver_stmt->bound_column_param_types ) { - driver_stmt->bound_column_param_types = - reinterpret_cast( sqlsrv_malloc( stmt->column_count, sizeof( pdo_param_type ), 0 )); - std::fill( driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count, - PDO_PARAM_ZVAL ); - } - - for( long i = 0; i < stmt->column_count; ++i ) { - - if (NULL== (bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, i))) && - (NULL == (bind_data = reinterpret_cast(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) { - - driver_stmt->bound_column_param_types[ i ] = PDO_PARAM_ZVAL; - continue; - } - - if( bind_data->param_type != PDO_PARAM_ZVAL ) { - - driver_stmt->bound_column_param_types[ i ] = bind_data->param_type; - bind_data->param_type = PDO_PARAM_ZVAL; - } - } - } - - SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori ); - bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset TSRMLS_CC ); - - // support for the PDO rowCount method. Since rowCount doesn't call a method, PDO relies on us to fill the - // pdo_stmt_t::row_count member - if( driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { - - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); - - // a row_count of -1 means no rows, but we change it to 0 - if( stmt->row_count == -1 ) { - - stmt->row_count = 0; - } - } - - // if no data was returned, then return false so data isn't retrieved - if( !data ) { - return 0; - } - - return 1; - } - - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE ("pdo_sqlsrv_stmt_fetch: Unexpected exception occurred."); - } -} - -// pdo_sqlsrv_stmt_get_col_data -// Called by the set of PDO Fetch functions. -// Retrieves a single column. PDO driver manager is responsible for freeing the -// returned buffer. Because PDO can request fields out of order and ODBC does not -// support out of order field requests, this function should also cache fields. -// Parameters: -// stmt - Statement to retrive the column for. -// colno - Index of the column that needs to be retrieved. Starts with 0. -// ptr - Returns the buffer containing the column data. -// len - Length of the buffer returned. -// caller_frees - Flag to let the PDO driver manager know that it is responsible for -// freeing the memory. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, - char **ptr, size_t *len, int *caller_frees TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: pdo_stmt object was null" ); - - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: driver_data object was null" ); - - CHECK_CUSTOM_ERROR((colno < 0), driver_stmt, PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX ) { - return 0; - } - - // Let PDO free the memory after use. - *caller_frees = 1; - - // set the metadata for the current column - pdo_column_data* column_data = &(stmt->columns[colno]); - - // translate the pdo type to a type the core layer understands - sqlsrv_phptype sqlsrv_php_type; - SQLSRV_ASSERT( colno >= 0 && colno < static_cast( driver_stmt->current_meta_data.size()), - "Invalid column number in pdo_sqlsrv_stmt_get_col_data" ); - sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast( driver_stmt->current_meta_data[ colno ]->field_type ), - static_cast( driver_stmt->current_meta_data[ colno ]->field_size ), - true, driver_stmt->fetch_numeric ); - - // set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding - sqlsrv_php_type.typeinfo.encoding = driver_stmt->encoding(); - - // if a column is bound to a type different than the column type, figure out a way to convert it to the - // type they want - if( stmt->bound_columns && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_ZVAL ) { - - sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type( driver_stmt, - driver_stmt->bound_column_param_types[ colno ] - TSRMLS_CC ); - - pdo_bound_param_data* bind_data = NULL; - bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, colno)); - - if( bind_data != NULL && !Z_ISUNDEF(bind_data->driver_params) ) { - - CHECK_CUSTOM_ERROR( Z_TYPE( bind_data->driver_params ) != IS_LONG, driver_stmt, - PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, colno + 1 ) { - throw pdo::PDOException(); - } - - CHECK_CUSTOM_ERROR( driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_STR - && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_LOB, driver_stmt, - PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, colno + 1 ) { - - throw pdo::PDOException(); - } - - sqlsrv_php_type.typeinfo.encoding = Z_LVAL( bind_data->driver_params ); - - switch( sqlsrv_php_type.typeinfo.encoding ) { - case SQLSRV_ENCODING_SYSTEM: - case SQLSRV_ENCODING_BINARY: - case SQLSRV_ENCODING_UTF8: - break; - default: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, colno ); - break; - } - } - } - - SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID; - core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast(ptr)), - reinterpret_cast( len ), true, &sqlsrv_phptype_out TSRMLS_CC ); - - zval* zval_ptr = ( zval* )( sqlsrv_malloc( sizeof( zval ))); - *zval_ptr = convert_to_zval( sqlsrv_phptype_out, reinterpret_cast( ptr ), *len ); - *ptr = reinterpret_cast( zval_ptr ); - - *len = sizeof( zval ); - - return 1; - } - - catch ( core::CoreException& ) { - return 0; - } - catch ( ... ) { - DIE ("pdo_sqlsrv_stmt_get_col_data: Unexpected exception occurred."); - } -} - -// pdo_sqlsrv_stmt_set_attr -// Maps to the PDOStatement::setAttribute. Sets the attribute on a statement. -// Parameters: -// stmt - Current statement on which the attribute should be set. -// attr - Represents any valid set of attribute constants supported by this driver. -// val - Attribute value. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - pdo_sqlsrv_stmt* pdo_stmt = static_cast( stmt->driver_data ); - sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); - - try { - - switch( attr ) { - - case SQLSRV_ATTR_DIRECT_QUERY: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY ); - break; - - case SQLSRV_ATTR_ENCODING: - set_stmt_encoding( driver_stmt, val TSRMLS_CC ); - break; - - case PDO_ATTR_CURSOR: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY ); - break; - - case SQLSRV_ATTR_QUERY_TIMEOUT: - core_sqlsrv_set_query_timeout( driver_stmt, val TSRMLS_CC ); - break; - - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY ); - break; - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - core_sqlsrv_set_buffered_query_limit( driver_stmt, val TSRMLS_CC ); - break; - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - pdo_stmt->fetch_numeric = ( zend_is_true( val )) ? true : false; - break; - - default: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); - break; - } - } - catch( core::CoreException& ) { - return 0; - } - - catch ( ... ) { - DIE ("pdo_sqlsrv_stmt_set_attr: Unexpected exception occurred."); - } - - return 1; -} - - -// pdo_sqlsrv_stmt_get_attr -// Maps to the PDOStatement::getAttribute. Gets the value of a given attribute on a statement. -// Parameters: -// stmt - Current statement for which the attribute value is requested. -// attr - Represents any valid set of attribute constants supported by this driver. -// return_value - Attribute value. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_get_attr( pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC ) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - pdo_sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); - SQLSRV_ASSERT(( driver_stmt != NULL ), "pdo_sqlsrv_stmt_get_attr: stmt->driver_data was null" ); - - try { - - switch( attr ) { - - case SQLSRV_ATTR_DIRECT_QUERY: - { - ZVAL_BOOL( return_value, driver_stmt->direct_query ); - break; - } - - case SQLSRV_ATTR_ENCODING: - { - ZVAL_LONG( return_value, driver_stmt->encoding() ); - break; - } - - case PDO_ATTR_CURSOR: - { - ZVAL_LONG( return_value, ( driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ? - PDO_CURSOR_SCROLL : PDO_CURSOR_FWDONLY )); - break; - } - - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - { - ZVAL_LONG( return_value, driver_stmt->cursor_type ); - break; - } - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - { - ZVAL_LONG( return_value, driver_stmt->buffered_query_limit ); - break; - } - - case SQLSRV_ATTR_QUERY_TIMEOUT: - { - ZVAL_LONG( return_value, ( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_stmt->query_timeout )); - break; - } - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - { - ZVAL_BOOL( return_value, driver_stmt->fetch_numeric ); - break; - } - - default: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); - break; - } - } - catch( core::CoreException& ) { - - return 0; - } - catch ( ... ) { - - DIE ("pdo_sqlsrv_stmt_get_attr: Unexpected exception occurred."); - } - - return 1; -} - -// pdo_sqlsrv_stmt_get_col_meta -// Maps to PDOStatement::getColumnMeta. Return extra metadata. -// Though we don't return any extra metadata, PDO relies on us to -// create the associative array that holds the standard information, -// so we create one and return it for PDO's use. -// Parameters: -// stmt - Current statement. -// colno - The index of the field for which to return the metadata. -// return_value - zval* consisting of the metadata. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." ); - - sqlsrv_malloc_auto_ptr core_meta_data; - - sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); - - SQLSRV_ASSERT( colno >= 0 && colno < SHRT_MAX, "pdo_sqlsrv_stmt_get_col_meta: tried to overflow a short" ); - core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); - // initialize the array to nothing, as PDO requires us to create it - core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC ); - - // add the following fields: flags, native_type, driver:decl_type, table - add_assoc_long( return_value, "flags", 0 ); - - // get the name of the data type - char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ]; - SQLSMALLINT out_buff_len; - SQLLEN not_used; - core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name, - sizeof( field_type_name ), &out_buff_len, ¬_used TSRMLS_CC ); - add_assoc_string( return_value, "sqlsrv:decl_type", field_type_name ); - - // get the PHP type of the column. The types returned here mirror the types returned by debug_zval_dump when - // given a variable of the same type. However, debug_zval_dump also gives the length of a string, and we only - // say string, since the length is given in another field of the metadata array. - long pdo_type = sql_type_to_pdo_type( core_meta_data->field_type ); - switch( pdo_type ) { - case PDO_PARAM_STR: - add_assoc_string( return_value, "native_type", "string" ); - break; - default: - DIE( "pdo_sqlsrv_stmt_get_col_data: Unknown PDO type returned" ); - break; - } - - // add the table name of the field. All the tests so far show this to always be "", but we adhere to the PDO spec - char table_name[ SQL_SERVER_IDENT_SIZE_MAX ]; - SQLLEN field_type_num; - core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TABLE_NAME, table_name, SQL_SERVER_IDENT_SIZE_MAX, - &out_buff_len, &field_type_num TSRMLS_CC ); - add_assoc_string( return_value, "table", table_name ); - - if( stmt->columns[ colno ].param_type == PDO_PARAM_ZVAL ) { - add_assoc_long( return_value, "pdo_type", pdo_type ); - } - - // this will ensure that the field_name field, which is an auto pointer gets freed. - (*core_meta_data).~field_meta_data(); - } - catch( core::CoreException& ) { - - return 0; - } - catch(...) { - - DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." ); - } - - return 1; -} - - -// pdo_sqlsrv_stmt_next_rowset -// Maps to PDOStatement::nextRowset. -// Move the cursor to the beginning of the next rowset in a multi-rowset result. -// Clears the field cache from the last row retrieved using pdo_sqlsrv_stmt_get_col_data. -// Calls core_sqlsrv_next_result using the core_stmt found within stmt->driver_data. -// If another result set is available, this function returns 1. Otherwise it returns 0. -// Parameters: -// stmt - PDOStatement object containing the result set. -// Return: -// 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - try { - - SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" ); - - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); - - core_sqlsrv_next_result( static_cast( stmt->driver_data ) TSRMLS_CC ); - - // clear the current meta data since the new result will generate new meta data - std::for_each( driver_stmt->current_meta_data.begin(), driver_stmt->current_meta_data.end(), meta_data_free ); - driver_stmt->current_meta_data.clear(); - - // if there are no more result sets, return that it failed. - if( driver_stmt->past_next_result_end == true ) { - return 0; - } - - stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); - - // return the row count regardless if there are any rows or not - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advancing to the next result set." ); - } - - return 1; -} - -// pdo_sqlsrv_stmt_param_hook -// Maps to PDOStatement::bindColumn. -// Called by PDO driver manager to bind a parameter or column. -// This function pulls several duties for binding parameters and columns. -// It takes an event as a parameter that explains what the function should do on -// behalf of a parameter or column. We only use one of these events, -// PDO_PARAM_EVT_EXEC_PRE, the remainder simply return. -// Paramters: -// stmt - PDO Statement object to bind a parameter. -// param - paramter to bind. -// event_type - Event to bind a parameter -// Return: -// Returns 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, - struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC) -{ - PDO_RESET_STMT_ERROR; - - try { - - switch( event_type ) { - - // since the param isn't reliable, we don't do anything here - case PDO_PARAM_EVT_ALLOC: - // if emulate prepare is on, set the bind_param_encoding so it can be used in PDO::quote when binding parameters on the client side - if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( stmt->dbh->driver_data ); - driver_dbh->bind_param_encoding = static_cast( Z_LVAL( param->driver_params )); - } - break; - case PDO_PARAM_EVT_FREE: - break; - // bind the parameter in the core layer - case PDO_PARAM_EVT_EXEC_PRE: - { - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - // skip column bindings - if( !param->is_param ) { - break; - } - - sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_param_hook: driver_data object was null" ); - - // prepare for binding parameters by flushing anything remaining in the result set - if( driver_stmt->executed && !driver_stmt->past_next_result_end ) { - - while( driver_stmt->past_next_result_end == false ) { - - core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false ); - } - } - - int direction = SQL_PARAM_INPUT; - SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE; - SQLULEN column_size = SQLSRV_UNKNOWN_SIZE; - SQLSMALLINT decimal_digits = 0; - // determine the direction of the parameter. By default it's input, but if the user specifies a size - // that means they want output, and if they include the flag, then it's input/output. - // It's invalid to specify the input/output flag but not specify a length - CHECK_CUSTOM_ERROR( (param->param_type & PDO_PARAM_INPUT_OUTPUT) && (param->max_value_len == 0), - driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) { - throw pdo::PDOException(); - } - if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) { - if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) { - direction = SQL_PARAM_INPUT_OUTPUT; - } - else { - direction = SQL_PARAM_OUTPUT; - } - } - // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant - // and the SQLSRV_PHPTYPE_* constant - int pdo_type = param->param_type; - SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; - switch( pdo_type & ~PDO_PARAM_INPUT_OUTPUT ) { - case PDO_PARAM_BOOL: - case PDO_PARAM_INT: - php_out_type = SQLSRV_PHPTYPE_INT; - break; - case PDO_PARAM_STR: - php_out_type = SQLSRV_PHPTYPE_STRING; - break; - // when the user states PDO::PARAM_NULL, they mean send a null no matter what the variable is - // since the core layer keys off the zval type, we substitute a null for what they gave us - case PDO_PARAM_NULL: - { - zval null_zval; - php_out_type = SQLSRV_PHPTYPE_NULL; - - ZVAL_NULL( &null_zval ); - zval_ptr_dtor( ¶m->parameter ); - param->parameter = null_zval; - break; - } - case PDO_PARAM_LOB: - php_out_type = SQLSRV_PHPTYPE_STREAM; - break; - case PDO_PARAM_STMT: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED ); - break; - default: - SQLSRV_ASSERT( false, "Unknown PDO::PARAM_* constant given." ); - break; - } - // set the column size parameter for bind_param if we are expecting something back - if( direction != SQL_PARAM_INPUT ) { - switch( php_out_type ) { - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_STREAM: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE ); - break; - case SQLSRV_PHPTYPE_INT: - column_size = SQLSRV_UNKNOWN_SIZE; - break; - case SQLSRV_PHPTYPE_STRING: - { - CHECK_CUSTOM_ERROR( param->max_value_len <= 0, driver_stmt, - PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, param->paramno + 1 ) { - throw pdo::PDOException(); - } - column_size = param->max_value_len; - break; - } - default: - SQLSRV_ASSERT( false, "Invalid PHP type for output parameter. Should have been caught already." ); - break; - } - } - // block all objects from being bound as input or input/output parameters since there is a - // weird case: - // $obj = date_create(); - // $s->bindParam( n, $obj, PDO::PARAM_INT ); // anything different than PDO::PARAM_STR - // that succeeds since the core layer implements DateTime object handling for the sqlsrv - // 2.0 driver. To be consistent and avoid surprises of one object type working and others - // not, we block all objects here. - CHECK_CUSTOM_ERROR( direction != SQL_PARAM_OUTPUT && Z_TYPE( param->parameter ) == IS_OBJECT, - driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) { - throw pdo::PDOException(); - } - // the encoding by default is that set on the statement - SQLSRV_ENCODING encoding = driver_stmt->encoding(); - // if the statement's encoding is the default, then use the one on the connection - if( encoding == SQLSRV_ENCODING_DEFAULT ) { - encoding = driver_stmt->conn->encoding(); - } - // if the user provided an encoding, use it instead - if( !Z_ISUNDEF(param->driver_params) ) { - CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) { - throw pdo::PDOException(); - } - CHECK_CUSTOM_ERROR( pdo_type != PDO_PARAM_STR && pdo_type != PDO_PARAM_LOB, driver_stmt, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, param->paramno + 1 ) { - throw pdo::PDOException(); - } - encoding = static_cast( Z_LVAL( param->driver_params )); - - switch( encoding ) { - case SQLSRV_ENCODING_SYSTEM: - case SQLSRV_ENCODING_BINARY: - case SQLSRV_ENCODING_UTF8: - break; - default: - THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, - param->paramno + 1 ); - break; - } - } - // and bind the parameter - core_sqlsrv_bind_param( driver_stmt, static_cast( param->paramno ), direction, &(param->parameter) , php_out_type, encoding, - sql_type, column_size, decimal_digits TSRMLS_CC ); - } - break; - // undo any work done by the core layer after the statement is executed - case PDO_PARAM_EVT_EXEC_POST: - { - PDO_VALIDATE_STMT; - PDO_LOG_STMT_ENTRY; - - // skip column bindings - if( !param->is_param ) { - break; - } - - core_sqlsrv_post_param( reinterpret_cast( stmt->driver_data ), param->paramno, - &(param->parameter) TSRMLS_CC ); - } - break; - case PDO_PARAM_EVT_FETCH_PRE: - break; - case PDO_PARAM_EVT_FETCH_POST: - break; - case PDO_PARAM_EVT_NORMALIZE: - break; - default: - DIE( "pdo_sqlsrv_stmt_param_hook: Unknown event type" ); - break; - } - } - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - DIE( "pdo_sqlsrv_stmt_param_hook: Unknown exception" ); - } - - return 1; -} - - -// Returns a sqlsrv_phptype for a given SQL Server data type. -sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_over_stream, bool prefer_number_to_string ) -{ - sqlsrv_phptype sqlsrv_phptype; - int local_encoding = this->encoding(); - // if the encoding on the connection changed - if( this->encoding() == SQLSRV_ENCODING_DEFAULT ) { - local_encoding = conn->encoding(); - SQLSRV_ASSERT( conn->encoding() != SQLSRV_ENCODING_DEFAULT || conn->encoding() == SQLSRV_ENCODING_INVALID, - "Invalid encoding on the connection. Must not be invalid or default." ); - } - - switch( sql_type ) { - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - if ( prefer_number_to_string ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = local_encoding; - } - break; - case SQL_FLOAT: - case SQL_REAL: - if ( prefer_number_to_string ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = local_encoding; - } - break; - case SQL_BIGINT: - case SQL_CHAR: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - case SQL_WCHAR: - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_TYPE_DATE: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_TIME2: - case SQL_TYPE_TIMESTAMP: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = local_encoding; - break; - case SQL_BINARY: - case SQL_LONGVARBINARY: - case SQL_VARBINARY: - case SQL_SS_UDT: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; - break; - default: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; - break; - } - - return sqlsrv_phptype; -} - +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_stmt.cpp +// +// Contents: Implements the PDOStatement object for the PDO_SQLSRV +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_pdo_sqlsrv.h" + +// *** internal variables and constants *** + +namespace { + +// Maps to the list of PDO::FETCH_ORI_* constants +SQLSMALLINT odbc_fetch_orientation[] = +{ + SQL_FETCH_NEXT, // PDO_FETCH_ORI_NEXT + SQL_FETCH_PRIOR, // PDO_FETCH_ORI_PRIOR + SQL_FETCH_FIRST, // PDO_FETCH_ORI_FIRST + SQL_FETCH_LAST, // PDO_FETCH_ORI_LAST + SQL_FETCH_ABSOLUTE, // PDO_FETCH_ORI_ABS + SQL_FETCH_RELATIVE // PDO_FETCH_ORI_REL +}; + +// max length of a field type +const int SQL_SERVER_IDENT_SIZE_MAX = 128; + +inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori (enum pdo_fetch_orientation ori) +{ + SQLSRV_ASSERT( ori >= PDO_FETCH_ORI_NEXT && ori <= PDO_FETCH_ORI_REL, "Fetch orientation out of range."); +#ifdef _WIN32 + OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); + OACR_WARNING_SUPPRESS( 26000, "Buffer length verified above" ); +#endif + return odbc_fetch_orientation[ori]; +} + +// Returns SQLSRV data type for a given PDO type. See pdo_param_type +// for list of supported pdo types. +SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( sqlsrv_stmt* driver_stmt, enum pdo_param_type pdo_type TSRMLS_DC ) +{ + switch( pdo_type ) { + + case PDO_PARAM_BOOL: + case PDO_PARAM_INT: + return SQLSRV_PHPTYPE_INT; + + case PDO_PARAM_STR: + return SQLSRV_PHPTYPE_STRING; + + case PDO_PARAM_NULL: + return SQLSRV_PHPTYPE_NULL; + + case PDO_PARAM_LOB: + // TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented. + return SQLSRV_PHPTYPE_STRING; + + case PDO_PARAM_STMT: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED ); + break; + + default: + DIE( "pdo_type_to_sqlsrv_php_type: Unexpected pdo_param_type encountered" ); + } + + return SQLSRV_PHPTYPE_INVALID; // to prevent compiler warning +} + +// Returns a pdo type for a given SQL type. See pdo_param_type +// for list of supported pdo types. +inline pdo_param_type sql_type_to_pdo_type( SQLSMALLINT sql_type ) +{ + pdo_param_type return_type = PDO_PARAM_STR; + + switch( sql_type ) { + + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + case SQL_BIGINT: + case SQL_BINARY: + case SQL_CHAR: + case SQL_DECIMAL: + case SQL_DOUBLE: + case SQL_FLOAT: + case SQL_GUID: + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_NUMERIC: + case SQL_REAL: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_UDT: + case SQL_SS_VARIANT: + case SQL_SS_XML: + case SQL_TYPE_DATE: + case SQL_TYPE_TIMESTAMP: + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_WCHAR: + case SQL_WLONGVARCHAR: + case SQL_WVARCHAR: + return_type = PDO_PARAM_STR; + break; + + default: { + DIE( "sql_type_to_pdo_type: Invalid SQL type provided." ); + break; + } + } + + return return_type; +} + +// Calls core_sqlsrv_set_scrollable function to set cursor. +// PDO supports two cursor types: PDO_CURSOR_FWDONLY, PDO_CURSOR_SCROLL. +void set_stmt_cursors( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + if( Z_TYPE_P( value_z ) != IS_LONG ) { + + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); + } + + zend_long pdo_cursor_type = Z_LVAL_P( value_z ); + long odbc_cursor_type = -1; + + switch( pdo_cursor_type ) { + + case PDO_CURSOR_FWDONLY: + odbc_cursor_type = SQL_CURSOR_FORWARD_ONLY; + break; + + case PDO_CURSOR_SCROLL: + odbc_cursor_type = SQL_CURSOR_STATIC; + break; + + default: + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); + } + + core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); +} + +void set_stmt_cursor_scroll_type( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + if( Z_TYPE_P( value_z ) != IS_LONG ) { + + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); + } + + if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { + + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE ); + } + + long odbc_cursor_type = static_cast( Z_LVAL_P( value_z ) ); + + core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); + + return; +} + +// Sets the statement encoding. Default encoding on the statement +// implies use the connection's encoding. +void set_stmt_encoding( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + // validate the value + if( Z_TYPE_P( value_z ) != IS_LONG ) { + + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + } + + zend_long attr_value = Z_LVAL_P( value_z ); + + switch( attr_value ) { + + // when the default encoding is applied to a statement, it means use the creating connection's encoding + case SQLSRV_ENCODING_DEFAULT: + case SQLSRV_ENCODING_BINARY: + case SQLSRV_ENCODING_SYSTEM: + case SQLSRV_ENCODING_UTF8: + stmt->set_encoding( static_cast( attr_value )); + break; + + default: + THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING ); + break; + } +} + +// internal helper function to free meta data structures allocated +void meta_data_free( field_meta_data* meta ) +{ + if( meta->field_name ) { + meta->field_name.reset(); + } + sqlsrv_free( meta ); +} + +zval convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN field_len ) +{ + zval out_zval; + + switch( sqlsrv_php_type ) { + + case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_FLOAT: + { + if( *in_val == NULL ) { + ZVAL_NULL( &out_zval ); + } + else { + + if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) { + ZVAL_LONG( &out_zval, **( reinterpret_cast( in_val ))); + } + else { + ZVAL_DOUBLE( &out_zval, **( reinterpret_cast( in_val ))); + } + } + + if( *in_val ) { + sqlsrv_free( *in_val ); + } + + break; + } + + case SQLSRV_PHPTYPE_STRING: + case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented + { + + if( *in_val == NULL ) { + + ZVAL_NULL( &out_zval ); + } + else { + + ZVAL_STRINGL( &out_zval, reinterpret_cast( *in_val ), field_len ); + sqlsrv_free( *in_val ); + } + break; + } + + case SQLSRV_PHPTYPE_DATETIME: + DIE( "Unsupported php type" ); + out_zval = *( reinterpret_cast( *in_val )); + break; + + case SQLSRV_PHPTYPE_NULL: + ZVAL_NULL( &out_zval ); + break; + + default: + DIE( "Unknown php type" ); + break; + } + + return out_zval; +} + +} // namespace + +int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC); +int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC); +int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, + zend_long offset TSRMLS_DC); +int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC); +int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC); +int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, + char **ptr, size_t *len, int *caller_frees TSRMLS_DC); +int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC); +int pdo_sqlsrv_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC); +int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC); +int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC); +int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC); + +struct pdo_stmt_methods pdo_sqlsrv_stmt_methods = { + + pdo_sqlsrv_stmt_dtor, + pdo_sqlsrv_stmt_execute, + pdo_sqlsrv_stmt_fetch, + pdo_sqlsrv_stmt_describe_col, + pdo_sqlsrv_stmt_get_col_data, + pdo_sqlsrv_stmt_param_hook, + pdo_sqlsrv_stmt_set_attr, + pdo_sqlsrv_stmt_get_attr, + pdo_sqlsrv_stmt_get_col_meta, + pdo_sqlsrv_stmt_next_rowset, + pdo_sqlsrv_stmt_close_cursor + +}; + +void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + set_stmt_cursors( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_encoding:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + set_stmt_encoding( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_direct_query:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); + pdo_stmt->direct_query = ( zend_is_true( value_z )) ? true : false; +} + +void stmt_option_cursor_scroll_type:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + set_stmt_cursor_scroll_type( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_emulate_prepares:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + pdo_stmt_t *pdo_stmt = static_cast( stmt->driver() ); + pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; +} + +void stmt_option_fetch_numeric:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); + pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false; +} + + +// log a function entry point +#ifndef _WIN32 +#define PDO_LOG_STMT_ENTRY \ +{ \ + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ + driver_stmt->set_func( __FUNCTION__ ); \ + int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ + char func[length+1]; \ + strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ + strcat_s( func, length+1, ": entering" ); \ + LOG( SEV_NOTICE, func ); \ +} +#else +#define PDO_LOG_STMT_ENTRY \ +{ \ + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ + driver_stmt->set_func( __FUNCTION__ ); \ + LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ +} +#endif + +// PDO SQLSRV statement destructor +pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void ) +{ + std::for_each( current_meta_data.begin(), current_meta_data.end(), meta_data_free ); + current_meta_data.clear(); + + if( bound_column_param_types ) { + sqlsrv_free( bound_column_param_types ); + bound_column_param_types = NULL; + } + + if( direct_query_subst_string ) { + // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme + // and we use estrdup to allocate this string, which uses emalloc + efree( reinterpret_cast( const_cast( direct_query_subst_string ))); + } +} + + +// pdo_sqlsrv_stmt_close_cursor +// Close any open cursors on the statement. Maps to PDO function PDOStatement::closeCursor. +// Parameters: +// *stmt - Pointer to current statement +// Return: +// Returns 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_close_cursor(pdo_stmt_t *stmt TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" ); + + sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); + + // to "close the cursor" means we make the statement ready for execution again. To do this, we + // skip all the result sets on the current statement. + while( driver_stmt->past_next_result_end == false ) { + + core_sqlsrv_next_result( driver_stmt TSRMLS_CC ); + } + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advanding to the next result set." ); + } + + return 1; +} + +// pdo_sqlsrv_stmt_describe_col +// Gets the metadata for a column based on the column number. +// Calls the core_sqlsrv_field_metadata function present in the core layer. +// Parameters: +// *stmt - pointer to current statement +// colno - Index of the column which requires description. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + SQLSRV_ASSERT(( colno >= 0 ), "pdo_sqlsrv_stmt_describe_col: Column number should be >= 0." ); + + sqlsrv_malloc_auto_ptr core_meta_data; + + try { + + core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast( stmt->driver_data ), colno TSRMLS_CC ); + } + + catch( core::CoreException& ) { + return 0; + } + + catch(...) { + DIE( "pdo_sqlsrv_stmt_describe_col: Unexpected exception occurred." ); + } + + pdo_column_data* column_data = &(stmt->columns[colno]); + + SQLSRV_ASSERT( column_data != NULL, "pdo_sqsrv_stmt_describe_col: pdo_column_data was null" ); + + // Set the name + column_data->name = zend_string_init( (const char*)core_meta_data->field_name.get(), core_meta_data->field_name_len, 0 ); + core_meta_data->field_name.reset(); + + // Set the maxlen + column_data->maxlen = ( core_meta_data->field_precision > 0 ) ? core_meta_data->field_precision : core_meta_data->field_size; + + // Set the precision + column_data->precision = core_meta_data->field_scale; + + // Set the param_type + column_data->param_type = PDO_PARAM_ZVAL; + + // store the field data for use by pdo_sqlsrv_stmt_get_col_data + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + SQLSRV_ASSERT( driver_stmt != NULL, "Invalid driver statement in pdo_sqlsrv_stmt_describe_col" ); + driver_stmt->current_meta_data.push_back( core_meta_data.get() ); + SQLSRV_ASSERT( driver_stmt->current_meta_data.size() == colno + 1, "Meta data vector out of sync with column numbers" ); + core_meta_data.transferred(); + + return 1; +} + + +// pdo_sqlsrv_stmt_dtor +// Maps to PDOStatement::__destruct. Destructor for the PDO Statement. +// Parameters: +// *stmt - pointer to current statement +// Return: +// 1 for success. +int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) +{ + sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + LOG( SEV_NOTICE, "pdo_sqlsrv_stmt_dtor: entering" ); + + // if a PDO statement didn't complete preparation, its driver_data can be NULL + if( driver_stmt == NULL ) { + + return 1; + } + + driver_stmt->~sqlsrv_stmt(); + + sqlsrv_free( driver_stmt ); + + stmt->driver_data = NULL; + + return 1; +} + +// pdo_sqlsrv_stmt_execute +// Maps to PDOStatement::Execute. Executes the prepared statement. +// Parameters: +// *stmt - pointer to the current statement. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_execute: driver_data object was null" ); + + // prepare for execution by flushing anything remaining in the result set if it wasn't already + // done before binding parameters + if( driver_stmt->executed && !driver_stmt->past_next_result_end ) { + + while( driver_stmt->past_next_result_end == false ) { + + core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false ); + } + } + + const char* query = NULL; + unsigned int query_len = 0; + + // if the user is doing a direct query (PDO::SQLSRV_ATTR_DIRECT_QUERY), set the query here + if( driver_stmt->direct_query ) { + + query = driver_stmt->direct_query_subst_string; + query_len = static_cast( driver_stmt->direct_query_subst_string_len ); + } + + // if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the + // subtituted query provided by PDO + if( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { + + query = stmt->active_query_string; + query_len = static_cast( stmt->active_query_stringlen ); + } + + core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len ); + + stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + + // return the row count regardless if there are any rows or not + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + // workaround for a bug in the PDO driver manager. It is fairly simple to crash the PDO driver manager with + // the following sequence: + // 1) Prepare and execute a statement (that has some results with it) + // 2) call PDOStatement::nextRowset until there are no more results + // 3) execute the statement again + // 4) call PDOStatement::getColumnMeta + // It crashes from what I can tell because there is no metadata because there was no call to + // pdo_stmt_sqlsrv_describe_col and stmt->columns is NULL on the second call to + // PDO::execute. My guess is that because stmt->executed is true, it optimizes away a necessary call to + // pdo_sqlsrv_stmt_describe_col. By setting the stmt->executed flag to 0, this call is not optimized away + // and the crash disappears. + if( stmt->columns == NULL ) { + stmt->executed = 0; + } + } + catch( core::CoreException& /*e*/ ) { + + return 0; + + } + catch( ... ) { + + DIE( "pdo_sqlsrv_stmt_execute: Unexpected exception occurred." ); + + } + + // success + return 1; +} + + + +// pdo_sqlsrv_stmt_fetch +// Maps to PDOStatement::fetch +// Move the cursor to the record indicated. If the cursor is moved off the end, +// or before the beginning if a scrollable cursor is created, then FAILURE is returned. +// Parameters: +// *stmt - pointer to current statement for which the cursor should be moved. +// ori - cursor orientation. Maps to the list of PDO::FETCH_ORI_* constants +// offset - For orientations that use it, offset to move to. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, + zend_long offset TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_fetch: pdo_stmt object was null" ); + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_fetch: driver_data object was null" ); + + // set the types for bound columns to zval so that PDO does no conversion when the value + // is returned by pdo_sqlsrv_get_col_data. Remember the types that were bound by the user + // and use it to manually convert data types + if( stmt->bound_columns ) { + + pdo_bound_param_data* bind_data = NULL; + + if( !driver_stmt->bound_column_param_types ) { + driver_stmt->bound_column_param_types = + reinterpret_cast( sqlsrv_malloc( stmt->column_count, sizeof( pdo_param_type ), 0 )); + std::fill( driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count, + PDO_PARAM_ZVAL ); + } + + for( long i = 0; i < stmt->column_count; ++i ) { + + if (NULL== (bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, i))) && + (NULL == (bind_data = reinterpret_cast(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) { + + driver_stmt->bound_column_param_types[ i ] = PDO_PARAM_ZVAL; + continue; + } + + if( bind_data->param_type != PDO_PARAM_ZVAL ) { + + driver_stmt->bound_column_param_types[ i ] = bind_data->param_type; + bind_data->param_type = PDO_PARAM_ZVAL; + } + } + } + + SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori ); + bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset TSRMLS_CC ); + + // support for the PDO rowCount method. Since rowCount doesn't call a method, PDO relies on us to fill the + // pdo_stmt_t::row_count member + if( driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { + + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + // a row_count of -1 means no rows, but we change it to 0 + if( stmt->row_count == -1 ) { + + stmt->row_count = 0; + } + } + + // if no data was returned, then return false so data isn't retrieved + if( !data ) { + return 0; + } + + return 1; + } + + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE ("pdo_sqlsrv_stmt_fetch: Unexpected exception occurred."); + } +} + +// pdo_sqlsrv_stmt_get_col_data +// Called by the set of PDO Fetch functions. +// Retrieves a single column. PDO driver manager is responsible for freeing the +// returned buffer. Because PDO can request fields out of order and ODBC does not +// support out of order field requests, this function should also cache fields. +// Parameters: +// stmt - Statement to retrive the column for. +// colno - Index of the column that needs to be retrieved. Starts with 0. +// ptr - Returns the buffer containing the column data. +// len - Length of the buffer returned. +// caller_frees - Flag to let the PDO driver manager know that it is responsible for +// freeing the memory. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, + char **ptr, size_t *len, int *caller_frees TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: pdo_stmt object was null" ); + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_data: driver_data object was null" ); + + CHECK_CUSTOM_ERROR((colno < 0), driver_stmt, PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX ) { + return 0; + } + + // Let PDO free the memory after use. + *caller_frees = 1; + + // translate the pdo type to a type the core layer understands + sqlsrv_phptype sqlsrv_php_type; + SQLSRV_ASSERT( colno >= 0 && colno < static_cast( driver_stmt->current_meta_data.size()), + "Invalid column number in pdo_sqlsrv_stmt_get_col_data" ); + sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast( driver_stmt->current_meta_data[ colno ]->field_type ), + static_cast( driver_stmt->current_meta_data[ colno ]->field_size ), + true ); + + // set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding + sqlsrv_php_type.typeinfo.encoding = driver_stmt->encoding(); + + // if a column is bound to a type different than the column type, figure out a way to convert it to the + // type they want + if( stmt->bound_columns && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_ZVAL ) { + + sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type( driver_stmt, + driver_stmt->bound_column_param_types[ colno ] + TSRMLS_CC ); + + pdo_bound_param_data* bind_data = NULL; + bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, colno)); + + if( bind_data != NULL && !Z_ISUNDEF(bind_data->driver_params) ) { + + CHECK_CUSTOM_ERROR( Z_TYPE( bind_data->driver_params ) != IS_LONG, driver_stmt, + PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, colno + 1 ) { + throw pdo::PDOException(); + } + + CHECK_CUSTOM_ERROR( driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_STR + && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_LOB, driver_stmt, + PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, colno + 1 ) { + + throw pdo::PDOException(); + } + + sqlsrv_php_type.typeinfo.encoding = Z_LVAL( bind_data->driver_params ); + + switch( sqlsrv_php_type.typeinfo.encoding ) { + case SQLSRV_ENCODING_SYSTEM: + case SQLSRV_ENCODING_BINARY: + case SQLSRV_ENCODING_UTF8: + break; + default: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, colno ); + break; + } + } + } + + SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID; + core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast(ptr)), + reinterpret_cast( len ), true, &sqlsrv_phptype_out TSRMLS_CC ); + + zval* zval_ptr = reinterpret_cast( sqlsrv_malloc( sizeof( zval ))); + *zval_ptr = convert_to_zval( sqlsrv_phptype_out, reinterpret_cast( ptr ), *len ); + *ptr = reinterpret_cast( zval_ptr ); + + *len = sizeof( zval ); + + return 1; + } + + catch ( core::CoreException& ) { + return 0; + } + catch ( ... ) { + DIE ("pdo_sqlsrv_stmt_get_col_data: Unexpected exception occurred."); + } +} + +// pdo_sqlsrv_stmt_set_attr +// Maps to the PDOStatement::setAttribute. Sets the attribute on a statement. +// Parameters: +// stmt - Current statement on which the attribute should be set. +// attr - Represents any valid set of attribute constants supported by this driver. +// val - Attribute value. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + pdo_sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); + + try { + + switch( attr ) { + + case SQLSRV_ATTR_DIRECT_QUERY: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY ); + break; + + case SQLSRV_ATTR_ENCODING: + set_stmt_encoding( driver_stmt, val TSRMLS_CC ); + break; + + case PDO_ATTR_CURSOR: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY ); + break; + + case SQLSRV_ATTR_QUERY_TIMEOUT: + core_sqlsrv_set_query_timeout( driver_stmt, val TSRMLS_CC ); + break; + + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY ); + break; + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + core_sqlsrv_set_buffered_query_limit( driver_stmt, val TSRMLS_CC ); + break; + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + driver_stmt->fetch_numeric = ( zend_is_true( val )) ? true : false; + break; + + default: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); + break; + } + } + catch( core::CoreException& ) { + return 0; + } + + catch ( ... ) { + DIE ("pdo_sqlsrv_stmt_set_attr: Unexpected exception occurred."); + } + + return 1; +} + + +// pdo_sqlsrv_stmt_get_attr +// Maps to the PDOStatement::getAttribute. Gets the value of a given attribute on a statement. +// Parameters: +// stmt - Current statement for which the attribute value is requested. +// attr - Represents any valid set of attribute constants supported by this driver. +// return_value - Attribute value. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_get_attr( pdo_stmt_t *stmt, zend_long attr, zval *return_value TSRMLS_DC ) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + pdo_sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); + SQLSRV_ASSERT(( driver_stmt != NULL ), "pdo_sqlsrv_stmt_get_attr: stmt->driver_data was null" ); + + try { + + switch( attr ) { + + case SQLSRV_ATTR_DIRECT_QUERY: + { + ZVAL_BOOL( return_value, driver_stmt->direct_query ); + break; + } + + case SQLSRV_ATTR_ENCODING: + { + ZVAL_LONG( return_value, driver_stmt->encoding() ); + break; + } + + case PDO_ATTR_CURSOR: + { + ZVAL_LONG( return_value, ( driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ? + PDO_CURSOR_SCROLL : PDO_CURSOR_FWDONLY )); + break; + } + + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + { + ZVAL_LONG( return_value, driver_stmt->cursor_type ); + break; + } + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + { + ZVAL_LONG( return_value, driver_stmt->buffered_query_limit ); + break; + } + + case SQLSRV_ATTR_QUERY_TIMEOUT: + { + ZVAL_LONG( return_value, ( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ? 0 : driver_stmt->query_timeout )); + break; + } + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + { + ZVAL_BOOL( return_value, driver_stmt->fetch_numeric ); + break; + } + + default: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); + break; + } + } + catch( core::CoreException& ) { + + return 0; + } + catch ( ... ) { + + DIE ("pdo_sqlsrv_stmt_get_attr: Unexpected exception occurred."); + } + + return 1; +} + +// pdo_sqlsrv_stmt_get_col_meta +// Maps to PDOStatement::getColumnMeta. Return extra metadata. +// Though we don't return any extra metadata, PDO relies on us to +// create the associative array that holds the standard information, +// so we create one and return it for PDO's use. +// Parameters: +// stmt - Current statement. +// colno - The index of the field for which to return the metadata. +// return_value - zval* consisting of the metadata. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_get_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: pdo_stmt object was null" ); + SQLSRV_ASSERT( stmt->columns != NULL, "pdo_sqlsrv_stmt_get_col_meta: columns are not available." ); + SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." ); + + sqlsrv_malloc_auto_ptr core_meta_data; + + sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); + + SQLSRV_ASSERT( colno >= 0 && colno < stmt->column_count, "pdo_sqlsrv_stmt_get_col_meta: invalid column number." ); + + core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); + // initialize the array to nothing, as PDO requires us to create it + core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC ); + + // add the following fields: flags, native_type, driver:decl_type, table + add_assoc_long( return_value, "flags", 0 ); + + // get the name of the data type + char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ]; + SQLSMALLINT out_buff_len; + SQLLEN not_used; + core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name, + sizeof( field_type_name ), &out_buff_len, ¬_used TSRMLS_CC ); + add_assoc_string( return_value, "sqlsrv:decl_type", field_type_name ); + + // get the PHP type of the column. The types returned here mirror the types returned by debug_zval_dump when + // given a variable of the same type. However, debug_zval_dump also gives the length of a string, and we only + // say string, since the length is given in another field of the metadata array. + long pdo_type = sql_type_to_pdo_type( core_meta_data->field_type ); + switch( pdo_type ) { + case PDO_PARAM_STR: + { + //Declarations eliminate compiler warnings about string constant to char* conversions + std::string key = "native_type"; + std::string str = "string"; + add_assoc_string( return_value, &key[0], &str[0] ); + } + break; + default: + DIE( "pdo_sqlsrv_stmt_get_col_data: Unknown PDO type returned" ); + break; + } + + // add the table name of the field. All the tests so far show this to always be "", but we adhere to the PDO spec + char table_name[ SQL_SERVER_IDENT_SIZE_MAX ]; + SQLLEN field_type_num; + core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TABLE_NAME, table_name, SQL_SERVER_IDENT_SIZE_MAX, + &out_buff_len, &field_type_num TSRMLS_CC ); + add_assoc_string( return_value, "table", table_name ); + + if( stmt->columns[ colno ].param_type == PDO_PARAM_ZVAL ) { + add_assoc_long( return_value, "pdo_type", pdo_type ); + } + + // this will ensure that the field_name field, which is an auto pointer gets freed. + (*core_meta_data).~field_meta_data(); + } + catch( core::CoreException& ) { + + return 0; + } + catch(...) { + + DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." ); + } + + return 1; +} + + +// pdo_sqlsrv_stmt_next_rowset +// Maps to PDOStatement::nextRowset. +// Move the cursor to the beginning of the next rowset in a multi-rowset result. +// Clears the field cache from the last row retrieved using pdo_sqlsrv_stmt_get_col_data. +// Calls core_sqlsrv_next_result using the core_stmt found within stmt->driver_data. +// If another result set is available, this function returns 1. Otherwise it returns 0. +// Parameters: +// stmt - PDOStatement object containing the result set. +// Return: +// 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + try { + + SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: pdo_stmt object was null" ); + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); + + core_sqlsrv_next_result( static_cast( stmt->driver_data ) TSRMLS_CC ); + + // clear the current meta data since the new result will generate new meta data + std::for_each( driver_stmt->current_meta_data.begin(), driver_stmt->current_meta_data.end(), meta_data_free ); + driver_stmt->current_meta_data.clear(); + + // if there are no more result sets, return that it failed. + if( driver_stmt->past_next_result_end == true ) { + return 0; + } + + stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + + // return the row count regardless if there are any rows or not + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_stmt_next_rowset: Unknown exception occurred while advancing to the next result set." ); + } + + return 1; +} + +// pdo_sqlsrv_stmt_param_hook +// Maps to PDOStatement::bindColumn. +// Called by PDO driver manager to bind a parameter or column. +// This function pulls several duties for binding parameters and columns. +// It takes an event as a parameter that explains what the function should do on +// behalf of a parameter or column. We only use one of these events, +// PDO_PARAM_EVT_EXEC_PRE, the remainder simply return. +// Paramters: +// stmt - PDO Statement object to bind a parameter. +// param - paramter to bind. +// event_type - Event to bind a parameter +// Return: +// Returns 0 for failure, 1 for success. +int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC) +{ + PDO_RESET_STMT_ERROR; + + try { + + switch( event_type ) { + + // since the param isn't reliable, we don't do anything here + case PDO_PARAM_EVT_ALLOC: + // if emulate prepare is on, set the bind_param_encoding so it can be used in PDO::quote when binding parameters on the client side + if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( stmt->dbh->driver_data ); + driver_dbh->bind_param_encoding = static_cast( Z_LVAL( param->driver_params )); + } + break; + case PDO_PARAM_EVT_FREE: + break; + // bind the parameter in the core layer + case PDO_PARAM_EVT_EXEC_PRE: + { + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + // skip column bindings + if( !param->is_param ) { + break; + } + + sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_param_hook: driver_data object was null" ); + + // prepare for binding parameters by flushing anything remaining in the result set + if( driver_stmt->executed && !driver_stmt->past_next_result_end ) { + + while( driver_stmt->past_next_result_end == false ) { + + core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false ); + } + } + + int direction = SQL_PARAM_INPUT; + SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE; + SQLULEN column_size = SQLSRV_UNKNOWN_SIZE; + SQLSMALLINT decimal_digits = 0; + // determine the direction of the parameter. By default it's input, but if the user specifies a size + // that means they want output, and if they include the flag, then it's input/output. + // It's invalid to specify the input/output flag but not specify a length + CHECK_CUSTOM_ERROR( (param->param_type & PDO_PARAM_INPUT_OUTPUT) && (param->max_value_len == 0), + driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) { + throw pdo::PDOException(); + } + if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) { + if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) { + direction = SQL_PARAM_INPUT_OUTPUT; + } + else { + direction = SQL_PARAM_OUTPUT; + } + } + // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant + // and the SQLSRV_PHPTYPE_* constant + int pdo_type = param->param_type; + SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; + switch( pdo_type & ~PDO_PARAM_INPUT_OUTPUT ) { + case PDO_PARAM_BOOL: + case PDO_PARAM_INT: + php_out_type = SQLSRV_PHPTYPE_INT; + break; + case PDO_PARAM_STR: + php_out_type = SQLSRV_PHPTYPE_STRING; + break; + // when the user states PDO::PARAM_NULL, they mean send a null no matter what the variable is + // since the core layer keys off the zval type, we substitute a null for what they gave us + case PDO_PARAM_NULL: + { + zval null_zval; + php_out_type = SQLSRV_PHPTYPE_NULL; + + ZVAL_NULL( &null_zval ); + zval_ptr_dtor( ¶m->parameter ); + param->parameter = null_zval; + break; + } + case PDO_PARAM_LOB: + php_out_type = SQLSRV_PHPTYPE_STREAM; + break; + case PDO_PARAM_STMT: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED ); + break; + default: + SQLSRV_ASSERT( false, "Unknown PDO::PARAM_* constant given." ); + break; + } + // set the column size parameter for bind_param if we are expecting something back + if( direction != SQL_PARAM_INPUT ) { + switch( php_out_type ) { + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_STREAM: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE ); + break; + case SQLSRV_PHPTYPE_INT: + column_size = SQLSRV_UNKNOWN_SIZE; + break; + case SQLSRV_PHPTYPE_STRING: + { + CHECK_CUSTOM_ERROR( param->max_value_len <= 0, driver_stmt, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, param->paramno + 1 ) { + throw pdo::PDOException(); + } + column_size = param->max_value_len; + break; + } + default: + SQLSRV_ASSERT( false, "Invalid PHP type for output parameter. Should have been caught already." ); + break; + } + } + // block all objects from being bound as input or input/output parameters since there is a + // weird case: + // $obj = date_create(); + // $s->bindParam( n, $obj, PDO::PARAM_INT ); // anything different than PDO::PARAM_STR + // that succeeds since the core layer implements DateTime object handling for the sqlsrv + // 2.0 driver. To be consistent and avoid surprises of one object type working and others + // not, we block all objects here. + CHECK_CUSTOM_ERROR( direction != SQL_PARAM_OUTPUT && Z_TYPE( param->parameter ) == IS_OBJECT, + driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) { + throw pdo::PDOException(); + } + // the encoding by default is that set on the statement + SQLSRV_ENCODING encoding = driver_stmt->encoding(); + // if the statement's encoding is the default, then use the one on the connection + if( encoding == SQLSRV_ENCODING_DEFAULT ) { + encoding = driver_stmt->conn->encoding(); + } + // if the user provided an encoding, use it instead + if( !Z_ISUNDEF(param->driver_params) ) { + CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) { + throw pdo::PDOException(); + } + CHECK_CUSTOM_ERROR( pdo_type != PDO_PARAM_STR && pdo_type != PDO_PARAM_LOB, driver_stmt, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, param->paramno + 1 ) { + throw pdo::PDOException(); + } + encoding = static_cast( Z_LVAL( param->driver_params )); + + switch( encoding ) { + case SQLSRV_ENCODING_SYSTEM: + case SQLSRV_ENCODING_BINARY: + case SQLSRV_ENCODING_UTF8: + break; + default: + THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, + param->paramno + 1 ); + break; + } + } + // and bind the parameter + core_sqlsrv_bind_param( driver_stmt, static_cast( param->paramno ), direction, &(param->parameter) , php_out_type, encoding, + sql_type, column_size, decimal_digits TSRMLS_CC ); + } + break; + // undo any work done by the core layer after the statement is executed + case PDO_PARAM_EVT_EXEC_POST: + { + PDO_VALIDATE_STMT; + PDO_LOG_STMT_ENTRY; + + // skip column bindings + if( !param->is_param ) { + break; + } + + core_sqlsrv_post_param( reinterpret_cast( stmt->driver_data ), param->paramno, + &(param->parameter) TSRMLS_CC ); + } + break; + case PDO_PARAM_EVT_FETCH_PRE: + break; + case PDO_PARAM_EVT_FETCH_POST: + break; + case PDO_PARAM_EVT_NORMALIZE: + break; + default: + DIE( "pdo_sqlsrv_stmt_param_hook: Unknown event type" ); + break; + } + } + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + DIE( "pdo_sqlsrv_stmt_param_hook: Unknown exception" ); + } + + return 1; +} + + +// Returns a sqlsrv_phptype for a given SQL Server data type. +sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_over_stream ) +{ + sqlsrv_phptype sqlsrv_phptype; + int local_encoding = this->encoding(); + // if the encoding on the connection changed + if( this->encoding() == SQLSRV_ENCODING_DEFAULT ) { + local_encoding = conn->encoding(); + SQLSRV_ASSERT( conn->encoding() != SQLSRV_ENCODING_DEFAULT || conn->encoding() == SQLSRV_ENCODING_INVALID, + "Invalid encoding on the connection. Must not be invalid or default." ); + } + + switch( sql_type ) { + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + if ( this->fetch_numeric ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + } + break; + case SQL_FLOAT: + case SQL_REAL: + if ( this->fetch_numeric ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + } + break; + case SQL_BIGINT: + case SQL_CHAR: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + case SQL_WCHAR: + case SQL_VARCHAR: + case SQL_WVARCHAR: + case SQL_TYPE_DATE: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_TIME2: + case SQL_TYPE_TIMESTAMP: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + break; + case SQL_BINARY: + case SQL_LONGVARBINARY: + case SQL_VARBINARY: + case SQL_SS_UDT: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + break; + default: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + break; + } + + return sqlsrv_phptype; +} + diff --git a/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp similarity index 90% rename from pdo_sqlsrv/pdo_util.cpp rename to source/pdo_sqlsrv/pdo_util.cpp index fe25efbba..c3f0fb36c 100644 --- a/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -1,607 +1,602 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: pdo_util.cpp -// -// Contents: Utility functions used by both connection or statement functions -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "pdo_sqlsrv.h" - -#include "zend_exceptions.h" - - -// *** internal constants *** -namespace { - -const char WARNING_TEMPLATE[] = "SQLSTATE: %1!s!\nError Code: %2!d!\nMError Message: %3!s!\n"; -const char EXCEPTION_MSG_TEMPLATE[] = "SQLSTATE[%s]: %s"; -char EXCEPTION_PROPERTY_MSG[] = "message"; -char EXCEPTION_PROPERTY_CODE[] = "code"; -char EXCEPTION_PROPERTY_ERRORINFO[] = "errorInfo"; -const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negative - -// buffer used to hold a formatted log message prior to actually logging it. -const int LOG_MSG_SIZE = 2048; -char log_msg[ LOG_MSG_SIZE ]; - -// internal error that says that FormatMessage failed -SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; - -// build the object and throw the PDO exception -void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ); - -} - -// pdo driver error messages -// errors have 3 components, the SQLSTATE (always 'IMSSP'), the error message, and an error code, which for us is always < 0 -pdo_error PDO_ERRORS[] = { - - { - SQLSRV_ERROR_DRIVER_NOT_INSTALLED, - { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 for SQL Server to " - "communicate with SQL Server. Access the following URL to download the ODBC Driver 11 for SQL Server " - "for %1!s!: " - "http://go.microsoft.com/fwlink/?LinkId=163712", -1, true } - }, - { - SQLSRV_ERROR_ZEND_HASH, - { IMSSP, (SQLCHAR*) "An error occurred creating or accessing a Zend hash table.", -2, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - { IMSSP, (SQLCHAR*) "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams " - "cannot be specified as output parameters.", -3, false } - }, - { - SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, - { IMSSP, (SQLCHAR*) "An invalid type for parameter %1!d! was specified. Only booleans, integers, floating point " - "numbers, strings, and streams may be used as parameters.", -4, true } - }, - { - SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, - { IMSSP, (SQLCHAR*) "An invalid SQL Server type for parameter %1!d! was specified.", -5, true } - }, - { - SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, - { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -6, true } - }, - { - SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -7, true } - }, - { - SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating string for an output param to UTF-8: %1!s!", -8, true } - }, - { - SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -9, true } - }, - { - SQLSRV_ERROR_ZEND_STREAM, - { IMSSP, (SQLCHAR*) "An error occurred reading from a PHP stream.", -10, false } - }, - { - SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -11, true } - }, - { - SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, - { IMSSP, (SQLCHAR*) "Failed to retrieve the server version. Unable to continue.", -12, false } - }, - { - SQLSRV_ERROR_FETCH_PAST_END, - { IMSSP, (SQLCHAR*) "There are no more rows in the active result set. Since this result set is not scrollable, " - "no more data may be retrieved.", -13, false } - }, - { - SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, - { IMSSP, (SQLCHAR*) "The statement must be executed before results can be retrieved.", -14, false } - }, - { - SQLSRV_ERROR_NO_FIELDS, - { IMSSP, (SQLCHAR*) "The active result for the query contains no fields.", -15, false } - }, - - { - SQLSRV_ERROR_FETCH_NOT_CALLED, - { IMSSP, (SQLCHAR*) "Internal pdo_sqlsrv error: Tried to retrieve a field before one of the PDOStatement::fetch " - "functions was called.", -16, false } - }, - { - SQLSRV_ERROR_NO_DATA, - { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -17, true } - }, - { - SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -18, true } - }, - { - SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, - { IMSSP, (SQLCHAR*) "Zend returned an error when creating an associative array.", -19, false } - }, - { - SQLSRV_ERROR_NEXT_RESULT_PAST_END, - { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -20, false } - }, - { - SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, - { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be" - " escaped with another right brace (}}).", -21, false } - }, - { - SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, - { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in the DSN string for keyword '%1!s!'. All right braces " - "must be escaped with another right brace (}}).", -22, true } - }, - { - SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -23, true } - }, - { - SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -24, true } - }, - { - SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, - { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of " - "key/value pairs.", -25, false } - }, - { - SQLSRV_ERROR_INVALID_CONNECTION_KEY, - { IMSSP, (SQLCHAR*) "An invalid connection option key type was received. Option key types must be strings.", -26, false } - }, - - { - SQLSRV_ERROR_INVALID_TYPE, - { IMSSP, (SQLCHAR*) "Invalid type.", -27, false } - }, - - { - PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, - {IMSSP, (SQLCHAR*)"An invalid column number was specified.", -28, false } - }, - - { - SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, - { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -29, true } - }, - { - SQLSRV_ERROR_INVALID_OPTION_KEY, - { IMSSP, (SQLCHAR*) "Invalid option key %1!s! specified.", -30, true } - }, - { - SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, - { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option PDO::SQLSRV_ATTR_QUERY_TIMEOUT.", -31, true } - }, - { - SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, - { IMSSP, (SQLCHAR*) "The value passed for the 'Scrollable' statement option is invalid.", -32, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_DBH_ATTR, - { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDO object.", -33, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, - { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDOStatement object.", -34, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_ENCODING, - { IMSSP, (SQLCHAR*) "An invalid encoding was specified for SQLSRV_ATTR_ENCODING.", -35, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, - { IMSSP, (SQLCHAR*) "An invalid type or value was given for the parameter driver data. Only encoding constants " - "such as PDO::SQLSRV_ENCODING_UTF8 may be used as parameter driver options.", -36, false } - }, - { - PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, - { IMSSP, (SQLCHAR*) "PDO::PARAM_STMT is not a supported parameter type.", -37, false } - }, - { - PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, - { IMSSP, (SQLCHAR*) "An unsupported attribute was designated on the PDO object.", -38, false } - }, - { - PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, - { IMSSP, (SQLCHAR*) "The given attribute is only supported on the PDOStatement object.", -39, false } - }, - { - PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, - { IMSSP, (SQLCHAR*) "A read-only attribute was designated on the PDO object.", -40, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_DSN_STRING, - {IMSSP, (SQLCHAR*)"An invalid DSN string was specified.", -41, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_DSN_KEY, - { IMSSP, (SQLCHAR*) "An invalid keyword '%1!s!' was specified in the DSN string.", -42, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, - { IMSSP, (SQLCHAR*) "An invalid statement option was specified.", -43, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, - { IMSSP, (SQLCHAR*) "An invalid cursor type was specified for either PDO::ATTR_CURSOR or " - "PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE", -44, false } - }, - { - PDO_SQLSRV_ERROR_PARAM_PARSE, - { IMSSP, (SQLCHAR*) "An error occurred substituting the named parameters.", -45, false } - }, - { - PDO_SQLSRV_ERROR_LAST_INSERT_ID, - { IMSSP, (SQLCHAR*) "An error occurred retrieving the last insert id.", -46, false } - }, - { - SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -47, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, - { IMSSP, (SQLCHAR*) "An invalid type or value was given as bound column driver data for column %1!d!. Only " - "encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.", -48, true } - }, - { - PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, - { IMSSP, (SQLCHAR*) "An encoding was specified for column %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR column types " - "can take an encoding option.", -49, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, - { IMSSP, (SQLCHAR*) "Invalid encoding specified for column %1!d!.", -50, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, - { IMSSP, (SQLCHAR*) "An encoding was specified for parameter %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an " - "encoding option.", -51, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, - { IMSSP, (SQLCHAR*) "Invalid encoding specified for parameter %1!d!.", -52, true } - }, - { - PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, - { IMSSP, (SQLCHAR*) "The PDO::ATTR_CURSOR and PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attributes may only be set in the " - "$driver_options array of PDO::prepare.", -53, false } - }, - { - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, - { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -54, true } - }, - { - SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, - { IMSSP, (SQLCHAR*) "Types for parameter value and PDO::PARAM_* constant must be compatible for input/output " - "parameter %1!d!.", -55, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, - { IMSSP, (SQLCHAR*) "Invalid direction specified for parameter %1!d!. Input/output parameters must have a length.", - -56, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, - { IMSSP, (SQLCHAR*) "Invalid size for output string parameter %1!d!. Input/output string parameters must have an " - "explicit length.", -57, true } - }, - { - PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, - { IMSSP, (SQLCHAR*) "This function is not implemented by this driver.", -58, false } - }, - { - /* The stream related errors are not currently used in PDO, but the core layer can throw the stream related - errors so having a mapping here */ - - SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, - { IMSSP, (SQLCHAR*) "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using " - "streams.", -59, false} - }, - { - SQLSRV_ERROR_STREAM_CREATE, - { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -60, false } - }, - { - SQLSRV_ERROR_MARS_OFF, - { IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. " - "To make the connection available for other queries, either fetch all results or cancel or free the statement. " - "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -61, false } - }, - { - SQLSRV_ERROR_FIELD_INDEX_ERROR, - { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. Cannot retrieve field %1!d! because its " - "index is less than the index of a field that has already been retrieved (%2!d!).", -62, true } - }, - { - PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, - { IMSSP, (SQLCHAR*) "An invalid value was specified for the keyword '%1!s!' in the DSN string.", -63, true } - }, - { - PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, - { IMSSP, (SQLCHAR*) "Server keyword was not specified in the DSN string.", -64, false } - }, - { - PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, - { IMSSP, (SQLCHAR*) "The DSN string ended unexpectedly.", -65, false } - }, - { - PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, - { IMSSP, (SQLCHAR*) "An extra semi-colon was encountered in the DSN string at character (byte-count) position '%1!d!' .", - -66, true } - }, - { - PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, - { IMSSP, (SQLCHAR*) "An expected right brace (}) was not found in the DSN string for the value of the keyword '%1!s!'.", - -67, true } - }, - { - PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, - { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_DIRECT_QUERY attribute may only be set in the $driver_options array of " - "PDO::prepare.", -68, false } - }, - { - PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, - { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attribute may only be set when PDO::ATTR_CURSOR is set to " - "PDO::CURSOR_SCROLL in the $driver_options array of PDO::prepare.", -69, false } - }, - { - SQLSRV_ERROR_INVALID_BUFFER_LIMIT, - { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not " - "positive. Only positive numbers are valid for this attribute.", -70, false } - }, - { - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, - { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -71, true } - }, - { UINT_MAX, {} } -}; - -// Returns a sqlsrv_error for a given error code. -sqlsrv_error_const* get_error_message(unsigned int sqlsrv_error_code) { - - sqlsrv_error_const *error_message = NULL; - int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr(g_pdo_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; - if( zr == FAILURE ) { - DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); - } - - SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null"); - - return error_message; -} - -// PDO error handler for the environment context. -bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ) -{ - SQLSRV_ASSERT(( ctx != NULL ), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null" ); - pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); - SQLSRV_ASSERT(( dbh != NULL ), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null" ); - - sqlsrv_error_auto_ptr error; - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); - } - else { - - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); - } - - strcpy_s( dbh->error_code, sizeof( pdo_error_type ), reinterpret_cast( error->sqlstate )); - - switch( dbh->error_mode ) { - - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error TSRMLS_CC ); - } - ctx.set_last_error( error ); - break; - - default: - DIE( "pdo_sqlsrv_handle_env_error: Unexpected error mode. %1!d!", dbh->error_mode ); - break; - } - - // we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count - // return error ignored = true for warnings. - return ( warning ? true : false ); - -} - -// pdo error handler for the dbh context. -bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ) -{ - pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); - SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" ); - - sqlsrv_error_auto_ptr error; - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); - } - else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); - } - - SQLSRV_ASSERT(strlen(reinterpret_cast(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow"); - strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast(error->sqlstate)); - - switch( dbh->error_mode ) { - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error TSRMLS_CC ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_WARNING: - if( !warning ) { - size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE - + MAX_DIGITS + 1; - char* msg = static_cast( sqlsrv_malloc( msg_len )); - core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, - error->native_message ); - php_error( E_WARNING, msg ); - sqlsrv_free( msg ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_SILENT: - ctx.set_last_error( error ); - break; - default: - DIE( "Unknown error mode. %1!d!", dbh->error_mode ); - break; - } - - // return error ignored = true for warnings. - return ( warning ? true : false ); -} - -// PDO error handler for the statement context. -bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ) -{ - pdo_stmt_t* pdo_stmt = reinterpret_cast( ctx.driver()); - SQLSRV_ASSERT( pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed" ); - - sqlsrv_error_auto_ptr error; - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); - } - else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); - } - - SQLSRV_ASSERT( strlen( reinterpret_cast( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow"); - strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast( error->sqlstate )); - - switch( pdo_stmt->dbh->error_mode ) { - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error TSRMLS_CC ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_WARNING: - if( !warning ) { - size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE - + MAX_DIGITS + 1; - char* msg = static_cast( sqlsrv_malloc(SQL_MAX_MESSAGE_LENGTH+1)); - core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, - error->native_message ); - php_error( E_WARNING, msg ); - sqlsrv_free( msg ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_SILENT: - ctx.set_last_error( error ); - break; - default: - DIE( "Unknown error mode. %1!d!", pdo_stmt->dbh->error_mode ); - break; - } - - // return error ignored = true for warnings. - return ( warning ? true : false ); -} - - -// Transfer a sqlsrv_context's error to a PDO zval. The standard format for a zval error is 3 elements: -// 0, native code -// 1, native message -// 2, SQLSTATE of the error (driver specific error messages are 'IMSSP') - -void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ) -{ - if( last_error ) { - - // SQLSTATE is already present in the zval. - add_next_index_long( pdo_zval, last_error->native_code ); - add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message )); - } -} - -// Formats the error message and writes to the php error log. -void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ) -{ - if( (severity & PDO_SQLSRV_G( log_severity )) == 0 ) { - return; - } - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); - // if an error occurs for FormatMessage, we just output an internal error occurred. - if( rc == 0 ) { - SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); - std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); - } - - php_log_err( log_msg TSRMLS_CC ); -} - -namespace { - -void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ) -{ - zval ex_obj; - ZVAL_UNDEF( &ex_obj ); - zend_class_entry* ex_class = pdo_get_exception_class(); - - int zr = object_init_ex( &ex_obj, ex_class ); - SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" ); - - sqlsrv_malloc_auto_ptr ex_msg; - size_t ex_msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + - 12 + 1; // 12 = "SQLSTATE[]: " - ex_msg = reinterpret_cast( sqlsrv_malloc( ex_msg_len )); - snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message ); - zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, - ex_msg TSRMLS_CC ); - zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, - reinterpret_cast( error->sqlstate ) TSRMLS_CC ); - - zval ex_error_info; - ZVAL_UNDEF( &ex_error_info ); - array_init( &ex_error_info ); - add_next_index_string( &ex_error_info, reinterpret_cast( error->sqlstate )); - add_next_index_long( &ex_error_info, error->native_code ); - add_next_index_string( &ex_error_info, reinterpret_cast( error->native_message )); - //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) - //and the refcount of the zend_array is incremented by 1 - zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, - &ex_error_info TSRMLS_CC ); - - //DELREF ex_error_info here to decrement the refcount of the zend_array is 1 - //the global hashtable EG(exception) then points to the zend_object in ex_obj in zend_throw_exception_object; - //this ensure when EG(exception) cleans itself at php shutdown, the zend_array allocated is properly destroyed - Z_DELREF( ex_error_info ); - zend_throw_exception_object( &ex_obj TSRMLS_CC ); -} - -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: pdo_util.cpp +// +// Contents: Utility functions used by both connection or statement functions +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_pdo_sqlsrv.h" + +#include "zend_exceptions.h" + + +// *** internal constants *** +namespace { + +const char WARNING_TEMPLATE[] = "SQLSTATE: %1!s!\nError Code: %2!d!\nError Message: %3!s!\n"; +const char EXCEPTION_MSG_TEMPLATE[] = "SQLSTATE[%s]: %s"; +char EXCEPTION_PROPERTY_MSG[] = "message"; +char EXCEPTION_PROPERTY_CODE[] = "code"; +char EXCEPTION_PROPERTY_ERRORINFO[] = "errorInfo"; +const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negative + +// the warning message is not the error message alone; it must take WARNING_TEMPLATE above into consideration without the formats +const int WARNING_MIN_LENGTH = strlen(WARNING_TEMPLATE) - strlen("%1!s!%2!d!%3!s!"); + +// buffer used to hold a formatted log message prior to actually logging it. +const int LOG_MSG_SIZE = 2048; +char log_msg[ LOG_MSG_SIZE ]; + +// internal error that says that FormatMessage failed +SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; + +// build the object and throw the PDO exception +void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ); + +} + +// pdo driver error messages +// errors have 3 components, the SQLSTATE (always 'IMSSP'), the error message, and an error code, which for us is always < 0 +pdo_error PDO_ERRORS[] = { + + { + SQLSRV_ERROR_DRIVER_NOT_INSTALLED, + { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 13 for SQL Server to " + "communicate with SQL Server. Access the following URL to download the ODBC Driver 13 for SQL Server " + "for %1!s!: " + "http://go.microsoft.com/fwlink/?LinkId=163712", -1, true } + }, + { + SQLSRV_ERROR_ZEND_HASH, + { IMSSP, (SQLCHAR*) "An error occurred creating or accessing a Zend hash table.", -2, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + { IMSSP, (SQLCHAR*) "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams " + "cannot be specified as output parameters.", -3, false } + }, + { + SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + { IMSSP, (SQLCHAR*) "An invalid type for parameter %1!d! was specified. Only booleans, integers, floating point " + "numbers, strings, and streams may be used as parameters.", -4, true } + }, + { + SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + { IMSSP, (SQLCHAR*) "An invalid SQL Server type for parameter %1!d! was specified.", -5, true } + }, + { + SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, + { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -6, true } + }, + { + SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -7, true } + }, + { + SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating string for an output param to UTF-8: %1!s!", -8, true } + }, + { + SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -9, true } + }, + { + SQLSRV_ERROR_ZEND_STREAM, + { IMSSP, (SQLCHAR*) "An error occurred reading from a PHP stream.", -10, false } + }, + { + SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -11, true } + }, + { + SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, + { IMSSP, (SQLCHAR*) "Failed to retrieve the server version. Unable to continue.", -12, false } + }, + { + SQLSRV_ERROR_FETCH_PAST_END, + { IMSSP, (SQLCHAR*) "There are no more rows in the active result set. Since this result set is not scrollable, " + "no more data may be retrieved.", -13, false } + }, + { + SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, + { IMSSP, (SQLCHAR*) "The statement must be executed before results can be retrieved.", -14, false } + }, + { + SQLSRV_ERROR_NO_FIELDS, + { IMSSP, (SQLCHAR*) "The active result for the query contains no fields.", -15, false } + }, + + { + SQLSRV_ERROR_FETCH_NOT_CALLED, + { IMSSP, (SQLCHAR*) "Internal pdo_sqlsrv error: Tried to retrieve a field before one of the PDOStatement::fetch " + "functions was called.", -16, false } + }, + { + SQLSRV_ERROR_NO_DATA, + { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -17, true } + }, + { + SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -18, true } + }, + { + SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, + { IMSSP, (SQLCHAR*) "Zend returned an error when creating an associative array.", -19, false } + }, + { + SQLSRV_ERROR_NEXT_RESULT_PAST_END, + { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -20, false } + }, + { + SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, + { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be" + " escaped with another right brace (}}).", -21, false } + }, + { + SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, + { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in the DSN string for keyword '%1!s!'. All right braces " + "must be escaped with another right brace (}}).", -22, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, + { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -23, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, + { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -24, true } + }, + { + SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, + { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of " + "key/value pairs.", -25, false } + }, + { + SQLSRV_ERROR_INVALID_CONNECTION_KEY, + { IMSSP, (SQLCHAR*) "An invalid connection option key type was received. Option key types must be strings.", -26, false } + }, + + { + SQLSRV_ERROR_INVALID_TYPE, + { IMSSP, (SQLCHAR*) "Invalid type.", -27, false } + }, + + { + PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, + {IMSSP, (SQLCHAR*)"An invalid column number was specified.", -28, false } + }, + + { + SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, + { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -29, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_KEY, + { IMSSP, (SQLCHAR*) "Invalid option key %1!s! specified.", -30, true } + }, + { + SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, + { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option PDO::SQLSRV_ATTR_QUERY_TIMEOUT.", -31, true } + }, + { + SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, + { IMSSP, (SQLCHAR*) "The value passed for the 'Scrollable' statement option is invalid.", -32, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DBH_ATTR, + { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDO object.", -33, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, + { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDOStatement object.", -34, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_ENCODING, + { IMSSP, (SQLCHAR*) "An invalid encoding was specified for SQLSRV_ATTR_ENCODING.", -35, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, + { IMSSP, (SQLCHAR*) "An invalid type or value was given for the parameter driver data. Only encoding constants " + "such as PDO::SQLSRV_ENCODING_UTF8 may be used as parameter driver options.", -36, false } + }, + { + PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, + { IMSSP, (SQLCHAR*) "PDO::PARAM_STMT is not a supported parameter type.", -37, false } + }, + { + PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, + { IMSSP, (SQLCHAR*) "An unsupported attribute was designated on the PDO object.", -38, false } + }, + { + PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, + { IMSSP, (SQLCHAR*) "The given attribute is only supported on the PDOStatement object.", -39, false } + }, + { + PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, + { IMSSP, (SQLCHAR*) "A read-only attribute was designated on the PDO object.", -40, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DSN_STRING, + {IMSSP, (SQLCHAR*)"An invalid DSN string was specified.", -41, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_DSN_KEY, + { IMSSP, (SQLCHAR*) "An invalid keyword '%1!s!' was specified in the DSN string.", -42, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, + { IMSSP, (SQLCHAR*) "An invalid statement option was specified.", -43, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, + { IMSSP, (SQLCHAR*) "An invalid cursor type was specified for either PDO::ATTR_CURSOR or " + "PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE", -44, false } + }, + { + PDO_SQLSRV_ERROR_PARAM_PARSE, + { IMSSP, (SQLCHAR*) "An error occurred substituting the named parameters.", -45, false } + }, + { + PDO_SQLSRV_ERROR_LAST_INSERT_ID, + { IMSSP, (SQLCHAR*) "An error occurred retrieving the last insert id.", -46, false } + }, + { + SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -47, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, + { IMSSP, (SQLCHAR*) "An invalid type or value was given as bound column driver data for column %1!d!. Only " + "encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.", -48, true } + }, + { + PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, + { IMSSP, (SQLCHAR*) "An encoding was specified for column %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR column types " + "can take an encoding option.", -49, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, + { IMSSP, (SQLCHAR*) "Invalid encoding specified for column %1!d!.", -50, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, + { IMSSP, (SQLCHAR*) "An encoding was specified for parameter %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an " + "encoding option.", -51, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, + { IMSSP, (SQLCHAR*) "Invalid encoding specified for parameter %1!d!.", -52, true } + }, + { + PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, + { IMSSP, (SQLCHAR*) "The PDO::ATTR_CURSOR and PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attributes may only be set in the " + "$driver_options array of PDO::prepare.", -53, false } + }, + { + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, + { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -54, true } + }, + { + SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, + { IMSSP, (SQLCHAR*) "Types for parameter value and PDO::PARAM_* constant must be compatible for input/output " + "parameter %1!d!.", -55, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, + { IMSSP, (SQLCHAR*) "Invalid direction specified for parameter %1!d!. Input/output parameters must have a length.", + -56, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, + { IMSSP, (SQLCHAR*) "Invalid size for output string parameter %1!d!. Input/output string parameters must have an " + "explicit length.", -57, true } + }, + { + PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, + { IMSSP, (SQLCHAR*) "This function is not implemented by this driver.", -58, false } + }, + { + /* The stream related errors are not currently used in PDO, but the core layer can throw the stream related + errors so having a mapping here */ + + SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, + { IMSSP, (SQLCHAR*) "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using " + "streams.", -59, false} + }, + { + SQLSRV_ERROR_STREAM_CREATE, + { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -60, false } + }, + { + SQLSRV_ERROR_MARS_OFF, + { IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. " + "To make the connection available for other queries, either fetch all results or cancel or free the statement. " + "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -61, false } + }, + { + SQLSRV_ERROR_FIELD_INDEX_ERROR, + { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. Cannot retrieve field %1!d! because its " + "index is less than the index of a field that has already been retrieved (%2!d!).", -62, true } + }, + { + PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, + { IMSSP, (SQLCHAR*) "An invalid value was specified for the keyword '%1!s!' in the DSN string.", -63, true } + }, + { + PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, + { IMSSP, (SQLCHAR*) "Server keyword was not specified in the DSN string.", -64, false } + }, + { + PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, + { IMSSP, (SQLCHAR*) "The DSN string ended unexpectedly.", -65, false } + }, + { + PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, + { IMSSP, (SQLCHAR*) "An extra semi-colon was encountered in the DSN string at character (byte-count) position '%1!d!' .", + -66, true } + }, + { + PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, + { IMSSP, (SQLCHAR*) "An expected right brace (}) was not found in the DSN string for the value of the keyword '%1!s!'.", + -67, true } + }, + { + PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, + { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_DIRECT_QUERY attribute may only be set in the $driver_options array of " + "PDO::prepare.", -68, false } + }, + { + PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, + { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attribute may only be set when PDO::ATTR_CURSOR is set to " + "PDO::CURSOR_SCROLL in the $driver_options array of PDO::prepare.", -69, false } + }, + { + SQLSRV_ERROR_INVALID_BUFFER_LIMIT, + { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not " + "positive. Only positive numbers are valid for this attribute.", -70, false } + }, + { + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, + { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -71, true } + }, + { UINT_MAX, {} } +}; + +// Returns a sqlsrv_error for a given error code. +sqlsrv_error_const* get_error_message(unsigned int sqlsrv_error_code) { + + sqlsrv_error_const *error_message = NULL; + int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr(g_pdo_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; + if( zr == FAILURE ) { + DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); + } + + SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null"); + + return error_message; +} + +// PDO error handler for the environment context. +bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ) +{ + SQLSRV_ASSERT(( ctx != NULL ), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null" ); + pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); + SQLSRV_ASSERT(( dbh != NULL ), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null" ); + + sqlsrv_error_auto_ptr error; + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + } + else { + + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + } + + strcpy_s( dbh->error_code, sizeof( pdo_error_type ), reinterpret_cast( error->sqlstate )); + + switch( dbh->error_mode ) { + + case PDO_ERRMODE_EXCEPTION: + if( !warning ) { + + pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + } + ctx.set_last_error( error ); + break; + + default: + DIE( "pdo_sqlsrv_handle_env_error: Unexpected error mode. %1!d!", dbh->error_mode ); + break; + } + + // we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count + // return error ignored = true for warnings. + return ( warning ? true : false ); + +} + +// pdo error handler for the dbh context. +bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ) +{ + pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); + SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" ); + + sqlsrv_error_auto_ptr error; + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + } + else { + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + } + + SQLSRV_ASSERT(strlen(reinterpret_cast(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow"); + strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast(error->sqlstate)); + + switch( dbh->error_mode ) { + case PDO_ERRMODE_EXCEPTION: + if( !warning ) { + + pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + } + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_WARNING: + if( !warning ) { + size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + + MAX_DIGITS + WARNING_MIN_LENGTH + 1; + sqlsrv_malloc_auto_ptr msg; + msg = static_cast( sqlsrv_malloc( msg_len ) ); + core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, + error->native_message ); + php_error( E_WARNING, msg ); + } + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_SILENT: + ctx.set_last_error( error ); + break; + default: + DIE( "Unknown error mode. %1!d!", dbh->error_mode ); + break; + } + + // return error ignored = true for warnings. + return ( warning ? true : false ); +} + +// PDO error handler for the statement context. +bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ) +{ + pdo_stmt_t* pdo_stmt = reinterpret_cast( ctx.driver()); + SQLSRV_ASSERT( pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed" ); + + sqlsrv_error_auto_ptr error; + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + } + else { + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + } + + SQLSRV_ASSERT( strlen( reinterpret_cast( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow"); + strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast( error->sqlstate )); + + switch( pdo_stmt->dbh->error_mode ) { + case PDO_ERRMODE_EXCEPTION: + if( !warning ) { + + pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + } + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_WARNING: + ctx.set_last_error( error ); + break; + case PDO_ERRMODE_SILENT: + ctx.set_last_error( error ); + break; + default: + DIE( "Unknown error mode. %1!d!", pdo_stmt->dbh->error_mode ); + break; + } + + // return error ignored = true for warnings. + return ( warning ? true : false ); +} + + +// Transfer a sqlsrv_context's error to a PDO zval. The standard format for a zval error is 3 elements: +// 0, native code +// 1, native message +// 2, SQLSTATE of the error (driver specific error messages are 'IMSSP') + +void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ) +{ + if( last_error ) { + // SQLSTATE is already present in the zval. + add_next_index_long( pdo_zval, last_error->native_code ); + add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message )); + } +} + +// Formats the error message and writes to the php error log. +void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ) +{ + if( (severity & PDO_SQLSRV_G( log_severity )) == 0 ) { + return; + } + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); + + // if an error occurs for FormatMessage, we just output an internal error occurred. + if( rc == 0 ) { + SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); + std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); + } + + php_log_err( log_msg TSRMLS_CC ); +} + +namespace { + +void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC ) +{ + zval ex_obj; + ZVAL_UNDEF( &ex_obj ); + + zend_class_entry* ex_class = php_pdo_get_exception(); + + int zr = object_init_ex( &ex_obj, ex_class ); + SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" ); + + sqlsrv_malloc_auto_ptr ex_msg; + size_t ex_msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + + 12 + 1; // 12 = "SQLSTATE[]: " + ex_msg = reinterpret_cast( sqlsrv_malloc( ex_msg_len )); + snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message ); + zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, + ex_msg TSRMLS_CC ); + zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, + reinterpret_cast( error->sqlstate ) TSRMLS_CC ); + + zval ex_error_info; + ZVAL_UNDEF( &ex_error_info ); + array_init( &ex_error_info ); + add_next_index_string( &ex_error_info, reinterpret_cast( error->sqlstate )); + add_next_index_long( &ex_error_info, error->native_code ); + add_next_index_string( &ex_error_info, reinterpret_cast( error->native_message )); + //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) + //and the refcount of the zend_array is incremented by 1 + zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, + &ex_error_info TSRMLS_CC ); + + //DELREF ex_error_info here to decrement the refcount of the zend_array is 1 + //the global hashtable EG(exception) then points to the zend_object in ex_obj in zend_throw_exception_object; + //this ensure when EG(exception) cleans itself at php shutdown, the zend_array allocated is properly destroyed + Z_DELREF( ex_error_info ); + zend_throw_exception_object( &ex_obj TSRMLS_CC ); +} + +} diff --git a/pdo_sqlsrv/pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h similarity index 90% rename from pdo_sqlsrv/pdo_sqlsrv.h rename to source/pdo_sqlsrv/php_pdo_sqlsrv.h index 959f6696f..348bf7d24 100644 --- a/pdo_sqlsrv/pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -1,403 +1,395 @@ -#ifndef PDO_SQLSRV_H -#define PDO_SQLSRV_H - -//--------------------------------------------------------------------------------------------------------------------------------- -// File: pdo_sqlsrv.h -// -// Contents: Declarations for the extension -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" -#include "version.h" - -extern "C" { - -#include "pdo/php_pdo.h" -#include "pdo/php_pdo_driver.h" -#include "pdo/php_pdo_int.h" - -} - -#include -#include - - -//********************************************************************************************************************************* -// Constants and Types -//********************************************************************************************************************************* - -// sqlsrv driver specific PDO attributes -enum PDO_SQLSRV_ATTR { - - // Currently there are only three custom attributes for this driver. - SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC, - SQLSRV_ATTR_QUERY_TIMEOUT, - SQLSRV_ATTR_DIRECT_QUERY, - SQLSRV_ATTR_CURSOR_SCROLL_TYPE, - SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, - SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, -}; - -// valid set of values for TransactionIsolation connection option -namespace PDOTxnIsolationValues { - - const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED"; - const char READ_COMMITTED[] = "READ_COMMITTED"; - const char REPEATABLE_READ[] = "REPEATABLE_READ"; - const char SERIALIZABLE[] = "SERIALIZABLE"; - const char SNAPSHOT[] = "SNAPSHOT"; -} - -//********************************************************************************************************************************* -// Global variables -//********************************************************************************************************************************* - -extern "C" { - -// request level variables -ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) - -unsigned int log_severity; -zend_long client_buffer_max_size; - -ZEND_END_MODULE_GLOBALS(pdo_sqlsrv) - -ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv); - -} - -// macros used to access the global variables. Use these to make global variable access agnostic to threads -#ifdef ZTS -#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v) -#else -#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v -#endif - -// INI settings and constants -// (these are defined as macros to allow concatenation as we do below) -#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" -#define INI_PDO_SQLSRV_LOG "log_severity" -#define INI_PREFIX "pdo_sqlsrv." - -PHP_INI_BEGIN() - STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, - zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, - client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) -PHP_INI_END() - -// henv context for creating connections -extern sqlsrv_context* g_henv_cp; -extern sqlsrv_context* g_henv_ncp; - - -//********************************************************************************************************************************* -// Initialization -//********************************************************************************************************************************* - -// module global variables (initialized in minit and freed in mshutdown) -extern HashTable* g_pdo_errors_ht; - -// module initialization -PHP_MINIT_FUNCTION(pdo_sqlsrv); -// module shutdown function -PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv); -// request initialization function -PHP_RINIT_FUNCTION(pdo_sqlsrv); -// request shutdown function -PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv); -// module info function (info returned by phpinfo()) -PHP_MINFO_FUNCTION(pdo_sqlsrv); - -extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP - -//********************************************************************************************************************************* -// PDO DSN Parser -//********************************************************************************************************************************* - -// Parser class used to parse DSN connection string. -class conn_string_parser -{ - enum States - { - FirstKeyValuePair, - Key, - Value, - ValueContent1, - ValueContent2, - RCBEncountered, - NextKeyValuePair, - }; - - private: - const char* conn_str; - sqlsrv_context* ctx; - int len; - int pos; - unsigned int current_key; - const char* current_key_name; - HashTable* conn_options_ht; - inline bool next( void ); - inline bool is_eos( void ); - inline bool is_white_space( char c ); - bool discard_white_spaces( void ); - int discard_trailing_white_spaces( const char* str, int len ); - void conn_string_parser::validate_key( const char *key, int key_len TSRMLS_DC ); - void add_key_value_pair( const char* value, int len TSRMLS_DC ); - - public: - conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ); - void parse_conn_string( TSRMLS_D ); -}; - -//********************************************************************************************************************************* -// Connection -//********************************************************************************************************************************* -extern const connection_option PDO_CONN_OPTS[]; - -int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC); - -// a core layer pdo dbh object. This object inherits and overrides the statement factory -struct pdo_sqlsrv_dbh : public sqlsrv_conn { - - zval* stmts; - bool direct_query; - long query_timeout; - zend_long client_buffer_max_size; - SQLSRV_ENCODING bind_param_encoding; - bool fetch_numeric; - - pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ); -}; - - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -struct stmt_option_encoding : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_scrollable : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_direct_query : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_cursor_scroll_type : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_emulate_prepares : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_fetch_numeric : public stmt_option_functor { - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; - -// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary -struct pdo_sqlsrv_stmt : public sqlsrv_stmt { - - pdo_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : - sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), - direct_query( false ), - direct_query_subst_string( NULL ), - direct_query_subst_string_len( 0 ), - bound_column_param_types( NULL ), - fetch_numeric( false ) - { - pdo_sqlsrv_dbh* db = static_cast( c ); - direct_query = db->direct_query; - fetch_numeric = db->fetch_numeric; - } - - virtual ~pdo_sqlsrv_stmt( void ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ); - - bool direct_query; // flag set if the query should be executed directly or prepared - const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters - size_t direct_query_subst_string_len; // length of query string used for direct queries - - // meta data for current result set - std::vector > current_meta_data; - pdo_param_type* bound_column_param_types; - bool fetch_numeric; -}; - - -//********************************************************************************************************************************* -// Error Handling Functions -//********************************************************************************************************************************* - -// represents the mapping between an error_code and the corresponding error message. -struct pdo_error { - - unsigned int error_code; - sqlsrv_error_const sqlsrv_error; -}; - -// called when an error occurs in the core layer. These routines are set as the error_callback in a -// context. The context is passed to this function since it contains the function - -bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ); -bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ); -bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, - va_list* print_args ); - -// pointer to the function to return the class entry for the PDO exception Set in MINIT -extern zend_class_entry* (*pdo_get_exception_class)( void ); - -// common routine to transfer a sqlsrv_context's error to a PDO zval -void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ); - -// reset the errors from the last operation -inline void pdo_reset_dbh_error( pdo_dbh_t* dbh TSRMLS_DC ) -{ - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error - - // release the last statement from the dbh so that error handling won't have a statement passed to it - if( dbh->query_stmt ) { - dbh->query_stmt = NULL; - zend_objects_store_del( Z_OBJ_P(&dbh->query_stmt_zval TSRMLS_CC) ); - } - - // if the driver isn't valid, just return (PDO calls close sometimes more than once?) - if( dbh->driver_data == NULL ) { - return; - } - - // reset the last error on the sqlsrv_context - sqlsrv_context* ctx = static_cast( dbh->driver_data ); - - if( ctx->last_error() ) { - ctx->last_error().reset(); - } -} - -#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); - -inline void pdo_reset_stmt_error( pdo_stmt_t* stmt ) -{ - strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error - - // if the driver isn't valid, just return (PDO calls close sometimes more than once?) - if( stmt->driver_data == NULL ) { - return; - } - - // reset the last error on the sqlsrv_context - sqlsrv_context* ctx = static_cast( stmt->driver_data ); - - if( ctx->last_error() ) { - ctx->last_error().reset(); - } -} - -#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt ); - -// validate the driver objects -#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); } -#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); } - - -//********************************************************************************************************************************* -// Utility Functions -//********************************************************************************************************************************* - -// List of PDO specific error messages. -enum PDO_ERROR_CODES { - - PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC, - PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, - PDO_SQLSRV_ERROR_INVALID_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, - PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, - PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, - PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, - PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, - PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, - PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, - PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, - PDO_SQLSRV_ERROR_PARAM_PARSE, - PDO_SQLSRV_ERROR_LAST_INSERT_ID, - PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, - PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, - PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, - PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, - PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, - PDO_SQLSRV_ERROR_INVALID_DSN_STRING, - PDO_SQLSRV_ERROR_INVALID_DSN_KEY, - PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, - PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, - PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, - PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, - SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, - PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, - PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, - PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, - PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, - -}; - -extern pdo_error PDO_ERRORS[]; - -#define THROW_PDO_ERROR( ctx, custom, ... ) \ - call_error_handler( ctx, custom TSRMLS_CC, false, __VA_ARGS__ ); \ - throw pdo::PDOException(); - -namespace pdo { - - // an error which occurred in our PDO driver, NOT an exception thrown by PDO - struct PDOException : public core::CoreException { - - PDOException() : CoreException() - { - } - }; - -} // namespace pdo - -// called pdo_parse_params in php_pdo_driver.h -// we renamed it for 2 reasons: 1) we can't have the same name since it would conflict with our dynamic linking, and -// 2) this is a more precise name -extern int (*pdo_subst_named_params)(pdo_stmt_t *stmt, char *inquery, size_t inquery_len, - char **outquery, size_t *outquery_len TSRMLS_DC); - -// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro -void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); - -#endif /* PDO_SQLSRV_H */ - +#ifndef PHP_PDO_SQLSRV_H +#define PHP_PDO_SQLSRV_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: php_pdo_sqlsrv.h +// +// Contents: Declarations for the extension +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" +#include "version.h" + +extern "C" { + +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" + +} + +#include +#include + + +//********************************************************************************************************************************* +// Constants and Types +//********************************************************************************************************************************* + +// sqlsrv driver specific PDO attributes +enum PDO_SQLSRV_ATTR { + + // Currently there are only three custom attributes for this driver. + SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC, + SQLSRV_ATTR_QUERY_TIMEOUT, + SQLSRV_ATTR_DIRECT_QUERY, + SQLSRV_ATTR_CURSOR_SCROLL_TYPE, + SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, + SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, +}; + +// valid set of values for TransactionIsolation connection option +namespace PDOTxnIsolationValues { + + const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED"; + const char READ_COMMITTED[] = "READ_COMMITTED"; + const char REPEATABLE_READ[] = "REPEATABLE_READ"; + const char SERIALIZABLE[] = "SERIALIZABLE"; + const char SNAPSHOT[] = "SNAPSHOT"; +} + +//********************************************************************************************************************************* +// Global variables +//********************************************************************************************************************************* + +extern "C" { + +// request level variables +ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) + +unsigned int log_severity; +zend_long client_buffer_max_size; + +ZEND_END_MODULE_GLOBALS(pdo_sqlsrv) + +ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv); + +} + +// macros used to access the global variables. Use these to make global variable access agnostic to threads +#ifdef ZTS +#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v) +#else +#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v +#endif + +// INI settings and constants +// (these are defined as macros to allow concatenation as we do below) +#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" +#define INI_PDO_SQLSRV_LOG "log_severity" +#define INI_PREFIX "pdo_sqlsrv." + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, + zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, + client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) +PHP_INI_END() + +// henv context for creating connections +extern sqlsrv_context* g_henv_cp; +extern sqlsrv_context* g_henv_ncp; + + +//********************************************************************************************************************************* +// Initialization +//********************************************************************************************************************************* + +// module global variables (initialized in minit and freed in mshutdown) +extern HashTable* g_pdo_errors_ht; + +#define phpext_pdo_sqlsrv_ptr &g_pdo_sqlsrv_module_entry + +// module initialization +PHP_MINIT_FUNCTION(pdo_sqlsrv); +// module shutdown function +PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv); +// request initialization function +PHP_RINIT_FUNCTION(pdo_sqlsrv); +// request shutdown function +PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv); +// module info function (info returned by phpinfo()) +PHP_MINFO_FUNCTION(pdo_sqlsrv); + +extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP + +//********************************************************************************************************************************* +// PDO DSN Parser +//********************************************************************************************************************************* + +// Parser class used to parse DSN connection string. +class conn_string_parser +{ + enum States + { + FirstKeyValuePair, + Key, + Value, + ValueContent1, + ValueContent2, + RCBEncountered, + NextKeyValuePair, + }; + + private: + const char* conn_str; + sqlsrv_context* ctx; + int len; + int pos; + unsigned int current_key; + const char* current_key_name; + HashTable* conn_options_ht; + inline bool next( void ); + inline bool is_eos( void ); + inline bool is_white_space( char c ); + bool discard_white_spaces( void ); + int discard_trailing_white_spaces( const char* str, int len ); + void validate_key( const char *key, int key_len TSRMLS_DC ); + void add_key_value_pair( const char* value, int len TSRMLS_DC ); + + public: + conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht ); + void parse_conn_string( TSRMLS_D ); +}; + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* +extern const connection_option PDO_CONN_OPTS[]; + +int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC); + +// a core layer pdo dbh object. This object inherits and overrides the statement factory +struct pdo_sqlsrv_dbh : public sqlsrv_conn { + + zval* stmts; + bool direct_query; + long query_timeout; + zend_long client_buffer_max_size; + SQLSRV_ENCODING bind_param_encoding; + bool fetch_numeric; + + pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ); +}; + + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +struct stmt_option_encoding : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_scrollable : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_direct_query : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_cursor_scroll_type : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_emulate_prepares : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_fetch_numeric : public stmt_option_functor { + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; + +// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary +struct pdo_sqlsrv_stmt : public sqlsrv_stmt { + + pdo_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : + sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), + direct_query( false ), + direct_query_subst_string( NULL ), + direct_query_subst_string_len( 0 ), + bound_column_param_types( NULL ), + fetch_numeric( false ) + { + pdo_sqlsrv_dbh* db = static_cast( c ); + direct_query = db->direct_query; + fetch_numeric = db->fetch_numeric; + } + + virtual ~pdo_sqlsrv_stmt( void ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ); + + bool direct_query; // flag set if the query should be executed directly or prepared + const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters + size_t direct_query_subst_string_len; // length of query string used for direct queries + + // meta data for current result set + std::vector > current_meta_data; + pdo_param_type* bound_column_param_types; + bool fetch_numeric; +}; + + +//********************************************************************************************************************************* +// Error Handling Functions +//********************************************************************************************************************************* + +// represents the mapping between an error_code and the corresponding error message. +struct pdo_error { + + unsigned int error_code; + sqlsrv_error_const sqlsrv_error; +}; + +// called when an error occurs in the core layer. These routines are set as the error_callback in a +// context. The context is passed to this function since it contains the function + +bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ); +bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ); +bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, + va_list* print_args ); + +// common routine to transfer a sqlsrv_context's error to a PDO zval +void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval ); + +// reset the errors from the last operation +inline void pdo_reset_dbh_error( pdo_dbh_t* dbh TSRMLS_DC ) +{ + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error + + // release the last statement from the dbh so that error handling won't have a statement passed to it + if( dbh->query_stmt ) { + dbh->query_stmt = NULL; + zend_objects_store_del( Z_OBJ_P(&dbh->query_stmt_zval TSRMLS_CC) ); + } + + // if the driver isn't valid, just return (PDO calls close sometimes more than once?) + if( dbh->driver_data == NULL ) { + return; + } + + // reset the last error on the sqlsrv_context + sqlsrv_context* ctx = static_cast( dbh->driver_data ); + + if( ctx->last_error() ) { + ctx->last_error().reset(); + } +} + +#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); + +inline void pdo_reset_stmt_error( pdo_stmt_t* stmt ) +{ + strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error + + // if the driver isn't valid, just return (PDO calls close sometimes more than once?) + if( stmt->driver_data == NULL ) { + return; + } + + // reset the last error on the sqlsrv_context + sqlsrv_context* ctx = static_cast( stmt->driver_data ); + + if( ctx->last_error() ) { + ctx->last_error().reset(); + } +} + +#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt ); + +// validate the driver objects +#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); } +#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); } + + +//********************************************************************************************************************************* +// Utility Functions +//********************************************************************************************************************************* + +// List of PDO specific error messages. +enum PDO_ERROR_CODES { + + PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC, + PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, + PDO_SQLSRV_ERROR_INVALID_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, + PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, + PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, + PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, + PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, + PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, + PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, + PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, + PDO_SQLSRV_ERROR_PARAM_PARSE, + PDO_SQLSRV_ERROR_LAST_INSERT_ID, + PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, + PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, + PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, + PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, + PDO_SQLSRV_ERROR_INVALID_DSN_STRING, + PDO_SQLSRV_ERROR_INVALID_DSN_KEY, + PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, + PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, + PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, + PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, + SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, + PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, + PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, + PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, + +}; + +extern pdo_error PDO_ERRORS[]; + +#define THROW_PDO_ERROR( ctx, custom, ... ) \ + call_error_handler( ctx, custom TSRMLS_CC, false, ## __VA_ARGS__ ); \ + throw pdo::PDOException(); + +namespace pdo { + + // an error which occurred in our PDO driver, NOT an exception thrown by PDO + struct PDOException : public core::CoreException { + + PDOException() : CoreException() + { + } + }; + +} // namespace pdo + +// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro +void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); + +#endif /* PHP_PDO_SQLSRV_H */ + diff --git a/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc similarity index 87% rename from pdo_sqlsrv/template.rc rename to source/pdo_sqlsrv/template.rc index c6a4f82a8..d36cd82fb 100644 --- a/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -1,83 +1,83 @@ -//---------------------------------------------------------------------------------------------------------------------------------- -// File: template.rc -// -// Contents: Version resource -// -// Microsoft Drivers 4.0 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#ifdef APSTUDIO_INVOKED -# error dont edit with MSVC -#endif - -#include "winresrc.h" -#include "main/php_version.h" -#include "version.h" - -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -#ifndef THANKS_GUYS -# define THANKS_GUYS "" -#endif - -#ifdef WANT_LOGO -0 ICON win32\build\php.ico -#endif - -#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build -#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel -#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build) -#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel) - -//Version -VS_VERSION_INFO VERSIONINFO - FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION - PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_MMDD,0 - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_DLL - FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright � 2001-2016 The PHP Group. All rights reserved.\0" - VALUE "CompanyName", "Microsoft Corp.\0" - VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0" - VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION) - VALUE "InternalName", FILE_NAME "\0" - VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" - VALUE "OriginalFilename", FILE_NAME "\0" - VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" - VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD) - VALUE "URL", "http://www.microsoft.com\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END - -#ifdef MC_INCLUDE -#include MC_INCLUDE -#endif - +//---------------------------------------------------------------------------------------------------------------------------------- +// File: template.rc +// +// Contents: Version resource +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifdef APSTUDIO_INVOKED +# error dont edit with MSVC +#endif + +#include "winresrc.h" +#include "main/php_version.h" +#include "shared/version.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifndef THANKS_GUYS +# define THANKS_GUYS "" +#endif + +#ifdef WANT_LOGO +0 ICON win32\build\php.ico +#endif + +#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build +#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel +#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build) +#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel) + +//Version +VS_VERSION_INFO VERSIONINFO + FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD + PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_RELEASE,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright © 2001-2016 The PHP Group. All rights reserved.\0" + VALUE "CompanyName", "Microsoft Corp.\0" + VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0" + VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD) + VALUE "InternalName", FILE_NAME "\0" + VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" + VALUE "OriginalFilename", FILE_NAME "\0" + VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" + VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE) + VALUE "URL", "http://www.microsoft.com\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#ifdef MC_INCLUDE +#include MC_INCLUDE +#endif + diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp new file mode 100644 index 000000000..b3aa3e7a2 --- /dev/null +++ b/source/shared/FormattedPrint.cpp @@ -0,0 +1,2805 @@ + +//----------------------------------------------------------------------------- +// File: FormattedPrint.cpp +// +// +// Contents: Contains functions for handling Windows format strings +// and UTF-16 on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include +#include + +#if !defined(_MSC_VER) +#include +#endif + +#include "StringFunctions.h" + +// XPLAT_ODBC_TODO VSTS 819733 - MPlat: Reconcile std c++ usage between platforms +#ifdef MPLAT_UNIX +// #include +#elif defined(MPLAT_WWOWH) +# define _ASSERTE assert +# include +# undef _M_IX86 +# undef min +# undef max +#endif +#include +#include +#ifdef MPLAT_UNIX + #include "sal_def.h" +#endif + +#ifndef _WIN32 +#define PTR_IS_INT64 1 +#else +#define PTR_IS_INT64 0 +#endif // !_WIN32 + +#ifndef _MSC_VER +// SQL Server does not have a long double type +#define LONGDOUBLE_IS_DOUBLE 1 +typedef double _LONGDOUBLE; +#endif + +// XPLAT_ODBC_TODO VSTS VSTS 718708 Localization +#define _SAFECRT_IMPL + +#if !defined(_countof) +#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0])) +#endif // _countof + +#ifndef _VALIDATE_RETURN +#define _VALIDATE_RETURN( expr, errorcode, retexpr ) \ + { \ + int _Expr_val=!!(expr); \ + if ( !( _Expr_val ) ) \ + { \ + assert(false); \ + errno = errorcode; \ + return ( retexpr ); \ + } \ + } +#endif /* _VALIDATE_RETURN */ + + +static const char *__nullstring = "(null)"; /* string to print on null ptr */ +static const wchar_t *__wnullstring = L"(null)";/* string to print on null ptr */ + +#define BUFFERSIZE 512 +#define MAXPRECISION BUFFERSIZE +#define _CVTBUFSIZE (309+40) /* # of digits in max. dp value + slop */ + + +/* flag definitions */ +#define FL_SIGN 0x00001 /* put plus or minus in front */ +#define FL_SIGNSP 0x00002 /* put space or minus in front */ +#define FL_LEFT 0x00004 /* left justify */ +#define FL_LEADZERO 0x00008 /* pad with leading zeros */ +#define FL_LONG 0x00010 /* long value given */ +#define FL_SHORT 0x00020 /* short value given */ +#define FL_SIGNED 0x00040 /* signed data given */ +#define FL_ALTERNATE 0x00080 /* alternate form requested */ +#define FL_NEGATIVE 0x00100 /* value is negative */ +#define FL_FORCEOCTAL 0x00200 /* force leading '0' for octals */ +#define FL_LONGDOUBLE 0x00400 /* long double value given */ +#define FL_WIDECHAR 0x00800 /* wide characters */ +#define FL_LONGLONG 0x01000 /* long long value given */ +#define FL_I64 0x08000 /* __int64 value given */ + + +/* state definitions */ +enum STATE { + ST_NORMAL, /* normal state; outputting literal chars */ + ST_PERCENT, /* just read '%' */ + ST_FLAG, /* just read flag character */ + ST_WIDTH, /* just read width specifier */ + ST_DOT, /* just read '.' */ + ST_PRECIS, /* just read precision specifier */ + ST_SIZE, /* just read size specifier */ + ST_TYPE /* just read type specifier */ + ,ST_INVALID /* Invalid format */ + +}; + +#define NUMSTATES (ST_INVALID + 1) + +/* character type values */ +enum CHARTYPE { + CH_OTHER, /* character with no special meaning */ + CH_PERCENT, /* '%' */ + CH_DOT, /* '.' */ + CH_STAR, /* '*' */ + CH_ZERO, /* '0' */ + CH_DIGIT, /* '1'..'9' */ + CH_FLAG, /* ' ', '+', '-', '#' */ + CH_SIZE, /* 'h', 'l', 'L', 'N', 'F', 'w' */ + CH_TYPE /* type specifying character */ +}; + + +static const unsigned char __lookuptable_s[] = { + /* ' ' */ 0x06, + /* '!' */ 0x80, + /* '"' */ 0x80, + /* '#' */ 0x86, + /* '$' */ 0x80, + /* '%' */ 0x81, + /* '&' */ 0x80, + /* ''' */ 0x00, + /* '(' */ 0x00, + /* ')' */ 0x10, + /* '*' */ 0x03, + /* '+' */ 0x86, + /* ',' */ 0x80, + /* '-' */ 0x86, + /* '.' */ 0x82, + /* '/' */ 0x80, + /* '0' */ 0x14, + /* '1' */ 0x05, + /* '2' */ 0x05, + /* '3' */ 0x45, + /* '4' */ 0x45, + /* '5' */ 0x45, + /* '6' */ 0x85, + /* '7' */ 0x85, + /* '8' */ 0x85, + /* '9' */ 0x05, + /* ':' */ 0x00, + /* ';' */ 0x00, + /* '<' */ 0x30, + /* '=' */ 0x30, + /* '>' */ 0x80, + /* '?' */ 0x50, + /* '@' */ 0x80, +#if defined (_SAFECRT_IMPL) + /* 'A' */ 0x80, // Disable %A format +#else /* defined (_SAFECRT_IMPL) */ + /* 'A' */ 0x88, +#endif /* defined (_SAFECRT_IMPL) */ + /* 'B' */ 0x00, + /* 'C' */ 0x08, + /* 'D' */ 0x00, + /* 'E' */ 0x28, + /* 'F' */ 0x27, + /* 'G' */ 0x38, + /* 'H' */ 0x50, + /* 'I' */ 0x57, + /* 'J' */ 0x80, + /* 'K' */ 0x00, + /* 'L' */ 0x07, + /* 'M' */ 0x00, + /* 'N' */ 0x37, + /* 'O' */ 0x30, + /* 'P' */ 0x30, + /* 'Q' */ 0x50, + /* 'R' */ 0x50, + /* 'S' */ 0x88, + /* 'T' */ 0x00, + /* 'U' */ 0x00, + /* 'V' */ 0x00, + /* 'W' */ 0x20, + /* 'X' */ 0x28, + /* 'Y' */ 0x80, + /* 'Z' */ 0x88, + /* '[' */ 0x80, + /* '\' */ 0x80, + /* ']' */ 0x00, + /* '^' */ 0x00, + /* '_' */ 0x00, + /* '`' */ 0x60, +#if defined (_SAFECRT_IMPL) + /* 'a' */ 0x60, // Disable %a format +#else /* defined (_SAFECRT_IMPL) */ + /* 'a' */ 0x68, +#endif /* defined (_SAFECRT_IMPL) */ + /* 'b' */ 0x60, + /* 'c' */ 0x68, + /* 'd' */ 0x68, + /* 'e' */ 0x68, + /* 'f' */ 0x08, + /* 'g' */ 0x08, + /* 'h' */ 0x07, + /* 'i' */ 0x78, + /* 'j' */ 0x70, + /* 'k' */ 0x70, + /* 'l' */ 0x77, + /* 'm' */ 0x70, + /* 'n' */ 0x70, + /* 'o' */ 0x08, + /* 'p' */ 0x08, + /* 'q' */ 0x00, + /* 'r' */ 0x00, + /* 's' */ 0x08, + /* 't' */ 0x00, + /* 'u' */ 0x08, + /* 'v' */ 0x00, + /* 'w' */ 0x07, + /* 'x' */ 0x08 +}; + +static inline CHARTYPE GetCharType( WCHAR wch ) +{ + return ((wch < (L' ') || wch > (L'x')) ? CH_OTHER : (enum CHARTYPE)(__lookuptable_s[wch - (L' ')] & 0xF)); +} +static inline CHARTYPE GetCharType( char ch ) +{ + return ((ch < (' ') || ch > ('x')) ? CH_OTHER : (enum CHARTYPE)(__lookuptable_s[ch - (' ')] & 0xF)); +} +static inline STATE GetState( CHARTYPE type, STATE oldState ) +{ + return (enum STATE)(__lookuptable_s[type * NUMSTATES + oldState] >> 4); +} + + +static bool isleadbyte(unsigned char ch) +{ + return (FALSE != IsDBCSLeadByte(ch) ); +} +static bool isleadwchar(WCHAR ch) +{ + return ((ch & 0xFC00) == 0xD800); +} +static bool _isleadbyte_l(unsigned char ch, _locale_t loc) +{ + // XPLAT_ODBC_TODO VSTS 718708 Localization + return ( FALSE != IsDBCSLeadByte(ch) ); +} + + + +#define _WCTOMB_S mplat_wctomb_s +errno_t mplat_wctomb_s( + int *pRetValue, + char *mbchar, + size_t sizeInBytes, + WCHAR wchar +) +{ + DWORD rc; + size_t cch = SystemLocale::FromUtf16( CP_ACP, &wchar, 1, mbchar, sizeInBytes, NULL, &rc ); + *pRetValue = (int)cch; + return (ERROR_SUCCESS == rc ? 0 : -1); +} + +#define _MBTOWC mplat_mbtowc +int mplat_mbtowc( + WCHAR * pwchar, + const char * pmb, + size_t n +) +{ + size_t cch = SystemLocale::NextChar( CP_ACP, pmb ) - pmb; + if ( 0 < cch ) + { + size_t cchActual = SystemLocale::ToUtf16( CP_ACP, pmb, cch, pwchar, 1, NULL ); + assert( cch == cchActual ); + } + return (int)cch; +} + +// Floating point print routines +void _CFLTCVT( double * dbl, char * buf, int bufSize, char fmt, int precision, int caps, _locale_t loc = NULL ) +{ + const size_t local_bufsize = 8; + char local_fmt[local_bufsize]; + + if ( 0 != caps ) + { + fmt -= ('a') - ('A'); /* convert format char to upper */ + } + int chars_printed = snprintf( local_fmt, local_bufsize, "%%.%d%c", precision-1, fmt ); + assert( 0 < chars_printed && (size_t)chars_printed < local_bufsize ); + + // We want to use the platform version of snprintf so temporarily undef. + // Formatting of floating pt values is complex so we didn't implement it here. + // Even porting the CRT code would've been difficult. Instead, we can use the + // platform's snprintf for just floating pt values. We have to undef to prevent + // recursing right back to here. +# undef snprintf +# if defined(MPLAT_WWOWH) +# undef _snprintf +# define snprintf _snprintf +# endif + chars_printed = snprintf( buf, bufSize, local_fmt, *dbl ); + assert( 0 < chars_printed && chars_printed < bufSize ); +# if defined(MPLAT_WWOWH) +# undef snprintf +# define _snprintf mplat_snprintf +# endif +# define snprintf mplat_snprintf +} + +#if !LONGDOUBLE_IS_DOUBLE +// SQL Server does not support the long double data type so this should never be called. +// It will be compiled out on Linux. +void _CLDCVT( _LONGDOUBLE * dbl, char * buf, int bufSize, char fmt, int precision, int caps ) +{ + assert(false); +} +#endif + +static enum STATE ProcessSizeA( char sizeCh, char fmt_ch, char next_fmt_ch, int * advance, int * flags ) +{ + *advance = 0; + switch (sizeCh) + { + case 'l': + /* + * In order to handle the ll case, we depart from the + * simple deterministic state machine. + */ + if ( 'l' == fmt_ch ) + { + *advance = 1; + *flags |= FL_LONGLONG; + } + else + { + *flags |= FL_LONG; + } + break; + + case 'I': + /* + * In order to handle the I, I32, and I64 size modifiers, we + * depart from the simple deterministic state machine. The + * code below scans for characters following the 'I', + * and defaults to 64 bit on WIN64 and 32 bit on WIN32 + */ +#if PTR_IS_INT64 + *flags |= FL_I64; /* 'I' => __int64 on WIN64 systems */ +#endif /* PTR_IS_INT64 */ + if ( '6' == fmt_ch && '4' == next_fmt_ch ) + { + *advance = 2; + *flags |= FL_I64; /* I64 => __int64 */ + } + else if ( '3' == fmt_ch && '2' == next_fmt_ch ) + { + *advance = 2; + *flags &= ~FL_I64; /* I32 => __int32 */ + } + else if ( + (fmt_ch == 'd') || + (fmt_ch == 'i') || + (fmt_ch == 'o') || + (fmt_ch == 'u') || + (fmt_ch == 'x') || + (fmt_ch == 'X') ) + { + /* + * Nothing further needed. %Id (et al) is + * handled just like %d, except that it defaults to 64 bits + * on WIN64. Fall through to the next iteration. + */ + } + else + { + return ST_NORMAL; + } + break; + + case 'h': + *flags |= FL_SHORT; + break; + + case 'w': + *flags |= FL_WIDECHAR; + } + + return ST_SIZE; +} + +static enum STATE ProcessSizeW( WCHAR sizeCh, WCHAR fmt_ch, WCHAR next_fmt_ch, int * advance, int * flags ) +{ + *advance = 0; + switch (sizeCh) + { + case L'l': + /* + * In order to handle the ll case, we depart from the + * simple deterministic state machine. + */ + if ( L'l' == fmt_ch ) + { + *advance = 1; + *flags |= FL_LONGLONG; + } + else + { + *flags |= FL_LONG; + } + break; + + case L'I': + /* + * In order to handle the I, I32, and I64 size modifiers, we + * depart from the simple deterministic state machine. The + * code below scans for characters following the 'I', + * and defaults to 64 bit on WIN64 and 32 bit on WIN32 + */ +#if PTR_IS_INT64 + *flags |= FL_I64; /* 'I' => __int64 on WIN64 systems */ +#endif /* PTR_IS_INT64 */ + if ( L'6' == fmt_ch && L'4' == next_fmt_ch ) + { + *advance = 2; + *flags |= FL_I64; /* I64 => __int64 */ + } + else if ( L'3' == fmt_ch && L'2' == next_fmt_ch ) + { + *advance = 2; + *flags &= ~FL_I64; /* I32 => __int32 */ + } + else if ( + (fmt_ch == L'd') || + (fmt_ch == L'i') || + (fmt_ch == L'o') || + (fmt_ch == L'u') || + (fmt_ch == L'x') || + (fmt_ch == L'X') ) + { + /* + * Nothing further needed. %Id (et al) is + * handled just like %d, except that it defaults to 64 bits + * on WIN64. Fall through to the next iteration. + */ + } + else + { + return ST_NORMAL; + } + break; + + case L'h': + *flags |= FL_SHORT; + break; + + case L'w': + *flags |= FL_WIDECHAR; + } + + return ST_SIZE; +} + +STATE ProcessSize( WCHAR sizeCh, const WCHAR * format, int * advance, int * flags ) +{ + WCHAR formatCh = *format; + WCHAR next_formatCh = (L'\0' == formatCh ? L'\0' : *(format+1)); + return ProcessSizeW( sizeCh, formatCh, next_formatCh, advance, flags ); +} + +STATE ProcessSize( char sizeCh, const char * format, int * advance, int * flags ) +{ + char formatCh = *format; + char next_formatCh = ('\0' == formatCh ? '\0' : *(format+1)); + return ProcessSizeA( sizeCh, formatCh, next_formatCh, advance, flags ); +} + +// Tools\vc\src\crt\amd64\output.c +int FormattedPrintA( IFormattedPrintOutput * output, const char *format, va_list argptr ) +{ + int hexadd=0; /* offset to add to number to get 'a'..'f' */ + char ch; /* character just read */ + int flags=0; /* flag word -- see #defines above for flag values */ + enum STATE state; /* current state */ + enum CHARTYPE chclass; /* class of current character */ + int radix; /* current conversion radix */ + int charsout; /* characters currently written so far, -1 = IO error */ + int fldwidth = 0; /* selected field width -- 0 means default */ + int precision = 0; /* selected precision -- -1 means default */ + char prefix[2]; /* numeric prefix -- up to two characters */ + int prefixlen=0; /* length of prefix -- 0 means no prefix */ + int capexp=0; /* non-zero = 'E' exponent signifient, zero = 'e' or unused */ + int no_output=0; /* non-zero = prodcue no output for this specifier */ + union { + char *sz; /* pointer text to be printed, not zero terminated */ + WCHAR *wz; + } text; + + int textlen; /* length of the text in bytes/wchars to be printed. + textlen is in multibyte or wide chars if _UNICODE */ + union { + char sz[BUFFERSIZE]; + } buffer; + WCHAR wchar; /* temp wchar_t */ + int buffersize; /* size of text.sz (used only for the call to _cfltcvt) */ + int bufferiswide=0; /* non-zero = buffer contains wide chars already */ + +#ifndef _SAFECRT_IMPL + _LocaleUpdate _loc_update(plocinfo); +#endif /* _SAFECRT_IMPL */ + + char *heapbuf = NULL; /* non-zero = test.sz using heap buffer to be freed */ + + int advance; /* count of how much helper fxns need format ptr incremented */ + + _VALIDATE_RETURN( ((output != NULL) && (format != NULL)), EINVAL, -1); + + charsout = 0; /* no characters written yet */ + textlen = 0; /* no text yet */ + state = ST_NORMAL; /* starting state */ + heapbuf = NULL; /* not using heap-allocated buffer */ + buffersize = 0; + + /* main loop -- loop while format character exist and no I/O errors */ + while ((ch = *format++) != '\0' && charsout >= 0) { + // Find char class and next state + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + /* execute code for each state */ + switch (state) { + + case ST_INVALID: + // "Incorrect format specifier" + assert( false ); + errno = EINVAL; + return -1; + + case ST_NORMAL: + + NORMAL_STATE: + + /* normal state -- just write character */ + bufferiswide = 0; +#ifdef _SAFECRT_IMPL + if (isleadbyte((unsigned char)ch)) { +#else /* _SAFECRT_IMPL */ + if (_isleadbyte_l((unsigned char)ch, _loc_update.GetLocaleT())) { +#endif /* _SAFECRT_IMPL */ + // XPLAT_ODBC_TODO VSTS 718708 Localization + // Deal with more than 2 storage units per character + output->WRITE_CHAR(ch, &charsout); + ch = *format++; + /* don't fall off format string */ + _VALIDATE_RETURN( (ch != '\0'), EINVAL, -1); + } + output->WRITE_CHAR(ch, &charsout); + break; + + case ST_PERCENT: + /* set default value of conversion parameters */ + prefixlen = fldwidth = no_output = capexp = 0; + flags = 0; + precision = -1; + bufferiswide = 0; /* default */ + break; + + case ST_FLAG: + /* set flag based on which flag character */ + switch (ch) { + case ('-'): + flags |= FL_LEFT; /* '-' => left justify */ + break; + case ('+'): + flags |= FL_SIGN; /* '+' => force sign indicator */ + break; + case (' '): + flags |= FL_SIGNSP; /* ' ' => force sign or space */ + break; + case ('#'): + flags |= FL_ALTERNATE; /* '#' => alternate form */ + break; + case ('0'): + flags |= FL_LEADZERO; /* '0' => pad with leading zeros */ + break; + } + break; + + case ST_WIDTH: + /* update width value */ + if (ch == ('*')) { + /* get width from arg list */ + fldwidth = va_arg(argptr, int); + if (fldwidth < 0) { + /* ANSI says neg fld width means '-' flag and pos width */ + flags |= FL_LEFT; + fldwidth = -fldwidth; + } + } + else { + /* add digit to current field width */ + fldwidth = fldwidth * 10 + (ch - ('0')); + } + break; + + case ST_DOT: + /* zero the precision, since dot with no number means 0 + not default, according to ANSI */ + precision = 0; + break; + + case ST_PRECIS: + /* update precison value */ + if (ch == ('*')) { + /* get precision from arg list */ + precision = va_arg(argptr, int); + if (precision < 0) + precision = -1; /* neg precision means default */ + } + else { + /* add digit to current precision */ + precision = precision * 10 + (ch - ('0')); + } + break; + + case ST_SIZE: + /* just read a size specifier, set the flags based on it */ + state = ProcessSize( ch, format, &advance, &flags ); + format += advance; + if ( ST_NORMAL == state ) + { + goto NORMAL_STATE; + } + break; + + case ST_TYPE: + /* we have finally read the actual type character, so we */ + /* now format and "print" the output. We use a big switch */ + /* statement that sets 'text' to point to the text that should */ + /* be printed, and 'textlen' to the length of this text. */ + /* Common code later on takes care of justifying it and */ + /* other miscellaneous chores. Note that cases share code, */ + /* in particular, all integer formatting is done in one place. */ + /* Look at those funky goto statements! */ + + switch (ch) { + + case ('C'): /* ISO wide character */ + if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR))) + flags |= FL_WIDECHAR; /* ISO std. */ + /* fall into 'c' case */ + + case ('c'): { + /* print a single character specified by int argument */ + if (flags & (FL_LONG|FL_WIDECHAR)) { + errno_t e = 0; + wchar = (WCHAR) va_arg(argptr, int); + /* convert to multibyte character */ + e = _WCTOMB_S(&textlen, buffer.sz, _countof(buffer.sz), wchar); + + /* check that conversion was successful */ + if (e != 0) + no_output = 1; + } else { + /* format multibyte character */ + /* this is an extension of ANSI */ + unsigned short temp; + temp = (unsigned short) va_arg(argptr, int); + { + buffer.sz[0] = (char) temp; + textlen = 1; + } + } + text.sz = buffer.sz; + } + break; + + case ('Z'): { + // 'Z' format specifier disabled + _VALIDATE_RETURN(0, EINVAL, -1); + } + break; + + case ('S'): /* ISO wide character string */ + if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR))) + flags |= FL_WIDECHAR; + + case ('s'): { + /* print a string -- */ + /* ANSI rules on how much of string to print: */ + /* all if precision is default, */ + /* min(precision, length) if precision given. */ + /* prints '(null)' if a null string is passed */ + + int i; + char *p; /* temps */ + WCHAR *pwch; + + /* At this point it is tempting to use strlen(), but */ + /* if a precision is specified, we're not allowed to */ + /* scan past there, because there might be no null */ + /* at all. Thus, we must do our own scan. */ + + i = (precision == -1) ? INT_MAX : precision; + text.sz = (char *)va_arg(argptr, void *); + + /* scan for null upto i characters */ + if (flags & (FL_LONG|FL_WIDECHAR)) { + if (text.wz == NULL) /* NULL passed, use special string */ + text.wz = (WCHAR *)__wnullstring; + bufferiswide = 1; + pwch = text.wz; + while ( i-- && *pwch ) + ++pwch; + textlen = (int)(pwch - text.wz); + /* textlen now contains length in wide chars */ + } else { + if (text.sz == NULL) /* NULL passed, use special string */ + text.sz = (char*) __nullstring; + p = text.sz; + while (i-- && *p) + ++p; + textlen = (int)(p - text.sz); /* length of the string */ + } + } + break; + + + case ('n'): { + // We will not support %n + _VALIDATE_RETURN(0, EINVAL, -1); + } + break; + + case ('E'): + case ('G'): + case ('A'): + capexp = 1; /* capitalize exponent */ + ch += ('a') - ('A'); /* convert format char to lower */ + /* DROP THROUGH */ + case ('e'): + case ('f'): + case ('g'): + case ('a'): { + /* floating point conversion -- we call cfltcvt routines */ + /* to do the work for us. */ + flags |= FL_SIGNED; /* floating point is signed conversion */ + text.sz = buffer.sz; /* put result in buffer */ + buffersize = BUFFERSIZE; + + /* compute the precision value */ + if (precision < 0) + precision = 6; /* default precision: 6 */ + else if (precision == 0 && ch == ('g')) + precision = 1; /* ANSI specified */ + else if (precision > MAXPRECISION) + precision = MAXPRECISION; + + if (precision > BUFFERSIZE - _CVTBUFSIZE) { + /* conversion will potentially overflow local buffer */ + /* so we need to use a heap-allocated buffer. */ + heapbuf = (char *)malloc(_CVTBUFSIZE + precision); + if (heapbuf != NULL) + { + text.sz = heapbuf; + buffersize = _CVTBUFSIZE + precision; + } + else + /* malloc failed, cap precision further */ + precision = BUFFERSIZE - _CVTBUFSIZE; + } + +#ifdef _SAFECRT_IMPL + /* for safecrt, we pass along the FL_ALTERNATE flag to _safecrt_cfltcvt */ + if (flags & FL_ALTERNATE) + { + capexp |= FL_ALTERNATE; + } +#endif /* _SAFECRT_IMPL */ + +#if !LONGDOUBLE_IS_DOUBLE + /* do the conversion */ + if (flags & FL_LONGDOUBLE) { + _LONGDOUBLE tmp; + tmp=va_arg(argptr, _LONGDOUBLE); + /* Note: assumes ch is in ASCII range */ + _CLDCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp); + } else +#endif /* !LONGDOUBLE_IS_DOUBLE */ + { + double tmp; + tmp=va_arg(argptr, double); + /* Note: assumes ch is in ASCII range */ + /* In safecrt, we provide a special version of _cfltcvt which internally calls printf (see safecrt_output_s.c) */ +#ifndef _SAFECRT_IMPL + _cfltcvt_l(&tmp, text.sz, buffersize, (char)ch, precision, capexp, _loc_update.GetLocaleT()); +#else /* _SAFECRT_IMPL */ + _CFLTCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp); +#endif /* _SAFECRT_IMPL */ + } + +#ifndef _SAFECRT_IMPL + /* For safecrt, this is done already in _safecrt_cfltcvt */ + + /* '#' and precision == 0 means force a decimal point */ + if ((flags & FL_ALTERNATE) && precision == 0) + { + _forcdecpt_l(text.sz, _loc_update.GetLocaleT()); + } + + /* 'g' format means crop zero unless '#' given */ + if (ch == ('g') && !(flags & FL_ALTERNATE)) + { + _cropzeros_l(text.sz, _loc_update.GetLocaleT()); + } +#endif /* _SAFECRT_IMPL */ + + /* check if result was negative, save '-' for later */ + /* and point to positive part (this is for '0' padding) */ + if (*text.sz == '-') { + flags |= FL_NEGATIVE; + ++text.sz; + } + + textlen = (int)strlen(text.sz); /* compute length of text */ + } + break; + + case ('d'): + case ('i'): + /* signed decimal output */ + flags |= FL_SIGNED; + radix = 10; + goto COMMON_INT; + + case ('u'): + radix = 10; + goto COMMON_INT; + + case ('p'): + /* write a pointer -- this is like an integer or long */ + /* except we force precision to pad with zeros and */ + /* output in big hex. */ + + precision = 2 * sizeof(void *); /* number of hex digits needed */ +#if PTR_IS_INT64 + flags |= FL_I64; /* assume we're converting an int64 */ +#endif /* !PTR_IS_INT */ + /* DROP THROUGH to hex formatting */ + + case ('X'): + /* unsigned upper hex output */ + hexadd = ('A') - ('9') - 1; /* set hexadd for uppercase hex */ + goto COMMON_HEX; + + case ('x'): + /* unsigned lower hex output */ + hexadd = ('a') - ('9') - 1; /* set hexadd for lowercase hex */ + /* DROP THROUGH TO COMMON_HEX */ + + COMMON_HEX: + radix = 16; + if (flags & FL_ALTERNATE) { + /* alternate form means '0x' prefix */ + prefix[0] = ('0'); + prefix[1] = (char)(('x') - ('a') + ('9') + 1 + hexadd); /* 'x' or 'X' */ + prefixlen = 2; + } + goto COMMON_INT; + + case ('o'): + /* unsigned octal output */ + radix = 8; + if (flags & FL_ALTERNATE) { + /* alternate form means force a leading 0 */ + flags |= FL_FORCEOCTAL; + } + /* DROP THROUGH to COMMON_INT */ + + COMMON_INT: { + /* This is the general integer formatting routine. */ + /* Basically, we get an argument, make it positive */ + /* if necessary, and convert it according to the */ + /* correct radix, setting text and textlen */ + /* appropriately. */ + + ULONGLONG number; /* number to convert */ + int digit; /* ascii value of digit */ + LONGLONG l; /* temp long value */ + + /* 1. read argument into l, sign extend as needed */ + if (flags & FL_I64) + l = va_arg(argptr, LONGLONG); + else if (flags & FL_LONGLONG) + l = va_arg(argptr, LONGLONG); + else + + if (flags & FL_SHORT) { + if (flags & FL_SIGNED) + l = (short) va_arg(argptr, int); /* sign extend */ + else + l = (unsigned short) va_arg(argptr, int); /* zero-extend*/ + + } else + { + if (flags & FL_SIGNED) + l = (int)va_arg(argptr, int); /* sign extend */ + else + l = (unsigned int) va_arg(argptr, int); /* zero-extend*/ + } + + /* 2. check for negative; copy into number */ + if ( (flags & FL_SIGNED) && l < 0) { + number = -l; + flags |= FL_NEGATIVE; /* remember negative sign */ + } else { + number = l; + } + + if ( (flags & FL_I64) == 0 && (flags & FL_LONGLONG) == 0 ) { + /* + * Unless printing a full 64-bit value, insure values + * here are not in cananical longword format to prevent + * the sign extended upper 32-bits from being printed. + */ + number &= 0xffffffff; + } + + /* 3. check precision value for default; non-default */ + /* turns off 0 flag, according to ANSI. */ + if (precision < 0) + precision = 1; /* default precision */ + else { + flags &= ~FL_LEADZERO; + if (precision > MAXPRECISION) + precision = MAXPRECISION; + } + + /* 4. Check if data is 0; if so, turn off hex prefix */ + if (number == 0) + prefixlen = 0; + + /* 5. Convert data to ASCII -- note if precision is zero */ + /* and number is zero, we get no digits at all. */ + + text.sz = &buffer.sz[BUFFERSIZE-1]; /* last digit at end of buffer */ + + while (precision-- > 0 || number != 0) { + digit = (int)(number % radix) + '0'; + number /= radix; /* reduce number */ + if (digit > '9') { + /* a hex digit, make it a letter */ + digit += hexadd; + } + *text.sz-- = (char)digit; /* store the digit */ + } + + textlen = (int)((char *)&buffer.sz[BUFFERSIZE-1] - text.sz); /* compute length of number */ + ++text.sz; /* text points to first digit now */ + + + /* 6. Force a leading zero if FORCEOCTAL flag set */ + if ((flags & FL_FORCEOCTAL) && (textlen == 0 || text.sz[0] != '0')) { + *--text.sz = '0'; + ++textlen; /* add a zero */ + } + } + break; + } + + /* At this point, we have done the specific conversion, and */ + /* 'text' points to text to print; 'textlen' is length. Now we */ + /* justify it, put on prefixes, leading zeros, and then */ + /* print it. */ + + if (!no_output) { + int padding; /* amount of padding, negative means zero */ + + if (flags & FL_SIGNED) { + if (flags & FL_NEGATIVE) { + /* prefix is a '-' */ + prefix[0] = ('-'); + prefixlen = 1; + } + else if (flags & FL_SIGN) { + /* prefix is '+' */ + prefix[0] = ('+'); + prefixlen = 1; + } + else if (flags & FL_SIGNSP) { + /* prefix is ' ' */ + prefix[0] = (' '); + prefixlen = 1; + } + } + + /* calculate amount of padding -- might be negative, */ + /* but this will just mean zero */ + padding = fldwidth - textlen - prefixlen; + + /* put out the padding, prefix, and text, in the correct order */ + + if (!(flags & (FL_LEFT | FL_LEADZERO))) { + /* pad on left with blanks */ + output->WRITE_MULTI_CHAR((' '), padding, &charsout); + } + + /* write prefix */ + output->WRITE_STRING(prefix, prefixlen, &charsout); + + if ((flags & FL_LEADZERO) && !(flags & FL_LEFT)) { + /* write leading zeros */ + output->WRITE_MULTI_CHAR(('0'), padding, &charsout); + } + + /* write text */ + if (bufferiswide && (textlen > 0)) { + WCHAR *p; + int retval, count; + errno_t e = 0; + char L_buffer[MB_LEN_MAX+1]; + + p = text.wz; + count = textlen; + while (count--) { + e = _WCTOMB_S(&retval, L_buffer, _countof(L_buffer), *p++); + if (e != 0 || retval == 0) { + charsout = -1; + break; + } + output->WRITE_STRING(L_buffer, retval, &charsout); + } + } else { + output->WRITE_STRING(text.sz, textlen, &charsout); + } + + if (charsout >= 0 && (flags & FL_LEFT)) { + /* pad on right with blanks */ + output->WRITE_MULTI_CHAR((' '), padding, &charsout); + } + + /* we're done! */ + } + if (heapbuf) { + free(heapbuf); + heapbuf = NULL; + } + break; + } + } + + /* The format string shouldn't be incomplete - i.e. when we are finished + with the format string, the last thing we should have encountered + should have been a regular char to be output or a type specifier. Else + the format string was incomplete */ + _VALIDATE_RETURN(((state == ST_NORMAL) || (state == ST_TYPE)), EINVAL, -1); + + return charsout; /* return value = number of characters written */ +} + +// Tools\vc\src\crt\amd64\output.c +int FormattedPrintW( IFormattedPrintOutput * output, const WCHAR *format, va_list argptr ) +{ + int hexadd=0; /* offset to add to number to get 'a'..'f' */ + WCHAR ch; /* character just read */ + int flags=0; /* flag word -- see #defines above for flag values */ + enum STATE state; /* current state */ + enum CHARTYPE chclass; /* class of current character */ + int radix; /* current conversion radix */ + int charsout; /* characters currently written so far, -1 = IO error */ + int fldwidth = 0; /* selected field width -- 0 means default */ + int precision = 0; /* selected precision -- -1 means default */ + WCHAR prefix[2]; /* numeric prefix -- up to two characters */ + int prefixlen=0; /* length of prefix -- 0 means no prefix */ + int capexp=0; /* non-zero = 'E' exponent signifient, zero = 'e' or unused */ + int no_output=0; /* non-zero = prodcue no output for this specifier */ + union { + char *sz; /* pointer text to be printed, not zero terminated */ + WCHAR *wz; + } text; + + int textlen; /* length of the text in bytes/wchars to be printed. + textlen is in multibyte or wide chars if _UNICODE */ + union { + char sz[BUFFERSIZE]; + WCHAR wz[BUFFERSIZE]; + } buffer; + WCHAR wchar; /* temp wchar_t */ + int buffersize; /* size of text.sz (used only for the call to _cfltcvt) */ + int bufferiswide=0; /* non-zero = buffer contains wide chars already */ + +#ifndef _SAFECRT_IMPL + _LocaleUpdate _loc_update(plocinfo); +#endif /* _SAFECRT_IMPL */ + + char *heapbuf = NULL; /* non-zero = test.sz using heap buffer to be freed */ + + int advance; /* count of how much helper fxns need format ptr incremented */ + + _VALIDATE_RETURN( ((output != NULL) && (format != NULL)), EINVAL, -1); + + charsout = 0; /* no characters written yet */ + textlen = 0; /* no text yet */ + state = ST_NORMAL; /* starting state */ + heapbuf = NULL; /* not using heap-allocated buffer */ + buffersize = 0; + + /* main loop -- loop while format character exist and no I/O errors */ + while ((ch = *format++) != L'\0' && charsout >= 0) { + // Find char class and next state + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + /* execute code for each state */ + switch (state) { + + case ST_INVALID: + // "Incorrect format specifier" + assert( false ); + errno = EINVAL; + return -1; + + case ST_NORMAL: + + NORMAL_STATE: + + /* normal state -- just write character */ + bufferiswide = 1; + if (isleadwchar(ch)) { + // Deal with more than 2 storage units per character + output->WRITE_CHAR(ch, &charsout); + ch = *format++; + /* don't fall off format string */ + _VALIDATE_RETURN( (ch != L'\0'), EINVAL, -1); + } + output->WRITE_CHAR(ch, &charsout); + break; + + case ST_PERCENT: + /* set default value of conversion parameters */ + prefixlen = fldwidth = no_output = capexp = 0; + flags = 0; + precision = -1; + bufferiswide = 0; /* default */ + break; + + case ST_FLAG: + /* set flag based on which flag character */ + switch (ch) { + case L'-': + flags |= FL_LEFT; /* '-' => left justify */ + break; + case L'+': + flags |= FL_SIGN; /* '+' => force sign indicator */ + break; + case L' ': + flags |= FL_SIGNSP; /* ' ' => force sign or space */ + break; + case L'#': + flags |= FL_ALTERNATE; /* '#' => alternate form */ + break; + case L'0': + flags |= FL_LEADZERO; /* '0' => pad with leading zeros */ + break; + } + break; + + case ST_WIDTH: + /* update width value */ + if (ch == L'*') { + /* get width from arg list */ + fldwidth = va_arg(argptr, int); + if (fldwidth < 0) { + /* ANSI says neg fld width means '-' flag and pos width */ + flags |= FL_LEFT; + fldwidth = -fldwidth; + } + } + else { + /* add digit to current field width */ + fldwidth = fldwidth * 10 + (ch - L'0'); + } + break; + + case ST_DOT: + /* zero the precision, since dot with no number means 0 + not default, according to ANSI */ + precision = 0; + break; + + case ST_PRECIS: + /* update precison value */ + if (ch == L'*') { + /* get precision from arg list */ + precision = va_arg(argptr, int); + if (precision < 0) + precision = -1; /* neg precision means default */ + } + else { + /* add digit to current precision */ + precision = precision * 10 + (ch - L'0'); + } + break; + + case ST_SIZE: + /* just read a size specifier, set the flags based on it */ + state = ProcessSize( ch, format, &advance, &flags ); + format += advance; + if ( ST_NORMAL == state ) + { + goto NORMAL_STATE; + } + break; + + case ST_TYPE: + /* we have finally read the actual type character, so we */ + /* now format and "print" the output. We use a big switch */ + /* statement that sets 'text' to point to the text that should */ + /* be printed, and 'textlen' to the length of this text. */ + /* Common code later on takes care of justifying it and */ + /* other miscellaneous chores. Note that cases share code, */ + /* in particular, all integer formatting is done in one place. */ + /* Look at those funky goto statements! */ + + switch (ch) { + + case L'C': /* ISO wide character */ + if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR))) + flags |= FL_SHORT; + /* fall into 'c' case */ + + case L'c': { + /* print a single character specified by int argument */ + bufferiswide = 1; + wchar = (WCHAR) va_arg(argptr, int); + if (flags & FL_SHORT) { + /* format multibyte character */ + /* this is an extension of ANSI */ + char tempchar[2]; + { + tempchar[0] = (char)(wchar & 0x00ff); + tempchar[1] = '\0'; + } + +#ifdef _SAFECRT_IMPL + if (_MBTOWC(buffer.wz,tempchar, MB_CUR_MAX) < 0) +#else /* _SAFECRT_IMPL */ + if (_mbtowc_l(buffer.wz, + tempchar, + _loc_update.GetLocaleT()->locinfo->mb_cur_max, + _loc_update.GetLocaleT()) < 0) +#endif /* _SAFECRT_IMPL */ + { + /* ignore if conversion was unsuccessful */ + no_output = 1; + } + } else { + buffer.wz[0] = wchar; + } + text.wz = buffer.wz; + textlen = 1; /* print just a single character */ + } + break; + + case L'Z': { + // 'Z' format specifier disabled + _VALIDATE_RETURN(0, EINVAL, -1); + } + break; + + case L'S': /* ISO wide character string */ + if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR))) + flags |= FL_SHORT; + + case L's': { + /* print a string -- */ + /* ANSI rules on how much of string to print: */ + /* all if precision is default, */ + /* min(precision, length) if precision given. */ + /* prints '(null)' if a null string is passed */ + + int i; + char *p; /* temps */ + WCHAR *pwch; + + /* At this point it is tempting to use strlen(), but */ + /* if a precision is specified, we're not allowed to */ + /* scan past there, because there might be no null */ + /* at all. Thus, we must do our own scan. */ + + i = (precision == -1) ? INT_MAX : precision; + text.sz = (char *)va_arg(argptr, void *); + + /* scan for null upto i characters */ + if (flags & FL_SHORT) { + if (text.sz == NULL) /* NULL passed, use special string */ + text.sz = (char*) __nullstring; + p = text.sz; + for (textlen=0; textlen MAXPRECISION) + precision = MAXPRECISION; + + if (precision > BUFFERSIZE - _CVTBUFSIZE) { + /* conversion will potentially overflow local buffer */ + /* so we need to use a heap-allocated buffer. */ + heapbuf = (char *)malloc(_CVTBUFSIZE + precision); + if (heapbuf != NULL) + { + text.sz = heapbuf; + buffersize = _CVTBUFSIZE + precision; + } + else + /* malloc failed, cap precision further */ + precision = BUFFERSIZE - _CVTBUFSIZE; + } + +#ifdef _SAFECRT_IMPL + /* for safecrt, we pass along the FL_ALTERNATE flag to _safecrt_cfltcvt */ + if (flags & FL_ALTERNATE) + { + capexp |= FL_ALTERNATE; + } +#endif /* _SAFECRT_IMPL */ + +#if !LONGDOUBLE_IS_DOUBLE + /* do the conversion */ + if (flags & FL_LONGDOUBLE) { + _LONGDOUBLE tmp; + tmp=va_arg(argptr, _LONGDOUBLE); + /* Note: assumes ch is in ASCII range */ + _CLDCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp); + } else +#endif /* !LONGDOUBLE_IS_DOUBLE */ + { + double tmp; + tmp=va_arg(argptr, double); + /* Note: assumes ch is in ASCII range */ + /* In safecrt, we provide a special version of _cfltcvt which internally calls printf (see safecrt_output_s.c) */ +#ifndef _SAFECRT_IMPL + _cfltcvt_l(&tmp, text.sz, buffersize, (char)ch, precision, capexp, _loc_update.GetLocaleT()); +#else /* _SAFECRT_IMPL */ + _CFLTCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp); +#endif /* _SAFECRT_IMPL */ + } + +#ifndef _SAFECRT_IMPL + /* For safecrt, this is done already in _safecrt_cfltcvt */ + + /* '#' and precision == 0 means force a decimal point */ + if ((flags & FL_ALTERNATE) && precision == 0) + { + _forcdecpt_l(text.sz, _loc_update.GetLocaleT()); + } + + /* 'g' format means crop zero unless '#' given */ + if (ch == L'g' && !(flags & FL_ALTERNATE)) + { + _cropzeros_l(text.sz, _loc_update.GetLocaleT()); + } +#endif /* _SAFECRT_IMPL */ + + /* check if result was negative, save '-' for later */ + /* and point to positive part (this is for '0' padding) */ + if (*text.sz == '-') { + flags |= FL_NEGATIVE; + ++text.sz; + } + + textlen = (int)strlen(text.sz); /* compute length of text */ + } + break; + + case L'd': + case L'i': + /* signed decimal output */ + flags |= FL_SIGNED; + radix = 10; + goto COMMON_INT; + + case L'u': + radix = 10; + goto COMMON_INT; + + case L'p': + /* write a pointer -- this is like an integer or long */ + /* except we force precision to pad with zeros and */ + /* output in big hex. */ + + precision = 2 * sizeof(void *); /* number of hex digits needed */ +#if PTR_IS_INT64 + flags |= FL_I64; /* assume we're converting an int64 */ +#endif /* !PTR_IS_INT */ + /* DROP THROUGH to hex formatting */ + + case L'X': + /* unsigned upper hex output */ + hexadd = L'A' - L'9' - 1; /* set hexadd for uppercase hex */ + goto COMMON_HEX; + + case L'x': + /* unsigned lower hex output */ + hexadd = L'a' - L'9' - 1; /* set hexadd for lowercase hex */ + /* DROP THROUGH TO COMMON_HEX */ + + COMMON_HEX: + radix = 16; + if (flags & FL_ALTERNATE) { + /* alternate form means '0x' prefix */ + prefix[0] = L'0'; + prefix[1] = (WCHAR)(L'x' - L'a' + L'9' + 1 + hexadd); /* 'x' or 'X' */ + prefixlen = 2; + } + goto COMMON_INT; + + case L'o': + /* unsigned octal output */ + radix = 8; + if (flags & FL_ALTERNATE) { + /* alternate form means force a leading 0 */ + flags |= FL_FORCEOCTAL; + } + /* DROP THROUGH to COMMON_INT */ + + COMMON_INT: { + /* This is the general integer formatting routine. */ + /* Basically, we get an argument, make it positive */ + /* if necessary, and convert it according to the */ + /* correct radix, setting text and textlen */ + /* appropriately. */ + + ULONGLONG number; /* number to convert */ + int digit; /* ascii value of digit */ + LONGLONG l; /* temp long value */ + + /* 1. read argument into l, sign extend as needed */ + if (flags & FL_I64) + l = va_arg(argptr, LONGLONG); + else if (flags & FL_LONGLONG) + l = va_arg(argptr, LONGLONG); + else + + if (flags & FL_SHORT) { + if (flags & FL_SIGNED) + l = (short) va_arg(argptr, int); /* sign extend */ + else + l = (unsigned short) va_arg(argptr, int); /* zero-extend*/ + + } else + { + if (flags & FL_SIGNED) + l = (int)va_arg(argptr, int); /* sign extend */ + else + l = (unsigned int) va_arg(argptr, int); /* zero-extend*/ + } + + /* 2. check for negative; copy into number */ + if ( (flags & FL_SIGNED) && l < 0) { + number = -l; + flags |= FL_NEGATIVE; /* remember negative sign */ + } else { + number = l; + } + + if ( (flags & FL_I64) == 0 && (flags & FL_LONGLONG) == 0 ) { + /* + * Unless printing a full 64-bit value, insure values + * here are not in cananical longword format to prevent + * the sign extended upper 32-bits from being printed. + */ + number &= 0xffffffff; + } + + /* 3. check precision value for default; non-default */ + /* turns off 0 flag, according to ANSI. */ + if (precision < 0) + precision = 1; /* default precision */ + else { + flags &= ~FL_LEADZERO; + if (precision > MAXPRECISION) + precision = MAXPRECISION; + } + + /* 4. Check if data is 0; if so, turn off hex prefix */ + if (number == 0) + prefixlen = 0; + + /* 5. Convert data to ASCII -- note if precision is zero */ + /* and number is zero, we get no digits at all. */ + + text.sz = &buffer.sz[BUFFERSIZE-1]; /* last digit at end of buffer */ + + while (precision-- > 0 || number != 0) { + digit = (int)(number % radix) + '0'; + number /= radix; /* reduce number */ + if (digit > '9') { + /* a hex digit, make it a letter */ + digit += hexadd; + } + *text.sz-- = (char)digit; /* store the digit */ + } + + textlen = (int)((char *)&buffer.sz[BUFFERSIZE-1] - text.sz); /* compute length of number */ + ++text.sz; /* text points to first digit now */ + + + /* 6. Force a leading zero if FORCEOCTAL flag set */ + if ((flags & FL_FORCEOCTAL) && (textlen == 0 || text.sz[0] != '0')) { + *--text.sz = '0'; + ++textlen; /* add a zero */ + } + } + break; + } + + /* At this point, we have done the specific conversion, and */ + /* 'text' points to text to print; 'textlen' is length. Now we */ + /* justify it, put on prefixes, leading zeros, and then */ + /* print it. */ + + if (!no_output) { + int padding; /* amount of padding, negative means zero */ + + if (flags & FL_SIGNED) { + if (flags & FL_NEGATIVE) { + /* prefix is a '-' */ + prefix[0] = L'-'; + prefixlen = 1; + } + else if (flags & FL_SIGN) { + /* prefix is '+' */ + prefix[0] = L'+'; + prefixlen = 1; + } + else if (flags & FL_SIGNSP) { + /* prefix is ' ' */ + prefix[0] = L' '; + prefixlen = 1; + } + } + + /* calculate amount of padding -- might be negative, */ + /* but this will just mean zero */ + padding = fldwidth - textlen - prefixlen; + + /* put out the padding, prefix, and text, in the correct order */ + + if (!(flags & (FL_LEFT | FL_LEADZERO))) { + /* pad on left with blanks */ + output->WRITE_MULTI_CHAR(L' ', padding, &charsout); + } + + /* write prefix */ + output->WRITE_STRING(prefix, prefixlen, &charsout); + + if ((flags & FL_LEADZERO) && !(flags & FL_LEFT)) { + /* write leading zeros */ + output->WRITE_MULTI_CHAR(L'0', padding, &charsout); + } + + /* write text */ + if (!bufferiswide && textlen > 0) { + char *p; + int retval, count; + + p = text.sz; + count = textlen; + while (count-- > 0) { +#ifdef _SAFECRT_IMPL + retval = _MBTOWC(&wchar, p, MB_CUR_MAX); +#else /* _SAFECRT_IMPL */ + retval = _mbtowc_l(&wchar, + p, + _loc_update.GetLocaleT()->locinfo->mb_cur_max, + _loc_update.GetLocaleT()); +#endif /* _SAFECRT_IMPL */ + if (retval <= 0) { + charsout = -1; + break; + } + output->WRITE_CHAR(wchar, &charsout); + p += retval; + } + } else { + output->WRITE_STRING(text.wz, textlen, &charsout); + } + + if (charsout >= 0 && (flags & FL_LEFT)) { + /* pad on right with blanks */ + output->WRITE_MULTI_CHAR(L' ', padding, &charsout); + } + + /* we're done! */ + } + if (heapbuf) { + free(heapbuf); + heapbuf = NULL; + } + break; + } + } + + /* The format string shouldn't be incomplete - i.e. when we are finished + with the format string, the last thing we should have encountered + should have been a regular char to be output or a type specifier. Else + the format string was incomplete */ + _VALIDATE_RETURN(((state == ST_NORMAL) || (state == ST_TYPE)), EINVAL, -1); + + return charsout; /* return value = number of characters written */ +} + + +// Used for holding the size and value of a variable argument. +// Uses INT and LONGLONG to hold all possible values. Each is just a buffer to hold the right number of bits. +struct vararg_t +{ + enum ArgType_e + { + Unknown, + Int32, + Int64, + ShouldBeInt32, + ShouldBeInt64 + }; + + vararg_t() : int64Val(0), int32Val(0), argType(vararg_t::Unknown) {} + vararg_t( INT val ) : int64Val(0), int32Val(val), argType(vararg_t::Int32) {} + vararg_t( LONGLONG ptr ) : int64Val(ptr), int32Val(0), argType(vararg_t::Int64) {} + + ArgType_e Type() const { return argType; } + INT Int32Value() const { return int32Val; } + LONGLONG Int64Value() const { return int64Val; } + void * PtrValue() const + { +#if PTR_IS_INT64 + return reinterpret_cast(int64Val); +#else + return reinterpret_cast(int32Val); +#endif + } + + void SetForInt32() + { + assert( vararg_t::Unknown == argType ); + argType = vararg_t::ShouldBeInt32; + } + void SetForInt64() + { + assert( vararg_t::Unknown == argType ); + argType = vararg_t::ShouldBeInt64; + } + void SetForPtr() + { +#if PTR_IS_INT64 + SetForInt64(); +#else + SetForInt32(); +#endif + } + + void Int32Value( INT val ) + { + assert( vararg_t::Unknown == argType || vararg_t::ShouldBeInt32 == argType ); + assert( 0 == int64Val ); + argType = vararg_t::Int32; + int32Val = val; + } + void Int64Value( LONGLONG val ) + { + assert( vararg_t::Unknown == argType || vararg_t::ShouldBeInt64 == argType ); + assert( 0 == int32Val ); + argType = vararg_t::Int64; + int64Val = val; + } + +private: + LONGLONG int64Val; + INT int32Val; + ArgType_e argType; +}; + +// Caches the var arg values in the supplied vector. Types are determined by inspecting the format string. +// On error, sets errno and returns false +static bool GetFormatMessageArgsA( const char * format, std::vector< vararg_t > * argcache, va_list * Arguments ) +{ + if ( NULL == format ) + { + errno = EINVAL; + return false; + } + + const char *p = format; + char fmt_ch; + + while( '\0' != (fmt_ch = *p++) ) + { + if ( '%' != fmt_ch ) + { + // continue to next format spec + } + else if ( '0' == *p || '\0' == *p ) + { + // %0 or null term means end formatting + break; + } + else if ( *p < '1' || '9' < *p ) + { + // Escaped char, skip and keep going + ++p; + } + else + { + // Integer must be [1..99] + size_t argPos = *p++ - '0'; + if ( '0' <= *p && *p <= '9' ) + { + argPos *= 10; + argPos += *p++ - '0'; + } + assert( 0 < argPos && argPos < 100 ); + + if ( argcache->size() < argPos ) + { + // Haven't processed this arg, yet + argcache->resize( argPos ); + } + + if ( vararg_t::Unknown == argcache->at(argPos-1).Type() ) + { + if ( '!' != *p ) + { + // Assume %s as per spec + argcache->at(argPos-1).SetForPtr(); + } + else + { + // Step over the initial '!' and process format specification + ++p; + + char ch; + int flags = 0; + int advance = 0; + enum CHARTYPE chclass; + enum STATE state = ST_PERCENT; + bool found_terminator = false; + while ( !found_terminator && ('\0' != (ch = *p++)) ) + { + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + switch ( state ) + { + case ST_DOT: + case ST_FLAG: + break; + + case ST_WIDTH: + case ST_PRECIS: + if ( '*' == ch ) + { + argcache->at(argPos-1).SetForInt32(); + ++argPos; + if ( argcache->size() < argPos ) + { + argcache->resize( argPos ); + } + } + break; + + case ST_SIZE: + state = ProcessSize( ch, p, &advance, &flags ); + p += advance; + if ( ST_SIZE != state ) + { + // Size and type flags were inconsistent + errno = EINVAL; + return false; + } + break; + + case ST_TYPE: + // Group into 32-bit and 64-bit sized args + assert( vararg_t::Unknown == argcache->at(argPos-1).Type() ); + switch ( ch ) + { + case 'C': // chars + case 'c': + argcache->at(argPos-1).SetForInt32(); + break; + + case 'd': // ints + case 'i': + case 'u': + case 'X': + case 'x': + case 'o': + // INT args + if ( (flags & FL_I64) || (flags & FL_LONGLONG) ) + argcache->at(argPos-1).SetForInt64(); + else + argcache->at(argPos-1).SetForInt32(); + break; + + case 'S': // strings + case 's': + case 'p': // pointer + argcache->at(argPos-1).SetForPtr(); + break; + + case 'E': // doubles (not supported as per spec) + case 'e': + case 'G': + case 'g': + case 'A': + case 'a': + case 'f': + default: + errno = EINVAL; + return false; + } + break; + + case ST_NORMAL: + if ( '!' == ch ) + { + found_terminator = true; + break; + } + // Fall thru to error, missing terminating '!' + + default: + errno = EINVAL; + return false; + } + } + + if ( !found_terminator ) + { + // End of string before trailing '!' was found + errno = EINVAL; + return false; + } + } + } + } + } + + if ( 0 < argcache->size() && NULL == Arguments ) + { + errno = EINVAL; + return false; + } + + // Cache var arg values now that we know the number and sizes + for ( std::vector< vararg_t >::iterator arg = argcache->begin(); arg != argcache->end(); ++arg ) + { + if ( vararg_t::Unknown == arg->Type() ) + { + // Arg not referenced in format string so assume ptr sized. + // This is a decent assumption since every arg gets ptr-size bytes to ensure alignment + // of later arg values. Verified this behavior with both Windows and Linux. + arg->SetForPtr(); + } + + vararg_t::ArgType_e argtype = arg->Type(); + assert( vararg_t::ShouldBeInt32 == argtype || vararg_t::ShouldBeInt64 == argtype ); + + if ( vararg_t::ShouldBeInt32 == argtype ) + { + arg->Int32Value( (INT)va_arg(*Arguments, INT) ); + } + else + { + arg->Int64Value( (LONGLONG)va_arg(*Arguments, LONGLONG) ); + } + } + + return true; +} + +// Caches the var arg values in the supplied vector. Types are determined by inspecting the format string. +// On error, sets errno and returns false +static bool GetFormatMessageArgsW( const WCHAR * format, std::vector< vararg_t > * argcache, va_list * Arguments ) +{ + if ( NULL == format ) + { + errno = EINVAL; + return false; + } + + const WCHAR *p = format; + WCHAR fmt_ch; + + while( L'\0' != (fmt_ch = *p++) ) + { + if ( L'%' != fmt_ch ) + { + // continue to next format spec + } + else if ( L'0' == *p || L'\0' == *p ) + { + // %0 or null term means end formatting + break; + } + else if ( *p < L'1' || L'9' < *p ) + { + // Escaped char, skip and keep going + ++p; + } + else + { + // Integer must be [1..99] + size_t argPos = *p++ - L'0'; + if ( L'0' <= *p && *p <= L'9' ) + { + argPos *= 10; + argPos += *p++ - L'0'; + } + assert( 0 < argPos && argPos < 100 ); + + if ( argcache->size() < argPos ) + { + // Haven't processed this arg, yet + argcache->resize( argPos ); + } + + if ( vararg_t::Unknown == argcache->at(argPos-1).Type() ) + { + if ( L'!' != *p ) + { + // Assume %s as per spec + argcache->at(argPos-1).SetForPtr(); + } + else + { + // Step over the initial '!' and process format specification + ++p; + + WCHAR ch; + int flags = 0; + int advance = 0; + enum CHARTYPE chclass; + enum STATE state = ST_PERCENT; + bool found_terminator = false; + while ( !found_terminator && (L'\0' != (ch = *p++)) ) + { + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + switch ( state ) + { + case ST_DOT: + case ST_FLAG: + break; + + case ST_WIDTH: + case ST_PRECIS: + if ( L'*' == ch ) + { + argcache->at(argPos-1).SetForInt32(); + ++argPos; + if ( argcache->size() < argPos ) + { + argcache->resize( argPos ); + } + } + break; + + case ST_SIZE: + state = ProcessSize( ch, p, &advance, &flags ); + p += advance; + if ( ST_SIZE != state ) + { + // Size and type flags were inconsistent + errno = EINVAL; + return false; + } + break; + + case ST_TYPE: + // Group into 32-bit and 64-bit sized args + assert( vararg_t::Unknown == argcache->at(argPos-1).Type() ); + switch ( ch ) + { + case L'C': // chars + case L'c': + argcache->at(argPos-1).SetForInt32(); + break; + + case L'd': // ints + case L'i': + case L'u': + case L'X': + case L'x': + case L'o': + // INT args + if ( (flags & FL_I64) || (flags & FL_LONGLONG) ) + argcache->at(argPos-1).SetForInt64(); + else + argcache->at(argPos-1).SetForInt32(); + break; + + case L'S': // strings + case L's': + case L'p': // pointer + argcache->at(argPos-1).SetForPtr(); + break; + + case L'E': // doubles (not supported as per spec) + case L'e': + case L'G': + case L'g': + case L'A': + case L'a': + case L'f': + default: + errno = EINVAL; + return false; + } + break; + + case ST_NORMAL: + if ( L'!' == ch ) + { + found_terminator = true; + break; + } + // Fall thru to error, missing terminating '!' + + default: + errno = EINVAL; + return false; + } + } + + if ( !found_terminator ) + { + // End of string before trailing '!' was found + errno = EINVAL; + return false; + } + } + } + } + } + + if ( 0 < argcache->size() && NULL == Arguments ) + { + errno = EINVAL; + return false; + } + + // Cache var arg values now that we know the number and sizes + for ( std::vector< vararg_t >::iterator arg = argcache->begin(); arg != argcache->end(); ++arg ) + { + if ( vararg_t::Unknown == arg->Type() ) + { + // Arg not referenced in format string so assume ptr sized. + // This is a decent assumption since every arg gets ptr-size bytes to ensure alignment + // of later arg values. Verified this behavior with both Windows and Linux. + arg->SetForPtr(); + } + + vararg_t::ArgType_e argtype = arg->Type(); + assert( vararg_t::ShouldBeInt32 == argtype || vararg_t::ShouldBeInt64 == argtype ); + + if ( vararg_t::ShouldBeInt32 == argtype ) + { + arg->Int32Value( (INT)va_arg(*Arguments, INT) ); + } + else + { + arg->Int64Value( (LONGLONG)va_arg(*Arguments, LONGLONG) ); + } + } + + return true; +} + +// On success, returns the number of chars written into the buffer excluding null terminator. +// On error, sets errno and returns zero. +static DWORD FormatMessageToBufferA( const char * format, char * buffer, DWORD bufferWCharSize, const std::vector< vararg_t > & args ) +{ + char * msg = buffer; + DWORD bufsize = std::min(bufferWCharSize, (DWORD)64000); + DWORD msg_pos = 0; + const DWORD fmtsize = 32; + char fmt[fmtsize]; + DWORD fmt_pos; + char fmt_ch; + + const char * p = format; + while( msg_pos < bufsize && '\0' != (fmt_ch = *p++) ) + { + if ( '%' != fmt_ch ) + { + msg[msg_pos++] = fmt_ch; + } + else if ( '0' == *p || '\0' == *p ) + { + // %0 or null term means end formatting + break; + } + else if ( *p < '1' || '9' < *p ) + { + // Escaped char, print and keep going + // Eg. "%n" == '\n' + switch ( *p ) + { + case 'a': msg[msg_pos++] = '\a'; break; + case 'b': msg[msg_pos++] = '\b'; break; + case 'f': msg[msg_pos++] = '\f'; break; + case 'n': msg[msg_pos++] = '\n'; break; + case 'r': msg[msg_pos++] = '\r'; break; + case 't': msg[msg_pos++] = '\t'; break; + case 'v': msg[msg_pos++] = '\v'; break; + default: msg[msg_pos++] = *p; break; + } + ++p; + } + else + { + // Integer must be [1..99] + size_t argPos = *p++ - '0'; + if ( '0' <= *p && *p <= '9' ) + { + argPos *= 10; + argPos += *p++ - '0'; + } + assert( 0 < argPos && argPos < 100 ); + + fmt_pos = 0; + fmt[fmt_pos++] = '%'; + + if ( '!' != *p ) + { + // Assume %s as per spec + fmt[fmt_pos++] = 's'; + fmt[fmt_pos] = '\0'; + int chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].PtrValue() ); + if ( chars_printed < 0 ) + { + errno = EINVAL; + return 0; + } + msg_pos += chars_printed; + } + else + { + // Skip over '!' and build format string + ++p; + char ch; + int flags = 0; + int advance = 0; + enum CHARTYPE chclass; + enum STATE state = ST_PERCENT; + bool found_terminator = false; + while ( fmt_pos < fmtsize && !found_terminator && ('\0' != (ch = *p++)) ) + { + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + switch ( state ) + { + case ST_SIZE: + state = ProcessSize( ch, p, &advance, &flags ); + fmt[fmt_pos++] = ch; + while ( fmt_pos < fmtsize && 0 < advance-- ) + { + fmt[fmt_pos++] = *p++; + } + break; + + case ST_NORMAL: + assert( '!' == ch ); + found_terminator = true; + break; + + case ST_INVALID: + case ST_PERCENT: + errno = EINVAL; + return 0; + + default: + fmt[fmt_pos++] = ch; + break; + } + } + + if ( fmtsize <= fmt_pos ) + { + // Should not have a format string longer than 31 chars + // It can happen but shouldn't (eg. a bunch of size mods like %llllllllllllllld) + errno = EINVAL; + return 0; + } + + fmt[fmt_pos] = '\0'; + + // Format string might need up to 3 args (eg. %*.*d ) + // If more than one arg, then the first ones must be 32-bit ints + // Hence, first 64-bit arg tells us the last arg we need to send. + int chars_printed = 0; + if ( vararg_t::Int64 == args[argPos-1].Type() ) + { + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int64Value() ); + } + else if ( args.size() == argPos ) + { + // No more args so send the one Int + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value() ); + } + else if ( vararg_t::Int64 == args[argPos].Type() ) + { + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int64Value() ); + } + else if ( args.size() == (argPos+1) ) + { + // No more args so send the two Ints + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos-1].Int32Value() ); + } + else if ( vararg_t::Int64 == args[argPos+1].Type() ) + { + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int32Value(), args[argPos+1].Int64Value() ); + } + else + { + chars_printed = mplat_snprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int32Value(), args[argPos+1].Int32Value() ); + } + + if ( chars_printed < 0 ) + { + errno = EINVAL; + return 0; + } + msg_pos += chars_printed; + } + } + } + + if ( bufsize <= msg_pos ) + { + errno = ERANGE; + return 0; + } + + msg[msg_pos] = '\0'; + return msg_pos; +} + +// On success, returns the number of chars written into the buffer excluding null terminator. +// On error, sets errno and returns zero. +static DWORD FormatMessageToBufferW( const WCHAR * format, WCHAR * buffer, DWORD bufferWCharSize, const std::vector< vararg_t > & args ) +{ + WCHAR * msg = buffer; + DWORD bufsize = std::min(bufferWCharSize, (DWORD)64000); + DWORD msg_pos = 0; + const DWORD fmtsize = 32; + WCHAR fmt[fmtsize]; + DWORD fmt_pos; + WCHAR fmt_ch; + + const WCHAR * p = format; + while( msg_pos < bufsize && L'\0' != (fmt_ch = *p++) ) + { + if ( L'%' != fmt_ch ) + { + msg[msg_pos++] = fmt_ch; + } + else if ( L'0' == *p || L'\0' == *p ) + { + // %0 or null term means end formatting + break; + } + else if ( *p < L'1' || L'9' < *p ) + { + // Escaped char, print and keep going + // Eg. "%n" == '\n' + switch ( *p ) + { + case L'a': msg[msg_pos++] = L'\a'; break; + case L'b': msg[msg_pos++] = L'\b'; break; + case L'f': msg[msg_pos++] = L'\f'; break; + case L'n': msg[msg_pos++] = L'\n'; break; + case L'r': msg[msg_pos++] = L'\r'; break; + case L't': msg[msg_pos++] = L'\t'; break; + case L'v': msg[msg_pos++] = L'\v'; break; + default: msg[msg_pos++] = *p; break; + } + ++p; + } + else + { + // Integer must be [1..99] + size_t argPos = *p++ - L'0'; + if ( L'0' <= *p && *p <= L'9' ) + { + argPos *= 10; + argPos += *p++ - L'0'; + } + assert( 0 < argPos && argPos < 100 ); + + fmt_pos = 0; + fmt[fmt_pos++] = L'%'; + + if ( L'!' != *p ) + { + // Assume %s as per spec + fmt[fmt_pos++] = L's'; + fmt[fmt_pos] = L'\0'; + int chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].PtrValue() ); + if ( chars_printed < 0 ) + { + errno = EINVAL; + return 0; + } + msg_pos += chars_printed; + } + else + { + // Skip over '!' and build format string + ++p; + WCHAR ch; + int flags = 0; + int advance = 0; + enum CHARTYPE chclass; + enum STATE state = ST_PERCENT; + bool found_terminator = false; + while ( fmt_pos < fmtsize && !found_terminator && (L'\0' != (ch = *p++)) ) + { + chclass = GetCharType( ch ); + state = GetState( chclass, state ); + + switch ( state ) + { + case ST_SIZE: + state = ProcessSize( ch, p, &advance, &flags ); + fmt[fmt_pos++] = ch; + while ( fmt_pos < fmtsize && 0 < advance-- ) + { + fmt[fmt_pos++] = *p++; + } + break; + + case ST_NORMAL: + assert( L'!' == ch ); + found_terminator = true; + break; + + case ST_INVALID: + case ST_PERCENT: + errno = EINVAL; + return 0; + + default: + fmt[fmt_pos++] = ch; + break; + } + } + + if ( fmtsize <= fmt_pos ) + { + // Should not have a format string longer than 31 chars + // It can happen but shouldn't (eg. a bunch of size mods like %llllllllllllllld) + errno = EINVAL; + return 0; + } + + fmt[fmt_pos] = L'\0'; + + // Format string might need up to 3 args (eg. %*.*d ) + // If more than one arg, then the first ones must be 32-bit ints + // Hence, first 64-bit arg tells us the last arg we need to send. + int chars_printed = 0; + if ( vararg_t::Int64 == args[argPos-1].Type() ) + { + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int64Value() ); + } + else if ( args.size() == argPos ) + { + // No more args so send the one Int + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value() ); + } + else if ( vararg_t::Int64 == args[argPos].Type() ) + { + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int64Value() ); + } + else if ( args.size() == (argPos+1) ) + { + // No more args so send the two Ints + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos-1].Int32Value() ); + } + else if ( vararg_t::Int64 == args[argPos+1].Type() ) + { + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int32Value(), args[argPos+1].Int64Value() ); + } + else + { + chars_printed = mplat_snwprintf_s( &msg[msg_pos], bufsize-msg_pos, bufsize-msg_pos, fmt, args[argPos-1].Int32Value(), args[argPos].Int32Value(), args[argPos+1].Int32Value() ); + } + + if ( chars_printed < 0 ) + { + errno = EINVAL; + return 0; + } + msg_pos += chars_printed; + } + } + } + + if ( bufsize <= msg_pos ) + { + errno = ERANGE; + return 0; + } + + msg[msg_pos] = L'\0'; + return msg_pos; +} + + +DWORD FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPTSTR lpBuffer, DWORD nSize, va_list *Arguments) +{ + DWORD chars_printed = 0; + + // XPLAT_ODBC_TODO VSTS 718708 Localization by handling FORMAT_MESSAGE_FROM_HMODULE and dwLanguageId param + if ( dwFlags & FORMAT_MESSAGE_FROM_STRING ) + { + // Format specification allows for reordering of insertions relative to var arg position + // This means we need to walk thru the format specification to find the types of the var args in var arg order + // We extract the var args in order based on the identified types + // Finally, we re-walk the format specfication and perform the insertions + + // First pass thru the format string to determine all args and their types + // This first pass also validates the format string and will return an error + // if it is invalid. This allows FormatMessageToBuffer to have less error + // checking. + std::vector< vararg_t > args; + // Based on quick scan of RC files, the largest arg count was 7 so reserve 8 slots to reduce allocations + args.reserve(8); + if ( GetFormatMessageArgsA( reinterpret_cast(lpSource), &args, Arguments ) ) + { + if ( dwFlags == (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING) ) + { + *((char**)lpBuffer) = NULL; + + const DWORD max_size = 64000; + char local_buf[max_size]; + chars_printed = FormatMessageToBufferA( reinterpret_cast(lpSource), local_buf, max_size, args ); + if ( 0 < chars_printed ) + { + size_t buf_size = std::min( max_size, std::max(nSize, (chars_printed+1)) ); + char * return_buf = (char *)LocalAlloc(0, buf_size * sizeof(char)); + if ( NULL == return_buf ) + { + errno = ENOMEM; + } + else + { + mplat_cscpy(return_buf, local_buf); + *((char**)lpBuffer) = return_buf; + } + } + } + else if ( dwFlags == FORMAT_MESSAGE_FROM_STRING ) + { + chars_printed = FormatMessageToBufferA( reinterpret_cast(lpSource), lpBuffer, std::min(nSize, (DWORD)64000), args ); + } + } + } + else if ( dwFlags & FORMAT_MESSAGE_FROM_SYSTEM ) + { + // Since we don't have the Windows system error messages available use a fixed message + // Can not use a message ID for this since this same code is used by driver and tools, + // each having their own RLL file. Don't think we should be reserving an ID across all RLLs. + const char systemMsg[] = "Error code 0x%X"; + if ( dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER ) + { + *((char**)lpBuffer) = NULL; + + // Add 9 for up to 8 hex digits plus null term (ignore removal of format specs) + const size_t msgsize = (9 + sizeof(systemMsg)/sizeof(systemMsg[0])); + char * return_buf = (char *)LocalAlloc(0, msgsize * sizeof(char)); + if ( NULL == return_buf ) + { + errno = ENOMEM; + } + else + { + chars_printed = mplat_snprintf_s( return_buf, msgsize, msgsize, systemMsg, dwMessageId ); + // Assert that we did our buffer size math right + assert( chars_printed < msgsize ); + if ( 0 < chars_printed ) + { + *((char**)lpBuffer) = return_buf; + } + else + { + LocalFree( return_buf ); + errno = EINVAL; + } + } + } + else + { + chars_printed = mplat_snprintf_s( lpBuffer, nSize, nSize, systemMsg, dwMessageId ); + } + } + + return chars_printed; +} + + +// FormatMessage implementation details (see MSDN for more info) +// +// The Windows FormatMessage API is very rich, complex. This is not an exact duplication of that function. +// Instead, the most important aspects of this function have been implemented here along with constraints to +// match how we use it within SNAC, BCP, and SQLCMD. +// +// Only these combinations of dwFlags are supported: +// FORMAT_MESSAGE_FROM_STRING +// Writes formatted message into supplied buffer +// FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING +// Allocates a buffer, writes formatted message into that buffer, returns buffer in lpBufffer +// FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS +// Writes fixed, English message into the supplied buffer (do not have Windows resources to get real message) +// FORMAT_MESSAGE_FROM_HMODULE +// SQLCMD uses this to read strings from the RLL that have not been translated to the current lang +// +// dwLanguageId is ignored for FORMAT_MESSAGE_FROM_STRING as per spec +// For FORMAT_MESSAGE_FROM_SYSTEM, we don't have Windows resources so language is irrelevant +DWORD FormatMessageW(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPWSTR lpBuffer, DWORD nSize, va_list *Arguments) +{ + DWORD chars_printed = 0; + + // XPLAT_ODBC_TODO VSTS 718708 Localization by handling FORMAT_MESSAGE_FROM_HMODULE and dwLanguageId param + if ( dwFlags & FORMAT_MESSAGE_FROM_STRING ) + { + // Format specification allows for reordering of insertions relative to var arg position + // This means we need to walk thru the format specification to find the types of the var args in var arg order + // We extract the var args in order based on the identified types + // Finally, we re-walk the format specfication and perform the insertions + + // First pass thru the format string to determine all args and their types + // This first pass also validates the format string and will return an error + // if it is invalid. This allows FormatMessageToBuffer to have less error + // checking. + std::vector< vararg_t > args; + // Based on quick scan of RC files, the largest arg count was 7 so reserve 8 slots to reduce allocations + args.reserve(8); + if ( GetFormatMessageArgsW( reinterpret_cast(lpSource), &args, Arguments ) ) + { + if ( dwFlags == (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING) ) + { + *((WCHAR**)lpBuffer) = NULL; + + const DWORD max_size = 64000; + WCHAR local_buf[max_size]; + chars_printed = FormatMessageToBufferW( reinterpret_cast(lpSource), local_buf, max_size, args ); + if ( 0 < chars_printed ) + { + size_t buf_size = std::min( max_size, std::max(nSize, (chars_printed+1)) ); + WCHAR * return_buf = (WCHAR *)LocalAlloc(0, buf_size * sizeof(WCHAR)); + if ( NULL == return_buf ) + { + errno = ENOMEM; + } + else + { + mplat_wcscpy(return_buf, local_buf); + *((WCHAR**)lpBuffer) = return_buf; + } + } + } + else if ( dwFlags == FORMAT_MESSAGE_FROM_STRING ) + { + chars_printed = FormatMessageToBufferW( reinterpret_cast(lpSource), lpBuffer, std::min(nSize, (DWORD)64000), args ); + } + } + } + else if ( dwFlags & FORMAT_MESSAGE_FROM_SYSTEM ) + { + // Since we don't have the Windows system error messages available use a fixed message + // Can not use a message ID for this since this same code is used by driver and tools, + // each having their own RLL file. Don't think we should be reserving an ID across all RLLs. + const wchar_t systemMsg[] = L"Error code 0x%X"; + if ( dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER ) + { + *((WCHAR**)lpBuffer) = NULL; + + // Add 9 for up to 8 hex digits plus null term (ignore removal of format specs) + const size_t msgsize = (9 + sizeof(systemMsg)/sizeof(systemMsg[0])); + WCHAR * return_buf = (WCHAR *)LocalAlloc(0, msgsize * sizeof(WCHAR)); + if ( NULL == return_buf ) + { + errno = ENOMEM; + } + else + { + chars_printed = mplat_snwprintf_s( return_buf, msgsize, msgsize, (WCHAR *)systemMsg, dwMessageId ); + // Assert that we did our buffer size math right + assert( chars_printed < msgsize ); + if ( 0 < chars_printed ) + { + *((WCHAR**)lpBuffer) = return_buf; + } + else + { + LocalFree( return_buf ); + errno = EINVAL; + } + } + } + else + { + chars_printed = mplat_snwprintf_s( lpBuffer, nSize, nSize, (WCHAR *)systemMsg, dwMessageId ); + } + } + + return chars_printed; +} + +//--------Other definitions from xplat stub sources-------------- + +BOOL IsDBCSLeadByte(__inn BYTE TestChar) +{ + // XPLAT_ODBC_TODO: This is to allow BatchParser to function + // BatchParser will single step thru utf8 code points + // BatchParser needs to become utf8-aware + // VSTS 718708 Localization + if ( CP_UTF8 == SystemLocale::Singleton().AnsiCP() ) + return FALSE; + // XPLAT_ODBC_TODO + + return IsDBCSLeadByteEx(SystemLocale::Singleton().AnsiCP(), TestChar); +} + +BOOL IsDBCSLeadByteEx( + __inn UINT CodePage, + __inn BYTE TestChar) +{ + if ( 1 == SystemLocale::MaxCharCchSize(CodePage) ) + return FALSE; + + // Lead byte ranges for code pages, inclusive: + // CP932 + // 0x81-0x9f, 0xe0-0xfc + // CP936, CP949, CP950 + // 0x81-0xfe + assert( 932 == CodePage || 936 == CodePage || 949 == CodePage || 950 == CodePage ); + if ( 932 == CodePage ) + { + if ( TestChar < (unsigned char)0x81 + || (unsigned char)0xfc < TestChar + || ((unsigned char)0x9f < TestChar && TestChar < (unsigned char)0xe0) ) + { + return FALSE; + } + } + else if ( TestChar < (unsigned char)0x81 || TestChar == (unsigned char)0xff ) + return FALSE; + + return TRUE; +} + +int mplat_vsnwprintf( WCHAR * buffer, size_t count, const WCHAR * format, va_list args ) +{ + BufferOutput output( buffer, count ); + return FormattedPrintW( &output, format, args ); +} + +int mplat_snwprintf_s( WCHAR *buffer, size_t bufsize, size_t count, const WCHAR *format, ... ) +{ + va_list args; + va_start( args, format ); + int retcode = mplat_vsnwprintf( buffer, std::min(bufsize, count), format, args ); + va_end( args ); + return retcode; +} + +int mplat_vsnprintf( char * buffer, size_t count, const char * format, va_list args ) +{ + BufferOutput output( buffer, count ); + return FormattedPrintA( &output, format, args ); +} + +int mplat_snprintf_s( char *buffer, size_t bufsize, size_t count, const char *format, ... ) +{ + va_list args; + va_start( args, format ); + int retcode = mplat_vsnprintf( buffer, std::min(bufsize, count), format, args ); + va_end( args ); + return retcode; +} + +// Tools\vc\src\crt\amd64\wcscat.c +WCHAR * mplat_wcscpy( WCHAR * dst, const WCHAR * src ) +{ + WCHAR * cp = dst; + + while( (*cp++ = *src++) ) + ; /* Copy src over dst */ + + return( dst ); +} + +char * mplat_cscpy( char * dst, const char * src ) +{ + char * cp = dst; + + while( (*cp++ = *src++) ) + ; /* Copy src over dst */ + + return( dst ); +} + +size_t mplat_wcslen( const WCHAR * str ) +{ + const WCHAR * eos = str; + while( *eos++ ) + { + } + return( (size_t)(eos - str- 1) ); +} + +HLOCAL LocalAlloc(UINT uFlags, SIZE_T uBytes) +{ + assert(uFlags == 0); // For now + return malloc(uBytes); +} + +HLOCAL LocalFree(HLOCAL hMem) +{ + assert(hMem != NULL); + + free(hMem); + return NULL; +} diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h new file mode 100644 index 000000000..f143a2a45 --- /dev/null +++ b/source/shared/FormattedPrint.h @@ -0,0 +1,231 @@ +//----------------------------------------------------------------------------- +// File: FormattedPrint.h +// +// Contents: Contains functions for handling Windows format strings +// and UTF-16 on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef _FORMATTEDPRINT_H_ +#define _FORMATTEDPRINT_H_ + +#include "xplat_winnls.h" +#include "localization.hpp" + +inline bool __ascii_iswalpha(WCHAR c) { return( ('A' <= (c) && (c) <= 'Z') || ( 'a' <= (c) && (c) <= 'z') ); } +inline WCHAR __ascii_towupper(WCHAR c) { return( (((c) >= L'a') && ((c) <= L'z')) ? (WCHAR)((c) - L'a' + L'A') : (c) ); } +inline char __ascii_toupper(char c) { return( (((c) >= 'a') && ((c) <= 'z')) ? ((c) - 'a' + 'A') : (c) ); } + + + + +template< typename T > +struct IFormattedPrintOutput +{ + /* + Method names are all CAPS to match the original code for formatted print from the Windows CRT. + + pCumulativeOutputCount + Used to track running total of output storage units. + Note that the count is the memory size in sizeof(TCHAR) and not the display char count. + For example, a UTF-8 char+diacritical mark is two chars in memory but only one for display + so pCumulativeOutputCount will be incremented by 2 after output. + If an error is encountered, then set this to -1. + If the value is -1 upon entry of any callback, simply return and don't output anything. + */ + + // Writes a single character to the output. + virtual void WRITE_CHAR( T ch , int * pCumulativeOutputCount ) = 0; + + // Repeatedly writes a single character to the output. If there isn't enough room, writes to end of buffer. + // If repeatCount is <=0, then don't output anything and leave pCumulativeOutputCount as is. + virtual void WRITE_MULTI_CHAR( T ch, int repeatCount, int * pCumulativeOutputCount ) = 0; + + // Writes the supplied string to the output. If there isn't enough room, writes to end of buffer. + // If count is <=0, then don't output anything and leave pCumulativeOutputCount as is. + virtual void WRITE_STRING( const T * pch, int count, int * pCumulativeOutputCount ) = 0; + + // Ensure dtors are virtual + virtual ~IFormattedPrintOutput() { } +}; + +template< typename T > +class FormattedOutput : public IFormattedPrintOutput +{ +protected: + bool ShouldOutput( const int * pCumulativeOutputCount, int count ) const + { + assert( NULL != pCumulativeOutputCount ); + return ( (0 <= *pCumulativeOutputCount) && (0 < count) ); + } +}; + +int FormattedPrintA( IFormattedPrintOutput * output, const char *format, va_list argptr ); +int FormattedPrintW( IFormattedPrintOutput * output, const WCHAR *format, va_list argptr ); + +template< typename T > +class BufferOutput : public FormattedOutput +{ + T * m_buffer; + size_t m_countRemainingInBuffer; + + bool CanOutput() const + { + return ( 0 < m_countRemainingInBuffer ); + } + + // Stop these from being available + BufferOutput(); + BufferOutput( const BufferOutput & ); + BufferOutput & operator=( const BufferOutput & ); + +public: + BufferOutput( T * pcb, size_t bufsize ) + : m_buffer( pcb ), + m_countRemainingInBuffer( bufsize ) + { + assert( NULL != m_buffer ); + if ( m_countRemainingInBuffer < INT_MAX ) + { + memset( m_buffer, 0, m_countRemainingInBuffer * sizeof(T) ); + } + } + + virtual void WRITE_CHAR(T ch, int * pCumulativeOutputCount) + { + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, 1 ) ) + { + if ( CanOutput() ) + { + ++(*pCumulativeOutputCount); + --m_countRemainingInBuffer; + *m_buffer++ = ch; + } + else + { + *pCumulativeOutputCount = -1; + } + } + } + virtual void WRITE_MULTI_CHAR(T ch, int repeatCount, int * pCumulativeOutputCount) + { + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, repeatCount ) ) + { + if ( CanOutput() ) + { + while ( 0 != m_countRemainingInBuffer && 0 != repeatCount ) + { + *m_buffer++ = ch; + --m_countRemainingInBuffer; + --repeatCount; + ++(*pCumulativeOutputCount); + } + if ( 0 != repeatCount ) + { + // Not enough room in buffer + *pCumulativeOutputCount = -1; + } + } + else + { + *pCumulativeOutputCount = -1; + } + } + } + virtual void WRITE_STRING(const T * pch, int count, int * pCumulativeOutputCount) + { + assert( NULL != pch ); + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, count ) ) + { + if ( CanOutput() ) + { + while ( 0 != m_countRemainingInBuffer && 0 != count ) + { + *m_buffer++ = *pch++; + --m_countRemainingInBuffer; + --count; + ++(*pCumulativeOutputCount); + } + if ( 0 != count ) + { + // Not enough room in buffer + *pCumulativeOutputCount = -1; + } + } + else + { + *pCumulativeOutputCount = -1; + } + } + } +}; + + +template< typename T > +class FileOutput : public FormattedOutput +{ + FILE * m_file; + + // Stop these from being available + FileOutput(); + FileOutput( const FileOutput & ); + FileOutput & operator=( const FileOutput & ); + +public: + FileOutput( FILE * file ) + : m_file( file ) + { + assert( NULL != m_file ); + } + + virtual void WRITE_CHAR(T ch, int * pCumulativeOutputCount) + { + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, 1 ) ) + { + ++(*pCumulativeOutputCount); + if ( fputc( ch, m_file ) != ch ) + *pCumulativeOutputCount = -1; + } + } + virtual void WRITE_MULTI_CHAR(T ch, int repeatCount, int * pCumulativeOutputCount) + { + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, repeatCount ) ) + { + *pCumulativeOutputCount += repeatCount; + while ( 0 < repeatCount-- ) + { + if ( fputc( ch, m_file ) != ch ) + { + *pCumulativeOutputCount = -1; + return; + } + } + } + } + virtual void WRITE_STRING(const T * pch, int count, int * pCumulativeOutputCount) + { + if ( FormattedOutput::ShouldOutput( pCumulativeOutputCount, count ) ) + { + assert( NULL != pch ); + *pCumulativeOutputCount += count; + if ( (size_t)count != fwrite( pch, sizeof(T), count, m_file ) ) + *pCumulativeOutputCount = -1; + } + } +}; + + + +#endif // _FORMATTEDPRINT_H_ diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp new file mode 100644 index 000000000..086b9184b --- /dev/null +++ b/source/shared/StringFunctions.cpp @@ -0,0 +1,145 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: StringFunctions.cpp +// +// Contents: Contains functions for handling UTF-16 on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "StringFunctions.h" + +// Tools\vc\src\crt\amd64\memcpy_s.c +int mplat_memcpy_s( void * dest, size_t destSize, const void * src, size_t count ) +{ + if ( 0 == count ) + { + // nothing to do + return 0; + } + + // validation section + if ( NULL == dest ) + { + errno = EINVAL; + return EINVAL; + } + + if ( src == NULL || destSize < count ) + { + // zeroes the destination buffer + memset(dest, 0, destSize*sizeof(char)); + + if ( NULL == src ) + { + errno = EINVAL; + return EINVAL; + } + if ( destSize < count ) + { + errno = ERANGE; + return ERANGE; + } + + return EINVAL; + } + + memcpy(dest, src, count*sizeof(char)); + return 0; +} + +// Tools\vc\src\crt\amd64\strcpy_s.c +int mplat_strcpy_s( char * dest, size_t destSize, const char * src ) +{ + char * p; + size_t available; + + // validation section + if ( NULL == dest || 0 == destSize ) + { + errno = EINVAL; + return EINVAL; + } + if ( NULL == src ) + { + *dest = 0; + errno = EINVAL; + return EINVAL; + } + + p = dest; + available = destSize; + while ( (*p++ = *src++) != 0 && --available > 0 ) + { + } + + if ( 0 == available ) + { + *dest = 0; + errno = ERANGE; + return ERANGE; + } + return 0; +} + +// Tools\vc\src\crt\amd64\strcat_s.c +int mplat_strcat_s( char * dest, size_t destSize, const char * src ) +{ + char *p; + size_t available; + + // validation section + if ( NULL == dest || 0 == destSize ) + { + errno = EINVAL; + return EINVAL; + } + if ( NULL == src ) + { + *dest = 0; + errno = EINVAL; + return EINVAL; + } + + p = dest; + available = destSize; + while (available > 0 && *p != 0) + { + p++; + available--; + } + + if (available == 0) + { + *dest = 0; + errno = EINVAL; + return EINVAL; + } + + while ((*p++ = *src++) != 0 && --available > 0) + { + } + + if (available == 0) + { + *dest = 0; + errno = ERANGE; + return ERANGE; + } + *p = 0; + return 0; +} +// +// End copy functions +//---------------------------------------------------------------------------- + diff --git a/pdo_sqlsrv/version.h b/source/shared/StringFunctions.h similarity index 64% rename from pdo_sqlsrv/version.h rename to source/shared/StringFunctions.h index 93bb6d47f..bb9d5bc08 100644 --- a/pdo_sqlsrv/version.h +++ b/source/shared/StringFunctions.h @@ -1,27 +1,39 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: version.h -// Contents: Version number constants -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#define VER_FILEVERSION_STR "4.1.0.0" -#define _FILEVERSION 4,1,0,0 -#define SQLVERSION_MAJOR 4 -#define SQLVERSION_MINOR 1 -#define SQLVERSION_MMDD 0 -#define SQLVERSION_REVISION 0 - - - +//--------------------------------------------------------------------------------------------------------------------------------- +// File: StringFunctions.h +// +// Contents: Contains functions for handling UTF-16 on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#if !defined(_STRINGFUNCTIONS_H_) +#define _STRINGFUNCTIONS_H_ + +#include "xplat_winnls.h" + +// --------------------------------------------------------------------------- +// Declare internal versions of string handling functions +// Only the functions implemented are declared here + +// Copy +int mplat_memcpy_s(void *_S1, size_t _N1, const void *_S2, size_t _N); +int mplat_strcat_s( char *strDestination, size_t numberOfElements, const char *strSource ); +int mplat_strcpy_s(char * _Dst, size_t _SizeInBytes, const char * _Src); + +// Copy +#define memcpy_s mplat_memcpy_s +#define strcat_s mplat_strcat_s +#define strcpy_s mplat_strcpy_s + +#endif // _STRINGFUNCTIONS_H_ diff --git a/pdo_sqlsrv/core_conn.cpp b/source/shared/core_conn.cpp similarity index 77% rename from pdo_sqlsrv/core_conn.cpp rename to source/shared/core_conn.cpp index 97566d3c4..60040fd81 100644 --- a/pdo_sqlsrv/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -1,774 +1,833 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_conn.cpp -// -// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include -#include -#include -#include - -#include -#include - -// *** internal variables and constants *** - -namespace { - -// *** internal constants *** -// an arbitrary figure that should be large enough for most connection strings. -const int DEFAULT_CONN_STR_LEN = 2048; - -// length of buffer used to retrieve information for client and server info buffers -const int INFO_BUFFER_LEN = 256; - -// processor architectures -const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" }; - -// ODBC driver name. -const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"}; - -// default options if only the server is specified -const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}"; - -// connection option appended when no user name or password is given -const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};"; - -// connection option appended for MARS when MARS isn't explicitly mentioned -const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};"; - -// *** internal function prototypes *** - -void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, - HashTable* options_ht, const connection_option valid_conn_opts[], - void* driver,_Inout_ std::string& connection_string TSRMLS_DC ); -void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ); -const char* get_processor_arch( void ); -void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ); -connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, SQLULEN key_len TSRMLS_DC ); -void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ); - -} - -// core_sqlsrv_connect -// opens a connection and returns a sqlsrv_conn structure. -// Parameters: -// henv_cp - connection pooled env context -// henv_ncp - non connection pooled env context -// server - name of the server we're connecting to -// uid - username -// pwd - password -// options_ht - zend_hash list of options -// err - error callback to put into the connection's context -// valid_conn_opts[] - array of valid driver supported connection options. -// driver - reference to caller -// Return -// A sqlsrv_conn structure. An exception is thrown if an error occurs - -sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, - const char* server, const char* uid, const char* pwd, - HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[], - void* driver, const char* driver_func TSRMLS_DC ) - -{ - SQLRETURN r; - std::string conn_str; - conn_str.reserve( DEFAULT_CONN_STR_LEN ); - sqlsrv_malloc_auto_ptr conn; - sqlsrv_malloc_auto_ptr wconn_string; - unsigned int wconn_len = 0; - - try { - - sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv - - // check the connection pooling setting to determine which henv to use to allocate the connection handle - // we do this earlier because we have to allocate the connection handle prior to setting attributes on - // it in build_connection_string_and_set_conn_attr. - - if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - - zval* option_z = NULL; - int zr = SUCCESS; - - option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING); - if (option_z) { - - // if the option was found and it's not true, then use the non pooled environment handle - if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) { - - henv = &henv_ncp; - } - } - } - - SQLHANDLE temp_conn_h; - core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC ); - - conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); - conn->set_func( driver_func ); - - for ( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) { - conn_str = CONNECTION_STRING_DRIVER_NAME[i]; - build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, - conn_str TSRMLS_CC ); - - // We only support UTF-8 encoding for connection string. - // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW - wconn_len = static_cast( conn_str.length() + 1 ) * sizeof( wchar_t ); - - wconn_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast(conn_str.length()), &wconn_len); - CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) - { - throw core::CoreException(); - } - - SQLSMALLINT output_conn_size; - r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get()), - static_cast( wconn_len ), NULL, - 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); - // clear the connection string from memory to remove sensitive data (such as a password). - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn_str.clear(); - - if( !SQL_SUCCEEDED( r )) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; - SQLSMALLINT len; - SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); - bool missing_driver_error = ( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && - state[4] == '2' ); - // if it's a IM002, meaning that the correct ODBC driver is not installed - CHECK_CUSTOM_ERROR( missing_driver_error && ( i == DRIVER_VERSION::MAX ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { - throw core::CoreException(); - } - if ( !missing_driver_error ) { - break; - } - } else { - conn->driver_version = static_cast( i ); - break; - } - - } - CHECK_SQL_ERROR( r, conn ) { - throw core::CoreException(); - } - - CHECK_SQL_WARNING_AS_ERROR( r, conn ) { - throw core::CoreException(); - } - - // determine the version of the server we're connected to. The server version is left in the - // connection upon return. - determine_server_version( conn TSRMLS_CC ); - - } - catch( std::bad_alloc& ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn->invalidate(); - DIE( "C++ memory allocation failure building the connection string." ); - } - catch( std::out_of_range const& ex ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); - conn->invalidate(); - throw; - } - catch( std::length_error const& ex ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); - conn->invalidate(); - throw; - } - catch( core::CoreException& ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn->invalidate(); - throw; - } - - sqlsrv_conn* return_conn = conn; - conn.transferred(); - - return return_conn; -} - - - -// core_sqlsrv_begin_transaction -// Begins a transaction on a specified connection. The current transaction -// includes all statements on the specified connection that were executed after -// the call to core_sqlsrv_begin_transaction and before any calls to -// core_sqlsrv_rollback or core_sqlsrv_commit. -// The default transaction mode is auto-commit. This means that all queries -// are automatically committed upon success unless they have been designated -// as part of an explicit transaction by using core_sqlsrv_begin_transaction. -// Parameters: -// sqlsrv_conn*: The connection with which the transaction is associated. - -void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_OFF ), - SQL_IS_UINTEGER TSRMLS_CC ); - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_commit -// Commits the current transaction on the specified connection and returns the -// connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or -// core_sqlsrv_commit. -// Parameters: -// sqlsrv_conn*: The connection on which the transaction is active. - -void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." ); - - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), - SQL_IS_UINTEGER TSRMLS_CC ); - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_rollback -// Rolls back the current transaction on the specified connection and returns -// the connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or -// core_sqlsrv_commit. -// Parameters: -// sqlsrv_conn*: The connection on which the transaction is active. - -void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." ); - - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), - SQL_IS_UINTEGER TSRMLS_CC ); - - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_close -// Called when a connection resource is destroyed by the Zend engine. -// Parameters: -// conn - The current active connection. -void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ) -{ - // if the connection wasn't successful, just return. - if( conn == NULL ) - return; - - try { - - // rollback any transaction in progress (we don't care about the return result) - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); - } - catch( core::CoreException& ) { - LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." ); - } - - // disconnect from the server - SQLRETURN r = SQLDisconnect( conn->handle() ); - if( !SQL_SUCCEEDED( r )) { - LOG( SEV_ERROR, "Disconnect failed when closing the connection." ); - } - - // free the connection handle - conn->invalidate(); - - sqlsrv_free( conn ); -} - -// core_sqlsrv_prepare -// Create a statement object and prepare the SQL query passed in for execution at a later time. -// Parameters: -// stmt - statement to be prepared -// sql - T-SQL command to prepare -// sql_len - length of the T-SQL string - -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ) -{ - try { - - // convert the string from its encoding to UTf-16 - // if the string is empty, we initialize the fields and skip since an empty string is a - // failure case for utf16_string_from_mbcs_string - sqlsrv_malloc_auto_ptr wsql_string; - unsigned int wsql_len = 0; - if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { - wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t ))); - wsql_string[0] = L'\0'; - wsql_len = 0; - } - else { - - if (sql_len > INT_MAX) - { - LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); - throw core::CoreException(); - } - - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : - stmt->encoding() ); - wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - static_cast( sql_len ), &wsql_len ); - CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - get_last_error_message() ) { - throw core::CoreException(); - } - } - - // prepare our wide char query string - core::SQLPrepareW( stmt, reinterpret_cast( wsql_string.get() ), wsql_len TSRMLS_CC ); - } - catch( core::CoreException& ) { - - throw; - } -} - -// core_sqlsrv_get_server_version -// Determines the vesrion of the SQL Server we are connected to. Calls a helper function -// get_server_version to get the version of SQL Server. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// *server_version - zval for returning results. - -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); - if (NULL != buffer) { - sqlsrv_free( buffer ); - } - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_get_server_info -// Returns the Database name, the name of the SQL Server we are connected to -// and the version of the SQL Server. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// *server_info - zval for returning results. - -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval *server_info TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - // initialize the array - core::sqlsrv_array_init( *conn, server_info TSRMLS_CC ); - - // Get the database name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the server version - get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the server name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_get_client_info -// Returns the ODBC driver's dll name, version and the ODBC version. -// Parameters -// conn - The connection resource by which the client and server are connected. -// *client_info - zval for returning the results. - -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - // initialize the array - core::sqlsrv_array_init( *conn, client_info TSRMLS_CC ); - - // Get the ODBC driver's dll name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the ODBC driver's ODBC version - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the OBDC driver's version - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - } - - catch( core::CoreException& ) { - throw; - } -} - - -// core_is_conn_opt_value_escaped -// determine if connection string value is properly escaped. -// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that -// the value will be surrounded by { and } by the caller after it has been validated - -bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ) -{ - // if the value is already quoted, then only analyse the part inside the quotes and return it as - // unquoted since we quote it when adding it to the connection string. - if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) { - ++value; - value_len -= 2; - } - // check to make sure that all right braces are escaped - size_t i = 0; - while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) { - // skip both braces - if( value[i] == '}' ) - ++i; - ++i; - } - if( i < value_len && value[i] == '}' ) { - return false; - } - - return true; -} - - -// *** internal connection functions and classes *** - -namespace { - -connection_option const* get_connection_option( sqlsrv_conn* conn, SQLULEN key, - const connection_option conn_opts[] TSRMLS_DC ) -{ - for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) { - - if( key == conn_opts[ opt_idx ].conn_option_key ) { - - return &conn_opts[ opt_idx ]; - } - } - - SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." ); - return NULL; // avoid a compiler warning -} - -// says what it does, and does what it says -// rather than have attributes and connection strings as ODBC does, we unify them into a hash table -// passed to the connection, and then break them out ourselves and either set attributes or put the -// option in the connection string. - -void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, - HashTable* options, const connection_option valid_conn_opts[], - void* driver,_Inout_ std::string& connection_string TSRMLS_DC ) -{ - bool credentials_mentioned = false; - bool mars_mentioned = false; - connection_option const* conn_opt; - int zr = SUCCESS; - - try { - - // Add the server name - common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC ); - - // if uid is not present then we use trusted connection. - if(uid == NULL || strlen( uid ) == 0 ) { - - connection_string += "Trusted_Connection={Yes};"; - } - else { - - bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { - throw core::CoreException(); - } - - common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( uid ), connection_string TSRMLS_CC ); - - // if no password was given, then don't add a password to the connection string. Perhaps the UID - // given doesn't have a password? - if( pwd != NULL ) { - - escaped = core_is_conn_opt_value_escaped( pwd, strlen( pwd )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { - - throw core::CoreException(); - } - - common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC ); - } - } - - // if no options were given, then we set MARS the defaults and return immediately. - if( options == NULL || zend_hash_num_elements( options ) == 0 ) { - connection_string += CONNECTION_STRING_DEFAULT_OPTIONS; - return; - } - - // workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file - // if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn" - // flag is set to false. - if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) { - - zval* trace_value = NULL; - trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); - - if (trace_value == NULL || !zend_is_true(trace_value)) { - - zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE ); - } - } - - zend_string *key = NULL; - zend_ulong index = -1; - zval* data = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); - - conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); - - if( index == SQLSRV_CONN_OPTION_MARS ) { - mars_mentioned = true; - } - - conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - - // MARS on if not explicitly turned off - if( !mars_mentioned ) { - connection_string += CONNECTION_OPTION_MARS_ON; - } - - } - catch( core::CoreException& ) { - throw; - } -} - - -// get_server_version -// Helper function which returns the version of the SQL Server we are connected to. - -void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - *server_version = buffer; - len = buffer_len; - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - - -// get_processor_arch -// Calls GetSystemInfo to verify the what architecture of the processor is supported -// and return the string of the processor name. -const char* get_processor_arch( void ) -{ - SYSTEM_INFO sys_info; - GetSystemInfo( &sys_info); - switch( sys_info.wProcessorArchitecture ) { - - case PROCESSOR_ARCHITECTURE_INTEL: - return PROCESSOR_ARCH[0]; - - case PROCESSOR_ARCHITECTURE_AMD64: - return PROCESSOR_ARCH[1]; - - case PROCESSOR_ARCHITECTURE_IA64: - return PROCESSOR_ARCH[2]; - - default: - DIE( "Unknown Windows processor architecture." ); - return NULL; - } -} - - -// some features require a server of a certain version or later -// this function determines the version of the server we're connected to -// and stores it in the connection. Any errors are logged before return. -// Exception is thrown when the server version is either undetermined -// or is invalid (< 2000). - -void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) -{ - SQLSMALLINT info_len; - char p[ INFO_BUFFER_LEN ]; - core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); - - errno = 0; - char version_major_str[ 3 ]; - SERVER_VERSION version_major; - memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); - version_major_str[ 2 ] = '\0'; - version_major = static_cast( atoi( version_major_str )); - - CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION ) - { - throw core::CoreException(); - } - - // SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least - // that high - conn->server_version = version_major; -} - -void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) -{ - // wrap a connection option in a quote. It is presumed that any character that need to be escaped will - // be escaped, such as a closing }. - TSRMLS_C; - - if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) { - ++val; - val_len -= 2; - } - conn_str += odbc_name; - conn_str += "={"; - conn_str.append( val, val_len ); - conn_str += "};"; -} - -} // namespace - -// simply add the parsed value to the connection string -void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str - TSRMLS_DC ) -{ - const char* val_str = Z_STRVAL_P( value ); - size_t val_len = Z_STRLEN_P( value ); - common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC ); -} - -// do nothing for connection pooling since we handled it earlier when -// deciding which environment handle to use. -void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ - TSRMLS_DC ) -{ - TSRMLS_C; -} - -// helper function to evaluate whether a string value is true or false. -// Values = ("true" or "1") are treated as true values. Everything else is treated as false. -// Returns 1 for true and 0 for false. - -size_t core_str_zval_is_true( zval* value_z ) -{ - SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); - - char* value_in = Z_STRVAL_P( value_z ); - size_t val_len = Z_STRLEN_P( value_z ); - - // strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8) - size_t last_char = val_len - 1; - while( isspace( value_in[ last_char ] )) { - value_in[ last_char ] = '\0'; - val_len = last_char; - --last_char; - } - - // save adjustments to the value made by stripping whitespace at the end - Z_STRLEN_P( value_z ) = val_len; - - const char VALID_TRUE_VALUE_1[] = "true"; - const char VALID_TRUE_VALUE_2[] = "1"; - - if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) || - ( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len )) - ) { - - return 1; // true - } - - return 0; // false -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_conn.cpp +// +// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +#include + +#ifdef _WIN32 +#include +#include +#include +#endif // _WIN32 + +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +// *** internal variables and constants *** + +namespace { + +// *** internal constants *** +// an arbitrary figure that should be large enough for most connection strings. +const int DEFAULT_CONN_STR_LEN = 2048; + +// length of buffer used to retrieve information for client and server info buffers +const int INFO_BUFFER_LEN = 256; + +// processor architectures +const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" }; + +// ODBC driver name. +const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"}; + +// default options if only the server is specified +const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}"; + +// connection option appended when no user name or password is given +const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};"; + +// connection option appended for MARS when MARS isn't explicitly mentioned +const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};"; + +// *** internal function prototypes *** + +void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, + HashTable* options_ht, const connection_option valid_conn_opts[], + void* driver,_Inout_ std::string& connection_string TSRMLS_DC ); +void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ); +const char* get_processor_arch( void ); +void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ); +connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, SQLULEN key_len TSRMLS_DC ); +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ); + +} + +// core_sqlsrv_connect +// opens a connection and returns a sqlsrv_conn structure. +// Parameters: +// henv_cp - connection pooled env context +// henv_ncp - non connection pooled env context +// server - name of the server we're connecting to +// uid - username +// pwd - password +// options_ht - zend_hash list of options +// err - error callback to put into the connection's context +// valid_conn_opts[] - array of valid driver supported connection options. +// driver - reference to caller +// Return +// A sqlsrv_conn structure. An exception is thrown if an error occurs + +sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, + const char* server, const char* uid, const char* pwd, + HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[], + void* driver, const char* driver_func TSRMLS_DC ) + +{ + SQLRETURN r; + std::string conn_str; + conn_str.reserve( DEFAULT_CONN_STR_LEN ); + sqlsrv_malloc_auto_ptr conn; + sqlsrv_malloc_auto_ptr wconn_string; + unsigned int wconn_len = 0; + +#ifdef _WIN32 + sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv +#else + sqlsrv_context* henv = &henv_ncp; // by default do not use the connection pooling henv +#endif // _WIN32 + + try { + // Due to the limitations on connection pooling in unixODBC 2.3.1 driver manager, we do not consider + // the connection string attributes to set (enable/disable) connection pooling. + // Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section. + +#ifndef _WIN32 + char pooling_string[ 128 ] = {0}; + SQLGetPrivateProfileString( "ODBC", "Pooling", "0", pooling_string, sizeof( pooling_string ), "ODBCINST.INI" ); + + if ( pooling_string[ 0 ] == '1' || toupper( pooling_string[ 0 ] ) == 'Y' || + ( toupper( pooling_string[ 0 ] ) == 'O' && toupper( pooling_string[ 1 ] ) == 'N' )) + { + henv = &henv_cp; + } +#else + // check the connection pooling setting to determine which henv to use to allocate the connection handle + // we do this earlier because we have to allocate the connection handle prior to setting attributes on + // it in build_connection_string_and_set_conn_attr. + + if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { + + zval* option_z = NULL; + option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING); + if ( option_z ) { + // if the option was found and it's not true, then use the non pooled environment handle + if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) { + henv = &henv_ncp; + } + } + } +#endif // !_WIN32 + + SQLHANDLE temp_conn_h; + core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC ); + + conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); + conn->set_func( driver_func ); + + for( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) { + conn_str = CONNECTION_STRING_DRIVER_NAME[i]; + build_connection_string_and_set_conn_attr(conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str TSRMLS_CC); + + // We only support UTF-8 encoding for connection string. + // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW + wconn_len = static_cast( conn_str.length() + 1 ) * sizeof( SQLWCHAR ); + + wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast(conn_str.length()), &wconn_len ); + + CHECK_CUSTOM_ERROR( wconn_string == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) + { + throw core::CoreException(); + } + + SQLSMALLINT output_conn_size; +#ifndef _WIN32 + // unixODBC 2.3.1 requires a non-wide SQLDriverConnect call while pooling enabled. + // connection handle has been allocated using henv_cp, means pooling enabled in a PHP script + if ( henv == &henv_cp ) + { + r = SQLDriverConnect( conn->handle(), NULL, (SQLCHAR*)conn_str.c_str(), SQL_NTS, NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); + } + else + { + r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), static_cast( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); + } +#else + r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), static_cast( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); +#endif // !_WIN32 + + // clear the connection string from memory to remove sensitive data (such as a password). + memset( const_cast(conn_str.c_str()), 0, conn_str.size()); + memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes + conn_str.clear(); + if (!SQL_SUCCEEDED(r)) { + SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; + SQLSMALLINT len; + SQLRETURN r = SQLGetDiagField(SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len); + bool missing_driver_error = (SQL_SUCCEEDED(r) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && state[4] == '2'); + // if it's a IM002, meaning that the correct ODBC driver is not installed + CHECK_CUSTOM_ERROR(missing_driver_error && (i == DRIVER_VERSION::MAX), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { + throw core::CoreException(); + } + if (!missing_driver_error) { + break; + } + } + else { + conn->driver_version = static_cast( i ); + break; + } + } + CHECK_SQL_ERROR( r, conn ) { + throw core::CoreException(); + } + + CHECK_SQL_WARNING_AS_ERROR( r, conn ) { + throw core::CoreException(); + } + + // determine the version of the server we're connected to. The server version is left in the + // connection upon return. + // + // unixODBC 2.3.1: + // SQLGetInfo works when r = SQL_SUCCESS_WITH_INFO (non-pooled connection) + // but fails if the connection is using a pool, i.e. r= SQL_SUCCESS. + // Thus, in Linux, we don't call determine_server_version() for a connection that uses pool. +#ifndef _WIN32 + if ( r == SQL_SUCCESS_WITH_INFO ) { +#endif // !_WIN32 + determine_server_version( conn TSRMLS_CC ); +#ifndef _WIN32 + } +#endif // !_WIN32 + } + catch( std::bad_alloc& ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes + conn->invalidate(); + DIE( "C++ memory allocation failure building the connection string." ); + } + catch( std::out_of_range const& ex ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes + LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); + conn->invalidate(); + throw; + } + catch( std::length_error const& ex ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes + LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); + conn->invalidate(); + throw; + } + catch( core::CoreException& ) { + memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); + memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes + conn->invalidate(); + throw; + } + + sqlsrv_conn* return_conn = conn; + conn.transferred(); + + return return_conn; +} + + + +// core_sqlsrv_begin_transaction +// Begins a transaction on a specified connection. The current transaction +// includes all statements on the specified connection that were executed after +// the call to core_sqlsrv_begin_transaction and before any calls to +// core_sqlsrv_rollback or core_sqlsrv_commit. +// The default transaction mode is auto-commit. This means that all queries +// are automatically committed upon success unless they have been designated +// as part of an explicit transaction by using core_sqlsrv_begin_transaction. +// Parameters: +// sqlsrv_conn*: The connection with which the transaction is associated. + +void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." ); + + core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_OFF ), + SQL_IS_UINTEGER TSRMLS_CC ); + } + catch ( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_commit +// Commits the current transaction on the specified connection and returns the +// connection to the auto-commit mode. The current transaction includes all +// statements on the specified connection that were executed after the call to +// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or +// core_sqlsrv_commit. +// Parameters: +// sqlsrv_conn*: The connection on which the transaction is active. + +void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." ); + + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC ); + + core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), + SQL_IS_UINTEGER TSRMLS_CC ); + } + catch ( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_rollback +// Rolls back the current transaction on the specified connection and returns +// the connection to the auto-commit mode. The current transaction includes all +// statements on the specified connection that were executed after the call to +// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or +// core_sqlsrv_commit. +// Parameters: +// sqlsrv_conn*: The connection on which the transaction is active. + +void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." ); + + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); + + core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), + SQL_IS_UINTEGER TSRMLS_CC ); + + } + catch ( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_close +// Called when a connection resource is destroyed by the Zend engine. +// Parameters: +// conn - The current active connection. +void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ) +{ + // if the connection wasn't successful, just return. + if( conn == NULL ) + return; + + try { + + // rollback any transaction in progress (we don't care about the return result) + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); + } + catch( core::CoreException& ) { + LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." ); + } + + // disconnect from the server + SQLRETURN r = SQLDisconnect( conn->handle() ); + if( !SQL_SUCCEEDED( r )) { + LOG( SEV_ERROR, "Disconnect failed when closing the connection." ); + } + + // free the connection handle + conn->invalidate(); + + sqlsrv_free( conn ); +} + +// core_sqlsrv_prepare +// Create a statement object and prepare the SQL query passed in for execution at a later time. +// Parameters: +// stmt - statement to be prepared +// sql - T-SQL command to prepare +// sql_len - length of the T-SQL string + +void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ) +{ + try { + + // convert the string from its encoding to UTf-16 + // if the string is empty, we initialize the fields and skip since an empty string is a + // failure case for utf16_string_from_mbcs_string + sqlsrv_malloc_auto_ptr wsql_string; + unsigned int wsql_len = 0; + if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { + wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( SQLWCHAR ))); + wsql_string[0] = L'\0'; + wsql_len = 0; + } + else { + if( sql_len > INT_MAX ) { + LOG( SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } + + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding() ); + wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), static_cast( sql_len ), &wsql_len ); + CHECK_CUSTOM_ERROR( wsql_string == 0, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) { + throw core::CoreException(); + } + } + + // prepare our wide char query string + core::SQLPrepareW( stmt, reinterpret_cast( wsql_string.get() ), wsql_len TSRMLS_CC ); + } + catch( core::CoreException& ) { + + throw; + } +} + +// core_sqlsrv_get_server_version +// Determines the vesrion of the SQL Server we are connected to. Calls a helper function +// get_server_version to get the version of SQL Server. +// Parameters: +// conn - The connection resource by which the client and server are connected. +// *server_version - zval for returning results. + +void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); + core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); + if ( buffer != 0 ) { + sqlsrv_free( buffer ); + } + buffer.transferred(); + } + + catch( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_get_server_info +// Returns the Database name, the name of the SQL Server we are connected to +// and the version of the SQL Server. +// Parameters: +// conn - The connection resource by which the client and server are connected. +// *server_info - zval for returning results. + +void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval *server_info TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + // Get the database name + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + + // initialize the array + core::sqlsrv_array_init( *conn, server_info TSRMLS_CC ); + + core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + // Get the server version + get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + // Get the server name + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + } + + catch( core::CoreException& ) { + throw; + } +} + +// core_sqlsrv_get_client_info +// Returns the ODBC driver's dll name, version and the ODBC version. +// Parameters +// conn - The connection resource by which the client and server are connected. +// *client_info - zval for returning the results. + +void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + // Get the ODBC driver's dll name + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + + // initialize the array + core::sqlsrv_array_init( *conn, client_info TSRMLS_CC ); + +#ifndef _WIN32 + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverName", buffer, 0 /*duplicate*/ TSRMLS_CC ); +#else + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC ); +#endif // !_WIN32 + buffer.transferred(); + + // Get the ODBC driver's ODBC version + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + // Get the OBDC driver's version + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); + buffer.transferred(); + + } + + catch( core::CoreException& ) { + throw; + } +} + + +// core_is_conn_opt_value_escaped +// determine if connection string value is properly escaped. +// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that +// the value will be surrounded by { and } by the caller after it has been validated + +bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ) +{ + // if the value is already quoted, then only analyse the part inside the quotes and return it as + // unquoted since we quote it when adding it to the connection string. + if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) { + ++value; + value_len -= 2; + } + // check to make sure that all right braces are escaped + size_t i = 0; + while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) { + // skip both braces + if( value[i] == '}' ) + ++i; + ++i; + } + if( i < value_len && value[i] == '}' ) { + return false; + } + + return true; +} + + +// *** internal connection functions and classes *** + +namespace { + +connection_option const* get_connection_option( sqlsrv_conn* conn, SQLULEN key, + const connection_option conn_opts[] TSRMLS_DC ) +{ + for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) { + + if( key == conn_opts[ opt_idx ].conn_option_key ) { + + return &conn_opts[ opt_idx ]; + } + } + + SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." ); + return NULL; // avoid a compiler warning +} + +// says what it does, and does what it says +// rather than have attributes and connection strings as ODBC does, we unify them into a hash table +// passed to the connection, and then break them out ourselves and either set attributes or put the +// option in the connection string. + +void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, + HashTable* options, const connection_option valid_conn_opts[], + void* driver,_Inout_ std::string& connection_string TSRMLS_DC ) +{ + bool mars_mentioned = false; + connection_option const* conn_opt; + + try { + + // Add the server name + common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC ); + + // if uid is not present then we use trusted connection. + if(uid == NULL || strlen( uid ) == 0 ) { + + connection_string += "Trusted_Connection={Yes};"; + } + else { + + bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid )); + CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { + throw core::CoreException(); + } + + common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( uid ), connection_string TSRMLS_CC ); + + // if no password was given, then don't add a password to the connection string. Perhaps the UID + // given doesn't have a password? + if( pwd != NULL ) { + escaped = core_is_conn_opt_value_escaped( pwd, strlen( pwd )); + CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { + throw core::CoreException(); + } + + common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC ); + } + } + + // if no options were given, then we set MARS the defaults and return immediately. + if( options == NULL || zend_hash_num_elements( options ) == 0 ) { + connection_string += CONNECTION_STRING_DEFAULT_OPTIONS; + return; + } + + // workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file + // if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn" + // flag is set to false. + if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) { + + zval* trace_value = NULL; + trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); + + if (trace_value == NULL || !zend_is_true(trace_value)) { + + zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE ); + } + } + + zend_string *key = NULL; + zend_ulong index = -1; + zval* data = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); + + conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); + + if( index == SQLSRV_CONN_OPTION_MARS ) { + mars_mentioned = true; + } + + conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); + + // MARS on if not explicitly turned off + if( !mars_mentioned ) { + connection_string += CONNECTION_OPTION_MARS_ON; + } + + } + catch( core::CoreException& ) { + throw; + } +} + + +// get_server_version +// Helper function which returns the version of the SQL Server we are connected to. + +void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ) +{ + try { + + sqlsrv_malloc_auto_ptr buffer; + SQLSMALLINT buffer_len = 0; + + buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); + core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + *server_version = buffer; + len = buffer_len; + buffer.transferred(); + } + + catch( core::CoreException& ) { + throw; + } +} + + +// get_processor_arch +// Calls GetSystemInfo to verify the what architecture of the processor is supported +// and return the string of the processor name. +const char* get_processor_arch( void ) +{ +#ifndef _WIN32 + struct utsname sys_info; + if ( uname(&sys_info) == -1 ) + { + DIE( "Error retrieving system info" ); + } + if( strcmp(sys_info.machine, "x86") == 0 ) { + return PROCESSOR_ARCH[0]; + } else if ( strcmp(sys_info.machine, "x86_64") == 0) { + return PROCESSOR_ARCH[1]; + } else if ( strcmp(sys_info.machine, "ia64") == 0 ) { + return PROCESSOR_ARCH[2]; + } else { + DIE( "Unknown processor architecture." ); + } + return NULL; +#else + SYSTEM_INFO sys_info; + GetSystemInfo( &sys_info); + switch( sys_info.wProcessorArchitecture ) { + + case PROCESSOR_ARCHITECTURE_INTEL: + return PROCESSOR_ARCH[0]; + + case PROCESSOR_ARCHITECTURE_AMD64: + return PROCESSOR_ARCH[1]; + + case PROCESSOR_ARCHITECTURE_IA64: + return PROCESSOR_ARCH[2]; + + default: + DIE( "Unknown Windows processor architecture." ); + return NULL; + } + return NULL; +#endif // !_WIN32 +} + + +// some features require a server of a certain version or later +// this function determines the version of the server we're connected to +// and stores it in the connection. Any errors are logged before return. +// Exception is thrown when the server version is either undetermined +// or is invalid (< 2000). + +void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) +{ + SQLSMALLINT info_len; + char p[ INFO_BUFFER_LEN ]; + core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); + + errno = 0; + char version_major_str[ 3 ]; + SERVER_VERSION version_major; + memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); + + version_major_str[ 2 ] = '\0'; + version_major = static_cast( atoi( version_major_str )); + + CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION ) + { + throw core::CoreException(); + } + + // SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least + // that high + conn->server_version = version_major; +} + +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) +{ + // wrap a connection option in a quote. It is presumed that any character that need to be escaped will + // be escaped, such as a closing }. + TSRMLS_C; + + if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) { + ++val; + val_len -= 2; + } + conn_str += odbc_name; + conn_str += "={"; + conn_str.append( val, val_len ); + conn_str += "};"; +} + +} // namespace + +// simply add the parsed value to the connection string +void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str + TSRMLS_DC ) +{ + const char* val_str = Z_STRVAL_P( value ); + size_t val_len = Z_STRLEN_P( value ); + common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC ); +} + +// do nothing for connection pooling since we handled it earlier when +// deciding which environment handle to use. +void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ + TSRMLS_DC ) +{ + TSRMLS_C; +} + +// helper function to evaluate whether a string value is true or false. +// Values = ("true" or "1") are treated as true values. Everything else is treated as false. +// Returns 1 for true and 0 for false. + +size_t core_str_zval_is_true( zval* value_z ) +{ + SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); + + char* value_in = Z_STRVAL_P( value_z ); + size_t val_len = Z_STRLEN_P( value_z ); + + // strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8) + size_t last_char = val_len - 1; + while( isspace( value_in[ last_char ] )) { + value_in[ last_char ] = '\0'; + val_len = last_char; + --last_char; + } + + // save adjustments to the value made by stripping whitespace at the end + Z_STRLEN_P( value_z ) = val_len; + + const char VALID_TRUE_VALUE_1[] = "true"; + const char VALID_TRUE_VALUE_2[] = "1"; + + if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) || + ( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len )) + ) { + + return 1; // true + } + + return 0; // false +} diff --git a/pdo_sqlsrv/core_init.cpp b/source/shared/core_init.cpp similarity index 97% rename from pdo_sqlsrv/core_init.cpp rename to source/shared/core_init.cpp index 1b1e8e57b..4bf801676 100644 --- a/pdo_sqlsrv/core_init.cpp +++ b/source/shared/core_init.cpp @@ -1,175 +1,178 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_init.cpp -// -// Contents: common initialization routines shared by PDO and sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - - -// module global variables (initialized in minit and freed in mshutdown) -HMODULE g_sqlsrv_hmodule = NULL; -OSVERSIONINFO g_osversion; - - -// core_sqlsrv_minit -// Module initialization -// This function is called once per execution by the driver layer's MINIT function. -// The primary responsibility of this function is to allocate the two environment -// handles used by core_sqlsrv_connect to allocate either a pooled or non pooled ODBC -// connection handle. -// Parameters: -// henv_cp - Environment handle for pooled connection. -// henv_ncp - Environment handle for non-pooled connection. -// err - Driver specific error handler which handles any errors during initialization. -void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ) -{ - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); - - *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL - - try { - - // get the version of the OS we're running on. For now this governs certain flags used by - // WideCharToMultiByte. It might be relevant to other things in the future. - g_osversion.dwOSVersionInfoSize = sizeof( g_osversion ); - BOOL ver_return = GetVersionEx( &g_osversion ); - if( !ver_return ) { - LOG( SEV_ERROR, "Failed to retrieve Windows version information." ); - throw core::CoreException(); - } - - SQLHANDLE henv = SQL_NULL_HANDLE; - SQLRETURN r; - - // allocate the non pooled environment handle - // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so - // we use the direct ODBC function. - r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); - if( !SQL_SUCCEEDED( r )) { - throw core::CoreException(); - } - - *henv_ncp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); - (*henv_ncp)->set_func( driver_func ); - - // set to ODBC 3 - core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER - TSRMLS_CC ); - - // disable connection pooling - core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_OFF ), - SQL_IS_UINTEGER TSRMLS_CC ); - - // allocate the pooled envrionment handle - // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so - // we use the direct ODBC function. - r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); - if( !SQL_SUCCEEDED( r )) { - throw core::CoreException(); - } - - *henv_cp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); - (*henv_cp)->set_func( driver_func ); - - // set to ODBC 3 - core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC); - - // enable connection pooling - core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_ONE_PER_HENV ), - SQL_IS_UINTEGER TSRMLS_CC ); - - } - catch( core::CoreException& e ) { - - LOG( SEV_ERROR, "core_sqlsrv_minit: Failed to allocate environment handles." ); - - if( *henv_ncp != NULL ) { - // free the ODBC env handle allocated just above - SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); - delete *henv_ncp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) - *henv_ncp = NULL; - } - if( *henv_cp != NULL ) { - // free the ODBC env handle allocated just above - SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); - delete *henv_cp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) - *henv_cp = NULL; - } - - throw e; // rethrow for the driver to catch - } - catch( std::bad_alloc& e ) { - - LOG( SEV_ERROR, "core_sqlsrv_minit: Failed memory allocation for environment handles." ); - - if( *henv_ncp != NULL ) { - SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); - delete *henv_ncp; - *henv_ncp = NULL; - } - if( *henv_cp ) { - SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); - delete *henv_cp; - *henv_cp = NULL; - } - - throw e; // rethrow for the driver to catch - } -} - -// core_sqlsrv_mshutdown -// Module shutdown function -// Free the environment handles allocated in MINIT and unregister our stream wrapper. -// Resource types and constants are automatically released since we don't flag them as -// persistent when they are registered. -// Parameters: -// henv_cp - Pooled environment handle. -// henv_ncp - Non-pooled environment handle. -void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ) -{ - if( henv_ncp != SQL_NULL_HANDLE ) { - - henv_ncp.invalidate(); - } - delete &henv_ncp; - - if( henv_cp != SQL_NULL_HANDLE ) { - - henv_cp.invalidate(); - } - delete &henv_cp; - - return; -} - - -// DllMain for the extension. - -BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID ) -{ - switch( fdwReason ) { - case DLL_PROCESS_ATTACH: - // store the module handle for use by client_info and server_info - g_sqlsrv_hmodule = hinstDLL; - break; - default: - break; - } - - return TRUE; -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_init.cpp +// +// Contents: common initialization routines shared by PDO and sqlsrv +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + + +// module global variables (initialized in minit and freed in mshutdown) +HMODULE g_sqlsrv_hmodule = NULL; +OSVERSIONINFO g_osversion; + + +// core_sqlsrv_minit +// Module initialization +// This function is called once per execution by the driver layer's MINIT function. +// The primary responsibility of this function is to allocate the two environment +// handles used by core_sqlsrv_connect to allocate either a pooled or non pooled ODBC +// connection handle. +// Parameters: +// henv_cp - Environment handle for pooled connection. +// henv_ncp - Environment handle for non-pooled connection. +// err - Driver specific error handler which handles any errors during initialization. +void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ) +{ + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); + + *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL + + try { + +#ifdef _WIN32 + // get the version of the OS we're running on. For now this governs certain flags used by + // WideCharToMultiByte. It might be relevant to other things in the future. + g_osversion.dwOSVersionInfoSize = sizeof( g_osversion ); + BOOL ver_return = GetVersionEx( &g_osversion ); + if( !ver_return ) { + LOG( SEV_ERROR, "Failed to retrieve Windows version information." ); + throw core::CoreException(); + } +#endif //_WIN32 + + SQLHANDLE henv = SQL_NULL_HANDLE; + SQLRETURN r; + + // allocate the non pooled environment handle + // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so + // we use the direct ODBC function. + r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); + if( !SQL_SUCCEEDED( r )) { + throw core::CoreException(); + } + + *henv_ncp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); + (*henv_ncp)->set_func( driver_func ); + + // set to ODBC 3 + core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER + TSRMLS_CC ); + + // disable connection pooling + core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_OFF ), + SQL_IS_UINTEGER TSRMLS_CC ); + + // allocate the pooled envrionment handle + // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so + // we use the direct ODBC function. + r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); + if( !SQL_SUCCEEDED( r )) { + throw core::CoreException(); + } + + *henv_cp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); + (*henv_cp)->set_func( driver_func ); + + // set to ODBC 3 + core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC); + + // enable connection pooling + core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_ONE_PER_HENV ), + SQL_IS_UINTEGER TSRMLS_CC ); + + } + catch( core::CoreException& e ) { + + LOG( SEV_ERROR, "core_sqlsrv_minit: Failed to allocate environment handles." ); + + if( *henv_ncp != NULL ) { + // free the ODBC env handle allocated just above + SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); + delete *henv_ncp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) + *henv_ncp = NULL; + } + if( *henv_cp != NULL ) { + // free the ODBC env handle allocated just above + SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); + delete *henv_cp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) + *henv_cp = NULL; + } + + throw e; // rethrow for the driver to catch + } + catch( std::bad_alloc& e ) { + + LOG( SEV_ERROR, "core_sqlsrv_minit: Failed memory allocation for environment handles." ); + + if( *henv_ncp != NULL ) { + SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); + delete *henv_ncp; + *henv_ncp = NULL; + } + if( *henv_cp ) { + SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); + delete *henv_cp; + *henv_cp = NULL; + } + + throw e; // rethrow for the driver to catch + } +} + +// core_sqlsrv_mshutdown +// Module shutdown function +// Free the environment handles allocated in MINIT and unregister our stream wrapper. +// Resource types and constants are automatically released since we don't flag them as +// persistent when they are registered. +// Parameters: +// henv_cp - Pooled environment handle. +// henv_ncp - Non-pooled environment handle. +void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ) +{ + if( henv_ncp != SQL_NULL_HANDLE ) { + + henv_ncp.invalidate(); + } + delete &henv_ncp; + + if( henv_cp != SQL_NULL_HANDLE ) { + + henv_cp.invalidate(); + } + delete &henv_cp; + + return; +} + + +// DllMain for the extension. +#ifdef _WIN32 +BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID ) +{ + switch( fdwReason ) { + case DLL_PROCESS_ATTACH: + // store the module handle for use by client_info and server_info + g_sqlsrv_hmodule = hinstDLL; + break; + default: + break; + } + + return TRUE; +} +#endif diff --git a/pdo_sqlsrv/core_results.cpp b/source/shared/core_results.cpp similarity index 81% rename from pdo_sqlsrv/core_results.cpp rename to source/shared/core_results.cpp index 8b2657ed3..1f1593a2a 100644 --- a/pdo_sqlsrv/core_results.cpp +++ b/source/shared/core_results.cpp @@ -1,1361 +1,1581 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_results.cpp -// -// Contents: Result sets -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include -#include -#include - -using namespace core; - -// conversion matrix -// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported -// this is initialized the first time the buffered result set is created. -sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix; - -namespace { - -// *** internal types *** - -#pragma warning(disable:4200) - -// *** internal constants *** - -const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field - -// *** internal functions *** - -// return an integral type rounded up to a certain number -template -T align_to( T number ) -{ - DEBUG_SQLSRV_ASSERT( (number + align) > number, "Number to align overflowed" ); - return ((number % align) == 0) ? number : (number + align - (number % align)); -} - -// return a pointer address aligned to a certain address boundary -template -T* align_to( T* ptr ) -{ - size_t p_value = (size_t) ptr; - return align_to( p_value ); -} - -// set the nth bit of the bitstream starting at ptr -void set_bit( void* ptr, unsigned int bit ) -{ - unsigned char* null_bits = reinterpret_cast( ptr ); - null_bits += bit >> 3; - *null_bits |= 1 << ( 7 - ( bit & 0x7 )); -} - -// retrieve the nth bit from the bitstream starting at ptr -bool get_bit( void* ptr, unsigned int bit ) -{ - unsigned char* null_bits = reinterpret_cast( ptr ); - null_bits += bit >> 3; - return ((*null_bits & (1 << ( 7 - ( bit & 0x07 )))) != 0); -} - -// read in LOB field during buffered result creation -SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - zend_long mem_used TSRMLS_DC ); - -// dtor for each row in the cache -void cache_row_dtor(zval* data); - -// convert a number to a string using locales -// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of -// subclassing a new streambuf just to avoid the copy, it's easier to do the copy -template -SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - sqlsrv_error_auto_ptr& last_error ) -{ - // get to display size by removing the null terminator from buffer length - size_t display_size = ( buffer_length - sizeof( Char )) / sizeof( Char ); - - std::basic_ostringstream os; - // use the display size to determine the sql type. And if it is a double, set the precision accordingly - // the display sizes are set by the ODBC driver based on the precision of the sql type - // otherwise we can just use the default precision as long will not be truncated - size_t real_display_size = 14; - size_t float_display_size = 24; - size_t real_precision = 7; - size_t float_precision = 15; - // this is the case of sql type float(24) or real - if ( display_size == real_display_size ) { - os.precision( real_precision ); - } - // this is the case of sql type float(53) - else if ( display_size == float_display_size ) { - os.precision( float_precision ); - } - std::locale loc; - os.imbue( loc ); - std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf() ), os, ' ', *number_data ); - std::basic_string& str_num = os.str(); - - if( os.fail() ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "IMSSP", (SQLCHAR*) "Failed to convert number to string", -1 ); - return SQL_ERROR; - } - - if( str_num.size() * sizeof(Char) > (size_t) buffer_length ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); - return SQL_ERROR; - } - - *out_buffer_length = str_num.size() * sizeof( Char ); // str_num.size() already include the NULL terminator - memcpy_s( buffer, buffer_length, str_num.c_str(), *out_buffer_length ); - - return SQL_SUCCESS; -} - -template -SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) -{ - Number* number_data = reinterpret_cast( buffer ); - std::locale loc; // default locale should match system - std::basic_istringstream is; - is.str( string_data ); - is.imbue( loc ); - std::ios_base::iostate st = 0; - - std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream::_Iter( is.rdbuf( ) ), - std::basic_istream::_Iter(0), is, st, *number_data ); - - if( st & std::ios_base::failbit ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103 ); - return SQL_ERROR; - } - - *out_buffer_length = sizeof( Number ); - - return SQL_SUCCESS; -} - -// "closure" for the hash table destructor -struct row_dtor_closure { - - sqlsrv_buffered_result_set* results; - BYTE* row_data; - - row_dtor_closure( sqlsrv_buffered_result_set* st, BYTE* row ) : - results( st ), row_data( row ) - { - } -}; - -sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number ) -{ - SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ]; - SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLINTEGER native_code; - SQLSMALLINT wnative_message_len = 0; - - SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message, - SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len ); - if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - return NULL; - } - - // convert the error into the encoding of the context - SQLSRV_ENCODING enc = odbc->encoding(); - if( enc == SQLSRV_ENCODING_DEFAULT ) { - enc = odbc->conn->encoding(); - } - - // convert the error into the encoding of the context - sqlsrv_malloc_auto_ptr sql_state; - SQLLEN sql_state_len = 0; - if (!convert_string_from_utf16( enc, wsql_state, sizeof(wsql_state), (char**)&sql_state, sql_state_len )) { - return NULL; - } - - sqlsrv_malloc_auto_ptr native_message; - SQLLEN native_message_len = 0; - if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) { - return NULL; - } - - return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) sql_state, (SQLCHAR*) native_message, - native_code ); -} - -} // namespace - -// base class result set - -sqlsrv_result_set::sqlsrv_result_set( sqlsrv_stmt* stmt ) : - odbc( stmt ) -{ -} - - -// ODBC result set -// This object simply wraps ODBC function calls - -sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( sqlsrv_stmt* stmt ) : - sqlsrv_result_set( stmt ) -{ -} - -sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void ) -{ -} - -SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC ); -} - -SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC ); -} - -SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length, - out_buffer_length TSRMLS_CC ); -} - -sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( SQLSMALLINT record_number ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return odbc_get_diag_rec( odbc, record_number ); -} - -SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLRowCount( odbc TSRMLS_CC ); -} - - -// Buffered result set -// This class holds a result set in memory - -sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS_DC ) : - sqlsrv_result_set( stmt ), - cache(NULL), - col_count(0), - meta(NULL), - current(0), - last_field_index(-1), - read_so_far(0) -{ - // 10 is an arbitrary number for now for the initial size of the cache - ALLOC_HASHTABLE( cache ); - core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); - col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); - // there is no result set to buffer - if( col_count == 0 ) { - return; - } - - SQLULEN null_bytes = ( col_count / 8 ) + 1; // number of bits to reserve at the beginning of each row for NULL flags - meta = static_cast( sqlsrv_malloc( col_count * - sizeof( sqlsrv_buffered_result_set::meta_data ))); - - // set up the conversion matrix if this is the first time we're called - if( conv_matrix.size() == 0 ) { - - conv_matrix[ SQL_C_CHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::system_to_wide_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::string_to_double; - conv_matrix[ SQL_C_CHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::string_to_long; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::wide_to_system_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::wstring_to_double; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::wstring_to_long; - conv_matrix[ SQL_C_BINARY ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_BINARY ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::binary_to_system_string; - conv_matrix[ SQL_C_BINARY ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::binary_to_wide_string; - conv_matrix[ SQL_C_LONG ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::long_to_double; - conv_matrix[ SQL_C_LONG ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[ SQL_C_LONG ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[ SQL_C_LONG ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::long_to_system_string; - conv_matrix[ SQL_C_LONG ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::long_to_wide_string; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::double_to_system_string; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::double_to_long; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::double_to_wide_string; - } - - // get the meta data and calculate the size of a row buffer - SQLULEN offset = null_bytes; - for( SQLSMALLINT i = 0; i < col_count; ++i ) { - - core::SQLDescribeCol( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC ); - - offset = align_to<4>( offset ); - meta[i].offset = offset; - - switch( meta[i].type ) { - - // these types are the display size - case SQL_BIGINT: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, - reinterpret_cast( &meta[i].length ) TSRMLS_CC ); - meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space - offset += meta[i].length; - break; - - // these types are the column size - case SQL_BINARY: - case SQL_CHAR: - case SQL_SS_UDT: - case SQL_VARBINARY: - case SQL_VARCHAR: - // var* field types are length prefixed - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - offset += sizeof( void* ); - } - else { - meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space - offset += meta[i].length; - } - break; - - case SQL_WCHAR: - case SQL_WVARCHAR: - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - offset += sizeof( void* ); - } - else { - meta[i].length *= sizeof( WCHAR ); - meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space - offset += meta[i].length; - } - break; - - // these types are LOBs - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - meta[i].length = sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN; - offset += sizeof( void* ); - break; - - // these types are the ISO date size - case SQL_DATETIME: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_TYPE_TIMESTAMP: - core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, - reinterpret_cast( &meta[i].length ) TSRMLS_CC ); - meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space - offset += meta[i].length; - break; - - // these types are the native size - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - meta[i].length = sizeof( long ); - offset += meta[i].length; - break; - - case SQL_REAL: - case SQL_FLOAT: - meta[i].length = sizeof( double ); - offset += meta[i].length; - break; - - default: - SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); - break; - } - - switch( meta[i].type ) { - - case SQL_BIGINT: - case SQL_CHAR: - case SQL_DATETIME: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - case SQL_LONGVARCHAR: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_XML: - case SQL_TYPE_TIMESTAMP: - case SQL_VARCHAR: - meta[i].c_type = SQL_C_CHAR; - break; - - case SQL_SS_UDT: - case SQL_LONGVARBINARY: - case SQL_BINARY: - case SQL_VARBINARY: - meta[i].c_type = SQL_C_BINARY; - break; - - case SQL_WLONGVARCHAR: - case SQL_WCHAR: - case SQL_WVARCHAR: - meta[i].c_type = SQL_C_WCHAR; - break; - - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - meta[i].c_type = SQL_C_LONG; - break; - - case SQL_REAL: - case SQL_FLOAT: - meta[i].c_type = SQL_C_DOUBLE; - break; - - default: - SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); - break; - } - - } - - // read the data into the cache - // (offset from the above loop has the size of the row buffer necessary) - zend_long mem_used = 0; - unsigned long row_count = 0; - - while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { - - // allocate the row buffer - unsigned char* row = static_cast( sqlsrv_malloc( offset )); - memset( row, 0, offset ); - - // read the fields into the row buffer - for( SQLSMALLINT i = 0; i < col_count; ++i ) { - - SQLLEN out_buffer_temp = SQL_NULL_DATA; - SQLPOINTER buffer; - SQLLEN* out_buffer_length = &out_buffer_temp; - - switch( meta[i].c_type ) { - - case SQL_C_CHAR: - case SQL_C_WCHAR: - case SQL_C_BINARY: - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - out_buffer_length = &out_buffer_temp; - SQLPOINTER* lob_addr = reinterpret_cast( &row[ meta[i].offset ] ); - *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); - // a NULL pointer means NULL field - if( *lob_addr == NULL ) { - *out_buffer_length = SQL_NULL_DATA; - } - else { - *out_buffer_length = **reinterpret_cast( lob_addr ); - mem_used += *out_buffer_length; - } - } - else { - - mem_used += meta[i].length; - CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - - buffer = row + meta[i].offset + sizeof( SQLULEN ); - out_buffer_length = reinterpret_cast( row + meta[i].offset ); - core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); - } - break; - - case SQL_C_LONG: - case SQL_C_DOUBLE: - { - mem_used += meta[i].length; - CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - buffer = row + meta[i].offset; - out_buffer_length = &out_buffer_temp; - core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); - } - break; - - default: - SQLSRV_ASSERT( false, "Unknown C type" ); - break; - } - - if( *out_buffer_length == SQL_NULL_DATA ) { - unsigned char* null_bits = reinterpret_cast( row ); - set_bit( row, i ); - } - } - - SQLSRV_ASSERT( row_count < LONG_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); - - // add it to the cache - row_dtor_closure cl( this, row ); - sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); - } - -} - -sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void ) -{ - // free the rows - if( cache ) { - zend_hash_destroy( cache ); - FREE_HASHTABLE( cache ); - cache = NULL; - } - - // free the meta data - if( meta ) { - efree( meta ); - meta = NULL; - } -} - -SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) -{ - last_error = NULL; - last_field_index = -1; - read_so_far = 0; - - switch( orientation ) { - - case SQL_FETCH_NEXT: - offset = 1; - orientation = SQL_FETCH_RELATIVE; - break; - case SQL_FETCH_PRIOR: - offset = -1; - orientation = SQL_FETCH_RELATIVE; - break; - } - - switch( orientation ) { - - case SQL_FETCH_FIRST: - current = 1; - break; - case SQL_FETCH_LAST: - current = row_count( TSRMLS_C ); - break; - case SQL_FETCH_ABSOLUTE: - current = offset; - break; - case SQL_FETCH_RELATIVE: - current += offset; - break; - default: - SQLSRV_ASSERT( false, "Invalid fetch orientation. Should have been caught before here." ); - break; - } - - // check validity of current row - // the cursor can never get further away than just before the first row - if( current <= 0 && ( offset < 0 || orientation != SQL_FETCH_RELATIVE )) { - current = 0; - return SQL_NO_DATA; - } - - // the cursor can never get further away than just after the last row - if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) { - current = row_count( TSRMLS_C ) + 1; - return SQL_NO_DATA; - } - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) -{ - last_error = NULL; - field_index--; // convert from 1 based to 0 based - SQLSRV_ASSERT( field_index < column_count(), "Invalid field index requested" ); - - if( field_index != last_field_index ) { - last_field_index = field_index; - read_so_far = 0; - } - - unsigned char* row = get_row(); - - // if the field is null, then return SQL_NULL_DATA - if( get_bit( row, field_index )) { - *out_buffer_length = SQL_NULL_DATA; - return SQL_SUCCESS; - } - - // check to make sure the conversion type is valid - if( conv_matrix.find( meta[ field_index ].c_type ) == conv_matrix.end() || - conv_matrix.find( meta[ field_index ].c_type )->second.find( target_type ) == - conv_matrix.find( meta[ field_index ].c_type )->second.end() ) { - - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "07006", - (SQLCHAR*) "Restricted data type attribute violation", 0 ); - return SQL_ERROR; - } - - return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length, - out_buffer_length ); -} - -SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) -{ - SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); - SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE, - "Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); - SQLSRV_ASSERT( buffer_length >= SQL_SQLSTATE_BUFSIZE, - "Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field" ); - - if( last_error == NULL ) { - return SQL_NO_DATA; - } - - SQLSRV_ASSERT( last_error->sqlstate != NULL, - "Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" ); - - memcpy_s( diag_info_buffer, buffer_length, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE )); - - return SQL_SUCCESS; -} - -unsigned char* sqlsrv_buffered_result_set::get_row( void ) -{ - row_dtor_closure* cl_ptr; - cl_ptr = reinterpret_cast(zend_hash_index_find_ptr(cache, static_cast(current - 1))); - SQLSRV_ASSERT(cl_ptr != NULL, "Failed to find row %1!d! in the cache", current); - return cl_ptr->row_data; -} - -sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( SQLSMALLINT record_number ) -{ - // we only hold a single error if there is one, otherwise return the ODBC error(s) - if( last_error == NULL ) { - return odbc_get_diag_rec( odbc, record_number ); - } - if( record_number > 1 ) { - return NULL; - } - - return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code ); -} - -SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D ) -{ - last_error = NULL; - - return zend_hash_num_elements( cache ); -} - -// private functions -template -SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, _Out_ void* buffer, - SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - sqlsrv_error_auto_ptr& out_error ) -{ - // hex characters for the conversion loop below - static char hex_chars[] = "0123456789ABCDEF"; - - SQLSRV_ASSERT( out_error == NULL, "Pending error for sqlsrv_buffered_results_set::binary_to_string" ); - - SQLRETURN r = SQL_ERROR; - - // Set the amount of space necessary for null characters at the end of the data. - SQLSMALLINT extra = sizeof(Char); - - SQLSRV_ASSERT( ((buffer_length - extra) % (extra * 2)) == 0, "Must be multiple of 2 for binary to system string or " - "multiple of 4 for binary to wide string" ); - - // all fields will be treated as ODBC returns varchar(max) fields: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - // *2 is for each byte to hex conversion and * extra is for either system or wide string allocation - *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra; - - // copy as much as we can into the buffer - SQLLEN to_copy; - if( buffer_length < *out_buffer_length + extra ) { - to_copy = (buffer_length - extra); - out_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - r = SQL_SUCCESS; - to_copy = *out_buffer_length; - } - - // if there are bytes to copy as hex - if( to_copy > 0 ) { - // quick hex conversion routine - Char* h = reinterpret_cast( buffer ); - BYTE* b = reinterpret_cast( field_data ); - // to_copy contains the number of bytes to copy, so we divide the number in half (or quarter) - // to get the number of hex digits we can copy - SQLLEN to_copy_hex = to_copy / (2 * extra); - for( int i = 0; i < to_copy_hex; ++i ) { - *h = hex_chars[ (*b & 0xf0) >> 4 ]; - h++; - *h = hex_chars[ (*b++ & 0x0f) ]; - h++; - } - read_so_far += to_copy_hex; - *h = static_cast( 0 ); - } - else { - reinterpret_cast( buffer )[0] = '\0'; - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLCHAR* row = get_row(); - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); - } - - return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLCHAR* row = get_row(); - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); - } - - return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); -} - - -SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof(SQLLEN), "Buffer length must be able to find a long in " - "sqlsrv_buffered_result_set::double_to_long" ); - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - LONG* long_data = reinterpret_cast( buffer ); - - if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) { - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "22003", - (SQLCHAR*) "Numeric value out of range", 0 ); - return SQL_ERROR; - } - - if( *double_data != floor( *double_data )) { - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "01S07", - (SQLCHAR*) "Fractional truncation", 0 ); - return SQL_SUCCESS_WITH_INFO; - } - - *long_data = static_cast( *double_data ); - *out_buffer_length = sizeof( LONG ); - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_system_string" ); - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" ); - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof(double), "Buffer length must be able to find a long in sqlsrv_buffered_result_set::double_to_long" ); - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( buffer ); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - *double_data = static_cast( *long_data ); - *out_buffer_length = sizeof( double ); - - return SQL_SUCCESS; -} -SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to system string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_system_string" ); - - unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to wide string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" ); - - unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to double" ); - SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); - - unsigned char* row = get_row(); - char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" ); - SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); - - unsigned char* row = get_row(); - SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); - - unsigned char* row = get_row(); - char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); - - unsigned char* row = get_row(); - SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" ); - SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - SQLCHAR* field_data = NULL; - SQLULEN field_len = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; - } - else { - - field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; - } - - // all fields will be treated as ODBC returns varchar(max) fields: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR); - - // to_copy is the number of characters to copy, not including the null terminator - // supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair. - SQLLEN to_copy; - - if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) { - - to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR); // to_copy is the number of characters - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - - r = SQL_SUCCESS; - to_copy = field_len - read_so_far; - } - - if( to_copy > 0 ) { - - bool tried_again = false; - do { - if (to_copy > INT_MAX ) { - LOG(SEV_ERROR, "MultiByteToWideChar: Buffer length exceeded."); - throw core::CoreException(); - } - - int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast(to_copy), - static_cast(buffer), static_cast(to_copy)); - if( ch_space == 0 ) { - - switch( GetLastError() ) { - - case ERROR_NO_UNICODE_TRANSLATION: - // the theory here is the conversion failed because the end of the buffer we provided contained only - // half a character at the end - if( !tried_again ) { - to_copy--; - tried_again = true; - continue; - } - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); - break; - default: - SQLSRV_ASSERT( false, "Severe error translating Unicode" ); - break; - } - - return SQL_ERROR; - } - - ((WCHAR*)buffer)[ to_copy ] = L'\0'; - read_so_far += to_copy; - break; - - } while( true ); - } - else { - reinterpret_cast( buffer )[0] = L'\0'; - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::to_same_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - // Set the amount of space necessary for null characters at the end of the data. - SQLSMALLINT extra = 0; - - switch( meta[ field_index ].c_type ) { - case SQL_C_WCHAR: - extra = sizeof( SQLWCHAR ); - break; - case SQL_C_BINARY: - extra = 0; - break; - case SQL_C_CHAR: - extra = sizeof( SQLCHAR ); - break; - default: - SQLSRV_ASSERT( false, "Invalid type in get_string_data" ); - break; - } - - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); - } - - // all fields will be treated as ODBC returns varchar(max) fields: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - *out_buffer_length = *reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far; - - // copy as much as we can into the buffer - SQLLEN to_copy; - if( buffer_length < *out_buffer_length + extra ) { - to_copy = buffer_length - extra; - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - r = SQL_SUCCESS; - to_copy = *out_buffer_length; - } - - SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" ); - - if( to_copy > 0 ) { - memcpy_s( buffer, buffer_length, field_data + read_so_far, to_copy ); - read_so_far += to_copy; - } - if( extra ) { - OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); - memcpy_s( reinterpret_cast( buffer ) + to_copy, buffer_length, L"\0", extra ); - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - SQLCHAR* field_data = NULL; - SQLLEN field_len = NULL; - - // if this is the first time called for this field, just convert the entire string to system first then - // use that to read from instead of converting chunk by chunk. This is because it's impossible to know - // the total length of the string for output_buffer_length without doing the conversion and returning - // SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and - // to_same_string). - - if( read_so_far == 0 ) { - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; - } - else { - - field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; - } - - BOOL default_char_used = FALSE; - char default_char = '?'; - - // allocate enough to handle WC -> DBCS conversion if it happens - temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof(char), sizeof(char))); - temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), - (LPSTR) temp_string.get(), static_cast(field_len), &default_char, &default_char_used ); - - if( temp_length == 0 ) { - - switch( GetLastError() ) { - - case ERROR_NO_UNICODE_TRANSLATION: - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); - break; - default: - SQLSRV_ASSERT( false, "Severe error translating Unicode" ); - break; - } - - return SQL_ERROR; - } - } - - *out_buffer_length = (temp_length - read_so_far); - - SQLLEN to_copy = 0; - - if( (size_t) buffer_length < (temp_length - read_so_far + sizeof(char))) { - - to_copy = buffer_length - sizeof(char); - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - - to_copy = (temp_length - read_so_far); - r = SQL_SUCCESS; - } - - if( to_copy > 0 ) { - - memcpy_s( buffer, buffer_length, temp_string.get() + read_so_far, to_copy ); - } - SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); - OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); - ((SQLCHAR*) buffer)[ to_copy ] = '\0'; - read_so_far += to_copy; - - return r; -} - - -SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - return to_same_string( field_index, buffer, buffer_length, out_buffer_length ); -} - -SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invlid conversion to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this - - unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - memcpy_s( buffer, buffer_length, long_data, sizeof( LONG )); - *out_buffer_length = sizeof( LONG ); - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invlid conversion to double" ); - SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - memcpy_s( buffer, buffer_length, double_data, sizeof( double )); - *out_buffer_length = sizeof( double ); - - return SQL_SUCCESS; -} - -namespace { - -// called for each row in the cache when the cache is destroyed in the destructor -void cache_row_dtor( zval* data ) -{ - row_dtor_closure* cl = reinterpret_cast( Z_PTR_P( data ) ); - BYTE* row = cl->row_data; - // don't release this here, since this is called from the destructor of the result_set - sqlsrv_buffered_result_set* result_set = cl->results; - - for( SQLSMALLINT i = 0; i < result_set->column_count(); ++i ) { - - if( result_set->col_meta_data(i).length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - void* out_of_row_data = *reinterpret_cast( &row[ result_set->col_meta_data(i).offset ] ); - sqlsrv_free( out_of_row_data ); - } - } - - sqlsrv_free( row ); - sqlsrv_free( cl ); -} - -SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - zend_long mem_used TSRMLS_DC ) -{ - SQLSMALLINT extra = 0; - SQLULEN* output_buffer_len = NULL; - - // Set the amount of space necessary for null characters at the end of the data. - switch( meta.c_type ) { - case SQL_C_WCHAR: - extra = sizeof( SQLWCHAR ); - break; - case SQL_C_BINARY: - extra = 0; - break; - case SQL_C_CHAR: - extra = sizeof( SQLCHAR ); - break; - default: - SQLSRV_ASSERT( false, "Invalid type in read_lob_field" ); - break; - } - - SQLLEN already_read = 0; - SQLLEN to_read = INITIAL_FIELD_STRING_LEN; - sqlsrv_malloc_auto_ptr buffer; - buffer = static_cast( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN ))); - SQLRETURN r = SQL_SUCCESS; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLLEN last_field_len = 0; - bool full_length_returned = false; - - do { - - - output_buffer_len = reinterpret_cast( buffer.get() ); - r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ), - to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC ); - - // if the field is NULL, then return a NULL pointer - if( last_field_len == SQL_NULL_DATA ) { - return NULL; - } - - // if the last read was successful, we're done - if( r == SQL_SUCCESS ) { - // check to make sure we haven't overflown our memory limit - CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - break; - } - // else if it wasn't the truncated warning (01004) then we're done - else if( r == SQL_SUCCESS_WITH_INFO ) { - SQLSMALLINT len; - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - - if( !is_truncated_warning( state )) { - break; - } - } - - SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" ); - - // if the type of the field returns the total to be read, we use that and preallocate the buffer - if( last_field_len != SQL_NO_TOTAL ) { - - CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - - already_read += to_read - already_read; - to_read = last_field_len; - buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); - // record the size of the field since we have it available - *output_buffer_len = last_field_len; - full_length_returned = true; - } - // otherwise allocate another chunk of memory to read in - else { - already_read += to_read - already_read; - to_read *= 2; - CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); - } - - } while( true ); - - SQLSRV_ASSERT( output_buffer_len != NULL, "Output buffer not allocated properly" ); - - // most LOB field types return the total length in the last_field_len, but some field types such as XML - // only return the amount read on the last read - if( !full_length_returned ) { - *output_buffer_len = already_read + last_field_len; - } - - char* return_buffer = buffer; - buffer.transferred(); - return return_buffer; -} - -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_results.cpp +// +// Contents: Result sets +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +#include + +#include + +#ifndef _WIN32 +#include +#include +#endif // !_WIN32 + + +using namespace core; + +// conversion matrix +// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported +// this is initialized the first time the buffered result set is created. +sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix; + +namespace { + +// *** internal types *** + +#if defined(_MSC_VER) +#pragma warning(disable:4200) +#endif + +// *** internal constants *** + +const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field + +// *** internal functions *** + +// return an integral type rounded up to a certain number +template +T align_to( T number ) +{ + DEBUG_SQLSRV_ASSERT( (number + align) > number, "Number to align overflowed" ); + return ((number % align) == 0) ? number : (number + align - (number % align)); +} + +// return a pointer address aligned to a certain address boundary +template +T* align_to( T* ptr ) +{ + size_t p_value = (size_t) ptr; + return align_to( p_value ); +} + +// set the nth bit of the bitstream starting at ptr +void set_bit( void* ptr, unsigned int bit ) +{ + unsigned char* null_bits = reinterpret_cast( ptr ); + null_bits += bit >> 3; + *null_bits |= 1 << ( 7 - ( bit & 0x7 )); +} + +// retrieve the nth bit from the bitstream starting at ptr +bool get_bit( void* ptr, unsigned int bit ) +{ + unsigned char* null_bits = reinterpret_cast( ptr ); + null_bits += bit >> 3; + return ((*null_bits & (1 << ( 7 - ( bit & 0x07 )))) != 0); +} + +// read in LOB field during buffered result creation +SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, + zend_long mem_used TSRMLS_DC ); + +// dtor for each row in the cache +void cache_row_dtor(zval* data); + +size_t get_float_precision(SQLLEN buffer_length, size_t unitsize) +{ + SQLSRV_ASSERT(unitsize != 0, "Invalid unit size!"); + + // get to display size by removing the null terminator from buffer length + size_t display_size = (buffer_length - unitsize) / unitsize; + + // use the display size to determine the sql type. And if it is a double, set the precision accordingly + // the display sizes are set by the ODBC driver based on the precision of the sql type + // otherwise we can just use the default precision + size_t real_display_size = 14; + size_t float_display_size = 24; + size_t real_precision = 7; + size_t float_precision = 15; + + // For more information about display sizes for REAL vs FLOAT/DOUBLE: https://msdn.microsoft.com/en-us/library/ms713974(v=vs.85).aspx + // For more information about precision: https://msdn.microsoft.com/en-us/library/ms173773.aspx + + // this is the case of sql type float(24) or real + if ( display_size == real_display_size ) { + return real_precision; + } + // this is the case of sql type float(53) + else if ( display_size == float_display_size ) { + return float_precision; + } + + return 0; +} + +#ifndef _WIN32 +// copy the number into a char string using the num_put facet +template +SQLRETURN get_string_from_stream( Number number_data, std::basic_string &str_num, size_t precision, sqlsrv_error_auto_ptr& last_error) +{ + //std::locale loc( std::locale(""), new std::num_put ); // By default, SQL Server doesn't take user's locale into consideration + std::locale loc; + std::basic_ostringstream os; + + os.precision( precision ); + + os.imbue( loc ); + auto itert = std::ostreambuf_iterator( os.rdbuf() ); + std::use_facet< std::num_put>(loc).put( itert, os, ' ', number_data ); + str_num = os.str(); + + if ( os.fail()) { + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "IMSSP", ( SQLCHAR* ) "Failed to convert number to string", -1 ); + return SQL_ERROR; + } + + return SQL_SUCCESS; +} + +// copy the Char string into the output buffer - check first that it will fit +template +SQLRETURN copy_buffer( _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, std::basic_string &str, sqlsrv_error_auto_ptr& last_error ) +{ + *out_buffer_length = str.size() * sizeof( Char ); // NULL terminator is provided subsequently + + if ( *out_buffer_length > buffer_length ) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error(( SQLCHAR* ) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); + return SQL_ERROR; + } + + memcpy_s(buffer, *out_buffer_length, str.c_str(), *out_buffer_length); + + return SQL_SUCCESS; +} +#endif // !_WIN32 + +// convert a number to a string using locales +// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of +// subclassing a new streambuf just to avoid the copy, it's easier to do the copy +template +SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) +{ + size_t precision = 0; + +#ifdef _WIN32 + std::basic_ostringstream os; + precision = get_float_precision( buffer_length, sizeof( Char )); + os.precision( precision ); + std::locale loc; + os.imbue(loc); + std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf()), os, ' ', *number_data ); + std::basic_string& str_num = os.str(); + + if ( os.fail() ) { + last_error = new ( sqlsrv_malloc(sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "IMSSP", (SQLCHAR*) "Failed to convert number to string", -1 ); + return SQL_ERROR; + } + + if ( str_num.size() * sizeof( Char ) > ( size_t )buffer_length ) { + last_error = new ( sqlsrv_malloc(sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "HY090", ( SQLCHAR* ) "Buffer length too small to hold number as string", -1 ); + return SQL_ERROR; + } + + *out_buffer_length = str_num.size() * sizeof( Char ); // str_num.size() already include the NULL terminator + memcpy_s( buffer, buffer_length, str_num.c_str(), *out_buffer_length ); + + return SQL_SUCCESS; +#else + std::basic_string str_num; + SQLRETURN r; + + if ( std::is_integral::value ) + { + long num_data = *number_data; + r = get_string_from_stream( num_data, str_num, precision, last_error ); + } + else + { + precision = get_float_precision( buffer_length, sizeof( Char )); + r = get_string_from_stream( *number_data, str_num, precision, last_error ); + } + + if ( r == SQL_ERROR ) return SQL_ERROR; + + if ( std::is_same::value ) + { + std::basic_string str; + char *str_num_ptr = &str_num[0], *str_num_end = &str_num[0] + str_num.size(); + + for ( const auto &mb : str_num ) + { + char16_t ch16; + std::mbstate_t mbs = std::mbstate_t(); + + int len = mbrtoc16( &ch16, &mb, str_num_end - str_num_ptr, &mbs ); + if ( len > 0 || len == -3 ) + { + str.push_back( ch16 ); + if ( len > 0 ) + { + str_num_ptr += len; + } + } + } + + return copy_buffer( buffer, buffer_length, out_buffer_length, str, last_error ); + } + + return copy_buffer( buffer, buffer_length, out_buffer_length, str_num, last_error ); + +#endif // _WIN32 + +} + +template +SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) +{ + Number* number_data = reinterpret_cast( buffer ); +#ifdef _WIN32 + std::locale loc; // default locale should match system + std::basic_istringstream is; + is.str( string_data ); + is.imbue( loc ); + std::ios_base::iostate st = 0; + + std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream::_Iter( is.rdbuf()), std::basic_istream::_Iter( 0 ), is, st, *number_data ); + + if ( st & std::ios_base::failbit ) { + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "22003", ( SQLCHAR* ) "Numeric value out of range", 103 ); + return SQL_ERROR; + } + + *out_buffer_length = sizeof( Number ); +#else + std::string str; + if ( std::is_same::value ) + { + // convert to regular character string first + char c_str[3] = ""; + mbstate_t mbs; + + SQLLEN i = 0; + while ( string_data[i] ) + { + memset( &mbs, 0, sizeof( mbs )); //set shift state to the initial state + memset( c_str, 0, sizeof( c_str )); + int len = c16rtomb( c_str, string_data[i++], &mbs ); // treat string_data as a char16_t string + str.append(std::string( c_str, len )); + } + } + else + { + str.append( std::string(( char * )string_data )); + } + + std::istringstream is( str ); + std::locale loc; // default locale should match system + is.imbue( loc ); + + auto& facet = std::use_facet>( is.getloc()); + std::istreambuf_iterator beg( is ), end; + std::ios_base::iostate err = std::ios_base::goodbit; + + if ( std::is_integral::value ) + { + long number; + facet.get( beg, end, is, err, number ); + + *number_data = number; + } + else + { + double number; + facet.get( beg, end, is, err, number ); + + *number_data = number; + } + + *out_buffer_length = sizeof( Number ); + + if ( is.fail() ) + { + last_error = new ( sqlsrv_malloc(sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "22003", ( SQLCHAR* ) "Numeric value out of range", 103 ); + return SQL_ERROR; + } +#endif // _WIN32 + return SQL_SUCCESS; + +} + +// "closure" for the hash table destructor +struct row_dtor_closure { + + sqlsrv_buffered_result_set* results; + BYTE* row_data; + + row_dtor_closure( sqlsrv_buffered_result_set* st, BYTE* row ) : + results( st ), row_data( row ) + { + } +}; + +sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number ) +{ + SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ]; + SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; + SQLINTEGER native_code; + SQLSMALLINT wnative_message_len = 0; + + SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message, + SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len ); + if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { + return NULL; + } + + // convert the error into the encoding of the context + SQLSRV_ENCODING enc = odbc->encoding(); + if( enc == SQLSRV_ENCODING_DEFAULT ) { + enc = odbc->conn->encoding(); + } + + // convert the error into the encoding of the context + sqlsrv_malloc_auto_ptr sql_state; + SQLLEN sql_state_len = 0; + if ( !convert_string_from_utf16( enc, wsql_state, SQL_SQLSTATE_BUFSIZE, (char**)&sql_state, sql_state_len )) { + return NULL; + } + + sqlsrv_malloc_auto_ptr native_message; + SQLLEN native_message_len = 0; + if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) { + return NULL; + } + + return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) sql_state, (SQLCHAR*) native_message, + native_code ); +} + +} // namespace + +// base class result set + +sqlsrv_result_set::sqlsrv_result_set( sqlsrv_stmt* stmt ) : + odbc( stmt ) +{ +} + + +// ODBC result set +// This object simply wraps ODBC function calls + +sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( sqlsrv_stmt* stmt ) : + sqlsrv_result_set( stmt ) +{ +} + +sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void ) +{ +} + +SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC ); +} + +SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC ); +} + +SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length, + out_buffer_length TSRMLS_CC ); +} + +sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( SQLSMALLINT record_number ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return odbc_get_diag_rec( odbc, record_number ); +} + +SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D ) +{ + SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); + return core::SQLRowCount( odbc TSRMLS_CC ); +} + + +// Buffered result set +// This class holds a result set in memory + +sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS_DC ) : + sqlsrv_result_set( stmt ), + cache(NULL), + col_count(0), + meta(NULL), + current(0), + last_field_index(-1), + read_so_far(0), + temp_length(0) +{ + // 10 is an arbitrary number for now for the initial size of the cache + ALLOC_HASHTABLE( cache ); + core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); + col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); + // there is no result set to buffer + if( col_count == 0 ) { + return; + } + + SQLULEN null_bytes = ( col_count / 8 ) + 1; // number of bits to reserve at the beginning of each row for NULL flags + meta = static_cast( sqlsrv_malloc( col_count * + sizeof( sqlsrv_buffered_result_set::meta_data ))); + + // set up the conversion matrix if this is the first time we're called + if( conv_matrix.size() == 0 ) { + + conv_matrix[ SQL_C_CHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[ SQL_C_CHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::system_to_wide_string; + conv_matrix[ SQL_C_CHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; + conv_matrix[ SQL_C_CHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::string_to_double; + conv_matrix[ SQL_C_CHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::string_to_long; + conv_matrix[ SQL_C_WCHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[ SQL_C_WCHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; + conv_matrix[ SQL_C_WCHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::wide_to_system_string; + conv_matrix[ SQL_C_WCHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::wstring_to_double; + conv_matrix[ SQL_C_WCHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::wstring_to_long; + conv_matrix[ SQL_C_BINARY ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[ SQL_C_BINARY ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::binary_to_system_string; + conv_matrix[ SQL_C_BINARY ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::binary_to_wide_string; + conv_matrix[ SQL_C_LONG ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::long_to_double; + conv_matrix[ SQL_C_LONG ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::to_long; + conv_matrix[ SQL_C_LONG ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_long; + conv_matrix[ SQL_C_LONG ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::long_to_system_string; + conv_matrix[ SQL_C_LONG ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::long_to_wide_string; + conv_matrix[ SQL_C_DOUBLE ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::to_double; + conv_matrix[ SQL_C_DOUBLE ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_double; + conv_matrix[ SQL_C_DOUBLE ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::double_to_system_string; + conv_matrix[ SQL_C_DOUBLE ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::double_to_long; + conv_matrix[ SQL_C_DOUBLE ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::double_to_wide_string; + } + + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : + stmt->encoding()); + + // get the meta data and calculate the size of a row buffer + SQLULEN offset = null_bytes; + for( SQLSMALLINT i = 0; i < col_count; ++i ) { + + core::SQLDescribeColW( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC ); + + offset = align_to( offset ); + meta[i].offset = offset; + + switch( meta[i].type ) { + + // these types are the display size + case SQL_BIGINT: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + core::SQLColAttributeW( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, + reinterpret_cast( &meta[i].length ) TSRMLS_CC ); + meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space + offset += meta[i].length; + break; + case SQL_CHAR: + case SQL_VARCHAR: + if ( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + offset += sizeof( void* ); + } + else { + // If encoding is set to UTF-8, the following types are not necessarily column size. + // We need to call SQLGetData with c_type SQL_C_WCHAR and set the size accordingly. + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + meta[i].length *= sizeof( WCHAR ); + meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space + offset += meta[i].length; + } + else { + meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space + offset += meta[i].length; + } + } + break; + + // these types are the column size + case SQL_BINARY: + case SQL_SS_UDT: + case SQL_VARBINARY: + // var* field types are length prefixed + if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + offset += sizeof( void* ); + } + else { + meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space + offset += meta[i].length; + } + break; + + case SQL_WCHAR: + case SQL_WVARCHAR: + if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + offset += sizeof( void* ); + } + else { + meta[i].length *= sizeof( WCHAR ); + meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space + offset += meta[i].length; + } + break; + + // these types are LOBs + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + meta[i].length = sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN; + offset += sizeof( void* ); + break; + + // these types are the ISO date size + case SQL_DATETIME: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_TYPE_TIMESTAMP: + core::SQLColAttributeW( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, + reinterpret_cast( &meta[i].length ) TSRMLS_CC ); + meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space + offset += meta[i].length; + break; + + // these types are the native size + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + meta[i].length = sizeof( long ); + offset += meta[i].length; + break; + + case SQL_REAL: + case SQL_FLOAT: + meta[i].length = sizeof( double ); + offset += meta[i].length; + break; + + default: + SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); + break; + } + + switch( meta[i].type ) { + + case SQL_BIGINT: + case SQL_DATETIME: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_XML: + case SQL_TYPE_TIMESTAMP: + meta[i].c_type = SQL_C_CHAR; + break; + + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + // If encoding is set to UTF-8, the following types are not necessarily column size. + // We need to call SQLGetData with c_type SQL_C_WCHAR and set the size accordingly. + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + meta[i].c_type = SQL_C_WCHAR; + } + else { + meta[i].c_type = SQL_C_CHAR; + } + break; + + case SQL_SS_UDT: + case SQL_LONGVARBINARY: + case SQL_BINARY: + case SQL_VARBINARY: + meta[i].c_type = SQL_C_BINARY; + break; + + case SQL_WLONGVARCHAR: + case SQL_WCHAR: + case SQL_WVARCHAR: + meta[i].c_type = SQL_C_WCHAR; + break; + + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + meta[i].c_type = SQL_C_LONG; + break; + + case SQL_REAL: + case SQL_FLOAT: + meta[i].c_type = SQL_C_DOUBLE; + break; + + default: + SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); + break; + } + + } + + // read the data into the cache + // (offset from the above loop has the size of the row buffer necessary) + zend_long mem_used = 0; + size_t row_count = 0; + + while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { + + // allocate the row buffer + unsigned char* row = static_cast( sqlsrv_malloc( offset )); + memset( row, 0, offset ); + + // read the fields into the row buffer + for( SQLSMALLINT i = 0; i < col_count; ++i ) { + + SQLLEN out_buffer_temp = SQL_NULL_DATA; + SQLPOINTER buffer; + SQLLEN* out_buffer_length = &out_buffer_temp; + + switch( meta[i].c_type ) { + + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_BINARY: + if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + out_buffer_length = &out_buffer_temp; + SQLPOINTER* lob_addr = reinterpret_cast( &row[ meta[i].offset ] ); + *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); + // a NULL pointer means NULL field + if( *lob_addr == NULL ) { + *out_buffer_length = SQL_NULL_DATA; + } + else { + *out_buffer_length = **reinterpret_cast( lob_addr ); + mem_used += *out_buffer_length; + } + } + else { + + mem_used += meta[i].length; + CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + + buffer = row + meta[i].offset + sizeof( SQLULEN ); + out_buffer_length = reinterpret_cast( row + meta[i].offset ); + core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, + false TSRMLS_CC ); + } + break; + + case SQL_C_LONG: + case SQL_C_DOUBLE: + { + mem_used += meta[i].length; + CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + buffer = row + meta[i].offset; + out_buffer_length = &out_buffer_temp; + core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, + false TSRMLS_CC ); + } + break; + + default: + SQLSRV_ASSERT( false, "Unknown C type" ); + break; + } + + row_count++; + if( *out_buffer_length == SQL_NULL_DATA ) { + set_bit( row, i ); + } + } + + SQLSRV_ASSERT( row_count < INT_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); + + // add it to the cache + row_dtor_closure cl( this, row ); + sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); + } + +} + +sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void ) +{ + // free the rows + if( cache ) { + zend_hash_destroy( cache ); + FREE_HASHTABLE( cache ); + cache = NULL; + } + + // free the meta data + if( meta ) { + efree( meta ); + meta = NULL; + } +} + +SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) +{ + last_error = NULL; + last_field_index = -1; + read_so_far = 0; + + switch( orientation ) { + + case SQL_FETCH_NEXT: + offset = 1; + orientation = SQL_FETCH_RELATIVE; + break; + case SQL_FETCH_PRIOR: + offset = -1; + orientation = SQL_FETCH_RELATIVE; + break; + } + + switch( orientation ) { + + case SQL_FETCH_FIRST: + current = 1; + break; + case SQL_FETCH_LAST: + current = row_count( TSRMLS_C ); + break; + case SQL_FETCH_ABSOLUTE: + current = offset; + break; + case SQL_FETCH_RELATIVE: + current += offset; + break; + default: + SQLSRV_ASSERT( false, "Invalid fetch orientation. Should have been caught before here." ); + break; + } + + // check validity of current row + // the cursor can never get further away than just before the first row + if( current <= 0 && ( offset < 0 || orientation != SQL_FETCH_RELATIVE )) { + current = 0; + return SQL_NO_DATA; + } + + // the cursor can never get further away than just after the last row + if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) { + current = row_count( TSRMLS_C ) + 1; + return SQL_NO_DATA; + } + + return SQL_SUCCESS; +} + +SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ) +{ + last_error = NULL; + field_index--; // convert from 1 based to 0 based + SQLSRV_ASSERT( field_index < column_count(), "Invalid field index requested" ); + + if( field_index != last_field_index ) { + last_field_index = field_index; + read_so_far = 0; + } + + unsigned char* row = get_row(); + + // if the field is null, then return SQL_NULL_DATA + if( get_bit( row, field_index )) { + *out_buffer_length = SQL_NULL_DATA; + return SQL_SUCCESS; + } + + + // check to make sure the conversion type is valid + conv_matrix_t::const_iterator conv_iter = conv_matrix.find( meta[field_index].c_type ); + if( conv_iter == conv_matrix.end() || conv_iter->second.find( target_type ) == conv_iter->second.end() ) { + last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0 ); + return SQL_ERROR; + } + + return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length, + out_buffer_length ); +} + +SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) +{ + SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); + SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE, + "Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); + SQLSRV_ASSERT( buffer_length >= SQL_SQLSTATE_BUFSIZE, + "Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field" ); + + if( last_error == 0 ) { + return SQL_NO_DATA; + } + + SQLSRV_ASSERT( last_error->sqlstate != NULL, + "Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" ); + + SQLSMALLINT bufsize = ( buffer_length < SQL_SQLSTATE_BUFSIZE ) ? buffer_length : SQL_SQLSTATE_BUFSIZE; + + memcpy_s( diag_info_buffer, buffer_length, last_error->sqlstate, bufsize); + + return SQL_SUCCESS; +} + +unsigned char* sqlsrv_buffered_result_set::get_row( void ) +{ + row_dtor_closure* cl_ptr; + cl_ptr = reinterpret_cast(zend_hash_index_find_ptr(cache, static_cast(current - 1))); + SQLSRV_ASSERT(cl_ptr != NULL, "Failed to find row %1!d! in the cache", current); + return cl_ptr->row_data; +} + +sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( SQLSMALLINT record_number ) +{ + // we only hold a single error if there is one, otherwise return the ODBC error(s) + if( last_error == 0 ) { + return odbc_get_diag_rec( odbc, record_number ); + } + if( record_number > 1 ) { + return NULL; + } + + return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code ); +} + +SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D ) +{ + last_error = NULL; + + return zend_hash_num_elements( cache ); +} + +// private functions +template +SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, _Out_ void* buffer, + SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + sqlsrv_error_auto_ptr& out_error ) +{ + // hex characters for the conversion loop below + static char hex_chars[] = "0123456789ABCDEF"; + + SQLSRV_ASSERT( out_error == 0, "Pending error for sqlsrv_buffered_results_set::binary_to_string" ); + + SQLRETURN r = SQL_ERROR; + + // Set the amount of space necessary for null characters at the end of the data. + SQLSMALLINT extra = sizeof(Char); + + SQLSRV_ASSERT( ((buffer_length - extra) % (extra * 2)) == 0, "Must be multiple of 2 for binary to system string or " + "multiple of 4 for binary to wide string" ); + + // all fields will be treated as ODBC returns varchar(max) fields: + // the entire length of the string is returned the first + // call in out_buffer_len. Successive calls return how much is + // left minus how much has already been read by previous reads + // *2 is for each byte to hex conversion and * extra is for either system or wide string allocation + *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra; + + // copy as much as we can into the buffer + SQLLEN to_copy; + if( buffer_length < *out_buffer_length + extra ) { + to_copy = (buffer_length - extra); + out_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + r = SQL_SUCCESS; + to_copy = *out_buffer_length; + } + + // if there are bytes to copy as hex + if( to_copy > 0 ) { + // quick hex conversion routine + Char* h = reinterpret_cast( buffer ); + BYTE* b = reinterpret_cast( field_data ); + // to_copy contains the number of bytes to copy, so we divide the number in half (or quarter) + // to get the number of hex digits we can copy + SQLLEN to_copy_hex = to_copy / (2 * extra); + for( int i = 0; i < to_copy_hex; ++i ) { + *h = hex_chars[ (*b & 0xf0) >> 4 ]; + h++; + *h = hex_chars[ (*b++ & 0x0f) ]; + h++; + } + read_so_far += to_copy_hex; + *h = static_cast( 0 ); + } + else { + reinterpret_cast( buffer )[0] = '\0'; + } + + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLCHAR* row = get_row(); + SQLCHAR* field_data = NULL; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + } + else { + + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + } + + return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLCHAR* row = get_row(); + SQLCHAR* field_data = NULL; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + } + else { + + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + } + + return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); +} + + +SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof(SQLLEN), "Buffer length must be able to find a long in " + "sqlsrv_buffered_result_set::double_to_long" ); + + unsigned char* row = get_row(); + double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + LONG* long_data = reinterpret_cast( buffer ); + + if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) { + last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "22003", + (SQLCHAR*) "Numeric value out of range", 0 ); + return SQL_ERROR; + } + + if( *double_data != floor( *double_data )) { + last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "01S07", + (SQLCHAR*) "Fractional truncation", 0 ); + return SQL_SUCCESS_WITH_INFO; + } + + *long_data = static_cast( *double_data ); + *out_buffer_length = sizeof( LONG ); + + return SQL_SUCCESS; +} + +SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" ); + SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_system_string" ); + + unsigned char* row = get_row(); + double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + SQLRETURN r = SQL_SUCCESS; +#ifdef _WIN32 + r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); +#else + r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); +#endif // _WIN32 + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" ); + SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" ); + + unsigned char* row = get_row(); + double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + SQLRETURN r = SQL_SUCCESS; +#ifdef _WIN32 + r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); +#else + r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); +#endif // _WIN32 + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof(double), "Buffer length must be able to find a long in sqlsrv_buffered_result_set::double_to_long" ); + + unsigned char* row = get_row(); + double* double_data = reinterpret_cast( buffer ); + LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + + *double_data = static_cast( *long_data ); + *out_buffer_length = sizeof( double ); + + return SQL_SUCCESS; +} +SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to system string" ); + SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_system_string" ); + + unsigned char* row = get_row(); + LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + SQLRETURN r = SQL_SUCCESS; +#ifdef _WIN32 + r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); +#else + r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); +#endif // _WIN32 + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to wide string" ); + SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" ); + + unsigned char* row = get_row(); + LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + SQLRETURN r = SQL_SUCCESS; +#ifdef _WIN32 + r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); +#else + r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); +#endif // _WIN32 + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to double" ); + SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); + + unsigned char* row = get_row(); + char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + + return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" ); + SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); + + unsigned char* row = get_row(); + SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); + + return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); + + unsigned char* row = get_row(); + char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + + return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); + + unsigned char* row = get_row(); + SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); + + return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); +} + +SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( last_error == 0, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" ); + SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" ); + + SQLRETURN r = SQL_ERROR; + unsigned char* row = get_row(); + + SQLCHAR* field_data = NULL; + SQLULEN field_len = 0; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; + } + else { + + field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; + } + + // all fields will be treated as ODBC returns varchar(max) fields: + // the entire length of the string is returned the first + // call in out_buffer_len. Successive calls return how much is + // left minus how much has already been read by previous reads + *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR); + + // to_copy is the number of characters to copy, not including the null terminator + // supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair. + SQLLEN to_copy; + + if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) { + + to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR); // to_copy is the number of characters + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + + r = SQL_SUCCESS; + to_copy = field_len - read_so_far; + } + + if( to_copy > 0 ) { + + bool tried_again = false; + do { + if (to_copy > INT_MAX ) { + LOG(SEV_ERROR, "MultiByteToWideChar: Buffer length exceeded."); + throw core::CoreException(); + } + +#ifndef _WIN32 + int ch_space = SystemLocale::ToUtf16( CP_ACP, (LPCSTR) field_data, static_cast(to_copy), + static_cast(buffer), static_cast(to_copy)); + +#else + int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast(to_copy), + static_cast(buffer), static_cast(to_copy)); +#endif // !_WIN32 + + if( ch_space == 0 ) { + + switch( GetLastError() ) { + + case ERROR_NO_UNICODE_TRANSLATION: + // the theory here is the conversion failed because the end of the buffer we provided contained only + // half a character at the end + if( !tried_again ) { + to_copy--; + tried_again = true; + continue; + } + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); + break; + default: + SQLSRV_ASSERT( false, "Severe error translating Unicode" ); + break; + } + + return SQL_ERROR; + } + + ((WCHAR*)buffer)[ to_copy ] = L'\0'; + read_so_far += to_copy; + break; + + } while( true ); + } + else { + reinterpret_cast( buffer )[0] = L'\0'; + } + + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( last_error == 0, "Pending error for sqlsrv_buffered_results_set::to_same_string" ); + + SQLRETURN r = SQL_ERROR; + unsigned char* row = get_row(); + + // Set the amount of space necessary for null characters at the end of the data. + SQLSMALLINT extra = 0; + + switch( meta[ field_index ].c_type ) { + case SQL_C_WCHAR: + extra = sizeof( SQLWCHAR ); + break; + case SQL_C_BINARY: + extra = 0; + break; + case SQL_C_CHAR: + extra = sizeof( SQLCHAR ); + break; + default: + SQLSRV_ASSERT( false, "Invalid type in get_string_data" ); + break; + } + + SQLCHAR* field_data = NULL; + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + } + else { + + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + } + + // all fields will be treated as ODBC returns varchar(max) fields: + // the entire length of the string is returned the first + // call in out_buffer_len. Successive calls return how much is + // left minus how much has already been read by previous reads + *out_buffer_length = *reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far; + + // copy as much as we can into the buffer + SQLLEN to_copy; + if( buffer_length < *out_buffer_length + extra ) { + to_copy = buffer_length - extra; + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + r = SQL_SUCCESS; + to_copy = *out_buffer_length; + } + + SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" ); + + if( to_copy > 0 ) { + memcpy_s( buffer, buffer_length, field_data + read_so_far, to_copy ); + read_so_far += to_copy; + } + if( extra ) { + OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); + memcpy_s( reinterpret_cast( buffer ) + to_copy, buffer_length, L"\0", extra ); + } + + return r; +} + +SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( last_error == 0, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" ); + + SQLRETURN r = SQL_ERROR; + unsigned char* row = get_row(); + + SQLCHAR* field_data = NULL; + SQLLEN field_len = 0; + + // if this is the first time called for this field, just convert the entire string to system first then + // use that to read from instead of converting chunk by chunk. This is because it's impossible to know + // the total length of the string for output_buffer_length without doing the conversion and returning + // SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and + // to_same_string). + + if( read_so_far == 0 ) { + + if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); + field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; + } + else { + + field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); + field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; + } + + // allocate enough to handle WC -> DBCS conversion if it happens + temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof(char), sizeof(char))); + +#ifndef _WIN32 + temp_length = SystemLocale::FromUtf16( CP_ACP, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), + (LPSTR) temp_string.get(), static_cast(field_len) ); +#else + BOOL default_char_used = FALSE; + char default_char = '?'; + + temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), + (LPSTR) temp_string.get(), static_cast(field_len), &default_char, &default_char_used ); +#endif // !_WIN32 + if( temp_length == 0 ) { + + switch( GetLastError() ) { + + case ERROR_NO_UNICODE_TRANSLATION: + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); + break; + default: + SQLSRV_ASSERT( false, "Severe error translating Unicode" ); + break; + } + + return SQL_ERROR; + } + } + + *out_buffer_length = (temp_length - read_so_far); + + SQLLEN to_copy = 0; + + if( (size_t) buffer_length < (temp_length - read_so_far + sizeof(char))) { + + to_copy = buffer_length - sizeof(char); + last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) + sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); + r = SQL_SUCCESS_WITH_INFO; + } + else { + + to_copy = (temp_length - read_so_far); + r = SQL_SUCCESS; + } + + if( to_copy > 0 ) { + memcpy_s( buffer, buffer_length, temp_string.get() + read_so_far, to_copy ); + } + SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); + OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); + ((SQLCHAR*) buffer)[ to_copy ] = '\0'; + read_so_far += to_copy; + + return r; +} + + +SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + return to_same_string( field_index, buffer, buffer_length, out_buffer_length ); +} + +SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this + + unsigned char* row = get_row(); + LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + memcpy_s( buffer, buffer_length, long_data, sizeof( LONG )); + *out_buffer_length = sizeof( LONG ); + + return SQL_SUCCESS; +} + +SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to double" ); + SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this + + unsigned char* row = get_row(); + double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + memcpy_s( buffer, buffer_length, double_data, sizeof( double )); + *out_buffer_length = sizeof( double ); + + return SQL_SUCCESS; +} + +namespace { + +// called for each row in the cache when the cache is destroyed in the destructor +void cache_row_dtor( zval* data ) +{ + row_dtor_closure* cl = reinterpret_cast( Z_PTR_P( data ) ); + BYTE* row = cl->row_data; + // don't release this here, since this is called from the destructor of the result_set + sqlsrv_buffered_result_set* result_set = cl->results; + + for( SQLSMALLINT i = 0; i < result_set->column_count(); ++i ) { + + if( result_set->col_meta_data(i).length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + + void* out_of_row_data = *reinterpret_cast( &row[ result_set->col_meta_data(i).offset ] ); + sqlsrv_free( out_of_row_data ); + } + } + + sqlsrv_free( row ); + sqlsrv_free( cl ); +} + +SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, + zend_long mem_used TSRMLS_DC ) +{ + SQLSMALLINT extra = 0; + SQLULEN* output_buffer_len = NULL; + + // Set the amount of space necessary for null characters at the end of the data. + switch( meta.c_type ) { + case SQL_C_WCHAR: + extra = sizeof( SQLWCHAR ); + break; + case SQL_C_BINARY: + extra = 0; + break; + case SQL_C_CHAR: + extra = sizeof( SQLCHAR ); + break; + default: + SQLSRV_ASSERT( false, "Invalid type in read_lob_field" ); + break; + } + + SQLLEN already_read = 0; + SQLLEN to_read = INITIAL_FIELD_STRING_LEN; + sqlsrv_malloc_auto_ptr buffer; + buffer = static_cast( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN ))); + SQLRETURN r = SQL_SUCCESS; + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLLEN last_field_len = 0; + bool full_length_returned = false; + + do { + + + output_buffer_len = reinterpret_cast( buffer.get() ); + r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ), + to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC ); + + // if the field is NULL, then return a NULL pointer + if( last_field_len == SQL_NULL_DATA ) { + return NULL; + } + + // if the last read was successful, we're done + if( r == SQL_SUCCESS ) { + // check to make sure we haven't overflown our memory limit + CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + break; + } + // else if it wasn't the truncated warning (01004) then we're done + else if( r == SQL_SUCCESS_WITH_INFO ) { + SQLSMALLINT len; + core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len + TSRMLS_CC ); + + if( !is_truncated_warning( state )) { + break; + } + } + + SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" ); + + // if the type of the field returns the total to be read, we use that and preallocate the buffer + if( last_field_len != SQL_NO_TOTAL ) { + + CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + + already_read += to_read - already_read; + to_read = last_field_len; + buffer.resize( to_read + extra + sizeof( SQLULEN )); + output_buffer_len = reinterpret_cast( buffer.get() ); + // record the size of the field since we have it available + *output_buffer_len = last_field_len; + full_length_returned = true; + } + // otherwise allocate another chunk of memory to read in + else { + already_read += to_read - already_read; + to_read *= 2; + CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { + + throw core::CoreException(); + } + buffer.resize( to_read + extra + sizeof( SQLULEN )); + output_buffer_len = reinterpret_cast( buffer.get() ); + } + + } while( true ); + + SQLSRV_ASSERT( output_buffer_len != NULL, "Output buffer not allocated properly" ); + + // most LOB field types return the total length in the last_field_len, but some field types such as XML + // only return the amount read on the last read + if( !full_length_returned ) { + *output_buffer_len = already_read + last_field_len; + } + + char* return_buffer = buffer; + buffer.transferred(); + return return_buffer; +} + +} diff --git a/sqlsrv/core_sqlsrv.h b/source/shared/core_sqlsrv.h similarity index 94% rename from sqlsrv/core_sqlsrv.h rename to source/shared/core_sqlsrv.h index c511b057c..9b6c2d2de 100644 --- a/sqlsrv/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1,2263 +1,2341 @@ -#ifndef CORE_SQLSRV_H -#define CORE_SQLSRV_H - -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_sqlsrv.h -// -// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -//********************************************************************************************************************************* -// Includes -//********************************************************************************************************************************* - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef PHP_WIN32 -#define PHP_SQLSRV_API __declspec(dllexport) -#else -#define PHP_SQLSRV_API -#endif - -// OACR is an internal Microsoft static code analysis tool -#if defined(OACR) -#include -OACR_WARNING_PUSH -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) -OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." ) -#else -// define to eliminate static analysis hints in the code -#define OACR_WARNING_SUPPRESS( warning, msg ) -#endif - -extern "C" { - -#pragma warning(push) -#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) - -#ifdef ZTS -#include "TSRM.h" -#endif - -#if _MSC_VER >= 1400 -// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. -// php.h defines this constant as unsigned int which causes a compile error -// in ws2tcpip.h. Fortunately php.h allows an override by defining -// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define -// socklen_t here and override the php.h version. -typedef int socklen_t; -#define HAVE_SOCKLEN_T -#endif - -#include "php.h" -#include "php_globals.h" -#include "php_ini.h" -#include "ext/standard/php_standard.h" -#include "ext/standard/info.h" - -#pragma warning(pop) - -#if ZEND_DEBUG -// debug build causes warning C4505 to pop up from the Zend header files -#pragma warning( disable: 4505 ) -#endif - -} // extern "C" - -#if defined(OACR) -OACR_WARNING_POP -#endif - -#include -#include - -#if !defined(WC_ERR_INVALID_CHARS) -// imported from winnls.h as it isn't included by 5.3.0 -#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars -#endif - -// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when -// we use std::copy, which causes compilation to fail since we compile with warnings as errors. -#if defined(ZEND_DEBUG) && defined(inline) -#undef inline -#endif - -#include -#include -#include -#include -#include -#include -#include -// included for SQL Server specific constants -#include "msodbcsql.h" - -//********************************************************************************************************************************* -// Constants and Types -//********************************************************************************************************************************* - -// constants for maximums in SQL Server -const int SS_MAXCOLNAMELEN = 128; -const int SQL_SERVER_MAX_FIELD_SIZE = 8000; -const int SQL_SERVER_MAX_PRECISION = 38; -const int SQL_SERVER_MAX_TYPE_SIZE = 0; -const int SQL_SERVER_MAX_PARAMS = 2100; - -// max size of a date time string when converting from a DateTime object to a string -const int MAX_DATETIME_STRING_LEN = 256; - -// precision and scale for the date time types between servers -const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; -const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; -const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; -const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; - -// types for conversions on output parameters (though they can be used for input parameters, they are ignored) -enum SQLSRV_PHPTYPE { - MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type - SQLSRV_PHPTYPE_NULL = 1, - SQLSRV_PHPTYPE_INT, - SQLSRV_PHPTYPE_FLOAT, - SQLSRV_PHPTYPE_STRING, - SQLSRV_PHPTYPE_DATETIME, - SQLSRV_PHPTYPE_STREAM, - MAX_SQLSRV_PHPTYPE, // highest value for a php type - SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid -}; - -// encodings supported by this extension. These basically translate into the use of SQL_C_CHAR or SQL_C_BINARY when getting -// information as a string or a stream. -enum SQLSRV_ENCODING { - SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables. - SQLSRV_ENCODING_DEFAULT, // use what is the connection's default for a statement, use system if a connection - SQLSRV_ENCODING_BINARY, // use SQL_C_BINARY when using SQLGetData - SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData - SQLSRV_ENCODING_SYSTEM = SQLSRV_ENCODING_CHAR, - SQLSRV_ENCODING_UTF8 = CP_UTF8, -}; - -// the array keys used when returning a row via sqlsrv_fetch_array and sqlsrv_fetch_object. -enum SQLSRV_FETCH_TYPE { - MIN_SQLSRV_FETCH = 1, // lowest value for fetch type - SQLSRV_FETCH_NUMERIC = 1, // return an array with only numeric indices - SQLSRV_FETCH_ASSOC = 2, // return an array with keys made from the field names - SQLSRV_FETCH_BOTH = 3, // return an array indexed with both numbers and keys - MAX_SQLSRV_FETCH = 3, // highest value for fetch type -}; - -// buffer size of a sql state (including the null character) -const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1; - -// buffer size allocated to retrieve data from a PHP stream. This number -// was chosen since PHP doesn't return more than 8k at a time even if -// the amount requested was more. -const int PHP_STREAM_BUFFER_SIZE = 8192; - -// SQL types for parameters encoded in an integer. The type corresponds to the SQL type ODBC constants. -// The size is the column size or precision, and scale is the decimal digits for precise numeric types. - -union sqlsrv_sqltype { - struct typeinfo_t { - int type:9; - int size:14; - int scale:8; - } typeinfo; - - zend_long value; -}; - - -// SQLSRV PHP types (as opposed to the Zend PHP type constants). Contains the type (see SQLSRV_PHPTYPE) -// and the encoding for strings and streams (see SQLSRV_ENCODING) - -union sqlsrv_phptype { - - struct typeinfo_t { - unsigned type:8; - unsigned encoding:16; - } typeinfo; - - zend_long value; -}; - -// static assert for enforcing compile time conditions -template -struct sqlsrv_static_assert; - -template <> -struct sqlsrv_static_assert { static const int value = 1; }; - -#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() ) - - -//********************************************************************************************************************************* -// Logging -//********************************************************************************************************************************* -// log_callback -// a driver specific callback for logging messages -// severity - severity of the message: notice, warning, or error -// msg - the message to log in a FormatMessage style formatting -// print_args - args to the message -typedef void (*log_callback)( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); - -// each driver must register a log callback. This should be the first thing a driver does. -void core_sqlsrv_register_logger( log_callback ); - -// a simple wrapper around a PHP error logging function. -void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ... ); - -// a macro to make it convenient to use the function. -#define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, __VA_ARGS__ ) - -// mask for filtering which severities are written to the log -enum logging_severity { - SEV_ERROR = 0x01, - SEV_WARNING = 0x02, - SEV_NOTICE = 0x04, - SEV_ALL = -1, -}; - -// Kill the PHP process and log the message to PHP -void die( const char* msg, ... ); -#define DIE( msg, ... ) { die( msg, __VA_ARGS__ ); } - - -//********************************************************************************************************************************* -// Resource/Memory Management -//********************************************************************************************************************************* - -// the macro max is defined and overrides the call to max in the allocator class -#pragma push_macro( "max" ) -#undef max - -// new memory allocation/free debugging facilities to help us verify that all allocations are being -// released in a timely manner and not just at the end of the script. -// Zend has memory logging and checking, but it can generate a lot of noise for just one extension. -// It's meant for internal use but might be useful for people adding features to our extension. -// To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases -// must be done with sqlsrv_malloc and sqlsrv_free. -// #define SQLSRV_MEM_DEBUG 1 -#if defined( PHP_DEBUG ) && !defined( ZTS ) && defined( SQLSRV_MEM_DEBUG ) - -inline void* sqlsrv_malloc_trace( size_t size, const char* file, int line ) -{ - void* ptr = emalloc( size ); - LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); - return ptr; -} - -inline void* sqlsrv_malloc_trace( size_t element_count, size_t element_size, size_t extra, const char* file, int line ) -{ - OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); - - if(( element_count > 0 && element_size > 0 ) && - ( element_count > element_size * element_count || element_size > element_size * element_count )) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count > element_size * element_count + extra ) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count + extra == 0 ) { - DIE( "Allocation size must be more than 0" ); - } - - void* ptr = emalloc( element_size * element_count + extra ); - LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); - return ptr; -} - -inline void* sqlsrv_realloc_trace( void* buffer, size_t size, const char* file, int line ) -{ - void* ptr = erealloc( original, size ); - LOG( SEV_NOTICE, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original ); - return ptr; -} - -inline void sqlsrv_free_trace( void* ptr, const char* file, int line ) -{ - LOG( SEV_NOTICE, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line ); - efree( ptr ); -} - -#define sqlsrv_malloc( size ) sqlsrv_malloc_trace( size, __FILE__, __LINE__ ) -#define sqlsrv_malloc( count, size, extra ) sqlsrv_malloc_trace( count, size, extra, __FILE__, __LINE__ ) -#define sqlsrv_realloc( buffer, size ) sqlsrv_realloc_trace( buffer, size, __FILE__, __LINE__ ) -#define sqlsrv_free( ptr ) sqlsrv_free_trace( ptr, __FILE__, __LINE__ ) - -#else - -inline void* sqlsrv_malloc( size_t size ) -{ - return emalloc( size ); -} - -inline void* sqlsrv_malloc( size_t element_count, size_t element_size, size_t extra ) -{ - OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); - - if(( element_count > 0 && element_size > 0 ) && - ( element_count > element_size * element_count || element_size > element_size * element_count )) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count > element_size * element_count + extra ) { - DIE( "Integer overflow in sqlsrv_malloc" ); - } - - if( element_size * element_count + extra == 0 ) { - DIE( "Allocation size must be more than 0" ); - } - - return emalloc( element_size * element_count + extra ); -} - -inline void* sqlsrv_realloc( void* buffer, size_t size ) -{ - return erealloc( buffer, size ); -} - -inline void sqlsrv_free( void* ptr ) -{ - efree( ptr ); -} - -#endif - -// trait class that allows us to assign const types to an auto_ptr -template -struct remove_const { - typedef T type; -}; - -template -struct remove_const { - typedef T* type; -}; - -// allocator that uses the zend memory manager to manage memory -// this allows us to use STL classes that still work with Zend objects -template -struct sqlsrv_allocator { - - // typedefs used by the STL classes - typedef T value_type; - typedef value_type* pointer; - typedef const value_type* const_pointer; - typedef value_type& reference; - typedef const value_type& const_reference; - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; - - // conversion typedef (used by list and other STL classes) - template - struct rebind { - typedef sqlsrv_allocator other; - }; - - inline sqlsrv_allocator() {} - inline ~sqlsrv_allocator() {} - inline sqlsrv_allocator( sqlsrv_allocator const& ) {} - template - inline sqlsrv_allocator( sqlsrv_allocator const& ) {} - - // address (doesn't work if the class defines operator&) - inline pointer address( reference r ) - { - return &r; - } - - inline const_pointer address( const_reference r ) - { - return &r; - } - - // memory allocation/deallocation - inline pointer allocate( size_type cnt, - typename std::allocator::const_pointer = 0 ) - { - return reinterpret_cast( sqlsrv_malloc(cnt, sizeof (T), 0)); - } - - inline void deallocate( pointer p, size_type ) - { - sqlsrv_free(p); - } - - // size - inline size_type max_size( void ) const - { - return std::numeric_limits::max() / sizeof(T); - } - - // object construction/destruction - inline void construct( pointer p, const T& t ) - { - new(p) T(t); - } - - inline void destroy(pointer p) - { - p->~T(); - } - - // equality operators - inline bool operator==( sqlsrv_allocator const& ) - { - return true; - } - - inline bool operator!=( sqlsrv_allocator const& a ) - { - return !operator==(a); - } -}; - - -// base class for auto_ptrs that we define below. It provides common operators and functions -// used by all the classes. -template -class sqlsrv_auto_ptr { - -public: - - sqlsrv_auto_ptr( void ) : _ptr( NULL ) - { - } - - ~sqlsrv_auto_ptr( void ) - { - static_cast(this)->reset( NULL ); - } - - // call when ownership is transferred - void transferred( void ) - { - _ptr = NULL; - } - - // explicit function to get the pointer. - T* get( void ) const - { - return _ptr; - } - - // cast operator to allow auto_ptr to be used where a normal const * can be. - operator const T* () const - { - return _ptr; - } - - // cast operator to allow auto_ptr to be used where a normal pointer can be. - operator typename remove_const::type () const - { - return _ptr; - } - - operator bool() const - { - return _ptr != NULL; - } - - // there are a number of places where we allocate a block intended to be accessed as - // an array of elements, so this operator allows us to treat the memory as such. - T& operator[]( int index ) const - { - return _ptr[ index ]; - } - - // there are a number of places where we allocate a block intended to be accessed as - // an array of elements, so this operator allows us to treat the memory as such. - T& operator[]( unsigned int index ) const - { - return _ptr[ index ]; - } - - // there are a number of places where we allocate a block intended to be accessed as - // an array of elements, so this operator allows us to treat the memory as such. - T& operator[]( long index ) const - { - return _ptr[ index ]; - } - - - #ifdef __WIN64 - // there are a number of places where we allocate a block intended to be accessed as - // an array of elements, so this operator allows us to treat the memory as such. - T& operator[](std::size_t index) const - { - return _ptr[index]; - } - #endif - - // there are a number of places where we allocate a block intended to be accessed as - // an array of elements, so this operator allows us to treat the memory as such. - T& operator[]( unsigned short index ) const - { - return _ptr[ index ]; - } - - // access elements of a structure through the auto ptr - T* const operator->( void ) const - { - return _ptr; - } - - // value from reference operator (i.e., i = *(&i); or *i = blah;) - T& operator*() const - { - return *_ptr; - } - - // allow the use of the address-of operator to simulate a **. - // Note: this operator conflicts with storing these within an STL container. If you need - // to do that, then redefine this as getpp and change instances of &auto_ptr to auto_ptr.getpp() - T** operator&( void ) - { - return &_ptr; - } - -protected: - - sqlsrv_auto_ptr( T* ptr ) : - _ptr( ptr ) - { - } - - sqlsrv_auto_ptr( sqlsrv_auto_ptr& src ) - { - if( _ptr ) { - static_cast(this)->reset( src._ptr ); - } - src.transferred(); - } - - // assign a new pointer to the auto_ptr. It will free the previous memory block - // because ownership is deemed finished. - T* operator=( T* ptr ) - { - static_cast( this )->reset( ptr ); - - return ptr; - } - - T* _ptr; -}; - -// an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer -// in a variable of sqlsrv_malloc_auto_ptr. sqlsrv_malloc_auto_ptr will "own" that block and assure that it is -// freed until the variable is destroyed (out of scope) or ownership is transferred using the function -// "transferred". -// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. - -template -class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr > { - -public: - - sqlsrv_malloc_auto_ptr( void ) : - sqlsrv_auto_ptr >( NULL ) - { - } - - sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) : - sqlsrv_auto_ptr >( src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( T* ptr = NULL ) - { - if( _ptr ) - sqlsrv_free( (void*) _ptr ); - _ptr = ptr; - } - - T* operator=( T* ptr ) - { - return sqlsrv_auto_ptr >::operator=( ptr ); - } - - void operator=( sqlsrv_malloc_auto_ptr& src ) - { - T* p = src.get(); - src.transferred(); - this->_ptr = p; - } - - // DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. - // has the same parameter list as sqlsrv_realloc: new_size is the size in bytes of the newly allocated buffer - void resize( size_t new_size ) - { - _ptr = reinterpret_cast( sqlsrv_realloc( _ptr, new_size )); - } -}; - - -// auto ptr for Zend hash tables. Used to clean up a hash table allocated when -// something caused an early exit from the function. This is used when the hash_table is -// allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr. - -class hash_auto_ptr : public sqlsrv_auto_ptr { - -public: - - hash_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( HashTable* ptr = NULL ) - { - if( _ptr ) { - zend_hash_destroy( _ptr ); - FREE_HASHTABLE( _ptr ); - } - _ptr = ptr; - } - - HashTable* operator=( HashTable* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - -private: - - hash_auto_ptr( HashTable const& hash ); - - hash_auto_ptr( hash_auto_ptr const& hash ); -}; - - -// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr. -// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed -// (out of scope) or ownership is transferred using the function "transferred". - -class zval_auto_ptr : public sqlsrv_auto_ptr { - -public: - - zval_auto_ptr( void ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( zval* ptr = NULL ) - { - if( _ptr ) - zval_ptr_dtor(_ptr ); - _ptr = ptr; - } - - zval* operator=( zval* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - - -private: - - zval_auto_ptr( const zval_auto_ptr& src ); -}; - -#pragma pop_macro( "max" ) - - -//********************************************************************************************************************************* -// sqlsrv_error -//********************************************************************************************************************************* - -// *** PHP specific errors *** -// sqlsrv errors are held in a structure of this type used by the driver handle_error functions -// format is a flag that tells the driver error handler functions if there are parameters to use with FormatMessage -// into the error message before returning it. - -// base class which can be instatiated with aggregates (see error constants) -struct sqlsrv_error_const { - - SQLCHAR* sqlstate; - SQLCHAR* native_message; - SQLINTEGER native_code; - bool format; -}; - -// subclass which is used by the core layer to instantiate ODBC errors -struct sqlsrv_error : public sqlsrv_error_const { - - sqlsrv_error( void ) - { - sqlstate = NULL; - native_message = NULL; - native_code = -1; - format = false; - } - - sqlsrv_error( SQLCHAR* sql_state, SQLCHAR* message, SQLINTEGER code, bool printf_format = false ) - { - sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); - strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); - strcpy_s( reinterpret_cast( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast( message )); - native_code = code; - format = printf_format; - } - - sqlsrv_error( sqlsrv_error_const const& prototype ) - { - sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format ); - } - - ~sqlsrv_error( void ) - { - if( sqlstate != NULL ) { - sqlsrv_free( sqlstate ); - } - if( native_message != NULL ) { - sqlsrv_free( native_message ); - } - } -}; - - -// an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete -class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr { - -public: - - sqlsrv_error_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - sqlsrv_error_auto_ptr( sqlsrv_error_auto_ptr const& src ) : - sqlsrv_auto_ptr( (sqlsrv_error_auto_ptr&) src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( sqlsrv_error* ptr = NULL ) - { - if( _ptr ) { - _ptr->~sqlsrv_error(); - sqlsrv_free( (void*) _ptr ); - } - _ptr = ptr; - } - - sqlsrv_error* operator=( sqlsrv_error* ptr ) - { - return sqlsrv_auto_ptr::operator=( ptr ); - } - - // unlike traditional assignment operators, the chained assignment of an auto_ptr doesn't make much - // sense. Only the last one would have anything in it. - void operator=( sqlsrv_error_auto_ptr& src ) - { - sqlsrv_error* p = src.get(); - src.transferred(); - this->_ptr = p; - } -}; - - -//********************************************************************************************************************************* -// Context -//********************************************************************************************************************************* - -class sqlsrv_context; -struct sqlsrv_conn; - -// error_callback -// a driver specific callback for processing errors. -// ctx - the context holding the handles -// sqlsrv_error_code - specific error code to return. -typedef bool (*error_callback)( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool error TSRMLS_DC, va_list* print_args ); - -// sqlsrv_context -// a context holds relevant information to be passed with a connection and statement objects. - -class sqlsrv_context { - - public: - - sqlsrv_context( SQLSMALLINT type, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : - handle_( SQL_NULL_HANDLE ), - handle_type_( type ), - err_( e ), - name_( NULL ), - driver_( drv ), - last_error_(), - encoding_( encoding ) - { - } - - sqlsrv_context( SQLHANDLE h, SQLSMALLINT t, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : - handle_( h ), - handle_type_( t ), - err_( e ), - name_( NULL ), - driver_( drv ), - last_error_(), - encoding_( encoding ) - { - } - - sqlsrv_context( sqlsrv_context const& ctx ) : - handle_( ctx.handle_ ), - handle_type_( ctx.handle_type_ ), - err_( ctx.err_ ), - name_( ctx.name_ ), - driver_( ctx.driver_ ), - last_error_( ctx.last_error_ ) - { - } - - void set_func( const char* f ) - { - name_ = f; - } - - void set_last_error( sqlsrv_error_auto_ptr& last_error ) - { - last_error_ = last_error; - } - - sqlsrv_error_auto_ptr& last_error( void ) - { - return last_error_; - } - - // since the primary responsibility of a context is to hold an ODBC handle, we - // provide these convenience operators for using them interchangeably - operator SQLHANDLE ( void ) const - { - return handle_; - } - - error_callback error_handler( void ) const - { - return err_; - } - - SQLHANDLE handle( void ) const - { - return handle_; - } - - SQLSMALLINT handle_type( void ) const - { - return handle_type_; - } - - const char* func( void ) const - { - return name_; - } - - void* driver( void ) const - { - return driver_; - } - - void set_driver( void* driver ) - { - this->driver_ = driver; - } - - void invalidate( void ) - { - if( handle_ != SQL_NULL_HANDLE ) { - ::SQLFreeHandle( handle_type_, handle_ ); - - last_error_.reset(); - } - handle_ = SQL_NULL_HANDLE; - } - - bool valid( void ) - { - return handle_ != SQL_NULL_HANDLE; - } - - SQLSRV_ENCODING encoding( void ) const - { - return encoding_; - } - - void set_encoding( SQLSRV_ENCODING e ) - { - encoding_ = e; - } - - private: - SQLHANDLE handle_; // ODBC handle for this context - SQLSMALLINT handle_type_; // type of the ODBC handle - const char* name_; // function name currently executing this context - error_callback err_; // driver error callback if error occurs in core layer - void* driver_; // points back to the driver for PDO - sqlsrv_error_auto_ptr last_error_; // last error that happened on this object - SQLSRV_ENCODING encoding_; // encoding of the context -}; - -const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista - -// maps an IANA encoding to a code page -struct sqlsrv_encoding { - - const char* iana; - size_t iana_len; - unsigned int code_page; - bool not_for_connection; - - sqlsrv_encoding( const char* iana, unsigned int code_page, bool not_for_conn = false ): - iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn ) - { - } -}; - - -//********************************************************************************************************************************* -// Initialization -//********************************************************************************************************************************* - -// variables set during initialization -extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in -extern HashTable* g_encodings; // encodings supported by this driver - -void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ); -void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ); - -// environment context used by sqlsrv_connect for when a connection error occurs. -struct sqlsrv_henv { - - sqlsrv_context ctx; - - sqlsrv_henv( SQLHANDLE handle, error_callback e, void* drv ) : - ctx( handle, SQL_HANDLE_ENV, e, drv ) - { - } -}; - - -//********************************************************************************************************************************* -// Connection -//********************************************************************************************************************************* - -// supported server versions (determined at connection time) -enum SERVER_VERSION { - SERVER_VERSION_UNKNOWN = -1, - SERVER_VERSION_2000 = 8, - SERVER_VERSION_2005, - SERVER_VERSION_2008, // use this for anything 2008 or later -}; - -// supported driver versions. -enum DRIVER_VERSION : size_t { - MIN = 0, - ODBC_DRIVER_13 = MIN, - ODBC_DRIVER_11 = 1, - MAX = ODBC_DRIVER_11, -}; - -// forward decl -struct sqlsrv_stmt; -struct stmt_option; - -// *** connection resource structure *** -// this is the resource structure returned when a connection is made. -struct sqlsrv_conn : public sqlsrv_context { - - // instance variables - SERVER_VERSION server_version; // version of the server that we're connected to - - DRIVER_VERSION driver_version; - - // initialize with default values - sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) : - sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) - { - } - - // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be - // called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating - // the sqlsrv_conn with a connection. -}; - -enum SQLSRV_STMT_OPTIONS { - - SQLSRV_STMT_OPTION_INVALID, - SQLSRV_STMT_OPTION_QUERY_TIMEOUT, - SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, - SQLSRV_STMT_OPTION_SCROLLABLE, - SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, - - // Driver specific connection options - SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, - -}; - -namespace ODBCConnOptions { - -const char APP[] = "APP"; -const char ApplicationIntent[] = "ApplicationIntent"; -const char AttachDBFileName[] = "AttachDbFileName"; -const char CharacterSet[] = "CharacterSet"; -const char ConnectionPooling[] = "ConnectionPooling"; -const char Database[] = "Database"; -const char Encrypt[] = "Encrypt"; -const char Failover_Partner[] = "Failover_Partner"; -const char LoginTimeout[] = "LoginTimeout"; -const char MARS_ODBC[] = "MARS_Connection"; -const char MultiSubnetFailover[] = "MultiSubnetFailover"; -const char QuotedId[] = "QuotedId"; -const char TraceFile[] = "TraceFile"; -const char TraceOn[] = "TraceOn"; -const char TrustServerCertificate[] = "TrustServerCertificate"; -const char TransactionIsolation[] = "TransactionIsolation"; -const char WSID[] = "WSID"; -const char UID[] = "UID"; -const char PWD[] = "PWD"; -const char SERVER[] = "Server"; - -} - -enum SQLSRV_CONN_OPTIONS { - - SQLSRV_CONN_OPTION_INVALID, - SQLSRV_CONN_OPTION_APP, - SQLSRV_CONN_OPTION_CHARACTERSET, - SQLSRV_CONN_OPTION_CONN_POOLING, - SQLSRV_CONN_OPTION_DATABASE, - SQLSRV_CONN_OPTION_ENCRYPT, - SQLSRV_CONN_OPTION_FAILOVER_PARTNER, - SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, - SQLSRV_CONN_OPTION_MARS, - SQLSRV_CONN_OPTION_QUOTED_ID, - SQLSRV_CONN_OPTION_TRACE_FILE, - SQLSRV_CONN_OPTION_TRACE_ON, - SQLSRV_CONN_OPTION_TRANS_ISOLATION, - SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, - SQLSRV_CONN_OPTION_WSID, - SQLSRV_CONN_OPTION_ATTACHDBFILENAME, - SQLSRV_CONN_OPTION_APPLICATION_INTENT, - SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, - - // Driver specific connection options - SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, - -}; - - -#define NO_ATTRIBUTE -1 - -// type of connection attributes -enum CONN_ATTR_TYPE { - CONN_ATTR_INT, - CONN_ATTR_BOOL, - CONN_ATTR_STRING, - CONN_ATTR_INVALID, -}; - -// a connection option that includes the callback function that handles that option (e.g., adds it to the connection string or -// sets an attribute) -struct connection_option { - // the name of the option as passed in by the user - const char * sqlsrv_name; - unsigned int sqlsrv_len; - - unsigned int conn_option_key; - // the name of the option in the ODBC connection string - const char * odbc_name; - unsigned int odbc_len; - enum CONN_ATTR_TYPE value_type; - - // process the connection type - // return whether or not the function was successful in processing the connection option - void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC ); -}; - -// connection attribute functions -template -struct str_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), - static_cast(Z_STRLEN_P( value )) TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } - } -}; - -// simply add the parsed value to the connection string -struct conn_str_append_func { - - static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); -}; - -struct conn_null_func { - - static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ - TSRMLS_DC ); -}; - -// factory to create a connection (since they are subclassed to instantiate statements) -typedef sqlsrv_conn* (*driver_conn_factory)( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); - -// *** connection functions *** -sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, - const char* server, const char* uid, const char* pwd, - HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[], - void* driver, const char* driver_func TSRMLS_DC ); -void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ); -void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC ); -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ); -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); -bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); -size_t core_str_zval_is_true( zval* str_zval ); - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -struct stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ); -}; - -struct stmt_option_query_timeout : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_send_at_exec : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_buffered_query_limit : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); -}; - -// used to hold the table for statment options -struct stmt_option { - - const char * name; // name of the statement option - unsigned int name_len; // name length - unsigned int key; - std::unique_ptr func; // callback that actually handles the work of the option - -}; - -// holds the stream param and the encoding that it was assigned -struct sqlsrv_stream { - - zval* stream_z; - SQLSRV_ENCODING encoding; - SQLUSMALLINT field_index; - SQLSMALLINT sql_type; - sqlsrv_stmt* stmt; - std::size_t stmt_index; - - sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) : - stream_z( str_z ), encoding( enc ) - { - } - - sqlsrv_stream() : stream_z( NULL ), encoding( SQLSRV_ENCODING_INVALID ), stmt( NULL ) - { - } -}; - -// close any active stream -void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); - -extern php_stream_wrapper g_sqlsrv_stream_wrapper; - -// resource constants used when registering the stream type with PHP -#define SQLSRV_STREAM_WRAPPER "sqlsrv" -#define SQLSRV_STREAM "sqlsrv_stream" - -// holds the output parameter information. Strings also need the encoding and other information for -// after processing. Only integer, float, and strings are allowable output parameters. -struct sqlsrv_output_param { - - zval* param_z; - SQLSRV_ENCODING encoding; - SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement - SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer - bool is_bool; - - // string output param constructor - sqlsrv_output_param( zval* p_z, SQLSRV_ENCODING enc, int num, SQLUINTEGER buffer_len ) : - param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false ) - { - } - - // every other type output parameter constructor - sqlsrv_output_param( zval* p_z, int num, bool is_bool ) : - param_z( p_z ), - param_num( num ), - encoding( SQLSRV_ENCODING_INVALID ), - original_buffer_len( -1 ), - is_bool( is_bool ) - { - } -}; - -// forward decls -struct sqlsrv_result_set; - -// *** Statement resource structure *** -struct sqlsrv_stmt : public sqlsrv_context { - - void free_param_data( TSRMLS_D ); - virtual void new_result_set( TSRMLS_D ); - - sqlsrv_conn* conn; // Connection that created this statement - - bool executed; // Whether the statement has been executed yet (used for error messages) - bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row - sqlsrv_result_set* current_results; // Current result set - SQLULEN cursor_type; // Type of cursor for the current result set - bool has_rows; // Has_rows is set if there are actual rows in the row set - bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called - int last_field_index; // last field retrieved by core_sqlsrv_get_field - bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the - // last results - unsigned long query_timeout; // maximum allowed statement execution time - zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) - - // holds output pointers for SQLBindParameter - // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving - // memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold - std::deque param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter - zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP - zval output_params; // hold all the output parameters - zval param_streams; // track which streams to send data to the server - zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects - bool send_streams_at_exec; // send all stream data right after execution before returning - sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter - unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string - // to the server) - zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals - zval active_stream; // the currently active stream reading data from the database - - sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); - virtual ~sqlsrv_stmt( void ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string = false ) = 0; - -}; - -// *** field metadata struct *** -struct field_meta_data { - - sqlsrv_malloc_auto_ptr field_name; - SQLSMALLINT field_name_len; - SQLSMALLINT field_type; - SQLULEN field_size; - SQLULEN field_precision; - SQLSMALLINT field_scale; - SQLSMALLINT field_is_nullable; - - field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0), - field_scale (0), field_is_nullable(0) - { - } - - ~field_meta_data() - { - } -}; - -// *** statement constants *** -// unknown column size used by core_sqlsrv_bind_param when the user doesn't supply a value -const SQLULEN SQLSRV_UNKNOWN_SIZE = 0xffffffff; -const int SQLSRV_DEFAULT_SIZE = -1; // size given for an output parameter that doesn't really need one (e.g., int) - -// uninitialized query timeout value -const unsigned int QUERY_TIMEOUT_INVALID = 0xffffffff; - -// special buffered query constant -const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant - -// factory to create a statement -typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); - -// *** statement functions *** -sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, - const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ); -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, - SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, - SQLSMALLINT decimal_digits TSRMLS_DC ); -void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 ); -field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ); -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ); -void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, - _Out_ void*& field_value, _Out_ SQLLEN* field_length, bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC); -bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ); -void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true ); -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC ); -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ); -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ); -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); - - -//********************************************************************************************************************************* -// Result Set -//********************************************************************************************************************************* - -// Abstract the result set so that a result set can either be used as is from ODBC or buffered. -// This is not a complete abstraction of a result set. Only enough is abstracted to allow for -// information and capabilities normally not available when a result set is not buffered -// (e.g., forward only vs buffered means row count is available and cursor movement is possible). -// Otherwise, normal ODBC calls are still valid and should be used to get information about the -// result set (e.g., SQLNumResultCols). - -struct sqlsrv_result_set { - - sqlsrv_stmt* odbc; - - explicit sqlsrv_result_set( sqlsrv_stmt* ); - virtual ~sqlsrv_result_set( void ) { } - - virtual bool cached( int field_index ) = 0; - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0; - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC )= 0; - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0; - virtual SQLLEN row_count( TSRMLS_D ) = 0; -}; - -struct sqlsrv_odbc_result_set : public sqlsrv_result_set { - - explicit sqlsrv_odbc_result_set( sqlsrv_stmt* ); - virtual ~sqlsrv_odbc_result_set( void ); - - virtual bool cached( int field_index ) { return false; } - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ); - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); - virtual SQLLEN row_count( TSRMLS_D ); - - private: - // prevent invalid instantiations and assignments - sqlsrv_odbc_result_set( void ); - sqlsrv_odbc_result_set( sqlsrv_odbc_result_set& ); - sqlsrv_odbc_result_set& operator=( sqlsrv_odbc_result_set& ); -}; - -struct sqlsrv_buffered_result_set : public sqlsrv_result_set { - - struct meta_data { - SQLSMALLINT type; - SQLSMALLINT c_type; // convenience - SQLULEN offset; // in bytes - SQLULEN length; // in bytes - SQLSMALLINT scale; - - static const SQLULEN SIZE_UNKNOWN = 0; - }; - - // default maximum amount of memory that a buffered query can consume - #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings - static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB - static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; - - explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC ); - virtual ~sqlsrv_buffered_result_set( void ); - - virtual bool cached( int field_index ) { return true; } - virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); - virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ); - virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); - virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); - virtual SQLLEN row_count( TSRMLS_D ); - - // buffered result set specific - SQLSMALLINT column_count( void ) - { - return col_count; - } - - struct meta_data& col_meta_data( SQLSMALLINT i ) - { - return meta[i]; - } - - private: - // prevent invalid instantiations and assignments - sqlsrv_buffered_result_set( void ); - sqlsrv_buffered_result_set( sqlsrv_buffered_result_set& ); - sqlsrv_buffered_result_set& operator=( sqlsrv_buffered_result_set& ); - - HashTable* cache; // rows of data kept in index based hash table - SQLSMALLINT col_count; // number of columns in the current result set - meta_data* meta; // metadata for fields in the cache - SQLLEN current; // 1 based, 0 means before first row - sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here - SQLUSMALLINT last_field_index; // the last field data retrieved from - SQLLEN read_so_far; // position within string to read from (for partial reads of strings) - sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use - SQLLEN temp_length; // number of bytes in the temp conversion buffer - - typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; - - // two dimentional sparse matrix that holds the [from][to] functions that do conversions - static conv_matrix_t conv_matrix; - - // string conversion functions - SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // long conversion functions - SQLRETURN to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // double conversion functions - SQLRETURN to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // string to number conversion functions - // Future: See if these can be converted directly to template member functions - SQLRETURN string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - SQLRETURN wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ); - - // utility functions for conversions - unsigned char* get_row( void ); -}; - -//********************************************************************************************************************************* -// Utility -//********************************************************************************************************************************* - -// Simple macro to alleviate unused variable warnings. These are optimized out by the compiler. -// We use this since the unused variables are buried in the PHP_FUNCTION macro. -#define SQLSRV_UNUSED( var ) var; - -// do a heap check in debug mode, but only print errors, not all of the allocations -#define MEMCHECK_SILENT 1 - -// utility functions shared by multiple callers across files -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len); -bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len); -bool validate_string(char* string, SQLLEN& len); -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ); -wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, - unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); - -//********************************************************************************************************************************* -// Error handling routines and Predefined Errors -//********************************************************************************************************************************* - -enum SQLSRV_ERROR_CODES { - - SQLSRV_ERROR_ODBC, - SQLSRV_ERROR_DRIVER_NOT_INSTALLED, - SQLSRV_ERROR_ZEND_HASH, - SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, - SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, - SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, - SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, - SQLSRV_ERROR_ZEND_STREAM, - SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, - SQLSRV_ERROR_FETCH_PAST_END, - SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, - SQLSRV_ERROR_NO_FIELDS, - SQLSRV_ERROR_INVALID_TYPE, - SQLSRV_ERROR_FETCH_NOT_CALLED, - SQLSRV_ERROR_NO_DATA, - SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, - SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, - SQLSRV_ERROR_NEXT_RESULT_PAST_END, - SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, - SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, - SQLSRV_ERROR_INVALID_CONNECTION_KEY, - SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, - SQLSRV_ERROR_INVALID_OPTION_KEY, - SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, - SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, - SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, - SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, - SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, - SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, - SQLSRV_ERROR_STREAM_CREATE, - SQLSRV_ERROR_MARS_OFF, - SQLSRV_ERROR_FIELD_INDEX_ERROR, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, - SQLSRV_ERROR_INVALID_BUFFER_LIMIT, - - // Driver specific error codes starts from here. - SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, - -}; - -// the message returned by ODBC Driver 11 for SQL Server -static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command", - "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" }; - -// SQLSTATE for all internal errors -extern SQLCHAR IMSSP[]; - -// SQLSTATE for all internal warnings -extern SQLCHAR SSPWARN[]; - -// flags passed to sqlsrv_errors to filter its return values -enum error_handling_flags { - SQLSRV_ERR_ERRORS, - SQLSRV_ERR_WARNINGS, - SQLSRV_ERR_ALL -}; - -// *** internal error macros and functions *** -// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the -// errno is 1 based. It returns it as an array with 3 members: -// 1/SQLSTATE) sqlstate -// 2/code) driver specific error code -// 3/message) driver specific error message -// The fetch type determines if the indices are numeric, associative, or both. -bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, _Out_ sqlsrv_error_auto_ptr& error, - logging_severity severity TSRMLS_DC ); - -// format and return a driver specfic error -void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, - sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ); - - -// return the message for the HRESULT returned by GetLastError. Some driver errors use this to -// return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails. -const char* get_last_error_message( DWORD last_error = 0 ); - -// a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer -DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ); - -// convenience functions that overload either a reference or a pointer so we can use -// either in the CHECK_* functions. -inline bool call_error_handler( sqlsrv_context& ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) -{ - va_list print_params; - va_start( print_params, warning ); - bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); - va_end( print_params ); - return ignored; -} - -inline bool call_error_handler( sqlsrv_context* ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) -{ - va_list print_params; - va_start( print_params, warning ); - bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); - va_end( print_params ); - return ignored; -} - -// PHP equivalent of ASSERT. C asserts cause a dialog to show and halt the process which -// we don't want on a web server - -#define SQLSRV_ASSERT( condition, msg, ...) if( !(condition)) DIE( msg, __VA_ARGS__ ); - -#if defined( PHP_DEBUG ) - -#define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) \ - if( !(condition)) { \ - DIE (msg, __VA_ARGS__ ); \ - } - -#else - - #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) ((void)0) - -#endif - -// check to see if the sqlstate is 01004, truncated field retrieved. Used for retrieving large fields. -inline bool is_truncated_warning( SQLCHAR* state ) -{ -#if defined(ZEND_DEBUG) - if( state == NULL || strlen( reinterpret_cast( state )) != 5 ) { \ - DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \ - } -#endif - return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4'); -} - -// Macros for handling errors. These macros are simplified if statements that take boilerplate -// code down to a single line to avoid distractions in the code. - -#define CHECK_ERROR_EX( unique, condition, context, ssphp, ... ) \ - bool flag##unique = (condition); \ - bool ignored##unique = true; \ - if (flag##unique) { \ - ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, __VA_ARGS__ ); \ - } \ - if( !ignored##unique ) - -#define CHECK_ERROR_UNIQUE( unique, condition, context, ssphp, ...) \ - CHECK_ERROR_EX( unique, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_ERROR( condition, context, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, NULL, __VA_ARGS__ ) - -#define CHECK_CUSTOM_ERROR( condition, context, ssphp, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_SQL_ERROR( result, context, ... ) \ - SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ - CHECK_ERROR( result == SQL_ERROR, context, __VA_ARGS__ ) - -#define CHECK_WARNING_AS_ERROR_UNIQUE( unique, condition, context, ssphp, ... ) \ - bool ignored##unique = true; \ - if( condition ) { \ - ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, __VA_ARGS__ ); \ - } \ - if( !ignored##unique ) - -#define CHECK_SQL_WARNING_AS_ERROR( result, context, ... ) \ - CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__,( result == SQL_SUCCESS_WITH_INFO ), context, SQLSRV_ERROR_ODBC, __VA_ARGS__ ) - -#define CHECK_SQL_WARNING( result, context, ... ) \ - if( result == SQL_SUCCESS_WITH_INFO ) { \ - (void)call_error_handler( context, NULL TSRMLS_CC, /*warning*/ true, __VA_ARGS__ ); \ - } - -#define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \ - CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, __VA_ARGS__ ) - -#define CHECK_ZEND_ERROR( zr, ctx, error, ... ) \ - CHECK_ERROR_UNIQUE( __COUNTER__, ( zr == FAILURE ), ctx, error, __VA_ARGS__ ) \ - -#define CHECK_SQL_ERROR_OR_WARNING( result, context, ... ) \ - SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ - bool ignored = true; \ - if( result == SQL_ERROR ) { \ - ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, false, __VA_ARGS__ ); \ - } \ - else if( result == SQL_SUCCESS_WITH_INFO ) { \ - ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, true TSRMLS_CC, __VA_ARGS__ ); \ - } \ - if( !ignored ) - -// throw an exception after it has been hooked into the custom error handler -#define THROW_CORE_ERROR( ctx, custom, ... ) \ - (void)call_error_handler( ctx, custom TSRMLS_CC, /*warning*/ false, __VA_ARGS__ ); \ - throw core::CoreException(); - -//********************************************************************************************************************************* -// ODBC/Zend function wrappers -//********************************************************************************************************************************* - -namespace core { - - // base exception for the driver - struct CoreException : public std::exception { - - CoreException() - { - } - }; - - inline void check_for_mars_error( sqlsrv_stmt* stmt, SQLRETURN r TSRMLS_DC ) - { - // We check for the 'connection busy' error caused by having MultipleActiveResultSets off - // and return a more helpful message prepended to the ODBC errors if that error occurs - if( !SQL_SUCCEEDED( r )) { - - SQLCHAR err_msg[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLSMALLINT len = 0; - - SQLRETURN r = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, - err_msg, SQL_MAX_MESSAGE_LENGTH, &len ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - - throw CoreException(); - } - std::size_t driver_version = stmt->conn->driver_version; - if( !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); - } - } - } - - // *** ODBC wrappers *** - - // wrap the ODBC functions to throw exceptions rather than use the return value to signal errors - // some of the signatures have been altered to be more convenient since the return value is no longer - // required to return the status of the call (e.g., SQLNumResultCols). - // These functions take the sqlsrv_context type. However, since the error handling code can alter - // the context to hold the error, they are not passed as const. - - inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) - { - SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, - diag_info_buffer, buffer_length, out_buffer_length ); - - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - - return r; - } - - inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle, - _Out_writes_(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); - CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) { - throw CoreException(); - } - } - - inline void SQLBindParameter( sqlsrv_stmt* stmt, - SQLUSMALLINT ParameterNumber, - SQLSMALLINT InputOutputType, - SQLSMALLINT ValueType, - SQLSMALLINT ParameterType, - SQLULEN ColumnSize, - SQLSMALLINT DecimalDigits, - _Inout_ SQLPOINTER ParameterValuePtr, - SQLLEN BufferLength, - _Inout_ SQLLEN * StrLen_Or_IndPtr - TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, - DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, - _Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC ) - { - SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, - buffer_length, out_buffer_length, field_type_num ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLCHAR* col_name, SQLSMALLINT col_name_length, - _Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size, - _Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, - data_type, col_size, decimal_digits, nullable); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLEndTran( SQLSMALLINT handleType, sqlsrv_conn* conn, SQLSMALLINT completionType TSRMLS_DC ) - { - SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success - inline SQLRETURN SQLExecDirect( sqlsrv_stmt* stmt, char* sql TSRMLS_DC ) - { - SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast( sql ), SQL_NTS ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - - throw CoreException(); - } - return r; - } - - inline SQLRETURN SQLExecDirectW( sqlsrv_stmt* stmt, wchar_t* wsql TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast( wsql ), SQL_NTS ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - // SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success - inline SQLRETURN SQLExecute( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLExecute( stmt->handle() ); - - check_for_mars_error( stmt, r TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return r; - } - - inline SQLRETURN SQLFetchScroll( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) - { - SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - - // wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine - inline void SQLFreeHandle( sqlsrv_context& ctx TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() ); - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} - } - - inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) - { - SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); - - if( r == SQL_NO_DATA ) - return r; - - CHECK_SQL_ERROR( r, stmt ) { - throw CoreException(); - } - - if( handle_warning ) { - CHECK_SQL_WARNING_AS_ERROR( r, stmt ) { - throw CoreException(); - } - } - - return r; - } - - - inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, _Out_ SQLPOINTER info_value, SQLSMALLINT buffer_len, - _Out_ SQLSMALLINT* str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - - inline void SQLGetTypeInfo( sqlsrv_stmt* stmt, SQLUSMALLINT data_type TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLGetTypeInfo( stmt->handle(), data_type ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - // SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set. - inline SQLRETURN SQLMoreResults( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r = ::SQLMoreResults( stmt->handle() ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return r; - } - - inline SQLSMALLINT SQLNumResultCols( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - SQLSMALLINT num_cols; - r = ::SQLNumResultCols( stmt->handle(), &num_cols ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return num_cols; - } - - // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more - // parameters or when the parameters are all processed. - inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, _Out_ SQLPOINTER* value_ptr_ptr TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - return r; - } - - inline void SQLPrepareW( sqlsrv_stmt* stmt, SQLWCHAR * sql, SQLINTEGER sql_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLPrepareW( stmt->handle(), sql, sql_len ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline void SQLPutData( sqlsrv_stmt* stmt, SQLPOINTER data_ptr, SQLLEN strlen_or_ind TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - inline SQLLEN SQLRowCount( sqlsrv_stmt* stmt TSRMLS_DC ) - { - SQLRETURN r; - SQLLEN rows_affected; - - r = ::SQLRowCount( stmt->handle(), &rows_affected ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - - return rows_affected; - } - - - inline void SQLSetConnectAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - } - - - inline void SQLSetEnvAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len ); - CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { - throw CoreException(); - } - } - - inline void SQLSetConnectAttr( sqlsrv_conn* conn, SQLINTEGER attribute, SQLPOINTER value_ptr, SQLINTEGER value_len TSRMLS_DC ) - { - SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); - - CHECK_SQL_ERROR_OR_WARNING( r, conn ) { - throw CoreException(); - } - } - - inline void SQLSetStmtAttr( sqlsrv_stmt* stmt, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) - { - SQLRETURN r; - r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw CoreException(); - } - } - - - // *** zend wrappers *** - - //zend_resource_dtor sets the type of destroyed resources to -1 - #define RSRC_INVALID_TYPE -1 - - // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string - // so allocated memory inside of value_z should be released before assigning it to the new string - inline void sqlsrv_zval_stringl(zval* value_z, const char* str, const std::size_t str_len) - { - if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { - zend_string* temp_zstr = zend_string_init(str, str_len, 0); - zend_string_release(Z_STR_P(value_z)); - ZVAL_NEW_STR(value_z, temp_zstr); - } - else { - ZVAL_STRINGL(value_z, str, str_len); - } - } - - - // exception thrown when a zend function wrapped here fails. - - // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw - // exceptions when an error occurs. They are prefaced with sqlsrv_ because many of the zend functions are - // actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system. - // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error - // that can be thrown from it. - - inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, zend_ulong index, zval* value TSRMLS_DC) - { - int zr = ::add_index_zval( array, index, value ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_next_index_zval( sqlsrv_context& ctx, zval* array, zval* value TSRMLS_DC) - { - int zr = ::add_next_index_zval( array, value ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, const char* key TSRMLS_DC ) - { - int zr = ::add_assoc_null( array_z, key ); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, const char* key, zend_long val TSRMLS_DC ) - { - int zr = ::add_assoc_long( array_z, key, val ); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, const char* key, char* val, bool duplicate TSRMLS_DC ) - { - int zr = ::add_assoc_string(array_z, key, val); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - if (duplicate == 0) { - sqlsrv_free(val); - } - } - - inline void sqlsrv_array_init( sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) - { - int zr = ::array_init(new_array); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval* stream_z TSRMLS_DC ) - { - // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment - php_stream_from_zval_no_verify( stream, stream_z ); - CHECK_CUSTOM_ERROR( stream == NULL, ctx, SQLSRV_ERROR_ZEND_STREAM ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_get_current_data(sqlsrv_context& ctx, HashTable* ht, _Out_ zval*& output_data TSRMLS_DC) - { - int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_get_current_data_ptr(sqlsrv_context& ctx, HashTable* ht, _Out_ void*& output_data TSRMLS_DC) - { - int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, zend_ulong index TSRMLS_DC ) - { - int zr = ::zend_hash_index_del( ht, index ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, zend_ulong index, zval* data_z TSRMLS_DC ) - { - int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_index_update_ptr(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData TSRMLS_DC) - { - int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - - inline void sqlsrv_zend_hash_index_update_mem(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData, std::size_t size TSRMLS_DC) - { - int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, zval* data TSRMLS_DC ) - { - int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert_mem(sqlsrv_context& ctx, HashTable* ht, void* data, uint data_size TSRMLS_DC) - { - int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_next_index_insert_ptr(sqlsrv_context& ctx, HashTable* ht, void* data TSRMLS_DC) - { - int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } - - inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, HashTable* ht, uint32_t initial_size, - dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) - { - ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); - } - - inline void sqlsrv_zend_hash_add( sqlsrv_context& ctx, HashTable* ht, zend_string* key, unsigned int key_len, zval* data, - unsigned int data_size, zval* pDest TSRMLS_DC ) - { - int zr = (pDest = ::zend_hash_add(ht, key, data)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - -template -sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) -{ - return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC ); -} - -template -sqlsrv_conn* allocate_conn( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) -{ - return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC ); -} - -} // namespace core - -#endif // CORE_SQLSRV_H +#ifndef CORE_SQLSRV_H +#define CORE_SQLSRV_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_sqlsrv.h +// +// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +//********************************************************************************************************************************* +// Includes +//********************************************************************************************************************************* + +#ifdef SQL_WCHART_CONVERT +#undef SQL_WCHART_CONVERT +#endif +#ifndef _WCHART_DEFINED +#define _WCHART_DEFINED +#endif + +#include "php.h" +#include "php_globals.h" +#include "php_ini.h" +#include "ext/standard/php_standard.h" +#include "ext/standard/info.h" + +#ifndef _WIN32 +#include "FormattedPrint.h" +#include "StringFunctions.h" +#endif // !_WIN32 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef PHP_WIN32 +#define PHP_SQLSRV_API __declspec(dllexport) +#else +#define PHP_SQLSRV_API +#endif + +// #define MultiByteToWideChar SystemLocale::ToUtf16 + + + +#define stricmp strcasecmp +#define strnicmp strncasecmp + +#ifndef _WIN32 +#define GetLastError() errno +#define SetLastError(err) errno=err + +typedef struct _OSVERSIONINFOA { + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + CHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usage +} OSVERSIONINFOA, *POSVERSIONINFOA, *LPOSVERSIONINFOA; +typedef OSVERSIONINFOA OSVERSIONINFO; +#endif // !_WIN32 + + +// OACR is an internal Microsoft static code analysis tool +#if defined(OACR) +#include +OACR_WARNING_PUSH +OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) +OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) +OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) +OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) +OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) +OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." ) +#else +// define to eliminate static analysis hints in the code +#define OACR_WARNING_SUPPRESS( warning, msg ) +#endif + +extern "C" { + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#if _MSC_VER >= 1400 +// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. +// php.h defines this constant as unsigned int which causes a compile error +// in ws2tcpip.h. Fortunately php.h allows an override by defining +// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define +// socklen_t here and override the php.h version. +typedef int socklen_t; +#define HAVE_SOCKLEN_T +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if ZEND_DEBUG +// debug build causes warning C4505 to pop up from the Zend header files +#pragma warning( disable: 4505 ) +#endif + +} // extern "C" + +#if defined(OACR) +OACR_WARNING_POP +#endif + +#ifdef _WIN32 +#include +#include +#endif // _WIN32 + +#if !defined(WC_ERR_INVALID_CHARS) +// imported from winnls.h as it isn't included by 5.3.0 +#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars +#endif + +// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when +// we use std::copy, which causes compilation to fail since we compile with warnings as errors. +#if defined(ZEND_DEBUG) && defined(inline) +#undef inline +#endif + +#include +#include +#include +#include +#include +#include +// included for SQL Server specific constants +#include "msodbcsql.h" + +#ifdef _WIN32 +#include +#endif // _WIN32 + +//********************************************************************************************************************************* +// Constants and Types +//********************************************************************************************************************************* + +// constants for maximums in SQL Server +const int SS_MAXCOLNAMELEN = 128; +const int SQL_SERVER_MAX_FIELD_SIZE = 8000; +const int SQL_SERVER_MAX_PRECISION = 38; +const int SQL_SERVER_MAX_TYPE_SIZE = 0; +const int SQL_SERVER_MAX_PARAMS = 2100; + +// max size of a date time string when converting from a DateTime object to a string +const int MAX_DATETIME_STRING_LEN = 256; + +// precision and scale for the date time types between servers +const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; +const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; +const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; +const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; + +// types for conversions on output parameters (though they can be used for input parameters, they are ignored) +enum SQLSRV_PHPTYPE { + MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type + SQLSRV_PHPTYPE_NULL = 1, + SQLSRV_PHPTYPE_INT, + SQLSRV_PHPTYPE_FLOAT, + SQLSRV_PHPTYPE_STRING, + SQLSRV_PHPTYPE_DATETIME, + SQLSRV_PHPTYPE_STREAM, + MAX_SQLSRV_PHPTYPE, // highest value for a php type + SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid +}; + +// encodings supported by this extension. These basically translate into the use of SQL_C_CHAR or SQL_C_BINARY when getting +// information as a string or a stream. +enum SQLSRV_ENCODING { + SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables. + SQLSRV_ENCODING_DEFAULT, // use what is the connection's default for a statement, use system if a connection + SQLSRV_ENCODING_BINARY, // use SQL_C_BINARY when using SQLGetData + SQLSRV_ENCODING_CHAR, // use SQL_C_CHAR when using SQLGetData + SQLSRV_ENCODING_SYSTEM = SQLSRV_ENCODING_CHAR, + SQLSRV_ENCODING_UTF8 = CP_UTF8, +}; + +// the array keys used when returning a row via sqlsrv_fetch_array and sqlsrv_fetch_object. +enum SQLSRV_FETCH_TYPE { + MIN_SQLSRV_FETCH = 1, // lowest value for fetch type + SQLSRV_FETCH_NUMERIC = 1, // return an array with only numeric indices + SQLSRV_FETCH_ASSOC = 2, // return an array with keys made from the field names + SQLSRV_FETCH_BOTH = 3, // return an array indexed with both numbers and keys + MAX_SQLSRV_FETCH = 3, // highest value for fetch type +}; + +// buffer size of a sql state (including the null character) +const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1; + +// buffer size allocated to retrieve data from a PHP stream. This number +// was chosen since PHP doesn't return more than 8k at a time even if +// the amount requested was more. +const int PHP_STREAM_BUFFER_SIZE = 8192; + +// SQL types for parameters encoded in an integer. The type corresponds to the SQL type ODBC constants. +// The size is the column size or precision, and scale is the decimal digits for precise numeric types. + +union sqlsrv_sqltype { + struct typeinfo_t { + int type:9; + int size:14; + int scale:8; + } typeinfo; + + zend_long value; +}; + + +// SQLSRV PHP types (as opposed to the Zend PHP type constants). Contains the type (see SQLSRV_PHPTYPE) +// and the encoding for strings and streams (see SQLSRV_ENCODING) + +union sqlsrv_phptype { + + struct typeinfo_t { + unsigned type:8; + unsigned encoding:16; + } typeinfo; + + zend_long value; +}; + +// static assert for enforcing compile time conditions +template +struct sqlsrv_static_assert; + +template <> +struct sqlsrv_static_assert { static const int value = 1; }; + +#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() ) + + +//********************************************************************************************************************************* +// Logging +//********************************************************************************************************************************* +// log_callback +// a driver specific callback for logging messages +// severity - severity of the message: notice, warning, or error +// msg - the message to log in a FormatMessage style formatting +// print_args - args to the message +typedef void (*log_callback)( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); + +// each driver must register a log callback. This should be the first thing a driver does. +void core_sqlsrv_register_logger( log_callback ); + +// a simple wrapper around a PHP error logging function. +void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ... ); + +// a macro to make it convenient to use the function. +#define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, ## __VA_ARGS__ ) + +// mask for filtering which severities are written to the log +enum logging_severity { + SEV_ERROR = 0x01, + SEV_WARNING = 0x02, + SEV_NOTICE = 0x04, + SEV_ALL = -1, +}; + +// Kill the PHP process and log the message to PHP +void die( const char* msg, ... ); +#define DIE( msg, ... ) { die( msg, ## __VA_ARGS__ ); } + + +//********************************************************************************************************************************* +// Resource/Memory Management +//********************************************************************************************************************************* + +// the macro max is defined and overrides the call to max in the allocator class +#pragma push_macro( "max" ) +#undef max + +// new memory allocation/free debugging facilities to help us verify that all allocations are being +// released in a timely manner and not just at the end of the script. +// Zend has memory logging and checking, but it can generate a lot of noise for just one extension. +// It's meant for internal use but might be useful for people adding features to our extension. +// To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases +// must be done with sqlsrv_malloc and sqlsrv_free. +// #define SQLSRV_MEM_DEBUG 1 +#if defined( PHP_DEBUG ) && !defined( ZTS ) && defined( SQLSRV_MEM_DEBUG ) + +inline void* sqlsrv_malloc_trace( size_t size, const char* file, int line ) +{ + void* ptr = emalloc( size ); + LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); + return ptr; +} + +inline void* sqlsrv_malloc_trace( size_t element_count, size_t element_size, size_t extra, const char* file, int line ) +{ + OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); + + if(( element_count > 0 && element_size > 0 ) && + ( element_count > element_size * element_count || element_size > element_size * element_count )) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count > element_size * element_count + extra ) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count + extra == 0 ) { + DIE( "Allocation size must be more than 0" ); + } + + void* ptr = emalloc( element_size * element_count + extra ); + LOG( SEV_NOTICE, "emalloc returned %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr ); + return ptr; +} + +inline void* sqlsrv_realloc_trace( void* buffer, size_t size, const char* file, int line ) +{ + void* ptr = erealloc( original, size ); + LOG( SEV_NOTICE, "erealloc returned %5!08x! from %4!08x!: %1!d! bytes at %2!s!:%3!d!", size, file, line, ptr, original ); + return ptr; +} + +inline void sqlsrv_free_trace( void* ptr, const char* file, int line ) +{ + LOG( SEV_NOTICE, "efree %1!08x! at %2!s!:%3!d!", ptr, file, line ); + efree( ptr ); +} + +#define sqlsrv_malloc( size ) sqlsrv_malloc_trace( size, __FILE__, __LINE__ ) +#define sqlsrv_malloc( count, size, extra ) sqlsrv_malloc_trace( count, size, extra, __FILE__, __LINE__ ) +#define sqlsrv_realloc( buffer, size ) sqlsrv_realloc_trace( buffer, size, __FILE__, __LINE__ ) +#define sqlsrv_free( ptr ) sqlsrv_free_trace( ptr, __FILE__, __LINE__ ) + +#else + +inline void* sqlsrv_malloc( size_t size ) +{ + return emalloc( size ); +} + +inline void* sqlsrv_malloc( size_t element_count, size_t element_size, size_t extra ) +{ + OACR_WARNING_SUPPRESS( ALLOC_SIZE_OVERFLOW_IN_ALLOC_WRAPPER, "Overflow verified below" ); + + if(( element_count > 0 && element_size > 0 ) && + ( element_count > element_size * element_count || element_size > element_size * element_count )) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count > element_size * element_count + extra ) { + DIE( "Integer overflow in sqlsrv_malloc" ); + } + + if( element_size * element_count + extra == 0 ) { + DIE( "Allocation size must be more than 0" ); + } + + return emalloc( element_size * element_count + extra ); +} + +inline void* sqlsrv_realloc( void* buffer, size_t size ) +{ + return erealloc( buffer, size ); +} + +inline void sqlsrv_free( void* ptr ) +{ + efree( ptr ); +} + +#endif + +// trait class that allows us to assign const types to an auto_ptr +template +struct remove_const { + typedef T type; +}; + +template +struct remove_const { + typedef T* type; +}; + +// allocator that uses the zend memory manager to manage memory +// this allows us to use STL classes that still work with Zend objects +template +struct sqlsrv_allocator { + + // typedefs used by the STL classes + typedef T value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + // conversion typedef (used by list and other STL classes) + template + struct rebind { + typedef sqlsrv_allocator other; + }; + + inline sqlsrv_allocator() {} + inline ~sqlsrv_allocator() {} + inline sqlsrv_allocator( sqlsrv_allocator const& ) {} + template + inline sqlsrv_allocator( sqlsrv_allocator const& ) {} + + // address (doesn't work if the class defines operator&) + inline pointer address( reference r ) + { + return &r; + } + + inline const_pointer address( const_reference r ) + { + return &r; + } + + // memory allocation/deallocation + inline pointer allocate( size_type cnt, + typename std::allocator::const_pointer = 0 ) + { + return reinterpret_cast( sqlsrv_malloc(cnt, sizeof (T), 0)); + } + + inline void deallocate( pointer p, size_type ) + { + sqlsrv_free(p); + } + + // size + inline size_type max_size( void ) const + { + return std::numeric_limits::max() / sizeof(T); + } + + // object construction/destruction + inline void construct( pointer p, const T& t ) + { + new(p) T(t); + } + + inline void destroy(pointer p) + { + p->~T(); + } + + // equality operators + inline bool operator==( sqlsrv_allocator const& ) + { + return true; + } + + inline bool operator!=( sqlsrv_allocator const& a ) + { + return !operator==(a); + } +}; + + +// base class for auto_ptrs that we define below. It provides common operators and functions +// used by all the classes. +template +class sqlsrv_auto_ptr { + +public: + + sqlsrv_auto_ptr( void ) : _ptr( NULL ) + { + } + + ~sqlsrv_auto_ptr( void ) + { + static_cast(this)->reset( NULL ); + } + + // call when ownership is transferred + void transferred( void ) + { + _ptr = NULL; + } + + // explicit function to get the pointer. + T* get( void ) const + { + return _ptr; + } + + // cast operator to allow auto_ptr to be used where a normal const * can be. + operator const T* () const + { + return _ptr; + } + + // cast operator to allow auto_ptr to be used where a normal pointer can be. + operator typename remove_const::type () const + { + return _ptr; + } + + operator bool() const + { + return _ptr != NULL; + } + + // there are a number of places where we allocate a block intended to be accessed as + // an array of elements, so this operator allows us to treat the memory as such. + T& operator[]( int index ) const + { + return _ptr[ index ]; + } + + // there are a number of places where we allocate a block intended to be accessed as + // an array of elements, so this operator allows us to treat the memory as such. + T& operator[]( unsigned int index ) const + { + return _ptr[ index ]; + } + + // there are a number of places where we allocate a block intended to be accessed as + // an array of elements, so this operator allows us to treat the memory as such. + T& operator[]( long index ) const + { + return _ptr[ index ]; + } + + + #ifdef __WIN64 + // there are a number of places where we allocate a block intended to be accessed as + // an array of elements, so this operator allows us to treat the memory as such. + T& operator[](std::size_t index) const + { + return _ptr[index]; + } + #endif + + // there are a number of places where we allocate a block intended to be accessed as + // an array of elements, so this operator allows us to treat the memory as such. + T& operator[]( unsigned short index ) const + { + return _ptr[ index ]; + } + + // access elements of a structure through the auto ptr + T* const operator->( void ) const + { + return _ptr; + } + + // value from reference operator (i.e., i = *(&i); or *i = blah;) + T& operator*() const + { + return *_ptr; + } + + // allow the use of the address-of operator to simulate a **. + // Note: this operator conflicts with storing these within an STL container. If you need + // to do that, then redefine this as getpp and change instances of &auto_ptr to auto_ptr.getpp() + T** operator&( void ) + { + return &_ptr; + } + +protected: + + sqlsrv_auto_ptr( T* ptr ) : + _ptr( ptr ) + { + } + + sqlsrv_auto_ptr( sqlsrv_auto_ptr& src ) + { + if( _ptr ) { + static_cast(this)->reset( src._ptr ); + } + src.transferred(); + } + + // assign a new pointer to the auto_ptr. It will free the previous memory block + // because ownership is deemed finished. + T* operator=( T* ptr ) + { + static_cast( this )->reset( ptr ); + + return ptr; + } + + T* _ptr; +}; + +// an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer +// in a variable of sqlsrv_malloc_auto_ptr. sqlsrv_malloc_auto_ptr will "own" that block and assure that it is +// freed until the variable is destroyed (out of scope) or ownership is transferred using the function +// "transferred". +// DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. + +template +class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr > { + +public: + + sqlsrv_malloc_auto_ptr( void ) : + sqlsrv_auto_ptr >( NULL ) + { + } + + sqlsrv_malloc_auto_ptr( const sqlsrv_malloc_auto_ptr& src ) : + sqlsrv_auto_ptr >( src ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( T* ptr = NULL ) + { + if( sqlsrv_auto_ptr >::_ptr ) + sqlsrv_free( (void*) sqlsrv_auto_ptr >::_ptr ); + sqlsrv_auto_ptr >::_ptr = ptr; + } + + T* operator=( T* ptr ) + { + return sqlsrv_auto_ptr >::operator=( ptr ); + } + + void operator=( sqlsrv_malloc_auto_ptr& src ) + { + T* p = src.get(); + src.transferred(); + this->_ptr = p; + } + + // DO NOT CALL sqlsrv_realloc with a sqlsrv_malloc_auto_ptr. Use the resize member function. + // has the same parameter list as sqlsrv_realloc: new_size is the size in bytes of the newly allocated buffer + void resize( size_t new_size ) + { + sqlsrv_auto_ptr >::_ptr = reinterpret_cast( sqlsrv_realloc( sqlsrv_auto_ptr >::_ptr, new_size )); + } +}; + + +// auto ptr for Zend hash tables. Used to clean up a hash table allocated when +// something caused an early exit from the function. This is used when the hash_table is +// allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr. + +class hash_auto_ptr : public sqlsrv_auto_ptr { + +public: + + hash_auto_ptr( void ) : + sqlsrv_auto_ptr( NULL ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( HashTable* ptr = NULL ) + { + if( _ptr ) { + zend_hash_destroy( _ptr ); + FREE_HASHTABLE( _ptr ); + } + _ptr = ptr; + } + + HashTable* operator=( HashTable* ptr ) + { + return sqlsrv_auto_ptr::operator=( ptr ); + } + +private: + + hash_auto_ptr( HashTable const& hash ); + + hash_auto_ptr( hash_auto_ptr const& hash ); +}; + + +// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr. +// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed +// (out of scope) or ownership is transferred using the function "transferred". + +class zval_auto_ptr : public sqlsrv_auto_ptr { + +public: + + zval_auto_ptr( void ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( zval* ptr = NULL ) + { + if( _ptr ) + zval_ptr_dtor(_ptr ); + _ptr = ptr; + } + + zval* operator=( zval* ptr ) + { + return sqlsrv_auto_ptr::operator=( ptr ); + } + + +private: + + zval_auto_ptr( const zval_auto_ptr& src ); +}; + +#pragma pop_macro( "max" ) + + +//********************************************************************************************************************************* +// sqlsrv_error +//********************************************************************************************************************************* + +// *** PHP specific errors *** +// sqlsrv errors are held in a structure of this type used by the driver handle_error functions +// format is a flag that tells the driver error handler functions if there are parameters to use with FormatMessage +// into the error message before returning it. + +// base class which can be instatiated with aggregates (see error constants) +struct sqlsrv_error_const { + + SQLCHAR* sqlstate; + SQLCHAR* native_message; + SQLINTEGER native_code; + bool format; +}; + +// subclass which is used by the core layer to instantiate ODBC errors +struct sqlsrv_error : public sqlsrv_error_const { + + sqlsrv_error( void ) + { + sqlstate = NULL; + native_message = NULL; + native_code = -1; + format = false; + } + + sqlsrv_error( SQLCHAR* sql_state, SQLCHAR* message, SQLINTEGER code, bool printf_format = false ) + { + sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); + native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); + strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); + strcpy_s( reinterpret_cast( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast( message )); + native_code = code; + format = printf_format; + } + + sqlsrv_error( sqlsrv_error_const const& prototype ) + { + sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format ); + } + + ~sqlsrv_error( void ) + { + if( sqlstate != NULL ) { + sqlsrv_free( sqlstate ); + } + if( native_message != NULL ) { + sqlsrv_free( native_message ); + } + } +}; + + +// an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete +class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr { + +public: + + sqlsrv_error_auto_ptr( void ) : + sqlsrv_auto_ptr( NULL ) + { + } + + sqlsrv_error_auto_ptr( sqlsrv_error_auto_ptr const& src ) : + sqlsrv_auto_ptr( (sqlsrv_error_auto_ptr&) src ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( sqlsrv_error* ptr = NULL ) + { + if( _ptr ) { + _ptr->~sqlsrv_error(); + sqlsrv_free( (void*) _ptr ); + } + _ptr = ptr; + } + + sqlsrv_error* operator=( sqlsrv_error* ptr ) + { + return sqlsrv_auto_ptr::operator=( ptr ); + } + + // unlike traditional assignment operators, the chained assignment of an auto_ptr doesn't make much + // sense. Only the last one would have anything in it. + void operator=( sqlsrv_error_auto_ptr& src ) + { + sqlsrv_error* p = src.get(); + src.transferred(); + reset( p ); + } +}; + + +//********************************************************************************************************************************* +// Context +//********************************************************************************************************************************* + +class sqlsrv_context; +struct sqlsrv_conn; + +// error_callback +// a driver specific callback for processing errors. +// ctx - the context holding the handles +// sqlsrv_error_code - specific error code to return. +typedef bool (*error_callback)( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool error TSRMLS_DC, va_list* print_args ); + +// sqlsrv_context +// a context holds relevant information to be passed with a connection and statement objects. + +class sqlsrv_context { + + public: + + sqlsrv_context( SQLSMALLINT type, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : + handle_( SQL_NULL_HANDLE ), + handle_type_( type ), + name_( NULL ), + err_( e ), + driver_( drv ), + last_error_(), + encoding_( encoding ) + { + } + + sqlsrv_context( SQLHANDLE h, SQLSMALLINT t, error_callback e, void* drv, SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) : + handle_( h ), + handle_type_( t ), + name_( NULL ), + err_( e ), + driver_( drv ), + last_error_(), + encoding_( encoding ) + { + } + + sqlsrv_context( sqlsrv_context const& ctx ) : + handle_( ctx.handle_ ), + handle_type_( ctx.handle_type_ ), + name_( ctx.name_ ), + err_( ctx.err_ ), + driver_( ctx.driver_ ), + last_error_( ctx.last_error_ ) + { + } + + virtual ~sqlsrv_context() + { + } + + void set_func( const char* f ) + { + name_ = f; + } + + void set_last_error( sqlsrv_error_auto_ptr& last_error ) + { + last_error_ = last_error; + } + + sqlsrv_error_auto_ptr& last_error( void ) + { + return last_error_; + } + + // since the primary responsibility of a context is to hold an ODBC handle, we + // provide these convenience operators for using them interchangeably + operator SQLHANDLE ( void ) const + { + return handle_; + } + + error_callback error_handler( void ) const + { + return err_; + } + + SQLHANDLE handle( void ) const + { + return handle_; + } + + SQLSMALLINT handle_type( void ) const + { + return handle_type_; + } + + const char* func( void ) const + { + return name_; + } + + void* driver( void ) const + { + return driver_; + } + + void set_driver( void* driver ) + { + this->driver_ = driver; + } + + void invalidate( void ) + { + if( handle_ != SQL_NULL_HANDLE ) { + ::SQLFreeHandle( handle_type_, handle_ ); + + last_error_.reset(); + } + handle_ = SQL_NULL_HANDLE; + } + + bool valid( void ) + { + return handle_ != SQL_NULL_HANDLE; + } + + SQLSRV_ENCODING encoding( void ) const + { + return encoding_; + } + + void set_encoding( SQLSRV_ENCODING e ) + { + encoding_ = e; + } + + private: + SQLHANDLE handle_; // ODBC handle for this context + SQLSMALLINT handle_type_; // type of the ODBC handle + const char* name_; // function name currently executing this context + error_callback err_; // driver error callback if error occurs in core layer + void* driver_; // points back to the driver for PDO + sqlsrv_error_auto_ptr last_error_; // last error that happened on this object + SQLSRV_ENCODING encoding_; // encoding of the context +}; + +const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista + +// maps an IANA encoding to a code page +struct sqlsrv_encoding { + + const char* iana; + size_t iana_len; + unsigned int code_page; + bool not_for_connection; + + sqlsrv_encoding( const char* iana, unsigned int code_page, bool not_for_conn = false ): + iana( iana ), iana_len( strlen( iana )), code_page( code_page ), not_for_connection( not_for_conn ) + { + } +}; + + +//********************************************************************************************************************************* +// Initialization +//********************************************************************************************************************************* + +// variables set during initialization +extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in +extern HashTable* g_encodings; // encodings supported by this driver + +void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ); +void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ); + +// environment context used by sqlsrv_connect for when a connection error occurs. +struct sqlsrv_henv { + + sqlsrv_context ctx; + + sqlsrv_henv( SQLHANDLE handle, error_callback e, void* drv ) : + ctx( handle, SQL_HANDLE_ENV, e, drv ) + { + } +}; + + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* + +// supported server versions (determined at connection time) +enum SERVER_VERSION { + SERVER_VERSION_UNKNOWN = -1, + SERVER_VERSION_2000 = 8, + SERVER_VERSION_2005, + SERVER_VERSION_2008, // use this for anything 2008 or later +}; + +// supported driver versions. +enum DRIVER_VERSION : size_t { + MIN = 0, + ODBC_DRIVER_13 = MIN, + ODBC_DRIVER_11 = 1, + MAX = ODBC_DRIVER_11, +}; + +// forward decl +struct sqlsrv_stmt; +struct stmt_option; + +// *** connection resource structure *** +// this is the resource structure returned when a connection is made. +struct sqlsrv_conn : public sqlsrv_context { + + // instance variables + SERVER_VERSION server_version; // version of the server that we're connected to + + DRIVER_VERSION driver_version; + + // initialize with default values + sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv, SQLSRV_ENCODING encoding TSRMLS_DC ) : + sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) + { + server_version = SERVER_VERSION_UNKNOWN; + driver_version = ODBC_DRIVER_13; + } + + // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be + // called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating + // the sqlsrv_conn with a connection. +}; + +enum SQLSRV_STMT_OPTIONS { + + SQLSRV_STMT_OPTION_INVALID, + SQLSRV_STMT_OPTION_QUERY_TIMEOUT, + SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, + SQLSRV_STMT_OPTION_SCROLLABLE, + SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, + + // Driver specific connection options + SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, + +}; + +namespace ODBCConnOptions { + +const char APP[] = "APP"; +const char ApplicationIntent[] = "ApplicationIntent"; +const char AttachDBFileName[] = "AttachDbFileName"; +const char CharacterSet[] = "CharacterSet"; +const char ConnectionPooling[] = "ConnectionPooling"; +const char Database[] = "Database"; +const char Encrypt[] = "Encrypt"; +const char Failover_Partner[] = "Failover_Partner"; +const char LoginTimeout[] = "LoginTimeout"; +const char MARS_ODBC[] = "MARS_Connection"; +const char MultiSubnetFailover[] = "MultiSubnetFailover"; +const char QuotedId[] = "QuotedId"; +const char TraceFile[] = "TraceFile"; +const char TraceOn[] = "TraceOn"; +const char TrustServerCertificate[] = "TrustServerCertificate"; +const char TransactionIsolation[] = "TransactionIsolation"; +const char WSID[] = "WSID"; +const char UID[] = "UID"; +const char PWD[] = "PWD"; +const char SERVER[] = "Server"; + +} + +enum SQLSRV_CONN_OPTIONS { + + SQLSRV_CONN_OPTION_INVALID, + SQLSRV_CONN_OPTION_APP, + SQLSRV_CONN_OPTION_CHARACTERSET, + SQLSRV_CONN_OPTION_CONN_POOLING, + SQLSRV_CONN_OPTION_DATABASE, + SQLSRV_CONN_OPTION_ENCRYPT, + SQLSRV_CONN_OPTION_FAILOVER_PARTNER, + SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, + SQLSRV_CONN_OPTION_MARS, + SQLSRV_CONN_OPTION_QUOTED_ID, + SQLSRV_CONN_OPTION_TRACE_FILE, + SQLSRV_CONN_OPTION_TRACE_ON, + SQLSRV_CONN_OPTION_TRANS_ISOLATION, + SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, + SQLSRV_CONN_OPTION_WSID, + SQLSRV_CONN_OPTION_ATTACHDBFILENAME, + SQLSRV_CONN_OPTION_APPLICATION_INTENT, + SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, + + // Driver specific connection options + SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, + +}; + + +#define NO_ATTRIBUTE -1 + +// type of connection attributes +enum CONN_ATTR_TYPE { + CONN_ATTR_INT, + CONN_ATTR_BOOL, + CONN_ATTR_STRING, + CONN_ATTR_INVALID, +}; + +// a connection option that includes the callback function that handles that option (e.g., adds it to the connection string or +// sets an attribute) +struct connection_option { + // the name of the option as passed in by the user + const char * sqlsrv_name; + unsigned int sqlsrv_len; + + unsigned int conn_option_key; + // the name of the option in the ODBC connection string + const char * odbc_name; + unsigned int odbc_len; + enum CONN_ATTR_TYPE value_type; + + // process the connection type + // return whether or not the function was successful in processing the connection option + void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC ); +}; + +// simply add the parsed value to the connection string +struct conn_str_append_func { + + static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ); +}; + +struct conn_null_func { + + static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ + TSRMLS_DC ); +}; + +// factory to create a connection (since they are subclassed to instantiate statements) +typedef sqlsrv_conn* (*driver_conn_factory)( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); + +// *** connection functions *** +sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, + const char* server, const char* uid, const char* pwd, + HashTable* options_ht, error_callback err, const connection_option driver_conn_opt_list[], + void* driver, const char* driver_func TSRMLS_DC ); +void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ); +void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ); +void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC ); +void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ); +void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); +bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ); +size_t core_str_zval_is_true( zval* str_zval ); + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +struct stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ); +}; + +struct stmt_option_query_timeout : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_send_at_exec : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_buffered_query_limit : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* opt, zval* value_z TSRMLS_DC ); +}; + +// used to hold the table for statment options +struct stmt_option { + + const char * name; // name of the statement option + unsigned int name_len; // name length + unsigned int key; + std::unique_ptr func; // callback that actually handles the work of the option + +}; + +// holds the stream param and the encoding that it was assigned +struct sqlsrv_stream { + + zval* stream_z; + SQLSRV_ENCODING encoding; + SQLUSMALLINT field_index; + SQLSMALLINT sql_type; + sqlsrv_stmt* stmt; + + sqlsrv_stream( zval* str_z, SQLSRV_ENCODING enc ) : + stream_z( str_z ), encoding( enc ), field_index( 0 ), sql_type( SQL_UNKNOWN_TYPE ), stmt( NULL ) + { + } + + sqlsrv_stream() : stream_z( NULL ), encoding( SQLSRV_ENCODING_INVALID ), field_index( 0 ), sql_type( SQL_UNKNOWN_TYPE ), stmt( NULL ) + { + } +}; + +// close any active stream +void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); + +extern php_stream_wrapper g_sqlsrv_stream_wrapper; + +// resource constants used when registering the stream type with PHP +#define SQLSRV_STREAM_WRAPPER "sqlsrv" +#define SQLSRV_STREAM "sqlsrv_stream" + +// holds the output parameter information. Strings also need the encoding and other information for +// after processing. Only integer, float, and strings are allowable output parameters. +struct sqlsrv_output_param { + + zval* param_z; + SQLSRV_ENCODING encoding; + SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement + SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer + bool is_bool; + + // string output param constructor + sqlsrv_output_param( zval* p_z, SQLSRV_ENCODING enc, int num, SQLUINTEGER buffer_len ) : + param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false ) + { + } + + // every other type output parameter constructor + sqlsrv_output_param( zval* p_z, int num, bool is_bool ) : + param_z( p_z ), + encoding( SQLSRV_ENCODING_INVALID ), + param_num( num ), + original_buffer_len( -1 ), + is_bool( is_bool ) + { + } +}; + +// forward decls +struct sqlsrv_result_set; + +// *** Statement resource structure *** +struct sqlsrv_stmt : public sqlsrv_context { + + void free_param_data( TSRMLS_D ); + virtual void new_result_set( TSRMLS_D ); + + sqlsrv_conn* conn; // Connection that created this statement + + bool executed; // Whether the statement has been executed yet (used for error messages) + bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row + sqlsrv_result_set* current_results; // Current result set + SQLULEN cursor_type; // Type of cursor for the current result set + bool has_rows; // Has_rows is set if there are actual rows in the row set + bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called + int last_field_index; // last field retrieved by core_sqlsrv_get_field + bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the + // last results + unsigned long query_timeout; // maximum allowed statement execution time + zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) + + // holds output pointers for SQLBindParameter + // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving + // memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold + std::deque param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter + zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP + zval output_params; // hold all the output parameters + zval param_streams; // track which streams to send data to the server + zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects + bool send_streams_at_exec; // send all stream data right after execution before returning + sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter + unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string + // to the server) + zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals + zval active_stream; // the currently active stream reading data from the database + + sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); + virtual ~sqlsrv_stmt( void ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) = 0; + +}; + +// *** field metadata struct *** +struct field_meta_data { + + sqlsrv_malloc_auto_ptr field_name; + SQLSMALLINT field_name_len; + SQLSMALLINT field_type; + SQLULEN field_size; + SQLULEN field_precision; + SQLSMALLINT field_scale; + SQLSMALLINT field_is_nullable; + + field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0), + field_scale (0), field_is_nullable(0) + { + } + + ~field_meta_data() + { + } +}; + +// *** statement constants *** +// unknown column size used by core_sqlsrv_bind_param when the user doesn't supply a value +const SQLULEN SQLSRV_UNKNOWN_SIZE = 0xffffffff; +const int SQLSRV_DEFAULT_SIZE = -1; // size given for an output parameter that doesn't really need one (e.g., int) + +// uninitialized query timeout value +const unsigned int QUERY_TIMEOUT_INVALID = 0xffffffff; + +// special buffered query constant +#ifndef _WIN32 +const size_t SQLSRV_CURSOR_BUFFERED = 42; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant +#else +const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doesn't map to any other SQL_CURSOR_* constant +#endif // !_WIN32 + +// factory to create a statement +typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); + +// *** statement functions *** +sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, + const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ); +void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, + SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, + SQLSMALLINT decimal_digits TSRMLS_DC ); +void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql = NULL, int sql_len = 0 ); +field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ); +bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ); +void core_sqlsrv_get_field(sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_phptype, bool prefer_string, + _Out_ void*& field_value, _Out_ SQLLEN* field_length, bool cache_field, + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC); +bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ); +void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params = true, bool throw_on_errors = true ); +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong paramno, zval* param_z TSRMLS_DC ); +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ); +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ); +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); +void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); +bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); + + +//********************************************************************************************************************************* +// Result Set +//********************************************************************************************************************************* + +// Abstract the result set so that a result set can either be used as is from ODBC or buffered. +// This is not a complete abstraction of a result set. Only enough is abstracted to allow for +// information and capabilities normally not available when a result set is not buffered +// (e.g., forward only vs buffered means row count is available and cursor movement is possible). +// Otherwise, normal ODBC calls are still valid and should be used to get information about the +// result set (e.g., SQLNumResultCols). + +struct sqlsrv_result_set { + + sqlsrv_stmt* odbc; + + explicit sqlsrv_result_set( sqlsrv_stmt* ); + virtual ~sqlsrv_result_set( void ) { } + + virtual bool cached( int field_index ) = 0; + virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) = 0; + virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC )= 0; + virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; + virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ) = 0; + virtual SQLLEN row_count( TSRMLS_D ) = 0; +}; + +struct sqlsrv_odbc_result_set : public sqlsrv_result_set { + + explicit sqlsrv_odbc_result_set( sqlsrv_stmt* ); + virtual ~sqlsrv_odbc_result_set( void ); + + virtual bool cached( int field_index ) { return false; } + virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); + virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ); + virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); + virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); + virtual SQLLEN row_count( TSRMLS_D ); + + private: + // prevent invalid instantiations and assignments + sqlsrv_odbc_result_set( void ); + sqlsrv_odbc_result_set( sqlsrv_odbc_result_set& ); + sqlsrv_odbc_result_set& operator=( sqlsrv_odbc_result_set& ); +}; + +struct sqlsrv_buffered_result_set : public sqlsrv_result_set { + + struct meta_data { + SQLSMALLINT type; + SQLSMALLINT c_type; // convenience + SQLULEN offset; // in bytes + SQLULEN length; // in bytes + SQLSMALLINT scale; + + static const SQLULEN SIZE_UNKNOWN = 0; + }; + + // default maximum amount of memory that a buffered query can consume + #define INI_BUFFERED_QUERY_LIMIT_DEFAULT "10240" // default used by the php.ini settings + static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB + static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; + + explicit sqlsrv_buffered_result_set( sqlsrv_stmt* odbc TSRMLS_DC ); + virtual ~sqlsrv_buffered_result_set( void ); + + virtual bool cached( int field_index ) { return true; } + virtual SQLRETURN fetch( SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ); + virtual SQLRETURN get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ); + virtual SQLRETURN get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); + virtual sqlsrv_error* get_diag_rec( SQLSMALLINT record_number ); + virtual SQLLEN row_count( TSRMLS_D ); + + // buffered result set specific + SQLSMALLINT column_count( void ) + { + return col_count; + } + + struct meta_data& col_meta_data( SQLSMALLINT i ) + { + return meta[i]; + } + + private: + // prevent invalid instantiations and assignments + sqlsrv_buffered_result_set( void ); + sqlsrv_buffered_result_set( sqlsrv_buffered_result_set& ); + sqlsrv_buffered_result_set& operator=( sqlsrv_buffered_result_set& ); + + HashTable* cache; // rows of data kept in index based hash table + SQLSMALLINT col_count; // number of columns in the current result set + meta_data* meta; // metadata for fields in the cache + SQLLEN current; // 1 based, 0 means before first row + sqlsrv_error_auto_ptr last_error; // if an error occurred, it is kept here + SQLUSMALLINT last_field_index; // the last field data retrieved from + SQLLEN read_so_far; // position within string to read from (for partial reads of strings) + sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use + SQLLEN temp_length; // number of bytes in the temp conversion buffer + + typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; + + // two dimentional sparse matrix that holds the [from][to] functions that do conversions + static conv_matrix_t conv_matrix; + + // string conversion functions + SQLRETURN binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + + // long conversion functions + SQLRETURN to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + + // double conversion functions + SQLRETURN to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + + // string to number conversion functions + // Future: See if these can be converted directly to template member functions + SQLRETURN string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + SQLRETURN wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, + _Out_ SQLLEN* out_buffer_length ); + + // utility functions for conversions + unsigned char* get_row( void ); +}; + +//********************************************************************************************************************************* +// Utility +//********************************************************************************************************************************* + +// Simple macro to alleviate unused variable warnings. These are optimized out by the compiler. +// We use this since the unused variables are buried in the PHP_FUNCTION macro. +#define SQLSRV_UNUSED( var ) var; + +// do a heap check in debug mode, but only print errors, not all of the allocations +#define MEMCHECK_SILENT 1 + +// utility functions shared by multiple callers across files +bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len); +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len); +bool validate_string(char* string, SQLLEN& len); +bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const SQLWCHAR* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ); +SQLWCHAR* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); + +//********************************************************************************************************************************* +// Error handling routines and Predefined Errors +//********************************************************************************************************************************* + +enum SQLSRV_ERROR_CODES { + + SQLSRV_ERROR_ODBC, + SQLSRV_ERROR_DRIVER_NOT_INSTALLED, + SQLSRV_ERROR_ZEND_HASH, + SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, + SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, + SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, + SQLSRV_ERROR_ZEND_STREAM, + SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, + SQLSRV_ERROR_FETCH_PAST_END, + SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, + SQLSRV_ERROR_NO_FIELDS, + SQLSRV_ERROR_INVALID_TYPE, + SQLSRV_ERROR_FETCH_NOT_CALLED, + SQLSRV_ERROR_NO_DATA, + SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, + SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED, + SQLSRV_ERROR_NEXT_RESULT_PAST_END, + SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, + SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, + SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, + SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, + SQLSRV_ERROR_INVALID_CONNECTION_KEY, + SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, + SQLSRV_ERROR_INVALID_OPTION_KEY, + SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, + SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, + SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, + SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, + SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, + SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, + SQLSRV_ERROR_STREAM_CREATE, + SQLSRV_ERROR_MARS_OFF, + SQLSRV_ERROR_FIELD_INDEX_ERROR, + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, + SQLSRV_ERROR_INVALID_BUFFER_LIMIT, + + // Driver specific error codes starts from here. + SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, + +}; + +// the message returned by ODBC Driver 11 for SQL Server +static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command", + "[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" }; + +// SQLSTATE for all internal errors +extern SQLCHAR IMSSP[]; + +// SQLSTATE for all internal warnings +extern SQLCHAR SSPWARN[]; + +// flags passed to sqlsrv_errors to filter its return values +enum error_handling_flags { + SQLSRV_ERR_ERRORS, + SQLSRV_ERR_WARNINGS, + SQLSRV_ERR_ALL +}; + +// *** internal error macros and functions *** +// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the +// errno is 1 based. It returns it as an array with 3 members: +// 1/SQLSTATE) sqlstate +// 2/code) driver specific error code +// 3/message) driver specific error message +// The fetch type determines if the indices are numeric, associative, or both. +bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, _Out_ sqlsrv_error_auto_ptr& error, + logging_severity severity TSRMLS_DC ); + +// format and return a driver specfic error +void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, + sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ); + + +// return the message for the HRESULT returned by GetLastError. Some driver errors use this to +// return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails. +const char* get_last_error_message( DWORD last_error = 0 ); + +// a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer +DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ); + +// convenience functions that overload either a reference or a pointer so we can use +// either in the CHECK_* functions. +inline bool call_error_handler( sqlsrv_context& ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) +{ + va_list print_params; + va_start( print_params, warning ); + bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); + va_end( print_params ); + return ignored; +} + +inline bool call_error_handler( sqlsrv_context* ctx, unsigned long sqlsrv_error_code TSRMLS_DC, bool warning, ... ) +{ + va_list print_params; + va_start( print_params, warning ); + bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); + va_end( print_params ); + return ignored; +} + +// PHP equivalent of ASSERT. C asserts cause a dialog to show and halt the process which +// we don't want on a web server + +#define SQLSRV_ASSERT( condition, msg, ...) if( !(condition)) DIE( msg, ## __VA_ARGS__ ); + +#if defined( PHP_DEBUG ) + +#define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) \ + if( !(condition)) { \ + DIE (msg, ## __VA_ARGS__ ); \ + } + +#else + + #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) ((void)0) + +#endif + +// check to see if the sqlstate is 01004, truncated field retrieved. Used for retrieving large fields. +inline bool is_truncated_warning( SQLCHAR* state ) +{ +#if defined(ZEND_DEBUG) + if( state == NULL || strlen( reinterpret_cast( state )) != 5 ) { \ + DIE( "Incorrect SQLSTATE given to is_truncated_warning." ); \ + } +#endif + return (state[0] == '0' && state[1] == '1' && state[2] == '0' && state [3] == '0' && state [4] == '4'); +} + +// Macros for handling errors. These macros are simplified if statements that take boilerplate +// code down to a single line to avoid distractions in the code. + +#define CHECK_ERROR_EX( unique, condition, context, ssphp, ... ) \ + bool flag##unique = (condition); \ + bool ignored##unique = true; \ + if (flag##unique) { \ + ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, ## __VA_ARGS__ ); \ + } \ + if( !ignored##unique ) + +#define CHECK_ERROR_UNIQUE( unique, condition, context, ssphp, ...) \ + CHECK_ERROR_EX( unique, condition, context, ssphp, ## __VA_ARGS__ ) + +#define CHECK_ERROR( condition, context, ... ) \ + CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, 0, ## __VA_ARGS__ ) + +#define CHECK_CUSTOM_ERROR( condition, context, ssphp, ... ) \ + CHECK_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, ## __VA_ARGS__ ) + +#define CHECK_SQL_ERROR( result, context, ... ) \ + SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ + CHECK_ERROR( result == SQL_ERROR, context, ## __VA_ARGS__ ) + +#define CHECK_WARNING_AS_ERROR_UNIQUE( unique, condition, context, ssphp, ... ) \ + bool ignored##unique = true; \ + if( condition ) { \ + ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, ## __VA_ARGS__ ); \ + } \ + if( !ignored##unique ) + +#define CHECK_SQL_WARNING_AS_ERROR( result, context, ... ) \ + CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__,( result == SQL_SUCCESS_WITH_INFO ), context, SQLSRV_ERROR_ODBC, ## __VA_ARGS__ ) + +#define CHECK_SQL_WARNING( result, context, ... ) \ + if( result == SQL_SUCCESS_WITH_INFO ) { \ + (void)call_error_handler( context, 0 TSRMLS_CC, /*warning*/ true, ## __VA_ARGS__ ); \ + } + +#define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \ + CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, ## __VA_ARGS__ ) + +#define CHECK_ZEND_ERROR( zr, ctx, error, ... ) \ + CHECK_ERROR_UNIQUE( __COUNTER__, ( zr == FAILURE ), ctx, error, ## __VA_ARGS__ ) \ + +#define CHECK_SQL_ERROR_OR_WARNING( result, context, ... ) \ + SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ + bool ignored = true; \ + if( result == SQL_ERROR ) { \ + ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, false, ##__VA_ARGS__ ); \ + } \ + else if( result == SQL_SUCCESS_WITH_INFO ) { \ + ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, true TSRMLS_CC, ##__VA_ARGS__ ); \ + } \ + if( !ignored ) + +// throw an exception after it has been hooked into the custom error handler +#define THROW_CORE_ERROR( ctx, custom, ... ) \ + (void)call_error_handler( ctx, custom TSRMLS_CC, /*warning*/ false, ## __VA_ARGS__ ); \ + throw core::CoreException(); + +//********************************************************************************************************************************* +// ODBC/Zend function wrappers +//********************************************************************************************************************************* + +namespace core { + + // base exception for the driver + struct CoreException : public std::exception { + + CoreException() + { + } + }; + + inline void check_for_mars_error( sqlsrv_stmt* stmt, SQLRETURN r TSRMLS_DC ) + { + // We check for the 'connection busy' error caused by having MultipleActiveResultSets off + // and return a more helpful message prepended to the ODBC errors if that error occurs + if( !SQL_SUCCEEDED( r )) { + + SQLCHAR err_msg[ SQL_MAX_MESSAGE_LENGTH + 1 ] = { '\0' }; + SQLSMALLINT len = 0; + + SQLRETURN rtemp = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, + err_msg, SQL_MAX_MESSAGE_LENGTH, &len ); + + CHECK_SQL_ERROR_OR_WARNING( rtemp, stmt ) { + + throw CoreException(); + } + std::size_t driver_version = stmt->conn->driver_version; + if( !strcmp( reinterpret_cast( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) { + + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); + } + } + } + + // *** ODBC wrappers *** + + // wrap the ODBC functions to throw exceptions rather than use the return value to signal errors + // some of the signatures have been altered to be more convenient since the return value is no longer + // required to return the status of the call (e.g., SQLNumResultCols). + // These functions take the sqlsrv_context type. However, since the error handling code can alter + // the context to hold the error, they are not passed as const. + + inline SQLRETURN SQLGetDiagField( sqlsrv_context* ctx, SQLSMALLINT record_number, SQLSMALLINT diag_identifier, + _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) + { + SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, + diag_info_buffer, buffer_length, out_buffer_length ); + + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { + throw CoreException(); + } + + return r; + } + + inline void SQLAllocHandle( SQLSMALLINT HandleType, sqlsrv_context& InputHandle, + _Out_writes_(1) SQLHANDLE* OutputHandlePtr TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); + CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) { + throw CoreException(); + } + } + + inline void SQLBindParameter( sqlsrv_stmt* stmt, + SQLUSMALLINT ParameterNumber, + SQLSMALLINT InputOutputType, + SQLSMALLINT ValueType, + SQLSMALLINT ParameterType, + SQLULEN ColumnSize, + SQLSMALLINT DecimalDigits, + _Inout_ SQLPOINTER ParameterValuePtr, + SQLLEN BufferLength, + _Inout_ SQLLEN * StrLen_Or_IndPtr + TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, + DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline void SQLColAttribute( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, + _Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC ) + { + SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, + buffer_length, out_buffer_length, field_type_num ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + inline void SQLColAttributeW( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLUSMALLINT field_identifier, + _Out_ SQLPOINTER field_type_char, SQLSMALLINT buffer_length, + _Out_ SQLSMALLINT* out_buffer_length, _Out_ SQLLEN* field_type_num TSRMLS_DC ) + { + SQLRETURN r = ::SQLColAttributeW( stmt->handle(), field_index, field_identifier, field_type_char, + buffer_length, out_buffer_length, field_type_num ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + inline void SQLDescribeCol( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLCHAR* col_name, SQLSMALLINT col_name_length, + _Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size, + _Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, + data_type, col_size, decimal_digits, nullable); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + inline void SQLDescribeColW( sqlsrv_stmt* stmt, SQLSMALLINT colno, _Out_ SQLWCHAR* col_name, SQLSMALLINT col_name_length, + _Out_ SQLSMALLINT* col_name_length_out, SQLSMALLINT* data_type, _Out_ SQLULEN* col_size, + _Out_ SQLSMALLINT* decimal_digits, _Out_ SQLSMALLINT* nullable TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLDescribeColW( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, + data_type, col_size, decimal_digits, nullable ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + inline void SQLEndTran( SQLSMALLINT handleType, sqlsrv_conn* conn, SQLSMALLINT completionType TSRMLS_DC ) + { + SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType ); + + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { + throw CoreException(); + } + } + + // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success + inline SQLRETURN SQLExecDirect( sqlsrv_stmt* stmt, char* sql TSRMLS_DC ) + { + SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast( sql ), SQL_NTS ); + + check_for_mars_error( stmt, r TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + + throw CoreException(); + } + return r; + } + + inline SQLRETURN SQLExecDirectW( sqlsrv_stmt* stmt, SQLWCHAR* wsql TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast( wsql ), SQL_NTS ); + + check_for_mars_error( stmt, r TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + return r; + } + + // SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success + inline SQLRETURN SQLExecute( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLExecute( stmt->handle() ); + + check_for_mars_error( stmt, r TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return r; + } + + inline SQLRETURN SQLFetchScroll( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLLEN fetch_offset TSRMLS_DC ) + { + SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + return r; + } + + + // wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine + inline void SQLFreeHandle( sqlsrv_context& ctx TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() ); + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} + } + + inline SQLRETURN SQLGetData( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLSMALLINT target_type, + _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, + bool handle_warning TSRMLS_DC ) + { + SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); + + if( r == SQL_NO_DATA ) + return r; + + CHECK_SQL_ERROR( r, stmt ) { + throw CoreException(); + } + + if( handle_warning ) { + CHECK_SQL_WARNING_AS_ERROR( r, stmt ) { + throw CoreException(); + } + } + + return r; + } + + + inline void SQLGetInfo( sqlsrv_conn* conn, SQLUSMALLINT info_type, _Out_ SQLPOINTER info_value, SQLSMALLINT buffer_len, + _Out_ SQLSMALLINT* str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); + + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { + throw CoreException(); + } + } + + + inline void SQLGetTypeInfo( sqlsrv_stmt* stmt, SQLUSMALLINT data_type TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLGetTypeInfo( stmt->handle(), data_type ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + // SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set. + inline SQLRETURN SQLMoreResults( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r = ::SQLMoreResults( stmt->handle() ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return r; + } + + inline SQLSMALLINT SQLNumResultCols( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r; + SQLSMALLINT num_cols; + r = ::SQLNumResultCols( stmt->handle(), &num_cols ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return num_cols; + } + + // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more + // parameters or when the parameters are all processed. + inline SQLRETURN SQLParamData( sqlsrv_stmt* stmt, _Out_ SQLPOINTER* value_ptr_ptr TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + return r; + } + + inline void SQLPrepareW( sqlsrv_stmt* stmt, SQLWCHAR * sql, SQLINTEGER sql_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLPrepareW( stmt->handle(), sql, sql_len ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline void SQLPutData( sqlsrv_stmt* stmt, SQLPOINTER data_ptr, SQLLEN strlen_or_ind TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + inline SQLLEN SQLRowCount( sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r; + SQLLEN rows_affected; + + r = ::SQLRowCount( stmt->handle(), &rows_affected ); + + // On Linux platform + // DriverName: libmsodbcsql-13.0.so.0.0 + // DriverODBCVer: 03.52 + // DriverVer: 13.00.0000 + // unixODBC: 2.3.1 + // r = ::SQLRowCount( stmt->handle(), &rows_affected ); + // returns r=-1 for an empty result set. +#ifndef _WIN32 + if( r == -1 && rows_affected == -1 ) + return 0; +#endif // !_WIN32 + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + + return rows_affected; + } + + + inline void SQLSetConnectAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len ); + + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { + throw CoreException(); + } + } + + + inline void SQLSetEnvAttr( sqlsrv_context& ctx, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len ); + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { + throw CoreException(); + } + } + + inline void SQLSetConnectAttr( sqlsrv_conn* conn, SQLINTEGER attribute, SQLPOINTER value_ptr, SQLINTEGER value_len TSRMLS_DC ) + { + SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); + + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { + throw CoreException(); + } + } + + inline void SQLSetStmtAttr( sqlsrv_stmt* stmt, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER str_len TSRMLS_DC ) + { + SQLRETURN r; + r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + + + // *** zend wrappers *** + + //zend_resource_dtor sets the type of destroyed resources to -1 + #define RSRC_INVALID_TYPE -1 + + // wrapper for ZVAL_STRINGL macro. ZVAL_STRINGL always allocates memory when initialzing new string from char string + // so allocated memory inside of value_z should be released before assigning it to the new string + inline void sqlsrv_zval_stringl(zval* value_z, const char* str, const std::size_t str_len) + { + if (Z_TYPE_P(value_z) == IS_STRING && Z_STR_P(value_z) != NULL) { + zend_string* temp_zstr = zend_string_init(str, str_len, 0); + zend_string_release(Z_STR_P(value_z)); + ZVAL_NEW_STR(value_z, temp_zstr); + } + else { + ZVAL_STRINGL(value_z, str, str_len); + } + } + + + // exception thrown when a zend function wrapped here fails. + + // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw + // exceptions when an error occurs. They are prefaced with sqlsrv_ because many of the zend functions are + // actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system. + // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error + // that can be thrown from it. + + inline void sqlsrv_add_index_zval( sqlsrv_context& ctx, zval* array, zend_ulong index, zval* value TSRMLS_DC) + { + int zr = ::add_index_zval( array, index, value ); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_next_index_zval( sqlsrv_context& ctx, zval* array, zval* value TSRMLS_DC) + { + int zr = ::add_next_index_zval( array, value ); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_assoc_null( sqlsrv_context& ctx, zval* array_z, const char* key TSRMLS_DC ) + { + int zr = ::add_assoc_null( array_z, key ); + CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_assoc_long( sqlsrv_context& ctx, zval* array_z, const char* key, zend_long val TSRMLS_DC ) + { + int zr = ::add_assoc_long( array_z, key, val ); + CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_add_assoc_string( sqlsrv_context& ctx, zval* array_z, const char* key, char* val, bool duplicate TSRMLS_DC ) + { + int zr = ::add_assoc_string(array_z, key, val); + CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + if (duplicate == 0) { + sqlsrv_free(val); + } + } + + inline void sqlsrv_array_init( sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) + { + int zr = ::array_init(new_array); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_php_stream_from_zval_no_verify( sqlsrv_context& ctx, php_stream*& stream, zval* stream_z TSRMLS_DC ) + { + // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment + php_stream_from_zval_no_verify( stream, stream_z ); + CHECK_CUSTOM_ERROR( stream == NULL, ctx, SQLSRV_ERROR_ZEND_STREAM ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_get_current_data(sqlsrv_context& ctx, HashTable* ht, _Out_ zval*& output_data TSRMLS_DC) + { + int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_get_current_data_ptr(sqlsrv_context& ctx, HashTable* ht, _Out_ void*& output_data TSRMLS_DC) + { + int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_index_del( sqlsrv_context& ctx, HashTable* ht, zend_ulong index TSRMLS_DC ) + { + int zr = ::zend_hash_index_del( ht, index ); + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_index_update( sqlsrv_context& ctx, HashTable* ht, zend_ulong index, zval* data_z TSRMLS_DC ) + { + int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_index_update_ptr(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData TSRMLS_DC) + { + int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + + inline void sqlsrv_zend_hash_index_update_mem(sqlsrv_context& ctx, HashTable* ht, zend_ulong index, void* pData, std::size_t size TSRMLS_DC) + { + int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert( sqlsrv_context& ctx, HashTable* ht, zval* data TSRMLS_DC ) + { + int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert_mem(sqlsrv_context& ctx, HashTable* ht, void* data, uint data_size TSRMLS_DC) + { + int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_next_index_insert_ptr(sqlsrv_context& ctx, HashTable* ht, void* data TSRMLS_DC) + { + int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } + + inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, HashTable* ht, uint32_t initial_size, + dtor_func_t dtor_fn, zend_bool persistent TSRMLS_DC ) + { + ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); + } + +template +sqlsrv_stmt* allocate_stmt( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) +{ + return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC ); +} + +template +sqlsrv_conn* allocate_conn( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) +{ + return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC ); +} + +} // namespace core + +// connection attribute functions +template +struct str_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), + static_cast(Z_STRLEN_P( value )) TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +#endif // CORE_SQLSRV_H diff --git a/sqlsrv/core_stmt.cpp b/source/shared/core_stmt.cpp similarity index 91% rename from sqlsrv/core_stmt.cpp rename to source/shared/core_stmt.cpp index a4ef0d9dd..faa0c5235 100644 --- a/sqlsrv/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1,2471 +1,2500 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_stmt.cpp -// -// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -namespace { - -// certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the -// results of every field request, and if it is out of order, we cache those for preceding fields. -struct field_cache { - - void* value; - SQLLEN len; - sqlsrv_phptype type; - - field_cache( void* field_value, SQLLEN field_len, sqlsrv_phptype t ) - : type( t ) - { - // if the value is NULL, then just record a NULL pointer - if( field_value != NULL ) { - value = sqlsrv_malloc( field_len ); - memcpy_s( value, field_len, field_value, field_len ); - len = field_len; - } - else { - value = NULL; - len = 0; - } - } - - // no destructor because we don't want to release the memory when it goes out of scope, but instead we - // rely on the hash table destructor to free the memory -}; - -const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field - -// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads -const unsigned int UTF8_MIDBYTE_MASK = 0xc0; -const unsigned int UTF8_MIDBYTE_TAG = 0x80; -const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0; -const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0; -const unsigned int UTF8_3BYTESEQ_TAG = 0xe0; -const unsigned int UTF8_4BYTESEQ_TAG = 0xf0; -const unsigned int UTF8_NBYTESEQ_MASK = 0xf0; - -// constants used to convert from a DateTime object to a string which is sent to the server. -// Using the format defined by the ODBC documentation at http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx -namespace DateTime { - -const char DATETIME_CLASS_NAME[] = "DateTime"; -const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1; -const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P"; -const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT ); -const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u"; -const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT ); -const char DATE_FORMAT[] = "Y-m-d"; -const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT ); - -} - -// *** internal functions *** -// Only declarations are put here. Functions contain the documentation they need at their definition sites. -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ); -size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ); -bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC ); -bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z ); -void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC); -// returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); -void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); -// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) -void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); -void field_cache_dtor( zval* data_z ); -void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ); -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ); -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ); -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); -// assure there is enough space for the output parameter string -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, - SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, - SQLLEN& buffer_len TSRMLS_DC ); -void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ); -// send all the stream data -void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ); -// called when a bound output string parameter is to be destroyed -void sqlsrv_output_param_dtor( zval* data ); -// called when a bound stream parameter is to be destroyed. -void sqlsrv_stream_dtor( zval* data ); -bool is_streamable_type( SQLINTEGER sql_type ); - -} - -// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier. -sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : - sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ), - conn( c ), - executed( false ), - past_fetch_end( false ), - current_results( NULL ), - cursor_type( SQL_CURSOR_FORWARD_ONLY ), - has_rows( false ), - fetch_called( false ), - last_field_index( -1 ), - past_next_result_end( false ), - param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte - send_streams_at_exec( true ), - current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), - current_stream_read( 0 ), - query_timeout( QUERY_TIMEOUT_INVALID ), - buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) -{ - ZVAL_UNDEF( &active_stream ); - // initialize the input string parameters array (which holds zvals) - core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); - - // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) - ZVAL_NEW_ARR( ¶m_streams ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC); - - // initialize the (input only) datetime parameters of converted date time objects to strings - array_init( ¶m_datetime_buffers ); - - // initialize the output string parameters (which holds sqlsrv_output_param structures) - ZVAL_NEW_ARR( &output_params ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC); - - // initialize the field cache - ZVAL_NEW_ARR( &field_cache ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC); -} - -// desctructor for sqlsrv statement. -sqlsrv_stmt::~sqlsrv_stmt( void ) -{ - if( Z_TYPE( active_stream ) != IS_UNDEF ) { - TSRMLS_FETCH(); - close_active_stream( this TSRMLS_CC ); - } - - // delete any current results - if( current_results ) { - current_results->~sqlsrv_result_set(); - efree( current_results ); - current_results = NULL; - } - - invalidate(); - zval_ptr_dtor( ¶m_input_strings ); - zval_ptr_dtor( &output_params ); - zval_ptr_dtor( ¶m_streams ); - zval_ptr_dtor( ¶m_datetime_buffers ); - zval_ptr_dtor( &field_cache ); -} - - -// centralized place to release (without destroying the hash tables -// themselves) all the parameter data that accrues during the -// execution phase. -void sqlsrv_stmt::free_param_data( TSRMLS_D ) -{ - SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY, - "sqlsrv_stmt::free_param_data: Param zvals aren't arrays." ); - zend_hash_clean( Z_ARRVAL( param_input_strings )); - zend_hash_clean( Z_ARRVAL( output_params )); - zend_hash_clean( Z_ARRVAL( param_streams )); - zend_hash_clean( Z_ARRVAL( param_datetime_buffers )); - zend_hash_clean( Z_ARRVAL( field_cache )); -} - - -// to be called whenever a new result set is created, such as after an -// execute or next_result. Resets the state variables. - -void sqlsrv_stmt::new_result_set( TSRMLS_D ) -{ - this->fetch_called = false; - this->has_rows = false; - this->past_next_result_end = false; - this->past_fetch_end = false; - this->last_field_index = -1; - - // delete any current results - if( current_results ) { - current_results->~sqlsrv_result_set(); - efree( current_results ); - current_results = NULL; - } - - // create a new result set - if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { - current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC ); - } - else { - current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this ); - } -} - -// core_sqlsrv_create_stmt -// Common code to allocate a statement from either driver. Returns a valid driver statement object or -// throws an exception if an error occurs. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// stmt_factory - factory method to create a statement. -// options_ht - A HashTable of user provided options to be set on the statement. -// valid_stmt_opts - An array of valid driver supported statement options. -// err - callback for error handling -// driver - reference to caller -// Return -// Returns the created statement - -sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, - const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ) -{ - sqlsrv_malloc_auto_ptr stmt; - SQLHANDLE stmt_h = SQL_NULL_HANDLE; - - try { - - core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC ); - - stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC ); - - stmt->conn = conn; - - // handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free - // in the catch block below. - stmt_h = SQL_NULL_HANDLE; - - // process the options array given to core_sqlsrv_prepare. - if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - zend_ulong index = -1; - zend_string *key = NULL; - zval* value_z = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) { - - int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); - - const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); - - // if the key didn't match, then return the error to the script. - // The driver layer should ensure that the key is valid. - DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); - - // perform the actions the statement option needs done. - (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - } - - sqlsrv_stmt* return_stmt = stmt; - stmt.transferred(); - - return return_stmt; - } - catch( core::CoreException& ) - { - if( stmt ) { - - conn->set_last_error( stmt->last_error() ); - stmt->~sqlsrv_stmt(); - } - - // if allocating the handle failed before the statement was allocated, free the handle - if( stmt_h != SQL_NULL_HANDLE) { - ::SQLFreeHandle( SQL_HANDLE_STMT, stmt_h ); - } - - throw; - } - catch( ... ) { - - DIE( "core_sqlsrv_allocate_stmt: Unknown exception caught." ); - } -} - - -// core_sqlsrv_bind_param -// Binds a parameter using SQLBindParameter. It allocates memory and handles other details -// in translating between the driver and ODBC. -// Parameters: -// param_num - number of the parameter, 0 based -// param_z - zval of the parameter -// php_out_type - type to return for output parameter -// sql_type - ODBC constant for the SQL Server type (SQL_UNKNOWN_TYPE = 0 means not known, so infer defaults) -// column_size - length of the field on the server (SQLSRV_UKNOWN_SIZE means not known, so infer defaults) -// decimal_digits - if column_size is valid and the type contains a scale, this contains the scale -// Return: -// Nothing, though an exception is thrown if an error occurs -// The php type of the parameter is taken from the zval. -// The sql type is given as a hint if the driver provides it. - -void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, - SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, - SQLSMALLINT decimal_digits TSRMLS_DC ) -{ - SQLSMALLINT c_type; - SQLPOINTER buffer = NULL; - SQLLEN buffer_len = 0; - - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT, - "core_sqlsrv_bind_param: Invalid parameter direction." ); - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || php_out_type != SQLSRV_PHPTYPE_INVALID, - "core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param." ); - - try { - - // check is only < because params are 0 based - CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) { - throw core::CoreException(); - } - - // resize the statements array of int_ptrs if the parameter isn't already set. - if( stmt->param_ind_ptrs.size() < static_cast(param_num + 1) ) { - stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA ); - } - SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ]; - - zval* param_ref = param_z; - if ( Z_ISREF_P( param_z ) ) { - ZVAL_DEREF( param_z ); - } - bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL ); - bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE ); - // if the user asks for for a specific type for input and output, make sure the data type we send matches the data we - // type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so - // we always let that match if they want a string back. - if( direction == SQL_PARAM_INPUT_OUTPUT ) { - bool match = false; - switch( php_out_type ) { - case SQLSRV_PHPTYPE_INT: - if( zval_was_null || zval_was_bool ) { - convert_to_long( param_z ); - } - match = Z_TYPE_P( param_z ) == IS_LONG; - break; - case SQLSRV_PHPTYPE_FLOAT: - if( zval_was_null ) { - convert_to_double( param_z ); - } - match = Z_TYPE_P( param_z ) == IS_DOUBLE; - break; - case SQLSRV_PHPTYPE_STRING: - // anything can be converted to a string - convert_to_string( param_z ); - match = true; - break; - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_DATETIME: - case SQLSRV_PHPTYPE_STREAM: - SQLSRV_ASSERT( false, "Invalid type for an output parameter." ); - break; - default: - SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." ); - break; - } - CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) { - throw core::CoreException(); - } - } - - // if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so - // when the buffer is filled, the type is correct - if( direction == SQL_PARAM_OUTPUT ) { - switch( php_out_type ) { - case SQLSRV_PHPTYPE_INT: - convert_to_long( param_z ); - break; - case SQLSRV_PHPTYPE_FLOAT: - convert_to_double( param_z ); - break; - case SQLSRV_PHPTYPE_STRING: - convert_to_string( param_z ); - break; - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_DATETIME: - case SQLSRV_PHPTYPE_STREAM: - SQLSRV_ASSERT( false, "Invalid type for an output parameter" ); - break; - default: - SQLSRV_ASSERT( false, "Uknown SQLSRV_PHPTYPE_* constant given" ); - break; - } - } - - SQLSRV_ASSERT(( Z_TYPE_P( param_z ) != IS_STRING && Z_TYPE_P( param_z ) != IS_RESOURCE ) || - ( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 || - encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" ); - - // if the sql type is unknown, then set the default based on the PHP type passed in - if( sql_type == SQL_UNKNOWN_TYPE ) { - default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); - } - - // if the size is unknown, then set the default based on the PHP type passed in - if( column_size == SQLSRV_UNKNOWN_SIZE ) { - default_sql_size_and_scale( stmt, static_cast( param_num ), param_z, encoding, column_size, decimal_digits TSRMLS_CC ); - } - - // determine the ODBC C type - c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC ); - - // set the buffer based on the PHP parameter type - switch( Z_TYPE_P( param_z )) { - - case IS_NULL: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - ind_ptr = SQL_NULL_DATA; - buffer = NULL; - buffer_len = 0; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - { - // if it is boolean, set the lval to 0 or 1 - convert_to_long( param_z ); - buffer = ¶m_z->value; - buffer_len = sizeof( Z_LVAL_P( param_z )); - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool ); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - } - } - break; - case IS_DOUBLE: - { - buffer = ¶m_z->value; - buffer_len = sizeof( Z_DVAL_P( param_z )); - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_ref, static_cast( param_num ), false ); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - } - } - break; - case IS_STRING: - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) - if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) { - - zval wbuffer_z; - ZVAL_NULL( &wbuffer_z ); - - bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - param_num + 1, get_last_error_message() ) { - throw core::CoreException(); - } - buffer = Z_STRVAL_P( &wbuffer_z ); - buffer_len = Z_STRLEN_P( &wbuffer_z ); - core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC); - } - ind_ptr = buffer_len; - if( direction != SQL_PARAM_INPUT ) { - // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, - // we reallocate the string if it's interned - if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) { - core::sqlsrv_zval_stringl( param_z, static_cast(buffer), buffer_len ); - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - } - - // if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR) - // or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type, - // convert it to wchar first - if( direction == SQL_PARAM_INPUT_OUTPUT && - ( c_type == SQL_C_WCHAR || - ( c_type == SQL_C_BINARY && - ( sql_type == SQL_WCHAR || - sql_type == SQL_WVARCHAR || - sql_type == SQL_WLONGVARCHAR )))) { - - bool converted = convert_input_param_to_utf16( param_z, param_z ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - param_num + 1, get_last_error_message() ) { - throw core::CoreException(); - } - buffer = Z_STRVAL_P( param_z ); - buffer_len = Z_STRLEN_P( param_z ); - ind_ptr = buffer_len; - } - - // since this is an output string, assure there is enough space to hold the requested size and - // set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr) - resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, - buffer, buffer_len TSRMLS_CC ); - - // save the parameter to be adjusted and/or converted after the results are processed - sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len )); - - save_output_param_for_later( stmt, output_param TSRMLS_CC ); - - // For output parameters, if we set the column_size to be same as the buffer_len, - // then if there is a truncation due to the data coming from the server being - // greater than the column_size, we don't get any truncation error. In order to - // avoid this silent truncation, we set the column_size to be "MAX" size for - // string types. This will guarantee that there is no silent truncation for - // output parameters. - if( direction == SQL_PARAM_OUTPUT ) { - - switch( sql_type ) { - - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_WVARCHAR: - column_size = SQL_SS_LENGTH_UNLIMITED; - break; - - default: - break; - } - } - } - break; - case IS_RESOURCE: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - sqlsrv_stream stream_encoding( param_z, encoding ); - HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC ); - buffer = reinterpret_cast( param_num ); - Z_TRY_ADDREF_P( param_z ); // so that it doesn't go away while we're using it - buffer_len = 0; - ind_ptr = SQL_DATA_AT_EXEC; - } - break; - case IS_OBJECT: - { - SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); - zval function_z; - zval buffer_z; - zval format_z; - zval params[1]; - ZVAL_UNDEF( &function_z ); - ZVAL_UNDEF( &buffer_z ); - ZVAL_UNDEF( &format_z ); - ZVAL_UNDEF( params ); - - bool valid_class_name_found = false; - - zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC ); - - while( class_entry != NULL ) { - - if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && - stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) { - valid_class_name_found = true; - break; - } - - else { - - // Check the parent - class_entry = class_entry->parent; - } - } - - CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - - // if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error' - // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' - // sql type, it lacks the timezone. - if( sql_type == SQL_SS_TIMESTAMPOFFSET ) { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), - DateTime::DATETIMEOFFSET_FORMAT_LEN ); - } - else if( sql_type == SQL_TYPE_DATE ) { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); - } - else { - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); - } - // call the DateTime::format member function to convert the object to a string that SQL Server understands - core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); - params[0] = format_z; - // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the - // DateTime object and $format_z is the format string. - int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); - zend_string_release( Z_STR( format_z )); - zend_string_release( Z_STR( function_z )); - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - buffer = Z_STRVAL( buffer_z ); - zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z ); - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { - throw core::CoreException(); - } - buffer_len = Z_STRLEN( buffer_z ) - 1; - ind_ptr = buffer_len; - break; - } - case IS_ARRAY: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ); - break; - default: - DIE( "core_sqlsrv_bind_param: Unsupported PHP type. Only string, float, int, and streams (resource) are supported. " - "It is the responsibilty of the driver layer to convert a parameter to one of these types." ); - break; - } - - if( zval_was_null ) { - ind_ptr = SQL_NULL_DATA; - } - - core::SQLBindParameter( stmt, param_num + 1, direction, - c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); - } - catch( core::CoreException& e ) { - stmt->free_param_data( TSRMLS_C ); - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - throw e; - } -} - - -// core_sqlsrv_execute -// Executes the statement previously prepared -// Parameters: -// stmt - the core sqlsrv_stmt structure that contains the ODBC handle -// Return: -// true if there is data, false if there is not - -void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len ) -{ - SQLRETURN r; - - try { - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - if( sql ) { - - sqlsrv_malloc_auto_ptr wsql_string; - unsigned int wsql_len = 0; - if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { - wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t ))); - wsql_string[0] = L'\0'; - wsql_len = 0; - } - else { - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : - stmt->encoding() ); - wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - sql_len, &wsql_len ); - CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - get_last_error_message() ) { - throw core::CoreException(); - } - } - r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC ); - } - else { - r = core::SQLExecute( stmt TSRMLS_CC ); - } - - // if data is needed (streams were bound) and they should be sent at execute time, then do so now - if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) { - - send_param_streams( stmt TSRMLS_CC ); - } - - stmt->new_result_set( TSRMLS_C ); - stmt->executed = true; - - // if all the data has been sent and no data was returned then finalize the output parameters - if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { - - finalize_output_parameters( stmt TSRMLS_CC ); - } - // stream parameters are sent, clean the Hashtable - if ( stmt->send_streams_at_exec ) { - zend_hash_clean( Z_ARRVAL( stmt->param_streams )); - } - } - catch( core::CoreException& e ) { - - // if the statement executed but failed in a subsequent operation before returning, - // we need to cancel the statement and deref the output and stream parameters - if ( stmt->send_streams_at_exec ) { - zend_hash_clean( Z_ARRVAL( stmt->output_params )); - zend_hash_clean( Z_ARRVAL( stmt->param_streams )); - } - if( stmt->executed ) { - SQLCancel( stmt->handle() ); - // stmt->executed = false; should this be reset if something fails? - } - - throw e; - } -} - - -// core_sqlsrv_fetch -// Moves the cursor according to the parameters (by default, moves to the next row) -// Parameters: -// stmt - the sqlsrv_stmt of the cursor -// fetch_orientation - method to move the cursor -// fetch_offset - if the method has a parameter (such as number of rows to move or literal row number) -// Returns: -// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the -// user scrolls past a non-scrollable result set - -bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ) -{ - // pre-condition check - SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE, - "core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." ); - - try { - - // clear the field cache of the previous fetch - zend_hash_clean( Z_ARRVAL( stmt->field_cache )); - - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) { - throw core::CoreException(); - } - - SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); - - CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { - throw core::CoreException(); - } - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - // if the statement has rows and is not scrollable but doesn't yet have - // fetch_called, this must be the first time we've called sqlsrv_fetch. - if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->has_rows && !stmt->fetch_called ) { - stmt->fetch_called = true; - return true; - } - - // move to the record requested. For absolute records, we use a 0 based offset, so +1 since - // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. - SQLRETURN r = stmt->current_results->fetch( fetch_orientation, - ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 - TSRMLS_CC ); - if( r == SQL_NO_DATA ) { - // if this is a forward only cursor, mark that we've passed the end so future calls result in an error - if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { - stmt->past_fetch_end = true; - } - return false; - } - - // mark that we called fetch (which get_field, et. al. uses) and reset our last field retrieved - stmt->fetch_called = true; - stmt->last_field_index = -1; - stmt->has_rows = true; // since we made it this far, we must have at least one row - } - catch (core::CoreException& e) { - throw e; - } - catch ( ... ) { - DIE( "core_sqlsrv_fetch: Unexpected exception occurred." ); - } - - return true; -} - - -// Retrieves metadata for a field of a prepared statement. -// Parameters: -// colno - the index of the field for which to return the metadata. columns are 0 based in PDO -// Return: -// A field_meta_data* consisting of the field metadata. - -field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ) -{ - // pre-condition check - SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." ); - - sqlsrv_malloc_auto_ptr meta_data; - SQLSMALLINT field_name_len = 0; - - meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data(); - meta_data->field_name = static_cast( sqlsrv_malloc( SS_MAXCOLNAMELEN + 1 )); - - try { - core::SQLDescribeCol( stmt, colno + 1, meta_data->field_name.get(), SS_MAXCOLNAMELEN, &field_name_len, - &(meta_data->field_type), &(meta_data->field_size), &(meta_data->field_scale), - &(meta_data->field_is_nullable) TSRMLS_CC ); - } - catch( core::CoreException& e ) { - throw e; - } - - // depending on field type, we add the values into size or precision/scale. - switch( meta_data->field_type ) { - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_BIT: - case SQL_TINYINT: - case SQL_SMALLINT: - case SQL_INTEGER: - case SQL_BIGINT: - case SQL_REAL: - case SQL_FLOAT: - case SQL_DOUBLE: - { - meta_data->field_precision = meta_data->field_size; - meta_data->field_size = 0; - break; - } - default: { - break; - } - } - - // Set the field name lenth - meta_data->field_name_len = field_name_len; - - field_meta_data* result_field_meta_data = meta_data; - meta_data.transferred(); - return result_field_meta_data; -} - - -// core_sqlsrv_get_field -// Return the value of a column from ODBC -// Parameters: -// stmt - the sqlsrv_stmt from which to retrieve the column -// field_index - 0 based index for the column to retrieve -// sqlsrv_php_type_in - sqlsrv_php_type structure that tells what format to return the data in -// field_value - pointer to the data retrieved -// field_len - length of the data in the field_value buffer -// Returns: -// Nothing, excpetion thrown if an error occurs - -void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string, - _Out_ void*& field_value, _Out_ SQLLEN* field_len, bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) -{ - try { - - // close the stream to release the resource - close_active_stream(stmt TSRMLS_CC); - - // if the field has been retrieved before, return the previous result - field_cache* cached = NULL; - if (NULL != ( cached = static_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast( field_index ))))) { - // the field value is NULL - if( cached->value == NULL ) { - field_value = NULL; - *field_len = 0; - if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } - } - else { - - field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); - memcpy_s( field_value, ( cached->len * sizeof( char )), cached->value, cached->len ); - if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING) { - // prevent the 'string not null terminated' warning - reinterpret_cast( field_value )[ cached->len ] = '\0'; - } - *field_len = cached->len; - if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } - } - return; - } - - sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; - - SQLLEN sql_field_type = 0; - SQLLEN sql_field_len = 0; - - // Make sure that the statement was executed and not just prepared. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they - // may also be retrieved. - if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { - sqlsrv_phptype invalid; - invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { - SQLSRV_ASSERT((cached = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->field_cache), i))) == NULL, - "Field already cached." ); - core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, - sqlsrv_php_type_out TSRMLS_CC ); - // delete the value returned since we only want it cached, not the actual value - if( field_value ) { - efree( field_value ); - field_value = NULL; - *field_len = 0; - } - } - } - - // If the php type was not specified set the php type to be the default type. - if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { - - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Get the length of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); - - // Get the corresponding php type from the sql type. - sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast( sql_field_type ), static_cast( sql_field_len ), prefer_string ); - } - - // Verify that we have an acceptable type to convert. - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { - throw core::CoreException(); - } - - if( sqlsrv_php_type_out != NULL ) - *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); - - // Retrieve the data - core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - - // if the user wants us to cache the field, we'll do it - if( cache_field ) { - field_cache cache( field_value, *field_len, sqlsrv_php_type ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); - } - } - - catch( core::CoreException& e ) { - throw e; - } -} - -// core_sqlsrv_has_any_result -// return if any result set or rows affected message is waiting -// to be consumed and moved over by sqlsrv_next_result. -// Parameters: -// stmt - The statement object on which to check for results. -// Return: -// true if any results are present, false otherwise. - -bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - // Use SQLNumResultCols to determine if we have rows or not. - SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - // use SQLRowCount to determine if there is a rows status waiting - SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); - return (num_cols != 0) || (rows_affected > 0); -} - -// core_sqlsrv_next_result -// Advances to the next result set from the last executed query -// Parameters -// stmt - the sqlsrv_stmt structure -// Returns -// Nothing, exception thrown if problem occurs - -void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params, bool throw_on_errors ) -{ - try { - - // make sure that the statement has been executed. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) { - throw core::CoreException(); - } - - close_active_stream( stmt TSRMLS_CC ); - - SQLRETURN r; - if( throw_on_errors ) { - r = core::SQLMoreResults( stmt TSRMLS_CC ); - } - else { - r = SQLMoreResults( stmt->handle() ); - } - - if( r == SQL_NO_DATA ) { - - if( &(stmt->output_params) && finalize_output_params ) { - // if we're finished processing result sets, handle the output parameters - finalize_output_parameters( stmt TSRMLS_CC ); - } - - // mark we are past the end of all results - stmt->past_next_result_end = true; - return; - } - - stmt->new_result_set( TSRMLS_C ); - } - catch( core::CoreException& e ) { - - SQLCancel( stmt->handle() ); - throw e; - } -} - - -// core_sqlsrv_post_param -// Performs any actions post execution for each parameter. For now it cleans up input parameters memory from the statement -// Parameters: -// stmt - the sqlsrv_stmt structure -// param_num - 0 based index of the parameter -// param_z - parameter value itself. -// Returns: -// Nothing, exception thrown if problem occurs - -void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* param_z TSRMLS_DC ) -{ - SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); - SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); - - // if the parameter was an input string, delete it from the array holding input parameter strings - if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) { - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC ); - } - - // if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams - // PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it - // with sqlsrv_send_stream_data. - if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) { - sqlsrv_stream* stream_encoding = NULL; - stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(Z_ARRVAL(stmt->param_streams), param_num)); - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC ); - } -} - -//Calls SQLSetStmtAttr to set a cursor. -void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ) -{ - try { - - switch( cursor_type ) { - - case SQL_CURSOR_STATIC: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_DYNAMIC: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_KEYSET_DRIVEN: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQL_CURSOR_FORWARD_ONLY: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - case SQLSRV_CURSOR_BUFFERED: - core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); - break; - - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); - break; - } - - stmt->cursor_type = cursor_type; - - } - catch( core::CoreException& ) { - throw; - } -} - -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - if( Z_TYPE_P( value_z ) != IS_LONG ) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); - } - - core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); -} - -void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ) -{ - if( limit <= 0 ) { - - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); - } - - stmt->buffered_query_limit = limit; -} - - -// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout -// which accepts timeout parameter as a long. If the zval is not of type long -// than throws error. -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - try { - - // validate the value - if( Z_TYPE_P( value_z ) != IS_LONG || Z_LVAL_P( value_z ) < 0 ) { - - convert_to_string( value_z ); - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) ); - } - - core_sqlsrv_set_query_timeout( stmt, static_cast( Z_LVAL_P( value_z )) TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } -} - -// Overloaded. Accepts the timeout as a long. -void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." ); - - // set the statement attribute - core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC ); - - // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which - // is represented by -1. - long lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); - - // set the LOCK_TIMEOUT on the server. - char lock_timeout_sql[ 32 ]; - int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", - lock_timeout ); - - SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), - "stmt_option_query_timeout: sprintf_s failed. Shouldn't ever fail." ); - - core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC ); - - stmt->query_timeout = timeout; - } - catch( core::CoreException& ) { - throw; - } -} - -void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) -{ - TSRMLS_C; - - // zend_is_true does not fail. It either returns true or false. - stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false; -} - - -// core_sqlsrv_send_stream_packet -// send a single packet from a stream parameter to the database using -// ODBC. This will also handle the transition between parameters. It -// returns true if it is not done sending, false if it is finished. -// return_value is what should be returned to the script if it is -// given. Any errors that occur are posted here. -// Parameters: -// stmt - query to send the next packet for -// Returns: -// true if more data remains to be sent, false if all data processed - -bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - SQLRETURN r = SQL_SUCCESS; - - // if there no current parameter to process, get the next one - // (probably because this is the first call to sqlsrv_send_stream_data) - if( stmt->current_stream.stream_z == NULL ) { - - if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) { - - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); - stmt->current_stream_read = 0; - return false; - } - } - - try { - - // get the stream from the zval we bound - php_stream* param_stream = NULL; - core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); - - // if we're at the end, then release our current parameter - if( php_stream_eof( param_stream )) { - // if no data was actually sent prior, then send a NULL - if( stmt->current_stream_read == 0 ) { - // send an empty string, which is what a 0 length does. - char buff[1]; // temp storage to hand to SQLPutData - core::SQLPutData( stmt, buff, 0 TSRMLS_CC ); - } - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); - stmt->current_stream_read = 0; - } - // read the data from the stream, send it via SQLPutData and track how much we've sent. - else { - char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; - std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character - std::size_t read = php_stream_read( param_stream, buffer, buffer_size ); - - if (read > UINT_MAX) - { - LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); - throw core::CoreException(); - } - - stmt->current_stream_read += static_cast( read ); - if( read > 0 ) { - // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character - // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it - // twice. - // If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion - // since all other MBCS supported by SQL Server are 2 byte maximum size. - if( stmt->current_stream.encoding == CP_UTF8 ) { - - // the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a - // expansion of 2x the UTF-8 size. - wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; - // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate - int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, static_cast( read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); - if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) { - - // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more - // in, then reattempt the conversion. If it fails the second time, then an error is returned. - size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC ); - // read the missing bytes - size_t new_read = php_stream_read( param_stream, static_cast( buffer ) + read, - need_to_read ); - // if the bytes couldn't be read, then we return an error - CHECK_CUSTOM_ERROR( new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { - throw core::CoreException(); - } - // try the conversion again with the complete character - wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, - buffer, static_cast( read + new_read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); - // something else must be wrong if it failed - CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { - throw core::CoreException(); - } - } - core::SQLPutData( stmt, wbuffer, wsize * sizeof( wchar_t ) TSRMLS_CC ); - } - else { - core::SQLPutData( stmt, buffer, read TSRMLS_CC ); - } - } - } - - } - catch( core::CoreException& e ) { - stmt->free_param_data( TSRMLS_C ); - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - SQLCancel( stmt->handle() ); - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT ); - stmt->current_stream_read = 0; - throw e; - } - - return true; -} - -void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) -{ - TSRMLS_C; - - // This implementation should never get called. - DIE( "Not implemented." ); -} - -void stmt_option_query_timeout:: operator()( sqlsrv_stmt* stmt, stmt_option const* /**/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_send_at_exec:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC ); -} - -void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC ); -} - - -// internal function to release the active stream. Called by each main API function -// that will alter the statement and cancel any retrieval of data from a stream. -void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) -{ - // if there is no active stream, return - if( Z_TYPE( stmt->active_stream ) == IS_UNDEF ) { - return; - } - - php_stream* stream = NULL; - - // we use no verify since verify would return immediately and we want to assert, not return. - php_stream_from_zval_no_verify( stream, &( stmt->active_stream )); - - SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." ); - - php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here. - - SQLSRV_ASSERT( Z_TYPE( stmt->active_stream ) == IS_UNDEF, "close_active_stream: Active stream not closed." ); - -} - -// local routines not shared by other files (arranged alphabetically) - -namespace { - -bool is_streamable_type( SQLLEN sql_type ) -{ - switch( sql_type ) { - case SQL_CHAR: - case SQL_WCHAR: - case SQL_BINARY: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_SS_XML: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - return true; - } - - return false; -} - -void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ) -{ - try { - - switch( sql_type ) { - // for types that are fixed in size or for which the size is unknown, return the display size. - case SQL_BIGINT: - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - case SQL_GUID: - case SQL_FLOAT: - case SQL_DOUBLE: - case SQL_REAL: - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_BINARY: - case SQL_CHAR: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_SS_XML: - case SQL_SS_UDT: - case SQL_WLONGVARCHAR: - case SQL_DATETIME: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - { - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC ); - break; - } - - // for wide char types for which the size is known, return the octet length instead, since it will include the - // the number of bytes necessary for the string, not just the characters - case SQL_WCHAR: - case SQL_WVARCHAR: - { - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC ); - break; - } - - default: - DIE ( "Unexpected SQL type encountered in calc_string_size." ); - } - } - catch( core::CoreException& e ) { - throw e; - } -} - - -// calculates how many characters were cut off from the end of a buffer when reading -// in UTF-8 encoded text - -size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ) -{ - const char* last_char = buffer + buffer_end - 1; - size_t need_to_read = 0; - - // rewind until we are at the byte that starts the cut off character - while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) { - --last_char; - ++need_to_read; - } - - // determine how many bytes we need to read in based on the number of bytes in the character - // (# of high bits set) versus the number of bytes we've already read. - switch( *last_char & UTF8_NBYTESEQ_MASK ) { - case UTF8_2BYTESEQ_TAG1: - case UTF8_2BYTESEQ_TAG2: - need_to_read = 1 - need_to_read; - break; - case UTF8_3BYTESEQ_TAG: - need_to_read = 2 - need_to_read; - break; - case UTF8_4BYTESEQ_TAG: - need_to_read = 3 - need_to_read; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )); - break; - } - - return need_to_read; -} - - -// Caller is responsible for freeing the memory allocated for the field_value. -// The memory allocation has to happen in the core layer because otherwise -// the driver layer would have to calculate size of the field_value -// to decide the amount of memory allocation. -void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype - sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) -{ - try { - - close_active_stream( stmt TSRMLS_CC ); - - // make sure that fetch is called before trying to retrieve. - CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { - throw core::CoreException(); - } - - // make sure that fields are not retrieved incorrectly. - CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, - stmt->last_field_index ) { - throw core::CoreException(); - } - - switch( sqlsrv_php_type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_INT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( *field_len == SQL_NULL_DATA ) { - field_value = NULL; - break; - } - - field_value = field_value_temp; - field_value_temp.transferred(); - break; - } - - case SQLSRV_PHPTYPE_FLOAT: - { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), - field_len, true /*handle_warning*/ TSRMLS_CC ); - - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( *field_len == SQL_NULL_DATA ) { - field_value = NULL; - break; - } - - field_value = field_value_temp; - field_value_temp.transferred(); - break; - } - - case SQLSRV_PHPTYPE_STRING: - { - get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - break; - } - - // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and - // convert it to a DateTime object and return the created object - case SQLSRV_PHPTYPE_DATETIME: - { - char field_value_temp[ MAX_DATETIME_STRING_LEN ]; - zval params[1]; - zval field_value_temp_z; - zval function_z; - - ZVAL_UNDEF( &field_value_temp_z ); - ZVAL_UNDEF( &function_z ); - ZVAL_UNDEF( params ); - - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, - MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - zval_auto_ptr return_value_z; - return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); - ZVAL_UNDEF( return_value_z ); - - if( *field_len == SQL_NULL_DATA ) { - ZVAL_NULL( return_value_z ); - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - break; - } - - // Convert the string date to a DateTime object - core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); - core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 ); - params[0] = field_value_temp_z; - - if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, - params TSRMLS_CC ) == FAILURE) { - THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); - } - - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - zend_string_free( Z_STR( field_value_temp_z )); - zend_string_free( Z_STR( function_z )); - break; - } - - // create a stream wrapper around the field and return that object to the PHP script. calls to fread - // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file - // for how these fields are used. - case SQLSRV_PHPTYPE_STREAM: - { - - php_stream* stream = NULL; - sqlsrv_stream* ss = NULL; - SQLLEN sql_type; - - SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { - throw core::CoreException(); - } - - stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); - - CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { - throw core::CoreException(); - } - - ss = static_cast( stream->abstract ); - ss->stmt = stmt; - ss->field_index = field_index; - ss->sql_type = static_cast( sql_type ); - ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); - - zval_auto_ptr return_value_z; - return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); - ZVAL_UNDEF( return_value_z ); - - // turn our stream into a zval to be returned - php_stream_to_zval( stream, return_value_z ); - - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - break; - } - - case SQLSRV_PHPTYPE_NULL: - field_value = NULL; - *field_len = 0; - break; - - default: - DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); - break; - } - - // sucessfully retrieved the field, so update our last retrieved field - if( stmt->last_field_index < field_index ) { - stmt->last_field_index = field_index; - } - } - catch( core::CoreException& e ) { - throw e; - } -} - - -// check_for_next_stream_parameter -// see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise -// returns false -bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) -{ - int stream_index = 0; - SQLRETURN r = SQL_SUCCESS; - sqlsrv_stream* stream_encoding = NULL; - zval* param_z = NULL; - - // get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param - r = core::SQLParamData( stmt, reinterpret_cast( &stream_index ) TSRMLS_CC ); - // if no more data, we've exhausted the bound parameters, so return that we're done - if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - - // we're all done, so return false - return false; - } - - HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); - - // pull out the sqlsrv_encoding struct - stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(streams_ht, stream_index)); - SQLSRV_ASSERT(stream_encoding != NULL, "Stream parameter does not exist"); // if the index isn't in the hash, that's a serious error - - param_z = stream_encoding->stream_z; - - // make the next stream current - stmt->current_stream = sqlsrv_stream( param_z, stream_encoding->encoding ); - stmt->current_stream_read = 0; - - // there are more parameters - return true; -} - - -// utility routine to convert an input parameter from UTF-8 to UTF-16 - -bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z ) -{ - SQLSRV_ASSERT( input_param_z == converted_param_z || Z_TYPE_P( converted_param_z ) == IS_NULL, - "convert_input_param_z called with invalid parameter states" ); - - const char* buffer = Z_STRVAL_P( input_param_z ); - std::size_t buffer_len = Z_STRLEN_P( input_param_z ); - int wchar_size; - - if (buffer_len > INT_MAX) - { - LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); - throw core::CoreException(); - } - - // if the string is empty, then just return that the conversion succeeded as - // MultiByteToWideChar will "fail" on an empty string. - if( buffer_len == 0 ) { - core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); - return true; - } - - // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string - wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, - reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); - - // if there was a problem determining the size of the string, return false - if( wchar_size == 0 ) { - return false; - } - sqlsrv_malloc_auto_ptr wbuffer; - wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) )); - // convert the utf-8 string to a wchar string in the new buffer - int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), - static_cast( buffer_len ), wbuffer, wchar_size ); - // if there was a problem converting the string, then free the memory and return false - if( r == 0 ) { - return false; - } - - // null terminate the string, set the size within the zval, and return success - wbuffer[ wchar_size ] = L'\0'; - core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), - wchar_size * sizeof( wchar_t ) ); - sqlsrv_free(wbuffer); - wbuffer.transferred(); - - return true; -} - -// returns the ODBC C type constant that matches the PHP type and encoding given - -SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) -{ - SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P( param_z ); - - switch( php_type ) { - - case IS_NULL: - switch( encoding ) { - // The c type is set to match to the corresponding sql_type. For NULL cases, if the server type - // is a binary type, than the server expects the sql_type to be binary type as well, otherwise - // an error stating "Implicit conversion not allowed.." is thrown by the server. - // For all other server types, setting the sql_type to sql_char works fine. - case SQLSRV_ENCODING_BINARY: - sql_c_type = SQL_C_BINARY; - break; - default: - sql_c_type = SQL_C_CHAR; - break; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - //ODBC 64-bit long and integer type are 4 byte values. - if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) - { - sql_c_type = SQL_C_SBIGINT; - } - else - { - sql_c_type = SQL_C_SLONG; - } - break; - case IS_DOUBLE: - sql_c_type = SQL_C_DOUBLE; - break; - case IS_STRING: - case IS_RESOURCE: - switch( encoding ) { - case SQLSRV_ENCODING_CHAR: - sql_c_type = SQL_C_CHAR; - break; - case SQLSRV_ENCODING_BINARY: - sql_c_type = SQL_C_BINARY; - break; - case CP_UTF8: - sql_c_type = SQL_C_WCHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); - break; - } - break; - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in core_sqlsrv_bind_param. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - sql_c_type = SQL_C_CHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } - - return sql_c_type; -} - - -// given a zval and encoding, determine the appropriate sql type - -void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLSMALLINT& sql_type TSRMLS_DC ) -{ - sql_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P(param_z); - switch( php_type ) { - - case IS_NULL: - switch( encoding ) { - // Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases, - // if the server type is a binary type, than the server expects the sql_type to be binary type - // as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the - // server. For all other server types, setting the sql_type to sql_char works fine. - case SQLSRV_ENCODING_BINARY: - sql_type = SQL_BINARY; - break; - default: - sql_type = SQL_CHAR; - break; - } - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - //ODBC 64-bit long and integer type are 4 byte values. - if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) - { - sql_type = SQL_BIGINT; - } - else - { - sql_type = SQL_INTEGER; - } - - break; - case IS_DOUBLE: - sql_type = SQL_FLOAT; - break; - case IS_RESOURCE: - case IS_STRING: - switch( encoding ) { - case SQLSRV_ENCODING_CHAR: - sql_type = SQL_VARCHAR; - break; - case SQLSRV_ENCODING_BINARY: - sql_type = SQL_VARBINARY; - break; - case CP_UTF8: - sql_type = SQL_WVARCHAR; - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); - break; - } - break; - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in the calling function. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - // if the user is sending this type to SQL Server 2005 or earlier, make it appear - // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET - // since these are the date types of the highest precision for their respective server versions - if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { - sql_type = SQL_TYPE_TIMESTAMP; - } - else { - sql_type = SQL_SS_TIMESTAMPOFFSET; - } - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } - -} - - -// given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate) - -void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) -{ - int php_type = Z_TYPE_P( param_z ); - column_size = 0; - decimal_digits = 0; - - switch( php_type ) { - - case IS_NULL: - column_size = 1; - break; - // size is not necessary for these types, they are inferred by ODBC - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - case IS_DOUBLE: - case IS_RESOURCE: - break; - case IS_STRING: - { - size_t char_size = ( encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( wchar_t ) : sizeof( char ); - SQLULEN byte_len = Z_STRLEN_P( param_z ) * char_size; - if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { - column_size = SQL_SERVER_MAX_TYPE_SIZE; - } - else { - column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size; - } - break; - } - // it is assumed that an object is a DateTime since it's the only thing we support. - // verification that it's a real DateTime object occurs in the calling function. - // we convert the DateTime to a string before sending it to the server. - case IS_OBJECT: - // if the user is sending this type to SQL Server 2005 or earlier, make it appear - // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET - // since these are the date types of the highest precision for their respective server versions - if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { - column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION; - decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE; - } - else { - column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION; - decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE; - } - break; - default: - THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); - break; - } -} - -void field_cache_dtor( zval* data_z ) -{ - field_cache* cache = static_cast( Z_PTR_P( data_z )); - if( cache->value ) - { - sqlsrv_free( cache->value ); - } - sqlsrv_free( cache ); -} - - -// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output -// parameters will be present until all results are processed (since output parameters can depend on results -// while being processed). This function updates the lengths of output parameter strings from the ind_ptr -// parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary. -// For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server - -void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - if( Z_ISUNDEF(stmt->output_params) ) - return; - - bool converted = true; - HashTable* params_ht = Z_ARRVAL( stmt->output_params ); - zend_ulong index = -1; - zend_string* key = NULL; - void* output_param_temp = NULL; - - ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { - sqlsrv_output_param* output_param = static_cast( output_param_temp ); - zval* value_z = Z_REFVAL_P( output_param->param_z ); - switch( Z_TYPE_P( value_z )) { - case IS_STRING: - { - // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter - char* str = Z_STRVAL_P( value_z ); - SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ]; - if( str_len == SQL_NULL_DATA ) { - zend_string_release( Z_STR_P( value_z )); - ZVAL_NULL( value_z ); - continue; - } - - // if there was more to output than buffer size to hold it, then throw a truncation error - int null_size = 0; - switch( output_param->encoding ) { - case SQLSRV_ENCODING_UTF8: - null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16 - break; - case SQLSRV_ENCODING_SYSTEM: - null_size = 1; - break; - case SQLSRV_ENCODING_BINARY: - null_size = 0; - break; - default: - SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); - break; - } - CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { - throw core::CoreException(); - } - - // if it's not in the 8 bit encodings, then it's in UTF-16 - if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { - bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { - // ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated - // so we do that here if the length of the returned data is less than the original allocation. The - // original allocation null terminates the buffer already. - str[ str_len ] = '\0'; - core::sqlsrv_zval_stringl(value_z, str, str_len); - } - else { - core::sqlsrv_zval_stringl(value_z, str, str_len); - } - } - break; - case IS_LONG: - // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so - if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) { - ZVAL_NULL( value_z ); - } - else if( output_param->is_bool ) { - convert_to_boolean( value_z ); - } - else - { - ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); - } - break; - case IS_DOUBLE: - // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so - if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) { - ZVAL_NULL( value_z ); - } - break; - default: - DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); - break; - } - value_z = NULL; - } ZEND_HASH_FOREACH_END(); - - // empty the hash table since it's been processed - zend_hash_clean( Z_ARRVAL( stmt->output_params )); - return; -} - -void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, - _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) -{ - SQLRETURN r; - SQLSMALLINT c_type; - SQLLEN sql_field_type = 0; - SQLSMALLINT extra = 0; - SQLLEN field_len_temp; - SQLLEN sql_display_size = 0; - char* field_value_temp = NULL; - - try { - - DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, - "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); - - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); - } - - // Set the C type and account for null characters at the end of the data. - switch( sqlsrv_php_type.typeinfo.encoding ) { - case CP_UTF8: - c_type = SQL_C_WCHAR; - extra = sizeof( SQLWCHAR ); - break; - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - extra = 0; - break; - default: - c_type = SQL_C_CHAR; - extra = sizeof( SQLCHAR ); - break; - } - - // Get the SQL type of the field. - core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Calculate the field size. - calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); - - // if this is a large type, then read the first few bytes to get the actual length from SQLGetData - if( sql_display_size == 0 || sql_display_size == INT_MAX || - sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { - - field_len_temp = INITIAL_FIELD_STRING_LEN; - - field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); - - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), - &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( field_len_temp == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( r == SQL_SUCCESS_WITH_INFO ) { - - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - - stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - - if( is_truncated_warning( state )) { - - SQLLEN dummy_field_len; - - // for XML (and possibly other conditions) the field length returned is not the real field length, so - // in every pass, we double the allocation size to retrieve all the contents. - if( field_len_temp == SQL_NO_TOTAL ) { - - // reset the field_len_temp - field_len_temp = INITIAL_FIELD_STRING_LEN; - - do { - SQLLEN initial_field_len = field_len_temp; - // Double the size. - field_len_temp *= 2; - - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - field_len_temp -= initial_field_len; - - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, - field_len_temp + extra, &dummy_field_len, - false /*handle_warning*/ TSRMLS_CC ); - - // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL - // so we calculate the actual length of the string with that. - if( dummy_field_len != SQL_NO_TOTAL ) - field_len_temp += dummy_field_len; - else - field_len_temp += initial_field_len; - - if( r == SQL_SUCCESS_WITH_INFO ) { - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - } - - } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); - } - else { - - // We got the field_len_temp from SQLGetData call. - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - - // We have already recieved INITIAL_FIELD_STRING_LEN size data. - field_len_temp -= INITIAL_FIELD_STRING_LEN; - - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, - field_len_temp + extra, &dummy_field_len, - true /*handle_warning*/ TSRMLS_CC ); - - if( dummy_field_len == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - field_len_temp += INITIAL_FIELD_STRING_LEN; - } - - } // if( is_truncation_warning ( state ) ) - else { - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - } - } // if( r == SQL_SUCCESS_WITH_INFO ) - - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { - - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) - - else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { - - // only allow binary retrievals for char and binary types. All others get a string converted - // to the encoding type they asked for. - - // null terminator - if( c_type == SQL_C_CHAR ) { - sql_display_size += sizeof( SQLCHAR ); - } - - // For WCHAR multiply by sizeof(WCHAR) and include the null terminator - else if( c_type == SQL_C_WCHAR ) { - sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); - } - - field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); - - // get the data - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, - &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); - CHECK_SQL_ERROR( r, stmt ) { - throw core::CoreException(); - } - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - - if( field_len_temp == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { - - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) - - else { - - DIE( "Invalid sql_display_size" ); - return; // to eliminate a warning - } - - field_value = field_value_temp; - *field_len = field_len_temp; - - // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP - // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. - // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary - // operator to set add 1 to fill the null terminator - field_value_temp[field_len_temp] = '\0'; - } - - catch( core::CoreException& ) { - - field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - catch ( ... ) { - - field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - -} - - -// return the option from the stmt_opts array that matches the key. If no option found, -// NULL is returned. - -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ) -{ - for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { - - // if we find the key we're looking for, return it - if( key == stmt_opts[ i ].key ) { - return &stmt_opts[ i ]; - } - } - - return NULL; // no option found -} - -// is_fixed_size_type -// returns true if the SQL data type is a fixed length, as opposed to a variable length data type such as varchar or varbinary - -bool is_fixed_size_type( SQLINTEGER sql_type ) -{ - switch( sql_type ) { - - case SQL_BINARY: - case SQL_CHAR: - case SQL_WCHAR: - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_VARBINARY: - case SQL_LONGVARBINARY: - case SQL_SS_XML: - case SQL_SS_UDT: - return false; - } - - return true; -} - -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) -{ - switch( type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - case SQLSRV_PHPTYPE_DATETIME: - return true; - case SQLSRV_PHPTYPE_STRING: - case SQLSRV_PHPTYPE_STREAM: - { - if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR - || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - return true; - } - break; - } - } - - return false; -} - - -// verify there is enough space to hold the output string parameter, and allocate it if needed. The param_z -// is updated to have the new buffer with the correct size and its reference is incremented. The output -// string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and -// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter - -void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, - SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, - SQLLEN& buffer_len TSRMLS_DC ) -{ - SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); - buffer_len = Z_STRLEN_P( param_z ); - SQLLEN expected_len; - SQLLEN buffer_null_extra; - SQLLEN elem_size; - SQLLEN without_null_len; - - // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, - // as is a n(var)char/ntext field being returned as a binary field. - elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1; - - // account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning - expected_len = column_size * elem_size + elem_size; - - // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations - buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; - - // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter - without_null_len = column_size * elem_size; - - // increment to include the null terminator since the Zend length doesn't include the null terminator - buffer_len += elem_size; - - // if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new - // length. - if( buffer_len < expected_len ) { - SQLSRV_ASSERT( expected_len >= expected_len - buffer_null_extra, - "Integer overflow/underflow caused a corrupt field length." ); - - // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since - // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about - // not having a NULL terminator on a string. - zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); - - // A zval string len doesn't include the null. This calculates the length it should be - // regardless of whether the ODBC type contains the NULL or not. - - // null terminate the string to avoid a warning in debug PHP builds - ZSTR_VAL(param_z_string)[without_null_len] = '\0'; - ZVAL_NEW_STR(param_z, param_z_string); - - // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the - // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY - buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; - - // Zend string length doesn't include the null terminator - ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; - } - - buffer = Z_STRVAL_P(param_z); - - // The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which - // may be less than the size of the buffer since the output may be more than the input. If it is greater, - // than the error 22001 is returned by ODBC. - if( stmt->param_ind_ptrs[ paramno ] > buffer_len - (elem_size - buffer_null_extra)) { - stmt->param_ind_ptrs[ paramno ] = buffer_len - (elem_size - buffer_null_extra); - } -} - -// output parameters have their reference count incremented so that they do not disappear -// while the query is executed and processed. They are saved in the statement so that -// their reference count may be decremented later (after results are processed) - -void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ) -{ - HashTable* param_ht = Z_ARRVAL( stmt->output_params ); - zend_ulong paramno = static_cast( param.param_num ); - core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, ¶m, sizeof( sqlsrv_output_param )); - Z_TRY_ADDREF_P( param.param_z ); // we have a reference to the param -} - - -// send all the stream data - -void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ) -{ - while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { } -} - - -// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed -void sqlsrv_output_param_dtor( zval* data ) -{ - sqlsrv_output_param *output_param = static_cast( Z_PTR_P( data )); - zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold - sqlsrv_free( output_param ); -} - -// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed -void sqlsrv_stream_dtor( zval* data ) -{ - sqlsrv_stream* stream_encoding = static_cast( Z_PTR_P( data )); - zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold - sqlsrv_free( stream_encoding ); -} - -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_stmt.cpp +// +// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +namespace { + +// certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the +// results of every field request, and if it is out of order, we cache those for preceding fields. +struct field_cache { + + void* value; + SQLLEN len; + sqlsrv_phptype type; + + field_cache( void* field_value, SQLLEN field_len, sqlsrv_phptype t ) + : type( t ) + { + // if the value is NULL, then just record a NULL pointer + if( field_value != NULL ) { + value = sqlsrv_malloc( field_len ); + memcpy_s( value, field_len, field_value, field_len ); + len = field_len; + } + else { + value = NULL; + len = 0; + } + } + + // no destructor because we don't want to release the memory when it goes out of scope, but instead we + // rely on the hash table destructor to free the memory +}; + +const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field + +// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads +const unsigned int UTF8_MIDBYTE_MASK = 0xc0; +const unsigned int UTF8_MIDBYTE_TAG = 0x80; +const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0; +const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0; +const unsigned int UTF8_3BYTESEQ_TAG = 0xe0; +const unsigned int UTF8_4BYTESEQ_TAG = 0xf0; +const unsigned int UTF8_NBYTESEQ_MASK = 0xf0; + +// constants used to convert from a DateTime object to a string which is sent to the server. +// Using the format defined by the ODBC documentation at http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx +namespace DateTime { + +const char DATETIME_CLASS_NAME[] = "DateTime"; +const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1; +const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P"; +const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT ); +const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u"; +const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT ); +const char DATE_FORMAT[] = "Y-m-d"; +const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT ); + +} + +// *** internal functions *** +// Only declarations are put here. Functions contain the documentation they need at their definition sites. +void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ); +size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ); +bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC ); +bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z ); +void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype + sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC); +// returns the ODBC C type constant that matches the PHP type and encoding given +SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ); +void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); +// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) +void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); +void field_cache_dtor( zval* data_z ); +void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ); +void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, + _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ); +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ); +bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); +// assure there is enough space for the output parameter string +void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, + SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, + SQLLEN& buffer_len TSRMLS_DC ); +void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ); +// send all the stream data +void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ); +// called when a bound output string parameter is to be destroyed +void sqlsrv_output_param_dtor( zval* data ); +// called when a bound stream parameter is to be destroyed. +void sqlsrv_stream_dtor( zval* data ); +bool is_streamable_type( SQLINTEGER sql_type ); + +} + +// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier. +sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : + sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ), + conn( c ), + executed( false ), + past_fetch_end( false ), + current_results( NULL ), + cursor_type( SQL_CURSOR_FORWARD_ONLY ), + has_rows( false ), + fetch_called( false ), + last_field_index( -1 ), + past_next_result_end( false ), + query_timeout( QUERY_TIMEOUT_INVALID ), + buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ), + param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte + send_streams_at_exec( true ), + current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), + current_stream_read( 0 ) +{ + ZVAL_UNDEF( &active_stream ); + // initialize the input string parameters array (which holds zvals) + core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); + + // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) + ZVAL_NEW_ARR( ¶m_streams ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC); + + // initialize the (input only) datetime parameters of converted date time objects to strings + array_init( ¶m_datetime_buffers ); + + // initialize the output string parameters (which holds sqlsrv_output_param structures) + ZVAL_NEW_ARR( &output_params ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC); + + // initialize the field cache + ZVAL_NEW_ARR( &field_cache ); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC); +} + +// desctructor for sqlsrv statement. +sqlsrv_stmt::~sqlsrv_stmt( void ) +{ + if( Z_TYPE( active_stream ) != IS_UNDEF ) { + TSRMLS_FETCH(); + close_active_stream( this TSRMLS_CC ); + } + + // delete any current results + if( current_results ) { + current_results->~sqlsrv_result_set(); + efree( current_results ); + current_results = NULL; + } + + invalidate(); + zval_ptr_dtor( ¶m_input_strings ); + zval_ptr_dtor( &output_params ); + zval_ptr_dtor( ¶m_streams ); + zval_ptr_dtor( ¶m_datetime_buffers ); + zval_ptr_dtor( &field_cache ); +} + + +// centralized place to release (without destroying the hash tables +// themselves) all the parameter data that accrues during the +// execution phase. +void sqlsrv_stmt::free_param_data( TSRMLS_D ) +{ + SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY, + "sqlsrv_stmt::free_param_data: Param zvals aren't arrays." ); + zend_hash_clean( Z_ARRVAL( param_input_strings )); + zend_hash_clean( Z_ARRVAL( output_params )); + zend_hash_clean( Z_ARRVAL( param_streams )); + zend_hash_clean( Z_ARRVAL( param_datetime_buffers )); + zend_hash_clean( Z_ARRVAL( field_cache )); +} + + +// to be called whenever a new result set is created, such as after an +// execute or next_result. Resets the state variables. + +void sqlsrv_stmt::new_result_set( TSRMLS_D ) +{ + this->fetch_called = false; + this->has_rows = false; + this->past_next_result_end = false; + this->past_fetch_end = false; + this->last_field_index = -1; + + // delete any current results + if( current_results ) { + current_results->~sqlsrv_result_set(); + efree( current_results ); + current_results = NULL; + } + + // create a new result set + if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { + current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC ); + } + else { + current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this ); + } +} + +// core_sqlsrv_create_stmt +// Common code to allocate a statement from either driver. Returns a valid driver statement object or +// throws an exception if an error occurs. +// Parameters: +// conn - The connection resource by which the client and server are connected. +// stmt_factory - factory method to create a statement. +// options_ht - A HashTable of user provided options to be set on the statement. +// valid_stmt_opts - An array of valid driver supported statement options. +// err - callback for error handling +// driver - reference to caller +// Return +// Returns the created statement + +sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht, + const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC ) +{ + sqlsrv_malloc_auto_ptr stmt; + SQLHANDLE stmt_h = SQL_NULL_HANDLE; + sqlsrv_stmt* return_stmt = NULL; + + try { + + core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC ); + + stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC ); + + stmt->conn = conn; + + // handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free + // in the catch block below. + stmt_h = SQL_NULL_HANDLE; + + // process the options array given to core_sqlsrv_prepare. + if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { + zend_ulong index = -1; + zend_string *key = NULL; + zval* value_z = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) { + + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); + + const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); + + // if the key didn't match, then return the error to the script. + // The driver layer should ensure that the key is valid. + DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); + + // perform the actions the statement option needs done. + (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); + } + + return_stmt = stmt; + stmt.transferred(); + } + catch( core::CoreException& ) + { + if( stmt ) { + + conn->set_last_error( stmt->last_error() ); + stmt->~sqlsrv_stmt(); + } + + // if allocating the handle failed before the statement was allocated, free the handle + if( stmt_h != SQL_NULL_HANDLE) { + ::SQLFreeHandle( SQL_HANDLE_STMT, stmt_h ); + } + + throw; + } + catch( ... ) { + + DIE( "core_sqlsrv_allocate_stmt: Unknown exception caught." ); + } + + return return_stmt; +} + + +// core_sqlsrv_bind_param +// Binds a parameter using SQLBindParameter. It allocates memory and handles other details +// in translating between the driver and ODBC. +// Parameters: +// param_num - number of the parameter, 0 based +// param_z - zval of the parameter +// php_out_type - type to return for output parameter +// sql_type - ODBC constant for the SQL Server type (SQL_UNKNOWN_TYPE = 0 means not known, so infer defaults) +// column_size - length of the field on the server (SQLSRV_UKNOWN_SIZE means not known, so infer defaults) +// decimal_digits - if column_size is valid and the type contains a scale, this contains the scale +// Return: +// Nothing, though an exception is thrown if an error occurs +// The php type of the parameter is taken from the zval. +// The sql type is given as a hint if the driver provides it. + +void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z, + SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size, + SQLSMALLINT decimal_digits TSRMLS_DC ) +{ + SQLSMALLINT c_type; + SQLPOINTER buffer = NULL; + SQLLEN buffer_len = 0; + + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT, + "core_sqlsrv_bind_param: Invalid parameter direction." ); + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || php_out_type != SQLSRV_PHPTYPE_INVALID, + "core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param." ); + + try { + + // check is only < because params are 0 based + CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) { + throw core::CoreException(); + } + + // resize the statements array of int_ptrs if the parameter isn't already set. + if( stmt->param_ind_ptrs.size() < static_cast(param_num + 1) ) { + stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA ); + } + SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ]; + + zval* param_ref = param_z; + if ( Z_ISREF_P( param_z ) ) { + ZVAL_DEREF( param_z ); + } + bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL ); + bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE ); + // if the user asks for for a specific type for input and output, make sure the data type we send matches the data we + // type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so + // we always let that match if they want a string back. + if( direction == SQL_PARAM_INPUT_OUTPUT ) { + bool match = false; + switch( php_out_type ) { + case SQLSRV_PHPTYPE_INT: + if( zval_was_null || zval_was_bool ) { + convert_to_long( param_z ); + } + match = Z_TYPE_P( param_z ) == IS_LONG; + break; + case SQLSRV_PHPTYPE_FLOAT: + if( zval_was_null ) { + convert_to_double( param_z ); + } + match = Z_TYPE_P( param_z ) == IS_DOUBLE; + break; + case SQLSRV_PHPTYPE_STRING: + // anything can be converted to a string + convert_to_string( param_z ); + match = true; + break; + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_DATETIME: + case SQLSRV_PHPTYPE_STREAM: + SQLSRV_ASSERT( false, "Invalid type for an output parameter." ); + break; + default: + SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." ); + break; + } + CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) { + throw core::CoreException(); + } + } + + // if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so + // when the buffer is filled, the type is correct + if( direction == SQL_PARAM_OUTPUT ) { + switch( php_out_type ) { + case SQLSRV_PHPTYPE_INT: + convert_to_long( param_z ); + break; + case SQLSRV_PHPTYPE_FLOAT: + convert_to_double( param_z ); + break; + case SQLSRV_PHPTYPE_STRING: + convert_to_string( param_z ); + break; + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_DATETIME: + case SQLSRV_PHPTYPE_STREAM: + SQLSRV_ASSERT( false, "Invalid type for an output parameter" ); + break; + default: + SQLSRV_ASSERT( false, "Uknown SQLSRV_PHPTYPE_* constant given" ); + break; + } + } + + SQLSRV_ASSERT(( Z_TYPE_P( param_z ) != IS_STRING && Z_TYPE_P( param_z ) != IS_RESOURCE ) || + ( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 || + encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" ); + + // if the sql type is unknown, then set the default based on the PHP type passed in + if( sql_type == SQL_UNKNOWN_TYPE ) { + default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); + } + + // if the size is unknown, then set the default based on the PHP type passed in + if( column_size == SQLSRV_UNKNOWN_SIZE ) { + default_sql_size_and_scale( stmt, static_cast( param_num ), param_z, encoding, column_size, decimal_digits TSRMLS_CC ); + } + + // determine the ODBC C type + c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC ); + + // set the buffer based on the PHP parameter type + switch( Z_TYPE_P( param_z )) { + + case IS_NULL: + { + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); + ind_ptr = SQL_NULL_DATA; + buffer = NULL; + buffer_len = 0; + } + break; + case IS_TRUE: + case IS_FALSE: + case IS_LONG: + { + // if it is boolean, set the lval to 0 or 1 + convert_to_long( param_z ); + buffer = ¶m_z->value; + buffer_len = sizeof( Z_LVAL_P( param_z )); + ind_ptr = buffer_len; + if( direction != SQL_PARAM_INPUT ) { + // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned + sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool ); + save_output_param_for_later( stmt, output_param TSRMLS_CC ); + } + } + break; + case IS_DOUBLE: + { + buffer = ¶m_z->value; + buffer_len = sizeof( Z_DVAL_P( param_z )); + ind_ptr = buffer_len; + if( direction != SQL_PARAM_INPUT ) { + // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned + sqlsrv_output_param output_param( param_ref, static_cast( param_num ), false ); + save_output_param_for_later( stmt, output_param TSRMLS_CC ); + } + } + break; + case IS_STRING: + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) + if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) { + + zval wbuffer_z; + ZVAL_NULL( &wbuffer_z ); + + bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z ); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + param_num + 1, get_last_error_message() ) { + throw core::CoreException(); + } + buffer = Z_STRVAL_P( &wbuffer_z ); + buffer_len = Z_STRLEN_P( &wbuffer_z ); + core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC); + } + ind_ptr = buffer_len; + if( direction != SQL_PARAM_INPUT ) { + // PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion, + // we reallocate the string if it's interned + if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) { + core::sqlsrv_zval_stringl( param_z, static_cast(buffer), buffer_len ); + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + } + + // if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR) + // or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type, + // convert it to wchar first + if( direction == SQL_PARAM_INPUT_OUTPUT && + ( c_type == SQL_C_WCHAR || + ( c_type == SQL_C_BINARY && + ( sql_type == SQL_WCHAR || + sql_type == SQL_WVARCHAR || + sql_type == SQL_WLONGVARCHAR )))) { + + bool converted = convert_input_param_to_utf16( param_z, param_z ); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + param_num + 1, get_last_error_message() ) { + throw core::CoreException(); + } + buffer = Z_STRVAL_P( param_z ); + buffer_len = Z_STRLEN_P( param_z ); + ind_ptr = buffer_len; + } + + // since this is an output string, assure there is enough space to hold the requested size and + // set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr) + resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, + buffer, buffer_len TSRMLS_CC ); + + // save the parameter to be adjusted and/or converted after the results are processed + sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len )); + + save_output_param_for_later( stmt, output_param TSRMLS_CC ); + + // For output parameters, if we set the column_size to be same as the buffer_len, + // then if there is a truncation due to the data coming from the server being + // greater than the column_size, we don't get any truncation error. In order to + // avoid this silent truncation, we set the column_size to be "MAX" size for + // string types. This will guarantee that there is no silent truncation for + // output parameters. + if( direction == SQL_PARAM_OUTPUT ) { + + switch( sql_type ) { + + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_WVARCHAR: + column_size = SQL_SS_LENGTH_UNLIMITED; + break; + + default: + break; + } + } + } + break; + case IS_RESOURCE: + { + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); + sqlsrv_stream stream_encoding( param_z, encoding ); + HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC ); + buffer = reinterpret_cast( param_num ); + Z_TRY_ADDREF_P( param_z ); // so that it doesn't go away while we're using it + buffer_len = 0; + ind_ptr = SQL_DATA_AT_EXEC; + } + break; + case IS_OBJECT: + { + SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); + zval function_z; + zval buffer_z; + zval format_z; + zval params[1]; + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( &buffer_z ); + ZVAL_UNDEF( &format_z ); + ZVAL_UNDEF( params ); + + bool valid_class_name_found = false; + + zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC ); + + while( class_entry != NULL ) { + + if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL && + stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) { + valid_class_name_found = true; + break; + } + + else { + + // Check the parent + class_entry = class_entry->parent; + } + } + + CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { + throw core::CoreException(); + } + + // if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error' + // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' + // sql type, it lacks the timezone. + if( sql_type == SQL_SS_TIMESTAMPOFFSET ) { + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), + DateTime::DATETIMEOFFSET_FORMAT_LEN ); + } + else if( sql_type == SQL_TYPE_DATE ) { + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); + } + else { + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); + } + // call the DateTime::format member function to convert the object to a string that SQL Server understands + core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); + params[0] = format_z; + // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the + // DateTime object and $format_z is the format string. + int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); + zend_string_release( Z_STR( format_z )); + zend_string_release( Z_STR( function_z )); + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { + throw core::CoreException(); + } + buffer = Z_STRVAL( buffer_z ); + zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z ); + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) { + throw core::CoreException(); + } + buffer_len = Z_STRLEN( buffer_z ) - 1; + ind_ptr = buffer_len; + break; + } + case IS_ARRAY: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ); + break; + default: + DIE( "core_sqlsrv_bind_param: Unsupported PHP type. Only string, float, int, and streams (resource) are supported. " + "It is the responsibilty of the driver layer to convert a parameter to one of these types." ); + break; + } + + if( zval_was_null ) { + ind_ptr = SQL_NULL_DATA; + } + + core::SQLBindParameter( stmt, param_num + 1, direction, + c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); + } + catch( core::CoreException& e ) { + stmt->free_param_data( TSRMLS_C ); + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); + throw e; + } +} + + +// core_sqlsrv_execute +// Executes the statement previously prepared +// Parameters: +// stmt - the core sqlsrv_stmt structure that contains the ODBC handle +// Return: +// true if there is data, false if there is not + +void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len ) +{ + SQLRETURN r; + + try { + + // close the stream to release the resource + close_active_stream( stmt TSRMLS_CC ); + + if( sql ) { + + sqlsrv_malloc_auto_ptr wsql_string; + unsigned int wsql_len = 0; + if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { + wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( SQLWCHAR ))); + wsql_string[0] = L'\0'; + wsql_len = 0; + } + else { + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding() ); + wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), + sql_len, &wsql_len ); + CHECK_CUSTOM_ERROR( wsql_string == 0, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + get_last_error_message() ) { + throw core::CoreException(); + } + } + r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC ); + } + else { + r = core::SQLExecute( stmt TSRMLS_CC ); + } + + // if data is needed (streams were bound) and they should be sent at execute time, then do so now + if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) { + + send_param_streams( stmt TSRMLS_CC ); + } + + stmt->new_result_set( TSRMLS_C ); + stmt->executed = true; + + // if all the data has been sent and no data was returned then finalize the output parameters + if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { + + finalize_output_parameters( stmt TSRMLS_CC ); + } + // stream parameters are sent, clean the Hashtable + if ( stmt->send_streams_at_exec ) { + zend_hash_clean( Z_ARRVAL( stmt->param_streams )); + } + } + catch( core::CoreException& e ) { + + // if the statement executed but failed in a subsequent operation before returning, + // we need to cancel the statement and deref the output and stream parameters + if ( stmt->send_streams_at_exec ) { + zend_hash_clean( Z_ARRVAL( stmt->output_params )); + zend_hash_clean( Z_ARRVAL( stmt->param_streams )); + } + if( stmt->executed ) { + SQLCancel( stmt->handle() ); + // stmt->executed = false; should this be reset if something fails? + } + + throw e; + } +} + + +// core_sqlsrv_fetch +// Moves the cursor according to the parameters (by default, moves to the next row) +// Parameters: +// stmt - the sqlsrv_stmt of the cursor +// fetch_orientation - method to move the cursor +// fetch_offset - if the method has a parameter (such as number of rows to move or literal row number) +// Returns: +// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the +// user scrolls past a non-scrollable result set + +bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC ) +{ + // pre-condition check + SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE, + "core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." ); + + try { + + // clear the field cache of the previous fetch + zend_hash_clean( Z_ARRVAL( stmt->field_cache )); + + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) { + throw core::CoreException(); + } + + SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + + CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { + throw core::CoreException(); + } + + // close the stream to release the resource + close_active_stream( stmt TSRMLS_CC ); + + // if the statement has rows and is not scrollable but doesn't yet have + // fetch_called, this must be the first time we've called sqlsrv_fetch. + if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->has_rows && !stmt->fetch_called ) { + stmt->fetch_called = true; + return true; + } + + // move to the record requested. For absolute records, we use a 0 based offset, so +1 since + // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. + SQLRETURN r = stmt->current_results->fetch( fetch_orientation, ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 TSRMLS_CC ); + if( r == SQL_NO_DATA ) { + // if this is a forward only cursor, mark that we've passed the end so future calls result in an error + if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { + stmt->past_fetch_end = true; + } + return false; + } + + // mark that we called fetch (which get_field, et. al. uses) and reset our last field retrieved + stmt->fetch_called = true; + stmt->last_field_index = -1; + stmt->has_rows = true; // since we made it this far, we must have at least one row + } + catch (core::CoreException& e) { + throw e; + } + catch ( ... ) { + DIE( "core_sqlsrv_fetch: Unexpected exception occurred." ); + } + + return true; +} + + +// Retrieves metadata for a field of a prepared statement. +// Parameters: +// colno - the index of the field for which to return the metadata. columns are 0 based in PDO +// Return: +// A field_meta_data* consisting of the field metadata. + +field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC ) +{ + // pre-condition check + SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." ); + + sqlsrv_malloc_auto_ptr meta_data; + sqlsrv_malloc_auto_ptr field_name_temp; + SQLSMALLINT field_len_temp = 0; + SQLLEN field_name_len = 0; + + meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data(); + field_name_temp = static_cast( sqlsrv_malloc( ( SS_MAXCOLNAMELEN + 1 ) * sizeof( SQLWCHAR ) )); + SQLSRV_ENCODING encoding = ( (stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); + try{ + core::SQLDescribeColW( stmt, colno + 1, field_name_temp, SS_MAXCOLNAMELEN + 1, &field_len_temp, + &( meta_data->field_type ), & ( meta_data->field_size ), & ( meta_data->field_scale ), + &( meta_data->field_is_nullable ) TSRMLS_CC ); + } + catch ( core::CoreException& e ) { + throw e; + } + + bool converted = convert_string_from_utf16( encoding, field_name_temp, field_len_temp, ( char** ) &( meta_data->field_name ), field_name_len ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { + throw core::CoreException(); + } + + // depending on field type, we add the values into size or precision/scale. + switch( meta_data->field_type ) { + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TYPE_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_BIT: + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIGINT: + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: + { + meta_data->field_precision = meta_data->field_size; + meta_data->field_size = 0; + break; + } + default: { + break; + } + } + + // Set the field name lenth + meta_data->field_name_len = field_name_len; + + field_meta_data* result_field_meta_data = meta_data; + meta_data.transferred(); + return result_field_meta_data; +} + + +// core_sqlsrv_get_field +// Return the value of a column from ODBC +// Parameters: +// stmt - the sqlsrv_stmt from which to retrieve the column +// field_index - 0 based index for the column to retrieve +// sqlsrv_php_type_in - sqlsrv_php_type structure that tells what format to return the data in +// field_value - pointer to the data retrieved +// field_len - length of the data in the field_value buffer +// Returns: +// Nothing, excpetion thrown if an error occurs + +void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string, + _Out_ void*& field_value, _Out_ SQLLEN* field_len, bool cache_field, + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) +{ + try { + + // close the stream to release the resource + close_active_stream(stmt TSRMLS_CC); + + // if the field has been retrieved before, return the previous result + field_cache* cached = NULL; + if (NULL != ( cached = static_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast( field_index ))))) { + // the field value is NULL + if( cached->value == NULL ) { + field_value = NULL; + *field_len = 0; + if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } + } + else { + + field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); + memcpy_s( field_value, ( cached->len * sizeof( char )), cached->value, cached->len ); + if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING) { + // prevent the 'string not null terminated' warning + reinterpret_cast( field_value )[ cached->len ] = '\0'; + } + *field_len = cached->len; + if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } + } + return; + } + + sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; + + SQLLEN sql_field_type = 0; + SQLLEN sql_field_len = 0; + + // Make sure that the statement was executed and not just prepared. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } + + // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they + // may also be retrieved. + if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { + sqlsrv_phptype invalid; + invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { + SQLSRV_ASSERT( reinterpret_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), i )) == NULL, "Field already cached." ); + core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, sqlsrv_php_type_out TSRMLS_CC ); + // delete the value returned since we only want it cached, not the actual value + if( field_value ) { + efree( field_value ); + field_value = NULL; + *field_len = 0; + } + } + } + + // If the php type was not specified set the php type to be the default type. + if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { + + // Get the SQL type of the field. + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + + // Get the length of the field. + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); + + // Get the corresponding php type from the sql type. + sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast( sql_field_type ), static_cast( sql_field_len ), prefer_string ); + } + + // Verify that we have an acceptable type to convert. + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { + throw core::CoreException(); + } + + if( sqlsrv_php_type_out != NULL ) + *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); + + // Retrieve the data + core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + + // if the user wants us to cache the field, we'll do it + if( cache_field ) { + field_cache cache( field_value, *field_len, sqlsrv_php_type ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); + } + } + + catch( core::CoreException& e ) { + throw e; + } +} + +// core_sqlsrv_has_any_result +// return if any result set or rows affected message is waiting +// to be consumed and moved over by sqlsrv_next_result. +// Parameters: +// stmt - The statement object on which to check for results. +// Return: +// true if any results are present, false otherwise. + +bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + // Use SQLNumResultCols to determine if we have rows or not. + SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + // use SQLRowCount to determine if there is a rows status waiting + SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); + return (num_cols != 0) || (rows_affected > 0); +} + +// core_sqlsrv_next_result +// Advances to the next result set from the last executed query +// Parameters +// stmt - the sqlsrv_stmt structure +// Returns +// Nothing, exception thrown if problem occurs + +void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params, bool throw_on_errors ) +{ + try { + + // make sure that the statement has been executed. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) { + throw core::CoreException(); + } + + close_active_stream( stmt TSRMLS_CC ); + + SQLRETURN r; + if( throw_on_errors ) { + r = core::SQLMoreResults( stmt TSRMLS_CC ); + } + else { + r = SQLMoreResults( stmt->handle() ); + } + + if( r == SQL_NO_DATA ) { + + if( &(stmt->output_params) && finalize_output_params ) { + // if we're finished processing result sets, handle the output parameters + finalize_output_parameters( stmt TSRMLS_CC ); + } + + // mark we are past the end of all results + stmt->past_next_result_end = true; + return; + } + + stmt->new_result_set( TSRMLS_C ); + } + catch( core::CoreException& e ) { + + SQLCancel( stmt->handle() ); + throw e; + } +} + + +// core_sqlsrv_post_param +// Performs any actions post execution for each parameter. For now it cleans up input parameters memory from the statement +// Parameters: +// stmt - the sqlsrv_stmt structure +// param_num - 0 based index of the parameter +// param_z - parameter value itself. +// Returns: +// Nothing, exception thrown if problem occurs + +void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* param_z TSRMLS_DC ) +{ + SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); + SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); + + // if the parameter was an input string, delete it from the array holding input parameter strings + if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) { + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC ); + } + + // if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams + // PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it + // with sqlsrv_send_stream_data. + if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) { + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC ); + } +} + +//Calls SQLSetStmtAttr to set a cursor. +void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC ) +{ + try { + + switch( cursor_type ) { + + case SQL_CURSOR_STATIC: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQL_CURSOR_DYNAMIC: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQL_CURSOR_KEYSET_DRIVEN: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQL_CURSOR_FORWARD_ONLY: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + case SQLSRV_CURSOR_BUFFERED: + core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, + reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); + break; + + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); + break; + } + + stmt->cursor_type = cursor_type; + + } + catch( core::CoreException& ) { + throw; + } +} + +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + if( Z_TYPE_P( value_z ) != IS_LONG ) { + + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); + } + + core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); +} + +void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ) +{ + if( limit <= 0 ) { + + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); + } + + stmt->buffered_query_limit = limit; +} + + +// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout +// which accepts timeout parameter as a long. If the zval is not of type long +// than throws error. +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + try { + + // validate the value + if( Z_TYPE_P( value_z ) != IS_LONG || Z_LVAL_P( value_z ) < 0 ) { + + convert_to_string( value_z ); + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) ); + } + + core_sqlsrv_set_query_timeout( stmt, static_cast( Z_LVAL_P( value_z )) TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } +} + +// Overloaded. Accepts the timeout as a long. +void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC ) +{ + try { + + DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." ); + + // set the statement attribute + core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC ); + + // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which + // is represented by -1. + int lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); + + // set the LOCK_TIMEOUT on the server. + char lock_timeout_sql[ 32 ]; + + int written = snprintf( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", lock_timeout ); + SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), + "stmt_option_query_timeout: snprintf failed. Shouldn't ever fail." ); + + core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC ); + + stmt->query_timeout = timeout; + } + catch( core::CoreException& ) { + throw; + } +} + +void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ) +{ + TSRMLS_C; + + // zend_is_true does not fail. It either returns true or false. + stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false; +} + + +// core_sqlsrv_send_stream_packet +// send a single packet from a stream parameter to the database using +// ODBC. This will also handle the transition between parameters. It +// returns true if it is not done sending, false if it is finished. +// return_value is what should be returned to the script if it is +// given. Any errors that occur are posted here. +// Parameters: +// stmt - query to send the next packet for +// Returns: +// true if more data remains to be sent, false if all data processed + +bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + // if there no current parameter to process, get the next one + // (probably because this is the first call to sqlsrv_send_stream_data) + if( stmt->current_stream.stream_z == NULL ) { + + if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) { + + stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); + stmt->current_stream_read = 0; + return false; + } + } + + try { + + // get the stream from the zval we bound + php_stream* param_stream = NULL; + core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); + + // if we're at the end, then release our current parameter + if( php_stream_eof( param_stream )) { + // if no data was actually sent prior, then send a NULL + if( stmt->current_stream_read == 0 ) { + // send an empty string, which is what a 0 length does. + char buff[1]; // temp storage to hand to SQLPutData + core::SQLPutData( stmt, buff, 0 TSRMLS_CC ); + } + stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); + stmt->current_stream_read = 0; + } + // read the data from the stream, send it via SQLPutData and track how much we've sent. + else { + char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; + std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character + std::size_t read = php_stream_read( param_stream, buffer, buffer_size ); + + if (read > UINT_MAX) + { + LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); + throw core::CoreException(); + } + + stmt->current_stream_read += static_cast( read ); + if( read > 0 ) { + // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character + // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it + // twice. + // If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion + // since all other MBCS supported by SQL Server are 2 byte maximum size. + if( stmt->current_stream.encoding == CP_UTF8 ) { + + // the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a + // expansion of 2x the UTF-8 size. + SQLWCHAR wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; + int wbuffer_size = static_cast( sizeof( wbuffer ) / sizeof( SQLWCHAR )); + DWORD last_error_code = ERROR_SUCCESS; + // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate +#ifndef _WIN32 + int wsize = SystemLocale::ToUtf16Strict( stmt->current_stream.encoding, buffer, static_cast(read), wbuffer, wbuffer_size, &last_error_code ); +#else + int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, buffer, static_cast( read ), wbuffer, wbuffer_size ); + last_error_code = GetLastError(); +#endif // !_WIN32 + + if( wsize == 0 && last_error_code == ERROR_NO_UNICODE_TRANSLATION ) { + + // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more + // in, then reattempt the conversion. If it fails the second time, then an error is returned. + size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC ); + // read the missing bytes + size_t new_read = php_stream_read( param_stream, static_cast( buffer ) + read, + need_to_read ); + // if the bytes couldn't be read, then we return an error + CHECK_CUSTOM_ERROR( new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { + throw core::CoreException(); + } + // try the conversion again with the complete character +#ifndef _WIN32 + wsize = SystemLocale::ToUtf16Strict( stmt->current_stream.encoding, buffer, static_cast(read + new_read), wbuffer, static_cast(sizeof( wbuffer ) / sizeof( SQLWCHAR ))); +#else + wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS, buffer, static_cast( read + new_read ), wbuffer, static_cast( sizeof( wbuffer ) / sizeof( wchar_t ))); +#endif //!_WIN32 + // something else must be wrong if it failed + CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) { + throw core::CoreException(); + } + } + core::SQLPutData( stmt, wbuffer, wsize * sizeof( SQLWCHAR ) TSRMLS_CC ); + } + else { + core::SQLPutData( stmt, buffer, read TSRMLS_CC ); + } + } + } + + } + catch( core::CoreException& e ) { + stmt->free_param_data( TSRMLS_C ); + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); + SQLCancel( stmt->handle() ); + stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT ); + stmt->current_stream_read = 0; + throw e; + } + + return true; +} + +void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) +{ + TSRMLS_C; + + // This implementation should never get called. + DIE( "Not implemented." ); +} + +void stmt_option_query_timeout:: operator()( sqlsrv_stmt* stmt, stmt_option const* /**/, zval* value_z TSRMLS_DC ) +{ + core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_send_at_exec:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC ); +} + +void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC ); +} + + +// internal function to release the active stream. Called by each main API function +// that will alter the statement and cancel any retrieval of data from a stream. +void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +{ + // if there is no active stream, return + if( Z_TYPE( stmt->active_stream ) == IS_UNDEF ) { + return; + } + + php_stream* stream = NULL; + + // we use no verify since verify would return immediately and we want to assert, not return. + php_stream_from_zval_no_verify( stream, &( stmt->active_stream )); + + SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." ); + + php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here. + + SQLSRV_ASSERT( Z_TYPE( stmt->active_stream ) == IS_UNDEF, "close_active_stream: Active stream not closed." ); + +} + +// local routines not shared by other files (arranged alphabetically) + +namespace { + +bool is_streamable_type( SQLLEN sql_type ) +{ + switch( sql_type ) { + case SQL_CHAR: + case SQL_WCHAR: + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_WVARCHAR: + case SQL_SS_XML: + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + return true; + } + + return false; +} + +void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC ) +{ + try { + + switch( sql_type ) { + // for types that are fixed in size or for which the size is unknown, return the display size. + case SQL_BIGINT: + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + case SQL_GUID: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_REAL: + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TYPE_TIMESTAMP: + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_BINARY: + case SQL_CHAR: + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_SS_XML: + case SQL_SS_UDT: + case SQL_WLONGVARCHAR: + case SQL_DATETIME: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + { + // unixODBC 2.3.1 requires wide calls to support pooling + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC ); + break; + } + + // for wide char types for which the size is known, return the octet length instead, since it will include the + // the number of bytes necessary for the string, not just the characters + case SQL_WCHAR: + case SQL_WVARCHAR: + { + // unixODBC 2.3.1 requires wide calls to support pooling + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC ); + break; + } + + default: + DIE ( "Unexpected SQL type encountered in calc_string_size." ); + } + } + catch( core::CoreException& e ) { + throw e; + } +} + + +// calculates how many characters were cut off from the end of a buffer when reading +// in UTF-8 encoded text + +size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC ) +{ + const char* last_char = buffer + buffer_end - 1; + size_t need_to_read = 0; + + // rewind until we are at the byte that starts the cut off character + while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) { + --last_char; + ++need_to_read; + } + + // determine how many bytes we need to read in based on the number of bytes in the character + // (# of high bits set) versus the number of bytes we've already read. + switch( *last_char & UTF8_NBYTESEQ_MASK ) { + case UTF8_2BYTESEQ_TAG1: + case UTF8_2BYTESEQ_TAG2: + need_to_read = 1 - need_to_read; + break; + case UTF8_3BYTESEQ_TAG: + need_to_read = 2 - need_to_read; + break; + case UTF8_4BYTESEQ_TAG: + need_to_read = 3 - need_to_read; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )); + break; + } + + return need_to_read; +} + + +// Caller is responsible for freeing the memory allocated for the field_value. +// The memory allocation has to happen in the core layer because otherwise +// the driver layer would have to calculate size of the field_value +// to decide the amount of memory allocation. +void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype + sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) +{ + try { + + close_active_stream( stmt TSRMLS_CC ); + + // make sure that fetch is called before trying to retrieve. + CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { + throw core::CoreException(); + } + + // make sure that fields are not retrieved incorrectly. + CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index, + stmt->last_field_index ) { + throw core::CoreException(); + } + + switch( sqlsrv_php_type.typeinfo.type ) { + + case SQLSRV_PHPTYPE_INT: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); + *field_value_temp = 0; + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), + field_len, true /*handle_warning*/ TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( *field_len == SQL_NULL_DATA ) { + field_value = NULL; + break; + } + + field_value = field_value_temp; + field_value_temp.transferred(); + break; + } + + case SQLSRV_PHPTYPE_FLOAT: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), + field_len, true /*handle_warning*/ TSRMLS_CC ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( *field_len == SQL_NULL_DATA ) { + field_value = NULL; + break; + } + + field_value = field_value_temp; + field_value_temp.transferred(); + break; + } + + case SQLSRV_PHPTYPE_STRING: + { + get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + break; + } + + // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and + // convert it to a DateTime object and return the created object + case SQLSRV_PHPTYPE_DATETIME: + { + char field_value_temp[ MAX_DATETIME_STRING_LEN ]; + zval params[1]; + zval field_value_temp_z; + zval function_z; + + ZVAL_UNDEF( &field_value_temp_z ); + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( params ); + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, + MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + zval_auto_ptr return_value_z; + return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); + ZVAL_UNDEF( return_value_z ); + + if( *field_len == SQL_NULL_DATA ) { + ZVAL_NULL( return_value_z ); + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + break; + } + + // Convert the string date to a DateTime object + core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); + core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 ); + params[0] = field_value_temp_z; + + if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, + params TSRMLS_CC ) == FAILURE) { + THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); + } + + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + zend_string_free( Z_STR( field_value_temp_z )); + zend_string_free( Z_STR( function_z )); + break; + } + + // create a stream wrapper around the field and return that object to the PHP script. calls to fread + // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file + // for how these fields are used. + case SQLSRV_PHPTYPE_STREAM: + { + + php_stream* stream = NULL; + sqlsrv_stream* ss = NULL; + SQLLEN sql_type; + + SQLRETURN r = SQLColAttributeW( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { + throw core::CoreException(); + } + + stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); + + CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { + throw core::CoreException(); + } + + ss = static_cast( stream->abstract ); + ss->stmt = stmt; + ss->field_index = field_index; + ss->sql_type = static_cast( sql_type ); + ss->encoding = static_cast( sqlsrv_php_type.typeinfo.encoding ); + + zval_auto_ptr return_value_z; + return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); + ZVAL_UNDEF( return_value_z ); + + // turn our stream into a zval to be returned + php_stream_to_zval( stream, return_value_z ); + + field_value = reinterpret_cast( return_value_z.get()); + return_value_z.transferred(); + break; + } + + case SQLSRV_PHPTYPE_NULL: + field_value = NULL; + *field_len = 0; + break; + + default: + DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" ); + break; + } + + // sucessfully retrieved the field, so update our last retrieved field + if( stmt->last_field_index < field_index ) { + stmt->last_field_index = field_index; + } + } + catch( core::CoreException& e ) { + throw e; + } +} + + +// check_for_next_stream_parameter +// see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise +// returns false +bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +{ + zend_ulong stream_index = 0; + SQLRETURN r = SQL_SUCCESS; + sqlsrv_stream* stream_encoding = NULL; + zval* param_z = NULL; + + // get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param + r = core::SQLParamData( stmt, reinterpret_cast( &stream_index ) TSRMLS_CC ); + // if no more data, we've exhausted the bound parameters, so return that we're done + if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { + + // we're all done, so return false + return false; + } + + HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); + + // pull out the sqlsrv_encoding struct + stream_encoding = reinterpret_cast(zend_hash_index_find_ptr(streams_ht, stream_index)); + SQLSRV_ASSERT(stream_encoding != NULL, "Stream parameter does not exist"); // if the index isn't in the hash, that's a serious error + + param_z = stream_encoding->stream_z; + + // make the next stream current + stmt->current_stream = sqlsrv_stream( param_z, stream_encoding->encoding ); + stmt->current_stream_read = 0; + + // there are more parameters + return true; +} + + +// utility routine to convert an input parameter from UTF-8 to UTF-16 + +bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z ) +{ + SQLSRV_ASSERT( input_param_z == converted_param_z || Z_TYPE_P( converted_param_z ) == IS_NULL, + "convert_input_param_z called with invalid parameter states" ); + + const char* buffer = Z_STRVAL_P( input_param_z ); + std::size_t buffer_len = Z_STRLEN_P( input_param_z ); + int wchar_size; + + if (buffer_len > INT_MAX) + { + LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } + + // if the string is empty, then just return that the conversion succeeded as + // MultiByteToWideChar will "fail" on an empty string. + if( buffer_len == 0 ) { + core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); + return true; + } + + // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string +#ifndef _WIN32 + wchar_size = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); +#else + wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); +#endif // !_WIN32 + + // if there was a problem determining the size of the string, return false + if( wchar_size == 0 ) { + return false; + } + sqlsrv_malloc_auto_ptr wbuffer; + wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( SQLWCHAR ) )); + // convert the utf-8 string to a wchar string in the new buffer +#ifndef _WIN32 + int r = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast( buffer ), static_cast( buffer_len ), wbuffer, wchar_size ); +#else + int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), static_cast( buffer_len ), wbuffer, wchar_size ); +#endif // !_WIN32 + // if there was a problem converting the string, then free the memory and return false + if( r == 0 ) { + return false; + } + + // null terminate the string, set the size within the zval, and return success + wbuffer[ wchar_size ] = L'\0'; + core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), wchar_size * sizeof( SQLWCHAR ) ); + sqlsrv_free(wbuffer); + wbuffer.transferred(); + + return true; +} + +// returns the ODBC C type constant that matches the PHP type and encoding given + +SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC ) +{ + SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; + int php_type = Z_TYPE_P( param_z ); + + switch( php_type ) { + + case IS_NULL: + switch( encoding ) { + // The c type is set to match to the corresponding sql_type. For NULL cases, if the server type + // is a binary type, than the server expects the sql_type to be binary type as well, otherwise + // an error stating "Implicit conversion not allowed.." is thrown by the server. + // For all other server types, setting the sql_type to sql_char works fine. + case SQLSRV_ENCODING_BINARY: + sql_c_type = SQL_C_BINARY; + break; + default: + sql_c_type = SQL_C_CHAR; + break; + } + break; + case IS_TRUE: + case IS_FALSE: + sql_c_type = SQL_C_SLONG; + break; + case IS_LONG: + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) { + sql_c_type = SQL_C_SBIGINT; + } + else { + sql_c_type = SQL_C_SLONG; + } + break; + case IS_DOUBLE: + sql_c_type = SQL_C_DOUBLE; + break; + case IS_STRING: + case IS_RESOURCE: + switch( encoding ) { + case SQLSRV_ENCODING_CHAR: + sql_c_type = SQL_C_CHAR; + break; + case SQLSRV_ENCODING_BINARY: + sql_c_type = SQL_C_BINARY; + break; + case CP_UTF8: + sql_c_type = SQL_C_WCHAR; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); + break; + } + break; + // it is assumed that an object is a DateTime since it's the only thing we support. + // verification that it's a real DateTime object occurs in core_sqlsrv_bind_param. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + sql_c_type = SQL_C_CHAR; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); + break; + } + + return sql_c_type; +} + + +// given a zval and encoding, determine the appropriate sql type +void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLSMALLINT& sql_type TSRMLS_DC ) +{ + sql_type = SQL_UNKNOWN_TYPE; + int php_type = Z_TYPE_P(param_z); + switch( php_type ) { + + case IS_NULL: + switch( encoding ) { + // Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases, + // if the server type is a binary type, than the server expects the sql_type to be binary type + // as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the + // server. For all other server types, setting the sql_type to sql_char works fine. + case SQLSRV_ENCODING_BINARY: + sql_type = SQL_BINARY; + break; + default: + sql_type = SQL_CHAR; + break; + } + break; + case IS_TRUE: + case IS_FALSE: + sql_type = SQL_INTEGER; + break; + case IS_LONG: + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) { + sql_type = SQL_BIGINT; + } + else { + sql_type = SQL_INTEGER; + } + break; + case IS_DOUBLE: + sql_type = SQL_FLOAT; + break; + case IS_RESOURCE: + case IS_STRING: + switch( encoding ) { + case SQLSRV_ENCODING_CHAR: + sql_type = SQL_VARCHAR; + break; + case SQLSRV_ENCODING_BINARY: + sql_type = SQL_VARBINARY; + break; + case CP_UTF8: + sql_type = SQL_WVARCHAR; + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno ); + break; + } + break; + // it is assumed that an object is a DateTime since it's the only thing we support. + // verification that it's a real DateTime object occurs in the calling function. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + // if the user is sending this type to SQL Server 2005 or earlier, make it appear + // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET + // since these are the date types of the highest precision for their respective server versions + if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { + sql_type = SQL_TYPE_TIMESTAMP; + } + else { + sql_type = SQL_SS_TIMESTAMPOFFSET; + } + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); + break; + } + +} + + +// given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate) + +void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding, + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) +{ + int php_type = Z_TYPE_P( param_z ); + column_size = 0; + decimal_digits = 0; + + switch( php_type ) { + + case IS_NULL: + column_size = 1; + break; + // size is not necessary for these types, they are inferred by ODBC + case IS_TRUE: + case IS_FALSE: + case IS_LONG: + case IS_DOUBLE: + case IS_RESOURCE: + break; + case IS_STRING: + { + size_t char_size = (encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( SQLWCHAR ) : sizeof( char ); + SQLULEN byte_len = Z_STRLEN_P(param_z) * char_size; + if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { + column_size = SQL_SERVER_MAX_TYPE_SIZE; + } + else { + column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size; + } + break; + } + // it is assumed that an object is a DateTime since it's the only thing we support. + // verification that it's a real DateTime object occurs in the calling function. + // we convert the DateTime to a string before sending it to the server. + case IS_OBJECT: + // if the user is sending this type to SQL Server 2005 or earlier, make it appear + // as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET + // since these are the date types of the highest precision for their respective server versions + if( stmt->conn->server_version <= SERVER_VERSION_2005 ) { + column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION; + decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE; + } + else { + column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION; + decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE; + } + break; + default: + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno ); + break; + } +} + +void field_cache_dtor( zval* data_z ) +{ + field_cache* cache = static_cast( Z_PTR_P( data_z )); + if( cache->value ) + { + sqlsrv_free( cache->value ); + } + sqlsrv_free( cache ); +} + + +// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output +// parameters will be present until all results are processed (since output parameters can depend on results +// while being processed). This function updates the lengths of output parameter strings from the ind_ptr +// parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary. +// For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server + +void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + if( Z_ISUNDEF(stmt->output_params) ) + return; + + HashTable* params_ht = Z_ARRVAL( stmt->output_params ); + zend_ulong index = -1; + zend_string* key = NULL; + void* output_param_temp = NULL; + + ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { + sqlsrv_output_param* output_param = static_cast( output_param_temp ); + zval* value_z = Z_REFVAL_P( output_param->param_z ); + switch( Z_TYPE_P( value_z )) { + case IS_STRING: + { + // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter + char* str = Z_STRVAL_P( value_z ); + SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ]; + if( str_len == SQL_NULL_DATA || str_len == 0 ) { + zend_string_release( Z_STR_P( value_z )); + ZVAL_NULL( value_z ); + continue; + } + + // if there was more to output than buffer size to hold it, then throw a truncation error + int null_size = 0; + switch( output_param->encoding ) { + case SQLSRV_ENCODING_UTF8: + null_size = sizeof( SQLWCHAR ); // string isn't yet converted to UTF-8, still UTF-16 + break; + case SQLSRV_ENCODING_SYSTEM: + null_size = 1; + break; + case SQLSRV_ENCODING_BINARY: + null_size = 0; + break; + default: + SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); + break; + } + CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { + throw core::CoreException(); + } + + // if it's not in the 8 bit encodings, then it's in UTF-16 + if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) { + bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len); + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) { + // ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated + // so we do that here if the length of the returned data is less than the original allocation. The + // original allocation null terminates the buffer already. + str[ str_len ] = '\0'; + core::sqlsrv_zval_stringl(value_z, str, str_len); + } + else { + core::sqlsrv_zval_stringl(value_z, str, str_len); + } + } + break; + case IS_LONG: + // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so + if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) { + ZVAL_NULL( value_z ); + } + else if( output_param->is_bool ) { + convert_to_boolean( value_z ); + } + else { + ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); + } + break; + case IS_DOUBLE: + // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so + if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) { + ZVAL_NULL( value_z ); + } + break; + default: + DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); + break; + } + value_z = NULL; + } ZEND_HASH_FOREACH_END(); + + // empty the hash table since it's been processed + zend_hash_clean( Z_ARRVAL( stmt->output_params )); + return; +} + +void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type, + _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC ) +{ + SQLRETURN r; + SQLSMALLINT c_type; + SQLLEN sql_field_type = 0; + SQLSMALLINT extra = 0; + SQLLEN field_len_temp; + SQLLEN sql_display_size = 0; + char* field_value_temp = NULL; + + try { + + DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, + "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); + + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); + } + + // Set the C type and account for null characters at the end of the data. + switch( sqlsrv_php_type.typeinfo.encoding ) { + case CP_UTF8: + c_type = SQL_C_WCHAR; + extra = sizeof( SQLWCHAR ); + break; + case SQLSRV_ENCODING_BINARY: + c_type = SQL_C_BINARY; + extra = 0; + break; + default: + c_type = SQL_C_CHAR; + extra = sizeof( SQLCHAR ); + break; + } + + // Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + + // Calculate the field size. + calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); + + // if this is a large type, then read the first few bytes to get the actual length from SQLGetData + if( sql_display_size == 0 || sql_display_size == INT_MAX || + sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { + + field_len_temp = INITIAL_FIELD_STRING_LEN; + SQLLEN initiallen = field_len_temp + extra; + + + field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); + + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), + &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( field_len_temp == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + if( r == SQL_SUCCESS_WITH_INFO ) { + + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; + + stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); + + // with Linux connection pooling may not get a truncated warning back but the actual field_len_temp + // can be greater than the initallen value. +#ifndef _WIN32 + if( is_truncated_warning( state ) || initiallen < field_len_temp) { +#else + if( is_truncated_warning( state ) ) { +#endif // !_WIN32 + + SQLLEN dummy_field_len; + + // for XML (and possibly other conditions) the field length returned is not the real field length, so + // in every pass, we double the allocation size to retrieve all the contents. + if( field_len_temp == SQL_NO_TOTAL ) { + + // reset the field_len_temp + field_len_temp = INITIAL_FIELD_STRING_LEN; + + do { + SQLLEN initial_field_len = field_len_temp; + // Double the size. + field_len_temp *= 2; + + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); + + field_len_temp -= initial_field_len; + + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, + field_len_temp + extra, &dummy_field_len, + false /*handle_warning*/ TSRMLS_CC ); + + // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL + // so we calculate the actual length of the string with that. + if( dummy_field_len != SQL_NO_TOTAL ) + field_len_temp += dummy_field_len; + else + field_len_temp += initial_field_len; + + if( r == SQL_SUCCESS_WITH_INFO ) { + core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len + TSRMLS_CC ); + } + + } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); + } + else { + + // We got the field_len_temp from SQLGetData call. + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); + + // We have already recieved INITIAL_FIELD_STRING_LEN size data. + field_len_temp -= INITIAL_FIELD_STRING_LEN; + + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, + field_len_temp + extra, &dummy_field_len, + true /*handle_warning*/ TSRMLS_CC ); + + if( dummy_field_len == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + field_len_temp += INITIAL_FIELD_STRING_LEN; + } + + } // if( is_truncation_warning ( state ) ) + else { + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + } + } // if( r == SQL_SUCCESS_WITH_INFO ) + + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { + + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) + + else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { + + // only allow binary retrievals for char and binary types. All others get a string converted + // to the encoding type they asked for. + + // null terminator + if( c_type == SQL_C_CHAR ) { + sql_display_size += sizeof( SQLCHAR ); + } + + // For WCHAR multiply by sizeof(WCHAR) and include the null terminator + else if( c_type == SQL_C_WCHAR ) { + sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); + } + + field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); + + // get the data + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, + &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); + CHECK_SQL_ERROR( r, stmt ) { + throw core::CoreException(); + } + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + + if( field_len_temp == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } + + if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { + + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) + + else { + + DIE( "Invalid sql_display_size" ); + return; // to eliminate a warning + } + + field_value = field_value_temp; + *field_len = field_len_temp; + + // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP + // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. + // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary + // operator to set add 1 to fill the null terminator + + // with unixODBC connection pooling sometimes field_len_temp can be SQL_NO_DATA. + // In that cause do not set null terminator and set length to 0. + if ( field_len_temp > 0 ) + { + field_value_temp[field_len_temp] = '\0'; + } + else + { + *field_len = 0; + } + } + + catch( core::CoreException& ) { + + field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } + catch ( ... ) { + + field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } + +} + + +// return the option from the stmt_opts array that matches the key. If no option found, +// NULL is returned. + +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC ) +{ + for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { + + // if we find the key we're looking for, return it + if( key == stmt_opts[ i ].key ) { + return &stmt_opts[ i ]; + } + } + + return NULL; // no option found +} + +// is_fixed_size_type +// returns true if the SQL data type is a fixed length, as opposed to a variable length data type such as varchar or varbinary + +bool is_fixed_size_type( SQLINTEGER sql_type ) +{ + switch( sql_type ) { + + case SQL_BINARY: + case SQL_CHAR: + case SQL_WCHAR: + case SQL_VARCHAR: + case SQL_WVARCHAR: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + case SQL_SS_XML: + case SQL_SS_UDT: + return false; + } + + return true; +} + +bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) +{ + switch( type.typeinfo.type ) { + + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_FLOAT: + case SQLSRV_PHPTYPE_DATETIME: + return true; + case SQLSRV_PHPTYPE_STRING: + case SQLSRV_PHPTYPE_STREAM: + { + if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR + || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + return true; + } + break; + } + } + + return false; +} + + +// verify there is enough space to hold the output string parameter, and allocate it if needed. The param_z +// is updated to have the new buffer with the correct size and its reference is incremented. The output +// string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and +// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter + +void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding, + SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer, + SQLLEN& buffer_len TSRMLS_DC ) +{ + SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); + buffer_len = Z_STRLEN_P( param_z ); + SQLLEN expected_len; + SQLLEN buffer_null_extra; + SQLLEN elem_size; + SQLLEN without_null_len; + + // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, + // as is a n(var)char/ntext field being returned as a binary field. + elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1; + + // account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning + expected_len = column_size * elem_size + elem_size; + + // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations + buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; + + // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter + without_null_len = column_size * elem_size; + + // increment to include the null terminator since the Zend length doesn't include the null terminator + buffer_len += elem_size; + + // if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new + // length. + if( buffer_len < expected_len ) { + SQLSRV_ASSERT( expected_len >= expected_len - buffer_null_extra, + "Integer overflow/underflow caused a corrupt field length." ); + + // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since + // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about + // not having a NULL terminator on a string. + zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); + + // A zval string len doesn't include the null. This calculates the length it should be + // regardless of whether the ODBC type contains the NULL or not. + + // null terminate the string to avoid a warning in debug PHP builds + ZSTR_VAL(param_z_string)[without_null_len] = '\0'; + ZVAL_NEW_STR(param_z, param_z_string); + + // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the + // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY + buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; + + // Zend string length doesn't include the null terminator + ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; + } + + buffer = Z_STRVAL_P(param_z); + + // The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which + // may be less than the size of the buffer since the output may be more than the input. If it is greater, + // than the error 22001 is returned by ODBC. + if( stmt->param_ind_ptrs[ paramno ] > buffer_len - (elem_size - buffer_null_extra)) { + stmt->param_ind_ptrs[ paramno ] = buffer_len - (elem_size - buffer_null_extra); + } +} + +// output parameters have their reference count incremented so that they do not disappear +// while the query is executed and processed. They are saved in the statement so that +// their reference count may be decremented later (after results are processed) + +void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC ) +{ + HashTable* param_ht = Z_ARRVAL( stmt->output_params ); + zend_ulong paramno = static_cast( param.param_num ); + core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, ¶m, sizeof( sqlsrv_output_param )); + Z_TRY_ADDREF_P( param.param_z ); // we have a reference to the param +} + + +// send all the stream data + +void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC ) +{ + while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { } +} + + +// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed +void sqlsrv_output_param_dtor( zval* data ) +{ + sqlsrv_output_param *output_param = static_cast( Z_PTR_P( data )); + zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold + sqlsrv_free( output_param ); +} + +// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed +void sqlsrv_stream_dtor( zval* data ) +{ + sqlsrv_stream* stream_encoding = static_cast( Z_PTR_P( data )); + zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold + sqlsrv_free( stream_encoding ); +} + +} diff --git a/pdo_sqlsrv/core_stream.cpp b/source/shared/core_stream.cpp similarity index 84% rename from pdo_sqlsrv/core_stream.cpp rename to source/shared/core_stream.cpp index b85c63ce7..ad915ad75 100644 --- a/pdo_sqlsrv/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -1,261 +1,269 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_stream.cpp -// -// Contents: Implementation of PHP streams for reading SQL Server data -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" -#include - -namespace { - -// close a stream and free the PHP resources used by it - -int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) -{ - sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); - - // free the stream resources in the Zend engine - php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM ); - - // UNDEF the stream zval and delete our reference count to it. - ZVAL_UNDEF( &( ss->stmt->active_stream ) ); - - sqlsrv_free( ss ); - stream->abstract = NULL; - - return 0; -} - - -// read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are -// set when sqlsrv_get_field is called by the user specifying which field type they want. - -size_t sqlsrv_stream_read( php_stream* stream, _Out_writes_bytes_(count) char* buf, size_t count TSRMLS_DC ) -{ - SQLLEN read = 0; - SQLSMALLINT c_type = SQL_C_CHAR; - char* get_data_buffer = buf; - sqlsrv_malloc_auto_ptr temp_buf; - - sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); - - try { - - if( stream->eof ) { - return 0; - }; - - switch( ss->encoding ) { - case SQLSRV_ENCODING_CHAR: - c_type = SQL_C_CHAR; - break; - - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - break; - - case CP_UTF8: - { - c_type = SQL_C_WCHAR; - count /= 2; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes - if( count > PHP_STREAM_BUFFER_SIZE ) { - count = PHP_STREAM_BUFFER_SIZE; - } - - // use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16 - temp_buf = static_cast( sqlsrv_malloc( PHP_STREAM_BUFFER_SIZE )); - get_data_buffer = temp_buf; - break; - } - - default: - DIE( "Unknown encoding type when reading from a stream" ); - break; - } - - SQLRETURN r = SQLGetData( ss->stmt->handle(), ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read ); - - CHECK_SQL_ERROR( r, ss->stmt ) { - stream->eof = 1; - throw core::CoreException(); - } - - // if the stream returns either no data, NULL data, or returns data < than the count requested then - // we are at the "end of the stream" so we mark it - if( r == SQL_NO_DATA || read == SQL_NULL_DATA || ( static_cast( read ) <= count && read != SQL_NO_TOTAL )) { - stream->eof = 1; - } - - // if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator - // if it's not a binary encoded field - if( r == SQL_SUCCESS_WITH_INFO ) { - - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - - ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - - if( read == SQL_NO_TOTAL ) { - SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it " - "did not occur." ); - } - - if( is_truncated_warning( state ) ) { - switch( c_type ) { - - // As per SQLGetData documentation, if the length of character data exceeds the BufferLength, - // SQLGetData truncates the data to BufferLength less the length of null-termination character. - case SQL_C_BINARY: - read = count; - break; - case SQL_C_WCHAR: - read = ( count % 2 == 0 ? count - 2 : count - 3 ); - break; - case SQL_C_CHAR: - read = count - 1; - break; - default: - DIE( "sqlsrv_stream_read: should have never reached in this switch case."); - break; - } - } - else { - CHECK_SQL_WARNING( r, ss->stmt ); - } - } - - // if the encoding is UTF-8 - if (c_type == SQL_C_WCHAR) { - - count *= 2; // undo the shift to use the full buffer - - // flags set to 0 by default, which means that any invalid characters are dropped rather than causing - // an error. This happens only on XP. - DWORD flags = 0; - - // convert to UTF-8 - if (g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER) { - // Vista (and later) will detect invalid UTF-16 characters and raise an error. - flags = WC_ERR_INVALID_CHARS; - } - - if ( count > INT_MAX || (read >> 1) > INT_MAX ) - { - LOG(SEV_ERROR, "UTF-16 (wide character) string mapping: buffer length exceeded."); - throw core::CoreException(); - } - - int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast( temp_buf.get() ), - static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); - - if( enc_len == 0 ) { - - stream->eof = 1; - THROW_CORE_ERROR( ss->stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ); - } - - read = enc_len; - } - - return read; - } - - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." ); - return 0; - } -} - -// function table for stream operations. We only support reading and closing the stream -php_stream_ops sqlsrv_stream_ops = { - NULL, - sqlsrv_stream_read, - sqlsrv_stream_close, - NULL, - SQLSRV_STREAM, - NULL, - NULL, - NULL, - NULL -}; - -// open a stream and return the sqlsrv_stream_ops function table as part of the -// return value. There is only one valid way to open a stream, using sqlsrv_get_field on -// certain field types. A sqlsrv stream may only be opened in read mode. -static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, _In_ const char*, _In_ const char* mode, - int options, _In_ zend_string **, php_stream_context* STREAMS_DC TSRMLS_DC ) -{ - -#if ZEND_DEBUG - SQLSRV_UNUSED( __zend_orig_lineno ); - SQLSRV_UNUSED( __zend_orig_filename ); - SQLSRV_UNUSED( __zend_lineno ); - SQLSRV_UNUSED( __zend_filename ); - SQLSRV_UNUSED( __php_stream_call_depth ); -#endif - - sqlsrv_malloc_auto_ptr ss; - - ss = static_cast( sqlsrv_malloc( sizeof( sqlsrv_stream ))); - memset( ss, 0, sizeof( sqlsrv_stream )); - - // check for valid options - if( options != REPORT_ERRORS ) { - php_stream_wrapper_log_error( wrapper, options TSRMLS_CC, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" ); - return NULL; - } - - // allocate the stream from PHP - php_stream* php_str = php_stream_alloc( &sqlsrv_stream_ops, ss, 0, mode ); - if( php_str != NULL ) { - ss.transferred(); - } - - return php_str; -} - -// information structure that contains PHP stream wrapper info. We supply the minimal -// possible, including the open function and the name only. - -php_stream_wrapper_ops sqlsrv_stream_wrapper_ops = { - sqlsrv_stream_opener, - NULL, - NULL, - NULL, - NULL, - SQLSRV_STREAM_WRAPPER, - NULL, - NULL, - NULL, - NULL -}; - -} - -// structure used by PHP to get the function table for opening, closing, etc. of the stream -php_stream_wrapper g_sqlsrv_stream_wrapper = { - &sqlsrv_stream_wrapper_ops, - NULL, - 0 -}; +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_stream.cpp +// +// Contents: Implementation of PHP streams for reading SQL Server data +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +namespace { + +// close a stream and free the PHP resources used by it + +int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) +{ + sqlsrv_stream* ss = static_cast( stream->abstract ); + SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); + + // free the stream resources in the Zend engine + php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM ); + + // UNDEF the stream zval and delete our reference count to it. + ZVAL_UNDEF( &( ss->stmt->active_stream ) ); + + sqlsrv_free( ss ); + stream->abstract = NULL; + + return 0; +} + + +// read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are +// set when sqlsrv_get_field is called by the user specifying which field type they want. + +size_t sqlsrv_stream_read( php_stream* stream, _Out_writes_bytes_(count) char* buf, size_t count TSRMLS_DC ) +{ + SQLLEN read = 0; + SQLSMALLINT c_type = SQL_C_CHAR; + char* get_data_buffer = buf; + sqlsrv_malloc_auto_ptr temp_buf; + + sqlsrv_stream* ss = static_cast( stream->abstract ); + SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); + + try { + + if( stream->eof ) { + return 0; + }; + + switch( ss->encoding ) { + case SQLSRV_ENCODING_CHAR: + c_type = SQL_C_CHAR; + break; + + case SQLSRV_ENCODING_BINARY: + c_type = SQL_C_BINARY; + break; + + case CP_UTF8: + { + c_type = SQL_C_WCHAR; + count /= 2; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes + if( count > PHP_STREAM_BUFFER_SIZE ) { + count = PHP_STREAM_BUFFER_SIZE; + } + + // use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16 + temp_buf = static_cast( sqlsrv_malloc( PHP_STREAM_BUFFER_SIZE )); + memset(temp_buf, 0, PHP_STREAM_BUFFER_SIZE); + get_data_buffer = temp_buf; + break; + } + + default: + DIE( "Unknown encoding type when reading from a stream" ); + break; + } + + SQLRETURN r = SQLGetData( ss->stmt->handle(), ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read ); + + CHECK_SQL_ERROR( r, ss->stmt ) { + stream->eof = 1; + throw core::CoreException(); + } + + // if the stream returns either no data, NULL data, or returns data < than the count requested then + // we are at the "end of the stream" so we mark it + if( r == SQL_NO_DATA || read == SQL_NULL_DATA || ( static_cast( read ) <= count && read != SQL_NO_TOTAL )) { + stream->eof = 1; + } + + // if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator + // if it's not a binary encoded field + if( r == SQL_SUCCESS_WITH_INFO ) { + + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLSMALLINT len; + + ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); + + if( read == SQL_NO_TOTAL ) { + SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it " + "did not occur." ); + } + + // with unixODBC connection pooling enabled the truncated state may not be returned so check the actual length read + // with buffer length. + #ifndef _WIN32 + if( is_truncated_warning( state ) || count < read) { + #else + if( is_truncated_warning( state ) ) { + #endif // !_WIN32 + switch( c_type ) { + + // As per SQLGetData documentation, if the length of character data exceeds the BufferLength, + // SQLGetData truncates the data to BufferLength less the length of null-termination character. + case SQL_C_BINARY: + read = count; + break; + case SQL_C_WCHAR: + read = ( count % 2 == 0 ? count - 2 : count - 3 ); + break; + case SQL_C_CHAR: + read = count - 1; + break; + default: + DIE( "sqlsrv_stream_read: should have never reached in this switch case."); + break; + } + } + else { + CHECK_SQL_WARNING( r, ss->stmt ); + } + } + + // if the encoding is UTF-8 + if( c_type == SQL_C_WCHAR ) { + count *= 2; + // undo the shift to use the full buffer + // flags set to 0 by default, which means that any invalid characters are dropped rather than causing + // an error. This happens only on XP. + // convert to UTF-8 +#ifdef _WIN32 + DWORD flags = 0; + if( g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { + // Vista (and later) will detect invalid UTF-16 characters and raise an error. + flags = WC_ERR_INVALID_CHARS; + } +#endif // _WIN32 + if( count > INT_MAX || (read >> 1) > INT_MAX ) { + LOG(SEV_ERROR, "UTF-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } + +#ifndef _WIN32 + int enc_len = SystemLocale::FromUtf16( ss->encoding, reinterpret_cast( temp_buf.get() ), + static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); +#else + int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast( temp_buf.get() ), + static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); +#endif // !_WIN32 + if( enc_len == 0 ) { + + stream->eof = 1; + THROW_CORE_ERROR( ss->stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ); + } + + read = enc_len; + } + + return read; + } + + catch( core::CoreException& ) { + + return 0; + } + catch( ... ) { + + LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." ); + return 0; + } +} + +// function table for stream operations. We only support reading and closing the stream +php_stream_ops sqlsrv_stream_ops = { + NULL, + sqlsrv_stream_read, + sqlsrv_stream_close, + NULL, + SQLSRV_STREAM, + NULL, + NULL, + NULL, + NULL +}; + +// open a stream and return the sqlsrv_stream_ops function table as part of the +// return value. There is only one valid way to open a stream, using sqlsrv_get_field on +// certain field types. A sqlsrv stream may only be opened in read mode. +static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, _In_ const char*, _In_ const char* mode, + int options, _In_ zend_string **, php_stream_context* STREAMS_DC TSRMLS_DC ) +{ + +#if ZEND_DEBUG + SQLSRV_UNUSED( __zend_orig_lineno ); + SQLSRV_UNUSED( __zend_orig_filename ); + SQLSRV_UNUSED( __zend_lineno ); + SQLSRV_UNUSED( __zend_filename ); + SQLSRV_UNUSED( __php_stream_call_depth ); +#endif + + sqlsrv_malloc_auto_ptr ss; + + ss = static_cast( sqlsrv_malloc( sizeof( sqlsrv_stream ))); + memset( ss, 0, sizeof( sqlsrv_stream )); + + // check for valid options + if( options != REPORT_ERRORS ) { + php_stream_wrapper_log_error( wrapper, options TSRMLS_CC, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" ); + return NULL; + } + + // allocate the stream from PHP + php_stream* php_str = php_stream_alloc( &sqlsrv_stream_ops, ss, 0, mode ); + if( php_str != NULL ) { + ss.transferred(); + } + + return php_str; +} + +// information structure that contains PHP stream wrapper info. We supply the minimal +// possible, including the open function and the name only. + +php_stream_wrapper_ops sqlsrv_stream_wrapper_ops = { + sqlsrv_stream_opener, + NULL, + NULL, + NULL, + NULL, + SQLSRV_STREAM_WRAPPER, + NULL, + NULL, + NULL, + NULL +}; + +} + +// structure used by PHP to get the function table for opening, closing, etc. of the stream +php_stream_wrapper g_sqlsrv_stream_wrapper = { + &sqlsrv_stream_wrapper_ops, + NULL, + 0 +}; diff --git a/sqlsrv/core_util.cpp b/source/shared/core_util.cpp similarity index 82% rename from sqlsrv/core_util.cpp rename to source/shared/core_util.cpp index 8bc410f3c..94b5bf6de 100644 --- a/sqlsrv/core_util.cpp +++ b/source/shared/core_util.cpp @@ -1,400 +1,390 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_util.cpp -// -// Contents: Utility functions used by both connection or statement functions for both the PDO and sqlsrv drivers -// -// Comments: Mostly error handling and some type handling -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include - -namespace { - -// *** internal constants *** -log_callback g_driver_log; -// internal error that says that FormatMessage failed -SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; -// buffer used to hold a formatted log message prior to actually logging it. -char last_err_msg[ 2048 ]; // 2k to hold the error messages - -// routine used by utf16_string_from_mbcs_string -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, - unsigned int mbcs_len, - _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, - unsigned int utf16_len ); -} - -// SQLSTATE for all internal errors -SQLCHAR IMSSP[] = "IMSSP"; - -// SQLSTATE for all internal warnings -SQLCHAR SSPWARN[] = "01SSP"; - -// write to the php log if the severity and subsystem match the filters currently set in the INI or -// the script (sqlsrv_configure). -void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ...) -{ - SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." ); - - va_list args; - va_start( args, msg ); - - g_driver_log( severity TSRMLS_CC, msg, &args ); - - va_end( args ); -} - -void core_sqlsrv_register_logger( log_callback driver_logger ) -{ - g_driver_log = driver_logger; -} - - -// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new -// length in the len parameter. If no errors occurred during convertion, true is returned and the original -// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed -// and false is returned. - -bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len) -{ - SQLSRV_ASSERT( string != NULL, "String must be specified" ); - - if (validate_string(*string, len)) { - return true; - } - - char* outString = NULL; - SQLLEN outLen = 0; - - bool result = convert_string_from_utf16( encoding, - reinterpret_cast(*string), int(len / sizeof(wchar_t)), &outString, outLen); - - if (result) - { - sqlsrv_free( *string ); - *string = outString; - len = outLen; - } - - return result; -} - -bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len) -{ - char* string = Z_STRVAL_P(value_z); - - if (validate_string(string, len)) { - return true; - } - - char* outString = NULL; - SQLLEN outLen = 0; - - bool result = convert_string_from_utf16(encoding, - reinterpret_cast(string), int(len / sizeof(wchar_t)), &outString, outLen); - - if (result) - { - core::sqlsrv_zval_stringl(value_z, outString, outLen); - sqlsrv_free(outString); - len = outLen; - } - - return result; -} - -bool validate_string(char* string, SQLLEN& len) -{ - SQLSRV_ASSERT(string != NULL, "String must be specified"); - - //for the empty string, we simply returned we converted it - if (len == 0 && string[0] == '\0') { - return true; - } - - if ((len / sizeof(wchar_t)) > INT_MAX) - { - LOG(SEV_ERROR, "UTP-16 (wide character) string mapping: buffer length exceeded."); - throw core::CoreException(); - } - - return false; -} - -bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const wchar_t* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ) -{ - SQLSRV_ASSERT( inString != NULL, "Input string must be specified" ); - SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" ); - SQLSRV_ASSERT( *outString == NULL, "Output buffer pointer must not be set" ); - - if (cchInLen == 0 && inString[0] == L'\0') { - *outString = reinterpret_cast( sqlsrv_malloc ( 1 ) ); - *outString[0] = '\0'; - cchOutLen = 0; - return true; - } - - // flags set to 0 by default, which means that any invalid characters are dropped rather than causing - // an error. This happens only on XP. - DWORD flags = 0; - if( encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { - // Vista (and later) will detect invalid UTF-16 characters and raise an error. - flags = WC_ERR_INVALID_CHARS; - } - - // calculate the number of characters needed - cchOutLen = WideCharToMultiByte( encoding, flags, - inString, cchInLen, - NULL, 0, NULL, NULL ); - if( cchOutLen == 0 ) { - return false; - } - - // Create a buffer to fit the encoded string - char* newString = reinterpret_cast( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ )); - int rc = WideCharToMultiByte( encoding, flags, - inString, cchInLen, - newString, static_cast(cchOutLen), NULL, NULL ); - if( rc == 0 ) { - cchOutLen = 0; - sqlsrv_free( newString ); - return false; - } - - *outString = newString; - newString[cchOutLen] = '\0'; // null terminate the encoded string - - return true; -} - -// thin wrapper around convert_string_from_default_encoding that handles -// allocation of the destination string. An empty string passed in returns -// failure since it's a failure case for convert_string_from_default_encoding. -wchar_t* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len, - unsigned int* utf16_len ) -{ - *utf16_len = (mbcs_len + 1); - wchar_t* utf16_string = reinterpret_cast( sqlsrv_malloc( *utf16_len * sizeof( wchar_t ))); - *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, - utf16_string, *utf16_len ); - if( *utf16_len == 0 ) { - // we preserve the error and reset it because sqlsrv_free resets the last error - DWORD last_error = GetLastError(); - sqlsrv_free( utf16_string ); - SetLastError( last_error ); - return NULL; - } - - return utf16_string; -} - -// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the -// errno is 1 based. It returns it as an array with 3 members: -// 1/SQLSTATE) sqlstate -// 2/code) driver specific error code -// 3/message) driver specific error message -// The fetch type determines if the indices are numeric, associative, or both. - -bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_error_auto_ptr& error, logging_severity severity - TSRMLS_DC ) -{ - SQLHANDLE h = ctx.handle(); - SQLSMALLINT h_type = ctx.handle_type(); - - if( h == NULL ) { - return false; - } - - zval* ssphp_z = NULL; - int zr = SUCCESS; - zval* temp = NULL; - SQLRETURN r = SQL_SUCCESS; - SQLSMALLINT wmessage_len = 0; - SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ]; - SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLSRV_ENCODING enc = ctx.encoding(); - - switch( h_type ) { - - case SQL_HANDLE_STMT: - { - sqlsrv_stmt* stmt = static_cast( &ctx ); - if( stmt->current_results != NULL ) { - - error = stmt->current_results->get_diag_rec( record_number ); - // don't use the CHECK* macros here since it will trigger reentry into the error handling system - if( error == NULL ) { - return false; - } - break; - } - - // convert the error into the encoding of the context - if( enc == SQLSRV_ENCODING_DEFAULT ) { - enc = stmt->conn->encoding(); - } - } - - - default: - - error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); - r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message, - SQL_MAX_MESSAGE_LENGTH + 1, &wmessage_len ); - // don't use the CHECK* macros here since it will trigger reentry into the error handling system - if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - return false; - } - - SQLLEN sqlstate_len = 0; - convert_string_from_utf16(enc, wsqlstate, sizeof(wsqlstate), (char**)&error->sqlstate, sqlstate_len); - - SQLLEN message_len = 0; - convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); - break; - } - - - // log the error first - LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate ); - LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), error->native_code ); - LOG( severity, "%1!s!: message = %2!s!", ctx.func(), error->native_message ); - - error->format = false; - - return true; -} - -// format and return a driver specfic error -void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, - sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ) -{ - // allocate space for the formatted message - formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); - formatted_error->sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - formatted_error->native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast( custom_error->native_message ), 0, 0, - reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, args ); - if( rc == 0 ) { - strcpy_s( reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, - reinterpret_cast( INTERNAL_FORMAT_ERROR )); - } - - strcpy_s( reinterpret_cast( formatted_error->sqlstate ), SQL_SQLSTATE_BUFSIZE, - reinterpret_cast( custom_error->sqlstate )); - formatted_error->native_code = custom_error->native_code; - - // log the error - LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), formatted_error->sqlstate ); - LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), formatted_error->native_code ); - LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); -} - -DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ) -{ - va_list format_args; - va_start( format_args, format ); - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast(output_buffer), SQL_MAX_MESSAGE_LENGTH, &format_args ); - - va_end( format_args ); - - return rc; -} - -// return an error message for GetLastError using FormatMessage. -// this function returns the msg pointer so that it may be used within -// another function call such as handle_error -const char* get_last_error_message( DWORD last_error ) -{ - if( last_error == 0 ) { - last_error = GetLastError(); - } - - DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), - last_err_msg, sizeof( last_err_msg ), NULL ); - - if( r == 0 ) { - SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( last_err_msg )); - std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), last_err_msg ); - } - - return last_err_msg; -} - - -// die -// Terminate the PHP request with an error message -// We use this function rather than php_error directly because we use the FormatMessage syntax in most other -// places within the extension (e.g., LOG), whereas php_error uses the printf format syntax. There were -// places where we were using the FormatMessage syntax inadvertently with DIE which left messages without -// proper information. Rather than convert those messages and try and remember the difference between LOG and -// DIE, it is simpler to make the format syntax common between them. -void die( const char* msg, ... ) -{ - va_list format_args; - va_start( format_args, msg ); - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, last_err_msg, sizeof( last_err_msg ), &format_args ); - - va_end( format_args ); - - if( rc == 0 ) { - php_error( E_ERROR, reinterpret_cast( INTERNAL_FORMAT_ERROR )); - } - - php_error( E_ERROR, last_err_msg ); -} - -namespace { - -// convert from the default encoding specified by the "CharacterSet" -// connection option to UTF-16. mbcs_len and utf16_len are sizes in -// bytes. The return is the number of UTF-16 characters in the string -// returned in utf16_out_string. An empty string passed in will result as -// a failure since MBTWC returns 0 for both an empty string and failure -// to convert. -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, - unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) wchar_t* utf16_out_string, - unsigned int utf16_len ) -{ - unsigned int win_encoding = CP_ACP; - switch( php_encoding ) { - case SQLSRV_ENCODING_CHAR: - win_encoding = CP_ACP; - break; - // this shouldn't ever be set - case SQLSRV_ENCODING_BINARY: - DIE( "Invalid encoding." ); - break; - default: - win_encoding = php_encoding; - break; - } - unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len, - utf16_out_string, utf16_len ); - if( required_len == 0 ) { - return 0; - } - utf16_out_string[ required_len ] = '\0'; - - return required_len; -} - -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: core_util.cpp +// +// Contents: Utility functions used by both connection or statement functions for both the PDO and sqlsrv drivers +// +// Comments: Mostly error handling and some type handling +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" + +namespace { + +// *** internal constants *** +log_callback g_driver_log; +// internal error that says that FormatMessage failed +SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; +// buffer used to hold a formatted log message prior to actually logging it. +char last_err_msg[ 2048 ]; // 2k to hold the error messages + +// routine used by utf16_string_from_mbcs_string +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, + unsigned int mbcs_len, + _Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string, + unsigned int utf16_len ); +} + +// SQLSTATE for all internal errors +SQLCHAR IMSSP[] = "IMSSP"; + +// SQLSTATE for all internal warnings +SQLCHAR SSPWARN[] = "01SSP"; + +// write to the php log if the severity and subsystem match the filters currently set in the INI or +// the script (sqlsrv_configure). +void write_to_log( unsigned int severity TSRMLS_DC, const char* msg, ...) +{ + SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." ); + + va_list args; + va_start( args, msg ); + + g_driver_log( severity TSRMLS_CC, msg, &args ); + + va_end( args ); +} + +void core_sqlsrv_register_logger( log_callback driver_logger ) +{ + g_driver_log = driver_logger; +} + + +// convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new +// length in the len parameter. If no errors occurred during convertion, true is returned and the original +// utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed +// and false is returned. + +bool convert_string_from_utf16_inplace( SQLSRV_ENCODING encoding, char** string, SQLLEN& len) +{ + SQLSRV_ASSERT( string != NULL, "String must be specified" ); + + if (validate_string(*string, len)) { + return true; + } + + char* outString = NULL; + SQLLEN outLen = 0; + + bool result = convert_string_from_utf16( encoding, reinterpret_cast(*string), int(len / sizeof(SQLWCHAR)), &outString, outLen ); + + if (result) + { + sqlsrv_free( *string ); + *string = outString; + len = outLen; + } + + return result; +} + +bool convert_zval_string_from_utf16(SQLSRV_ENCODING encoding, zval* value_z, SQLLEN& len) +{ + char* string = Z_STRVAL_P(value_z); + + if( validate_string(string, len)) { + return true; + } + + char* outString = NULL; + SQLLEN outLen = 0; + bool result = convert_string_from_utf16( encoding, reinterpret_cast(string), int(len / sizeof(SQLWCHAR)), &outString, outLen ); + if( result ) { + core::sqlsrv_zval_stringl( value_z, outString, outLen ); + sqlsrv_free( outString ); + len = outLen; + } + return result; +} + +bool validate_string(char* string, SQLLEN& len) +{ + SQLSRV_ASSERT(string != NULL, "String must be specified"); + + //for the empty string, we simply returned we converted it + if( len == 0 && string[0] == '\0') { + return true; + } + if ((len / sizeof(SQLWCHAR)) > INT_MAX) { + LOG(SEV_ERROR, "UTP-16 (wide character) string mapping: buffer length exceeded."); + throw core::CoreException(); + } + return false; +} + +bool convert_string_from_utf16( SQLSRV_ENCODING encoding, const SQLWCHAR* inString, SQLINTEGER cchInLen, char** outString, SQLLEN& cchOutLen ) +{ + SQLSRV_ASSERT( inString != NULL, "Input string must be specified" ); + SQLSRV_ASSERT( outString != NULL, "Output buffer pointer must be specified" ); + SQLSRV_ASSERT( *outString == NULL, "Output buffer pointer must not be set" ); + + if (cchInLen == 0 && inString[0] == L'\0') { + *outString = reinterpret_cast( sqlsrv_malloc ( 1 ) ); + *outString[0] = '\0'; + cchOutLen = 0; + return true; + } + + // flags set to 0 by default, which means that any invalid characters are dropped rather than causing + // an error. This happens only on XP. + DWORD flags = 0; + if( encoding == CP_UTF8 && g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER ) { + // Vista (and later) will detect invalid UTF-16 characters and raise an error. + flags = WC_ERR_INVALID_CHARS; + } + + // calculate the number of characters needed +#ifndef _WIN32 + cchOutLen = SystemLocale::FromUtf16Strict( encoding, inString, cchInLen, NULL, 0 ); +#else + cchOutLen = WideCharToMultiByte( encoding, flags, + inString, cchInLen, + NULL, 0, NULL, NULL ); +#endif // !_WIN32 + + if( cchOutLen == 0 ) { + return false; + } + + // Create a buffer to fit the encoded string + char* newString = reinterpret_cast( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ )); + +#ifndef _WIN32 + int rc = SystemLocale::FromUtf16( encoding, inString, cchInLen, newString, static_cast(cchOutLen)); +#else + int rc = WideCharToMultiByte( encoding, flags, inString, cchInLen, newString, static_cast(cchOutLen), NULL, NULL ); +#endif // !_WIN32 + if( rc == 0 ) { + cchOutLen = 0; + sqlsrv_free( newString ); + return false; + } + + *outString = newString; + newString[cchOutLen] = '\0'; // null terminate the encoded string + + return true; +} + +// thin wrapper around convert_string_from_default_encoding that handles +// allocation of the destination string. An empty string passed in returns +// failure since it's a failure case for convert_string_from_default_encoding. +SQLWCHAR* utf16_string_from_mbcs_string( SQLSRV_ENCODING php_encoding, const char* mbcs_string, unsigned int mbcs_len, + unsigned int* utf16_len ) +{ + *utf16_len = (mbcs_len + 1); + SQLWCHAR* utf16_string = reinterpret_cast( sqlsrv_malloc( *utf16_len * sizeof( SQLWCHAR ))); + *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, utf16_string, *utf16_len ); + + if( *utf16_len == 0 ) { + // we preserve the error and reset it because sqlsrv_free resets the last error + DWORD last_error = GetLastError(); + sqlsrv_free( utf16_string ); + SetLastError( last_error ); + return NULL; + } + + return utf16_string; +} + +// call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the +// errno is 1 based. It returns it as an array with 3 members: +// 1/SQLSTATE) sqlstate +// 2/code) driver specific error code +// 3/message) driver specific error message +// The fetch type determines if the indices are numeric, associative, or both. + +bool core_sqlsrv_get_odbc_error( sqlsrv_context& ctx, int record_number, sqlsrv_error_auto_ptr& error, logging_severity severity + TSRMLS_DC ) +{ + SQLHANDLE h = ctx.handle(); + SQLSMALLINT h_type = ctx.handle_type(); + + if( h == NULL ) { + return false; + } + + SQLRETURN r = SQL_SUCCESS; + SQLSMALLINT wmessage_len = 0; + SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ] = { L'\0' }; + SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ] = { L'\0' }; + SQLSRV_ENCODING enc = ctx.encoding(); + + switch( h_type ) { + + case SQL_HANDLE_STMT: + { + sqlsrv_stmt* stmt = static_cast( &ctx ); + if( stmt->current_results != NULL ) { + + error = stmt->current_results->get_diag_rec( record_number ); + // don't use the CHECK* macros here since it will trigger reentry into the error handling system + if( error == 0 ) { + return false; + } + break; + } + // convert the error into the encoding of the context + if( enc == SQLSRV_ENCODING_DEFAULT ) { + enc = stmt->conn->encoding(); + } + } + default: + error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); + r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message, + SQL_MAX_MESSAGE_LENGTH + 1, &wmessage_len ); + // don't use the CHECK* macros here since it will trigger reentry into the error handling system + if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { + return false; + } + + // We need to calculate number of characters + SQLLEN wsqlstate_len = sizeof( wsqlstate ) / sizeof( SQLWCHAR ); + SQLLEN sqlstate_len = 0; + convert_string_from_utf16(enc, wsqlstate, wsqlstate_len, (char**)&error->sqlstate, sqlstate_len); + + SQLLEN message_len = 0; + convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); + break; + } + + + // log the error first + LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate ); + LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), error->native_code ); + LOG( severity, "%1!s!: message = %2!s!", ctx.func(), error->native_message ); + + error->format = false; + + return true; +} + +// format and return a driver specfic error +void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const const* custom_error, + sqlsrv_error_auto_ptr& formatted_error, logging_severity severity TSRMLS_DC, va_list* args ) +{ + // allocate space for the formatted message + formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); + formatted_error->sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); + formatted_error->native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast( custom_error->native_message ), 0, 0, + reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, args ); + if( rc == 0 ) { + strcpy_s( reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, + reinterpret_cast( INTERNAL_FORMAT_ERROR )); + } + + strcpy_s( reinterpret_cast( formatted_error->sqlstate ), SQL_SQLSTATE_BUFSIZE, + reinterpret_cast( custom_error->sqlstate )); + formatted_error->native_code = custom_error->native_code; + + // log the error + LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), formatted_error->sqlstate ); + LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), formatted_error->native_code ); + LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); +} + +DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ) +{ + va_list format_args; + va_start( format_args, format ); + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast(output_buffer), output_len, &format_args ); + va_end( format_args ); + return rc; +} + +// return an error message for GetLastError using FormatMessage. +// this function returns the msg pointer so that it may be used within +// another function call such as handle_error +const char* get_last_error_message( DWORD last_error ) +{ + if( last_error == 0 ) { + last_error = GetLastError(); + } + + DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + last_err_msg, sizeof( last_err_msg ), NULL ); + + if( r == 0 ) { + SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( last_err_msg )); + std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), last_err_msg ); + } + + return last_err_msg; +} + + +// die +// Terminate the PHP request with an error message +// We use this function rather than php_error directly because we use the FormatMessage syntax in most other +// places within the extension (e.g., LOG), whereas php_error uses the printf format syntax. There were +// places where we were using the FormatMessage syntax inadvertently with DIE which left messages without +// proper information. Rather than convert those messages and try and remember the difference between LOG and +// DIE, it is simpler to make the format syntax common between them. +void die( const char* msg, ... ) +{ + va_list format_args; + va_start( format_args, msg ); + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, last_err_msg, sizeof( last_err_msg ), &format_args ); + va_end( format_args ); + if( rc == 0 ) { + php_error( E_ERROR, reinterpret_cast( INTERNAL_FORMAT_ERROR )); + } + + php_error( E_ERROR, last_err_msg ); +} + +namespace { + +// convert from the default encoding specified by the "CharacterSet" +// connection option to UTF-16. mbcs_len and utf16_len are sizes in +// bytes. The return is the number of UTF-16 characters in the string +// returned in utf16_out_string. An empty string passed in will result as +// a failure since MBTWC returns 0 for both an empty string and failure +// to convert. +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, + unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string, + unsigned int utf16_len ) +{ + unsigned int win_encoding = CP_ACP; + switch( php_encoding ) { + case SQLSRV_ENCODING_CHAR: + win_encoding = CP_ACP; + break; + // this shouldn't ever be set + case SQLSRV_ENCODING_BINARY: + DIE( "Invalid encoding." ); + break; + default: + win_encoding = php_encoding; + break; + } +#ifndef _WIN32 + unsigned int required_len = SystemLocale::ToUtf16( win_encoding, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len ); +#else + unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len ); +#endif // !_Win32 + + if( required_len == 0 ) { + return 0; + } + utf16_out_string[ required_len ] = '\0'; + + return required_len; +} + +} diff --git a/source/shared/globalization.h b/source/shared/globalization.h new file mode 100644 index 000000000..62985cbba --- /dev/null +++ b/source/shared/globalization.h @@ -0,0 +1,517 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: Globalization.h +// +// Contents: Contains functions for handling Windows format strings +// and UTF-16 on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#if !defined(_GLOBALIZATION_H_) +#define _GLOBALIZATION_H_ + +#include "xplat.h" +#include "typedefs_for_linux.h" +#include + +#if defined(MPLAT_UNIX) +#include + +const iconv_t INVALID_ICONV = (iconv_t)(-1); + +class IConvCache : public SLIST_ENTRY +{ + iconv_t m_iconv; + + // Prevent copying + IConvCache( const IConvCache & ); + IConvCache & operator=( const IConvCache & ); + +public: + IConvCache( int dstIdx, int srcIdx ); + ~IConvCache(); + + iconv_t GetIConv() const + { + return m_iconv; + } +}; + +#endif + + +class EncodingConverter +{ + UINT m_dstCodePage; + UINT m_srcCodePage; +#if defined(MPLAT_UNIX) + const IConvCache * m_pCvtCache; + + bool IsValidIConv() const + { + return (NULL != m_pCvtCache && INVALID_ICONV != m_pCvtCache->GetIConv()); + } + + template< typename T > + struct iconv_buffer + { + char * m_pBytes; + size_t m_nBytesLeft; + + iconv_buffer( char * buffer, size_t cchSize ) + : m_pBytes(buffer), m_nBytesLeft(sizeof(T)*cchSize) {} + ~iconv_buffer() {} + + void Reset( char * buffer, size_t cchSize ) + { + m_pBytes = buffer; + m_nBytesLeft = cchSize*sizeof(T); + } + + void SkipSingleCh() + { + assert( sizeof(T) <= m_nBytesLeft ); + m_nBytesLeft -= sizeof(T); + m_pBytes += sizeof(T); + } + void SkipDoubleCh() + { + SkipSingleCh(); + // Only skip second half if there's bytes left and it is non-NULL + if ( m_nBytesLeft && 0 != *(UNALIGNED T *)m_pBytes ) + SkipSingleCh(); + } + void SkipUtf8Ch() + { + assert( 1 == sizeof(T) ); + const char * pNext = SystemLocale::NextChar( CP_UTF8, m_pBytes, m_nBytesLeft ); + assert( m_pBytes < pNext && (size_t)(pNext-m_pBytes) <= SystemLocale::MaxCharCchSize(CP_UTF8) ); + + UINT toTrim = (UINT)(pNext - m_pBytes); + assert( toTrim <= m_nBytesLeft ); + assert( 0 < toTrim ); + + m_nBytesLeft -= toTrim; + m_pBytes += toTrim; + } + + static char DefaultChar( UINT srcDataCP ) + { + return 0x3f; + } + static WCHAR DefaultWChar( UINT srcDataCP ) + { + return (CP_UTF8 == srcDataCP ? 0xfffd // Unicode to Unicode, use Unicode default char + : (932 == srcDataCP ? 0x30fb // 932 to Unicode has special default char + : 0x003f)); // WCP source, use '?' + } + void AssignDefault( UINT srcDataCP ) + { + assert( sizeof(T) <= m_nBytesLeft ); + if ( 1 == sizeof(T) ) + { + *m_pBytes = DefaultChar( srcDataCP ); + --m_nBytesLeft; + ++m_pBytes; + } + else + { + *(UNALIGNED T *)m_pBytes = DefaultWChar( srcDataCP ); + m_nBytesLeft -= sizeof(T); + m_pBytes += sizeof(T); + } + } + bool AssignDefaultUtf8( UINT srcDataCP ) + { + // This is a utf8 buffer so T must be char + assert( 1 == sizeof(T) ); + if ( CP_UTF16 == srcDataCP ) + { + // If source codepage is UTF16 then use Unicode default char + // UTF8 default char is 3 bytes long + if ( m_nBytesLeft < 3 ) + return false; + + *m_pBytes++ = (T)0xef; + *m_pBytes++ = (T)0xbf; + *m_pBytes++ = (T)0xbd; + m_nBytesLeft -= 3; + } + else if ( 932 == srcDataCP ) + { + // If source codepage is 932 then use special default char + // UTF8 default char for 932 is 3 bytes long + if ( m_nBytesLeft < 3 ) + return false; + + *m_pBytes++ = (T)0xe3; + *m_pBytes++ = (T)0x83; + *m_pBytes++ = (T)0xbb; + m_nBytesLeft -= 3; + } + else + { + *m_pBytes = DefaultChar( srcDataCP ); + ++m_pBytes; + --m_nBytesLeft; + } + return true; + } + + // Prevent compiler from generating these + iconv_buffer(); + iconv_buffer( const iconv_buffer & other ); + iconv_buffer & operator=( const iconv_buffer & other ); + }; + + template< class DestType > + bool AddDefault( iconv_buffer * dest, bool * pHasLoss, DWORD * pErrorCode ) const + { + if ( NULL != pHasLoss ) + *pHasLoss = true; + + if ( CP_UTF8 != m_dstCodePage ) + dest->AssignDefault( m_srcCodePage ); + else if ( !dest->AssignDefaultUtf8(m_srcCodePage) ) + { + // Not enough room for the default char + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return false; + } + return true; + } + + template< class DestType, class SrcType > + size_t Convert( + iconv_buffer & dest, + iconv_buffer & src, + bool failIfLossy = false, bool * pHasLoss = NULL, DWORD * pErrorCode = NULL ) const + { + if ( !IsValidIConv() ) + return 0; + + size_t iconv_ret; + size_t cchDest = dest.m_nBytesLeft/sizeof(DestType); + + if ( NULL != pHasLoss ) + *pHasLoss = false; + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + + while ( 0 < dest.m_nBytesLeft && 0 < src.m_nBytesLeft ) + { + // First clear any intermediate state left over from previous conversions + iconv_ret = iconv( m_pCvtCache->GetIConv(), NULL, NULL, NULL, NULL ); + assert( 0 == iconv_ret ); + + // Now attempt conversion + iconv_ret = iconv( m_pCvtCache->GetIConv(), &src.m_pBytes, &src.m_nBytesLeft, &dest.m_pBytes, &dest.m_nBytesLeft ); + if ( iconv_ret == (size_t)(-1) ) + { + // If there's no dest bytes left, then treat as E2BIG even if the error + // is EILSEQ, etc. We want E2BIG to take precedence like Windows. + int err = (0 < dest.m_nBytesLeft ? errno : E2BIG); + if ( E2BIG != err && failIfLossy ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_NO_UNICODE_TRANSLATION; + return 0; + } + + switch ( err ) + { + case EILSEQ: // Invalid multibyte sequence in input + if ( CP_UTF8 == m_srcCodePage ) + src.SkipUtf8Ch(); + else if ( 1 == sizeof(SrcType) ) + src.SkipDoubleCh(); // DBCS + else + src.SkipSingleCh(); // utf32 or incomplate utf16 surrogate + + if ( !AddDefault(&dest, pHasLoss, pErrorCode) ) + return 0; + + break; + case EINVAL: // Incomplete multibyte sequence in input + if ( CP_UTF8 == m_srcCodePage ) + src.SkipUtf8Ch(); + else + src.SkipSingleCh(); + + if ( !AddDefault(&dest, pHasLoss, pErrorCode) ) + return 0; + + break; + case E2BIG: // Output buffer is out of room + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + default: + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + } + } + + return cchDest - (dest.m_nBytesLeft / sizeof(DestType)); + } + +#elif defined(MPLAT_WWOWH) + + size_t ReturnCchResult( int cch, DWORD * pErrorCode ) const + { + if ( cch < 0 ) + cch = 0; + + if ( NULL != pErrorCode ) + *pErrorCode = (0 == cch ? GetLastError() : ERROR_SUCCESS); + + return cch; + } + +#endif // defined(MPLAT_WWOWH) + + +public: + EncodingConverter( UINT dstCodePage, UINT srcCodePage ); + ~EncodingConverter(); + + bool Initialize(); + + // Performs an encoding conversion. + // Returns the number of dest chars written. + // Input and output buffers should not overlap. + template< class DestType, class SrcType, class AllocT > + size_t Convert( + DestType ** destBuffer, + const SrcType * srcBuffer, size_t cchSource, + bool failIfLossy = false, bool * pHasLoss = NULL, DWORD * pErrorCode = NULL ) const + { +#if defined(MPLAT_UNIX) + + if ( !IsValidIConv() ) + return 0; + + iconv_buffer src( + reinterpret_cast< char * >( const_cast< SrcType * >(srcBuffer) ), + cchSource ); + + size_t cchDest = cchSource; + AutoArray< DestType, AllocT > newDestBuffer( cchDest ); + + iconv_buffer dest( + reinterpret_cast< char * >(newDestBuffer.m_ptr), + cchDest ); + + size_t cchPrevCvt = 0; + DWORD rcCvt; + while ( true ) + { + size_t cchCvt = Convert( dest, src, failIfLossy, pHasLoss, &rcCvt ); + if ( 0 == cchCvt ) + { + if ( ERROR_INSUFFICIENT_BUFFER == rcCvt ) + { + // Alloc more and continue + cchPrevCvt = cchDest; + cchDest *= 2; + if ( !newDestBuffer.Realloc(cchDest) ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_NOT_ENOUGH_MEMORY; + return 0; + } + // Fill newly allocated part of buffer + dest.Reset( reinterpret_cast< char * >(newDestBuffer.m_ptr+cchPrevCvt), cchDest ); + } + else + { + if ( NULL != pErrorCode ) + *pErrorCode = rcCvt; + return 0; + } + } + else + { + if ( NULL != pErrorCode ) + *pErrorCode = rcCvt; + *destBuffer = newDestBuffer.Detach(); + return cchPrevCvt + cchCvt; + } + } + +#elif defined(MPLAT_WWOWH) + // WWOWH unit testing code + // Can only convert between ansi and utf16 + if ( 1 == sizeof(DestType) && 2 == sizeof(SrcType) ) + { + // utf16 to ansi + const wchar_t * srcPtr = reinterpret_cast< const wchar_t * >( srcBuffer ); + BOOL loss = FALSE; + int converted = WideCharToMultiByte( + m_dstCodePage, 0, + srcPtr, (int)cchSource, + NULL, 0, + NULL, &loss ); + + if ( 0 < converted ) + { + AutoArray< char, AllocT > newDestBuffer( converted ); + char * dstPtr = newDestBuffer.m_ptr; + converted = WideCharToMultiByte( + m_dstCodePage, 0, + srcPtr, (int)cchSource, + newDestBuffer.m_ptr, converted, + NULL, &loss ); + if ( 0 < converted ) + *destBuffer = newDestBuffer.Detach(); + if ( NULL != pHasLoss ) + *pHasLoss = (FALSE != loss); + } + return ReturnCchResult( converted, pErrorCode ); + } + else if ( 2 == sizeof(DestType) && 1 == sizeof(SrcType) ) + { + // ansi to utf16 + const char * srcPtr = reinterpret_cast< const char * >( srcBuffer ); + int converted = MultiByteToWideChar( + m_srcCodePage, (failIfLossy ? MB_ERR_INVALID_CHARS : 0), + srcPtr, (int)cchSource, + NULL, 0 ); + + if ( 0 < converted ) + { + AutoArray< WCHAR, AllocT > newDestBuffer( converted ); + converted = MultiByteToWideChar( + m_srcCodePage, (failIfLossy ? MB_ERR_INVALID_CHARS : 0), + srcPtr, (int)cchSource, + newDestBuffer.m_ptr, converted ); + if ( 0 < converted ) + *destBuffer = newDestBuffer.Detach(); + if ( NULL != pHasLoss ) + *pHasLoss = false; + } + return ReturnCchResult( converted, pErrorCode ); + } + else + { + assert( false ); + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_NOT_SUPPORTED; + return 0; + } + +#endif // defined(MPLAT_WWOWH) + } + // Performs an encoding conversion. + // Returns the number of dest chars written. + // Input and output buffers should not overlap. + template< class DestType, class SrcType > + size_t Convert( + DestType * destBuffer, size_t cchDest, + const SrcType * srcBuffer, size_t cchSource, + bool failIfLossy = false, bool * pHasLoss = NULL, DWORD * pErrorCode = NULL ) const + { +#if defined(MPLAT_UNIX) + + if ( !IsValidIConv() ) + return 0; + + iconv_buffer src( + reinterpret_cast< char * >( const_cast< SrcType * >(srcBuffer) ), + cchSource ); + if ( 0 < cchDest ) + { + iconv_buffer dest( + reinterpret_cast< char * >(destBuffer), + cchDest ); + return Convert( dest, src, failIfLossy, pHasLoss, pErrorCode ); + } + else + { + // Use fixed size buffer iteratively to determine final required length + const size_t CCH_FIXED_SIZE = 256; + char fixed_buf[ CCH_FIXED_SIZE*sizeof(DestType) ]; + iconv_buffer dest( + &fixed_buf[0], + CCH_FIXED_SIZE ); + + bool hasLoss = false; + DWORD rcCvt = ERROR_SUCCESS; + size_t cchOnce = 0; + size_t cchCumulative = 0; + + while ( 0 < src.m_nBytesLeft + && 0 == (cchOnce = Convert(dest, src, failIfLossy, &hasLoss, &rcCvt)) + && ERROR_INSUFFICIENT_BUFFER == rcCvt ) + { + cchCumulative += CCH_FIXED_SIZE; + cchCumulative -= dest.m_nBytesLeft; + dest.Reset( &fixed_buf[0], CCH_FIXED_SIZE ); + } + if ( 0 < cchOnce ) + cchCumulative += cchOnce; + if ( NULL != pErrorCode ) + *pErrorCode = (0 < cchCumulative ? ERROR_SUCCESS : rcCvt); + if ( NULL != pHasLoss ) + *pHasLoss |= hasLoss; + return cchCumulative; + } + +#elif defined(MPLAT_WWOWH) + // WWOWH unit testing code + // Can only convert between ansi and utf16 + if ( 1 == sizeof(DestType) && 2 == sizeof(SrcType) ) + { + // utf16 to ansi + char * dstPtr = reinterpret_cast< char * >( destBuffer ); + const wchar_t * srcPtr = reinterpret_cast< const wchar_t * >( srcBuffer ); + BOOL loss = FALSE; + int converted = WideCharToMultiByte( + m_dstCodePage, 0, + srcPtr, (int)cchSource, + dstPtr, (int)cchDest, + NULL, &loss ); + if ( NULL != pHasLoss ) + *pHasLoss = (FALSE != loss); + return ReturnCchResult( converted, pErrorCode ); + } + else if ( 2 == sizeof(DestType) && 1 == sizeof(SrcType) ) + { + // ansi to utf16 + wchar_t * dstPtr = reinterpret_cast< wchar_t * >( destBuffer ); + const char * srcPtr = reinterpret_cast< const char * >( srcBuffer ); + int converted = MultiByteToWideChar( + m_srcCodePage, (failIfLossy ? MB_ERR_INVALID_CHARS : 0), + srcPtr, (int)cchSource, + dstPtr, (int)cchDest ); + if ( NULL != pHasLoss ) + *pHasLoss = false; + return ReturnCchResult( converted, pErrorCode ); + } + else + { + assert( false ); + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_NOT_SUPPORTED; + return 0; + } + +#endif // defined(MPLAT_WWOWH) + } +}; + +#endif // _GLOBALIZATION_H_ diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h new file mode 100644 index 000000000..71ee51964 --- /dev/null +++ b/source/shared/interlockedatomic.h @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: InterlockedAtomic.h +// +// Contents: Contains a portable abstraction for interlocked, atomic +// operations on int32_t and pointer types. +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __INTERLOCKEDATOMIC_H__ +#define __INTERLOCKEDATOMIC_H__ + +// Forward references and contract specifications +// + +// Increments and returns new value +LONG InterlockedIncrement( LONG volatile * atomic ); + +// Decrements and returns new value +LONG InterlockedDecrement( LONG volatile * atomic ); + +// Always returns old value +// Sets to new value if old value equals compareTo +LONG InterlockedCompareExchange( LONG volatile * atomic, LONG newValue, LONG compareTo ); + +// Sets to new value and returns old value +LONG InterlockedExchange( LONG volatile * atomic, LONG newValue ); + +// Sets to new value and returns old value +PVOID InterlockedExchangePointer( PVOID volatile * atomic, PVOID newValue); + +// Adds the amount and returns the old value +LONG InterlockedExchangeAdd( LONG volatile * atomic, LONG add ); + +// Always returns the old value +// Sets the new value if old value equals compareTo +PVOID InterlockedCompareExchangePointer( PVOID volatile * atomic, PVOID newValue, PVOID compareTo ); + + + +// Use conditional compilation to load the implementation +// +#if defined(_MSC_VER) +#include "InterlockedAtomic_WwoWH.h" +#elif defined(__GNUC__) +#include "interlockedatomic_gcc.h" +#else +#error "Unsupported compiler" +#endif + +#endif // __INTERLOCKEDATOMIC_H__ diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h new file mode 100644 index 000000000..6ab246e6c --- /dev/null +++ b/source/shared/interlockedatomic_gcc.h @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: InterlockedAtomic_gcc.h +// +// Contents: Contains a portable abstraction for interlocked, atomic +// operations on int32_t and pointer types. +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __INTERLOCKEDATOMIC_GCC_H__ +#define __INTERLOCKEDATOMIC_GCC_H__ + +#if !defined(__GNUC__) +#error "Incorrect compiler configuration in InterlockedAtomic.h. Was expecting GCC." +#endif + +inline LONG InterlockedIncrement( LONG volatile * atomic ) +{ + return __sync_add_and_fetch( atomic, 1 ); +} + +inline LONG InterlockedDecrement( LONG volatile * atomic ) +{ + return __sync_sub_and_fetch( atomic, 1 ); +} + +inline LONG InterlockedCompareExchange( LONG volatile * atomic, LONG newValue, LONG compareTo ) +{ + return __sync_val_compare_and_swap( atomic, compareTo, newValue ); +} + +inline LONG InterlockedExchange( LONG volatile * atomic, LONG newValue ) +{ + return __sync_lock_test_and_set( atomic, newValue ); +} + +inline PVOID InterlockedExchangePointer( PVOID volatile * atomic, PVOID newValue) +{ + return __sync_lock_test_and_set( atomic, newValue ); +} + +inline LONG InterlockedExchangeAdd( LONG volatile * atomic, LONG add ) +{ + return __sync_fetch_and_add( atomic, add ); +} + +inline PVOID InterlockedCompareExchangePointer( PVOID volatile * atomic, PVOID newValue, PVOID compareTo ) +{ + return __sync_val_compare_and_swap( atomic, compareTo, newValue ); +} + +#endif // __INTERLOCKEDATOMIC_GCC_H__ diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h new file mode 100644 index 000000000..43d0bde97 --- /dev/null +++ b/source/shared/interlockedslist.h @@ -0,0 +1,142 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: InterlockedSList.h +// +// Contents: Contains a portable abstraction for interlocked, singly +// linked list. +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __INTERLOCKEDSLIST_H__ +#define __INTERLOCKEDSLIST_H__ + +#include "interlockedatomic.h" + +#define SLIST_ENTRY SINGLE_LIST_ENTRY + +#define PSLIST_ENTRY PSINGLE_LIST_ENTRY + +typedef struct _SINGLE_LIST_ENTRY { + // Want a volatile pointer to non-volatile data so place after all type info + struct _SINGLE_LIST_ENTRY * volatile Next; +} SINGLE_LIST_ENTRY, *PSINGLE_LIST_ENTRY; + +typedef union _SLIST_HEADER { + // Provides 8 byte alignment for 32-bit builds. Technically, not needed for + // current implementation below but leaving for future use. + ULONGLONG Alignment; + struct { + // Want a volatile pointer to non-volatile data so place after all type info + PSLIST_ENTRY volatile Head; + volatile LONG Depth; + volatile LONG Mutex; + } List; +} SLIST_HEADER, *PSLIST_HEADER; + + +inline VOID InitializeSListHead( PSLIST_HEADER slist ) +{ + assert( NULL != slist ); + + slist->List.Head = NULL; + slist->List.Depth = 0; + slist->List.Mutex = 0; +} + +inline PSLIST_ENTRY InterlockedPopEntrySList( PSLIST_HEADER slist ) +{ + assert( NULL != slist ); + + // Exit prior to 'mutex' if we think it is empty + // Some callers (like sqlncli/msdart/dll/dynslist.h) rely on a NULL + // result from Pop to indicate the list is empty. This early exit + // is an optimization and not technically needed for correctness. + PSLIST_ENTRY oldHead = slist->List.Head; + if ( NULL == oldHead ) + { + return NULL; + } + + while ( 0 != slist->List.Mutex || 0 != InterlockedCompareExchange( &slist->List.Mutex, 1, 0 ) ) + { + // Spin until 'mutex' is free + } + + // We have the 'mutex' so proceed with update + oldHead = slist->List.Head; + if ( NULL != oldHead ) + { + slist->List.Head = oldHead->Next; + --(slist->List.Depth); + assert( 0 <= slist->List.Depth ); + } + + // Free the 'mutex' + slist->List.Mutex = 0; + + return oldHead; +} + +inline PSLIST_ENTRY InterlockedPushEntrySList( PSLIST_HEADER slist, PSLIST_ENTRY newEntry ) +{ + assert( NULL != slist ); + + while ( 0 != slist->List.Mutex || 0 != InterlockedCompareExchange( &slist->List.Mutex, 1, 0 ) ) + { + // Spin until 'mutex' is free + } + + // We have the 'mutex' so proceed with update + PSLIST_ENTRY oldHead = slist->List.Head; + newEntry->Next = oldHead; + slist->List.Head = newEntry; + ++(slist->List.Depth); + + // Free the 'mutex' + slist->List.Mutex = 0; + + return oldHead; +} + +inline PSLIST_ENTRY InterlockedFlushSList( PSLIST_HEADER slist ) +{ + assert( NULL != slist ); + + while ( 0 != slist->List.Mutex || 0 != InterlockedCompareExchange( &slist->List.Mutex, 1, 0 ) ) + { + // Spin until 'mutex' is free + } + + // We have the 'mutex' so proceed with update + PSLIST_ENTRY oldHead = slist->List.Head; + slist->List.Head = NULL; + slist->List.Depth = 0; + + // Free the 'mutex' + slist->List.Mutex = 0; + + return oldHead; +} + +// If the list has more than USHORT nodes then this method +// will not return reliable results. +inline USHORT QueryDepthSList( PSLIST_HEADER slist ) +{ + assert( NULL != slist ); + + return static_cast(slist->List.Depth); +} + + +#endif // __INTERLOCKEDSLIST_H__ diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp new file mode 100644 index 000000000..a73078897 --- /dev/null +++ b/source/shared/localization.hpp @@ -0,0 +1,878 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: Localization.hpp +// +// Contents: Contains portable classes for localization +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __LOCALIZATION_HPP__ +#define __LOCALIZATION_HPP__ + +#include +#include +#include "typedefs_for_linux.h" + +#ifdef MPLAT_UNIX +namespace std +{ + // Forward reference + class locale; +} +#endif + +#define CP_UTF8 65001 +#define CP_UTF16 1200 +#define CP_UTF32 12000 +#define CP_ACP 0 // default to ANSI code page + +// This class provides allocation policies for the SystemLocale and AutoArray classes. +// This is primarily needed for the self-allocating ToUtf16/FromUtf16 methods. +// SNI needs all its allocations to use its own allocator so it would create a separate +// class that obeys this interface and provide it as a template parameter. +template< typename ArrayT > +struct ArrayTAllocator +{ + static ArrayT * Alloc( size_t cch ) + { + return reinterpret_cast< ArrayT * >( malloc(cch*sizeof(ArrayT)) ); + } + // Realloc will free the 'old' memory if new memory was successfully allocated + // and copied to. + static ArrayT * Realloc( ArrayT * old, size_t cchNewSize ) + { + return reinterpret_cast< ArrayT * >( realloc(old, cchNewSize*sizeof(ArrayT)) ); + } + static void Free( ArrayT * mem ) + { + free( mem ); + } +}; + +// This is an auto_ptr-like class that is used with the SystemLocale. +// It allows for automatic freeing of the memory using the allocator policy. +// Callers would not normally use this class directly but would use one of the +// two specializations: AutoCharArray AutoWCharArray. +template< typename ArrayT, typename AllocT = ArrayTAllocator< ArrayT > > +struct AutoArray +{ + size_t m_cchSize; + ArrayT * m_ptr; + + AutoArray( const AutoArray & ); + AutoArray & operator=( const AutoArray & ); + + AutoArray() + : m_cchSize( 0 ), m_ptr( NULL ) + { + } + explicit AutoArray( size_t cchSize ) + : m_cchSize( cchSize ), m_ptr( AllocT::Alloc(cchSize) ) + { + } + virtual ~AutoArray() + { + Free(); + } + void Free() + { + if ( NULL != m_ptr ) + { + AllocT::Free( m_ptr ); + m_ptr = NULL; + m_cchSize = 0; + } + } + bool Realloc( size_t cchSize ) + { + ArrayT * newPtr = AllocT::Realloc( m_ptr, cchSize ); + if ( NULL != newPtr ) + { + // Safe to overwrite since Realloc freed m_ptr. + m_ptr = newPtr; + m_cchSize = cchSize; + return true; + } + return false; + } + ArrayT * Detach() + { + ArrayT * oldPtr = m_ptr; + m_ptr = NULL; + m_cchSize = 0; + return oldPtr; + } + void UpdateSize() + { + if ( NULL == m_ptr ) + { + m_cchSize = 0; + } + else + { + // XPLAT_ODBC_TODO VSTS 819733 MPlat: Reconcile std c++ usage between platforms + // Should use char_traits::length + ArrayT * end = m_ptr; + while ( (ArrayT)0 != *end++ ) + ; + // Want the null terminator included + m_cchSize = end - m_ptr; + } + } +}; + + +class SystemLocale +{ +public: + // ----------------------------------------------------------------------- + // Public Static Functions +#ifdef MPLAT_UNIX + static const SystemLocale & Singleton(); +#else + // Windows returns by value since this is an empty class + static const SystemLocale Singleton(); +#endif + +#ifdef MPLAT_UNIX + int GetResourcePath( char * buffer, size_t cchBuffer ) const; + + static const int MINS_PER_HOUR = 60; + static const int MINS_PER_DAY = 24 * MINS_PER_HOUR; + + // Returns the bias between the supplied utc and local times. + // utc = local + bias + static int BiasInMinutes( const struct tm & utc, const struct tm & local ) + { + int bias = 0; + if ( utc.tm_mon != local.tm_mon ) + { + // Offset crosses month boundary so one of two must be first day of month + if ( 1 == utc.tm_mday ) + bias += MINS_PER_DAY; + else + { + assert( 1 == local.tm_mday ); + bias -= MINS_PER_DAY; + } + } + else + { + bias += MINS_PER_DAY * (utc.tm_mday - local.tm_mday); + } + + bias += MINS_PER_HOUR * (utc.tm_hour - local.tm_hour); + bias += (utc.tm_min - local.tm_min); + + // Round based on diff in secs, in case utc/local straddle a day with leap seconds + int secs_diff = (utc.tm_sec - local.tm_sec); + if ( 29 < secs_diff ) + ++bias; + else if ( secs_diff < -29 ) + --bias; + + return bias; + } + + // Returns both standard and daylight savings biases for the current year + // utc = local + bias + // Both might be equal if DST is not honored + // If platform doesn't know if bias is DST or standard (ie. unknown) + // then standard time is assumed. + // Note that applying current year's biases to dates from other years may result + // in incorrect time adjustments since regions change their rules over time. + // The current SNAC driver code uses this approach as well so we are doing this + // to preserve consistent behavior. If SNAC changes to lookup the offsets that + // were effective for a given date then we should update our logic here as well. + static DWORD TimeZoneBiases( int * stdInMinutes, int * dstInMinutes ) + { + struct tm local, utc; + // Find current year + time_t now = time( NULL ); + if ( (time_t)(-1) == now || NULL == localtime_r(&now, &local) ) + return ERROR_INVALID_DATA; + + // Find bias for first of each month until both STD and DST are found + // Possible perf improvements (can wait until perf tests indicate a need): + // Just use Dec 21 and Jun 21 (near the two soltices) + // Or calc once and cache (must be thread safe) + bool foundUNK = false; + bool foundSTD = false; + bool foundDST = false; + int std_bias = 0; + int dst_bias = 0; + + local.tm_mday = 1; + for ( int mon = 0; mon < 12; ++mon ) + { + local.tm_mon = mon; + if ( (time_t)(-1) == (now = mktime(&local)) || NULL == gmtime_r(&now, &utc) ) + return ERROR_INVALID_DATA; + + if ( 0 < local.tm_isdst ) + { + if ( !foundDST ) + { + dst_bias = BiasInMinutes( utc, local ); + foundDST = true; + if ( foundSTD ) + break; // Done checking when both STD & DST are found + } + } + else + { + // Time is STD or unknown, put in STD + if ( !foundSTD ) + { + std_bias = BiasInMinutes( utc, local ); + if ( local.tm_isdst < 0 ) + foundUNK = true; + else + { + foundSTD = true; + if ( foundDST ) + break; // Done checking when both STD and DST are found + } + } + } + } + + // At least one of STD, DST, or unknown must have been set + assert( foundSTD || foundDST || foundUNK ); + + // For zones that don't observe DST (somewhat common), + // report DST bias as the same as STD + if ( !foundDST ) + dst_bias = std_bias; + + // For zones that ONLY observe DST (extremely rare if at all), + // report STD bias as the same as DST + if ( !foundSTD && !foundUNK ) + std_bias = dst_bias; + + *stdInMinutes = std_bias; + *dstInMinutes = dst_bias; + + return ERROR_SUCCESS; + } +#endif + + static DWORD CurrentLocalTime( LPSYSTEMTIME pTime ); + + // Multi-byte UTF8 code points start with '11xx xxxx' + static bool IsUtf8LeadByte( BYTE utf8 ) + { + return (0xC0 == (utf8 & 0xC0)); + } + + // Maximum number of storage units (char or WCHAR) + // for a code page (e.g. UTF16 == 2 for surrogates) + static UINT MaxCharCchSize( UINT codepage ); + + // Inspects the byte at start, and returns the start + // of the next code point (possibly multiple bytes later). + // If NULL or start points at null terminator, than start is returned. + // If start points at a dangling UTF8 trail byte, then (start+1) is + // returned since we can't know how large this code point is. + static char * NextChar( UINT codepage, const char * start ); +#ifdef MPLAT_UNIX + // This version is for non-null terminated strings. + // Last ptr will be one past end of buffer. + static char * NextChar( UINT codepage, const char * start, size_t cchBytesLeft ); +#endif + + // Given the start byte, how many total bytes are expected for + // this code point. If start is a UTF8 trail byte, then 1 is returned. + static UINT CchExpectedNextChar( UINT codepage, BYTE start ) + { + if ( 0 == (start & (char)0x80) ) + return 1; // ASCII + else if ( CP_UTF8 == codepage ) + return IsUtf8LeadByte(start) ? CchUtf8CodePt(start) : 1; + else if ( IsDBCSLeadByteEx(codepage, start) ) + return 2; + else + return 1; + } + + // Returns the number of bytes that need to be trimmed to avoid splitting + // a multi-byte code point sequence at the end of the buffer. + // Returns zero if a trailing UTF8 code value is found but no + // matching lead byte was found for it (ie. invalid, dangling trail byte). + _Ret_range_(0, cchBuffer) static UINT TrimPartialCodePt( UINT codepage, _In_count_(cchBuffer) const BYTE * buffer, size_t cchBuffer ) + { + if ( 0 == cchBuffer ) + return 0; + + if ( CP_UTF8 == codepage ) + { + return TrimPartialUtf8CodePt( buffer, cchBuffer ); + } + else + { + size_t i = cchBuffer; + for ( ; 0 < i; --i ) + { + if ( !IsDBCSLeadByteEx( codepage, buffer[i-1] ) ) + break; + } + // If odd, then last byte is truly a lead byte so return 1 byte to trim + return ((cchBuffer-i) & 1) ? 1 : 0; + } + } + + // For all transcoding functions + // Returns zero on error. Do not call GetLastError() since that is not portable (pErrorCode has result of GetLastError()). + // pHasDataLoss will be true if an unrecognized code point was encountered in the source and a default output instead. + // Replaces calls to MultiByteToWideChar and WideCharToMultiByte + + // Transcode between a code page and UTF16 + static size_t ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, + __out_ecount_opt(cchDest) WCHAR * dest, size_t cchDest, + DWORD * pErrorCode = NULL ); + static size_t ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, + __out_ecount_opt(cchDest) WCHAR * dest, size_t cchDest, + DWORD * pErrorCode = NULL ); + static size_t FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, + __out_ecount_opt(cchDest) char * dest, size_t cchDest, + bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL ); + static size_t FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, + __out_ecount_opt(cchDest) char * dest, size_t cchDest, + bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL); + // Allocates destination buffer to match required size + // Template is used so call can provide allocation policy + // Used instead of the Windows API pattern of calling with zero dest buffer size to find + // required buffer size, followed by second call with newly allocated buffer. + template< typename AllocT > + static size_t ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, __deref_out_ecount(1) WCHAR ** dest, DWORD * pErrorCode = NULL ); + template< typename AllocT > + static size_t ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, __deref_out_ecount(1) WCHAR ** dest, DWORD * pErrorCode = NULL ); + template< typename AllocT > + static size_t FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, __deref_out_ecount(1) char ** dest, bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL ); + template< typename AllocT > + static size_t FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, __deref_out_ecount(1) char ** dest, bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL); + + + + // ----------------------------------------------------------------------- + // Public Member Functions + +#ifndef TIME_ZONE_ID_UNKNOWN + #define TIME_ZONE_ID_UNKNOWN 0 + #define TIME_ZONE_ID_STANDARD 1 + #define TIME_ZONE_ID_DAYLIGHT 2 +#endif + // pTZInfo, if supplied, holds one of the above defined values + DWORD CurrentTimeZoneBias( LONG * offsetInMinutes, DWORD * pTZInfo = NULL ) const; + + // The Ansi code page, always UTF8 for Linux + UINT AnsiCP() const; + // Used for files (e.g. returns 437 on US Windows, UTF8 for Linux) + UINT OemCP() const; + // Returns UTF-16LE for all platforms (LE == Little Endian) + UINT WideCP() const + { + return CP_UTF16; + } + + // Performs case folding to lower case using the current system locale + // Replaces calls to LCMapStringA + size_t ToLower( const char * src, SSIZE_T cchSrc, __out_ecount_opt(cchDest) char * dest, size_t cchDest, DWORD * pErrorCode = NULL ) const; + +#ifndef CSTR_ERROR + #define CSTR_ERROR 0 // compare failed + #define CSTR_LESS_THAN 1 // string 1 less than string 2 + #define CSTR_EQUAL 2 // string 1 equal to string 2 + #define CSTR_GREATER_THAN 3 // string 1 greater than string 2 +#endif + // String comparison using the rules of the current system locale. + // Replaces calls to CompareString + // Ignoring width (Bing for "Full Width Characters") has no affect on Linux + // Return value is one of the above defined values. + // On error, pErrorCode has result of GetLastError() (do not call GetLastError directly since it isn't portable). + int Compare( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode = NULL ) const; + int CompareIgnoreCase( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode = NULL ) const; + int CompareIgnoreWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode = NULL ) const; + int CompareIgnoreCaseAndWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode = NULL ) const; + + + + +private: + // Prevent copying. + // Also prevents misuse of return from Singleton() method. + // Since return types are different on Windows vs Linux, + // callers should not cache the result of Singleton(). + SystemLocale( const SystemLocale & ); + SystemLocale & operator=( const SystemLocale & ); + +#ifdef MPLAT_UNIX +// MPLAT_UNIX ---------------------------------------------------------------- + + std::locale * m_pLocale; + + explicit SystemLocale( const char * localeName ); + ~SystemLocale(); + + static UINT ExpandSpecialCP( UINT codepage ) + { + // Convert CP_ACP, CP_OEM to CP_UTF8 + return (codepage < 2 ? CP_UTF8 : codepage); + } + +// MPLAT_UNIX ---------------------------------------------------------------- +#else +// !MPLAT_UNIX --------------------------------------------------------------- + + SystemLocale() {} + + static size_t ReturnCchResult( SSIZE_T cch, DWORD * pErrorCode ) + { + if ( cch < 0 ) + { + cch = 0; + } + if ( NULL != pErrorCode ) + { + *pErrorCode = (0 == cch ? GetLastError() : ERROR_SUCCESS); + } + return static_cast(cch); + } + + static int CompareWithFlags( DWORD flags, const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode = NULL ); + + static size_t FastAsciiMultiByteToWideChar + ( + UINT CodePage, + __in_ecount(cch) const char *pch, // IN | source string + SSIZE_T cch, // IN | count of characters or -1 + __out_ecount_opt(cwch) PWCHAR pwch, // IN | Result string + size_t cwch, // IN | count of wchars of result buffer or 0 + DWORD* pErrorCode, // OUT | optional pointer to return error code + bool bStrict = false // IN | Return error if invalid chars in src + ); + static size_t FastAsciiWideCharToMultiByte + ( + UINT CodePage, + const WCHAR *pwch, // IN | source string + SSIZE_T cwch, // IN | count of characters or -1 + __out_bcount(cch) char *pch, // IN | Result string + size_t cch, // IN | Length of result buffer or 0 + BOOL *pfDataLoss, // OUT | True if there was data loss during CP conversion + DWORD *pErrorCode // OUT | optional pointer to return error code + ); + +// !MPLAT_UNIX --------------------------------------------------------------- +#endif + + // Returns the number of bytes this UTF8 code point expects + static UINT CchUtf8CodePt( BYTE codept ) + { + assert( IsUtf8LeadByte(codept) ); + + // Initial byte of utf8 sequence indicates its length + // 110x xxxx = 2 bytes + // 1110 xxxx = 3 bytes + // 1111 0xxx = 4 bytes + // 1111 10xx = 5 bytes, future Unicode extension not covered by this logic + // 1111 110x = 6 bytes, future Unicode extension not covered by this logic + UINT expected_size = (0xC0 == (codept & 0xE0)) ? 2 : (0xE0 == (codept & 0xF0)) ? 3 : 4; + + // Verify constraints + assert( 4 == MaxCharCchSize(CP_UTF8) ); + + return expected_size; + } + + // Returns the number of bytes that need to be trimmed to avoid splitting + // a UTF8 code point sequence at the end of the buffer. + // Returns zero for ASCII. + // Also returns zero if a trailing UTF8 code value is found but no + // matching lead byte was found for it (ie. invalid, dangling trail byte). + static UINT TrimPartialUtf8CodePt( const BYTE * buffer, size_t cchBuffer ) + { + if ( 0 == cchBuffer ) + return 0; + + if ( 0 == (buffer[cchBuffer-1] & 0x80) ) + { + // Last char is ASCII so no trim needed + return 0; + } + + // Last char is non-initial byte of multibyte utf8 sequence + // Need to determine if it is the last (ie. no trim need) + UINT cchMax = MaxCharCchSize( CP_UTF8 ); + for ( UINT i = 1; 0 < cchBuffer && i <= cchMax; --cchBuffer, ++i ) + { + if ( IsUtf8LeadByte(buffer[cchBuffer-1]) ) + { + // Found initial byte, verify size of sequence + UINT cchExpected = CchUtf8CodePt( buffer[cchBuffer-1] ); + if ( i == cchExpected ) + return 0; // utf8 sequence is complete so no trim needed + else + { + assert( i <= cchBuffer ); + return i; // trim the incomplete sequence + } + } + } + + // Did not find initial utf8 byte so trim nothing + return 0; + } +}; + + + +// Convenience wrapper for converting from UTF16 into a newly +// allocated char[]. Class behaves like auto_ptr (will free in dtor, +// but has Release method so caller can take ownership of memory). +template< typename AllocT = ArrayTAllocator< char > > +struct AutoCharArray : public AutoArray< char, AllocT > +{ + size_t AllocConvertFromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL ) + { + char * converted = NULL; + size_t cchCvt = SystemLocale::FromUtf16< AllocT >( destCodePage, src, cchSrc, &converted, pHasDataLoss, pErrorCode ); + if ( 0 < cchCvt ) + { + this->Free(); + this->m_ptr = converted; + this->m_cchSize = cchCvt; + } + return cchCvt; + } +}; + +// Convenience wrapper for converting to UTF16 into a newly +// allocated WCHAR[]. Class behaves like auto_ptr (will free in dtor, +// but has Release method so caller can take ownership of memory). +template< typename AllocT = ArrayTAllocator< WCHAR > > +struct AutoWCharArray : public AutoArray< WCHAR, AllocT > +{ + size_t AllocConvertToUtf16( UINT destCodePage, const char * src, SSIZE_T cchSrc, bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL ) + { + WCHAR * converted = NULL; + size_t cchCvt = SystemLocale::ToUtf16< AllocT >( destCodePage, src, cchSrc, &converted, pErrorCode ); + if ( 0 < cchCvt ) + { + this->Free(); + this->m_ptr = converted; + this->m_cchSize = cchCvt; + } + return cchCvt; + } +}; + + + +// --------------------------------------------------------------------------- +// Inlines that vary by platform + +#if defined(MPLAT_UNIX) +// MPLAT_UNIX ---------------------------------------------------------------- + +#include "globalization.h" + +inline UINT SystemLocale::AnsiCP() const +{ + return CP_UTF8; +} + +inline UINT SystemLocale::OemCP() const +{ + return CP_UTF8; +} + +inline UINT SystemLocale::MaxCharCchSize( UINT codepage ) +{ + codepage = ExpandSpecialCP( codepage ); + switch ( codepage ) + { + case CP_UTF8: + return 4; + case 932: + case 936: + case 949: + case 950: + case CP_UTF16: + return 2; + default: + return 1; + } +} + +inline int SystemLocale::CompareIgnoreWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + // XPLAT_ODBC_TODO: VSTS 806013 MPLAT: Support IgnoreWidth for SNI string comparisons + return Compare( left, cchLeft, right, cchRight, pErrorCode ); +} + +inline int SystemLocale::CompareIgnoreCaseAndWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + // XPLAT_ODBC_TODO: VSTS 806013 MPLAT: Support IgnoreWidth for SNI string comparisons + return CompareIgnoreCase( left, cchLeft, right, cchRight, pErrorCode ); +} + +template< typename AllocT > +inline size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR ** dest, DWORD * pErrorCode ) +{ + srcCodePage = ExpandSpecialCP( srcCodePage ); + EncodingConverter cvt( CP_UTF16, srcCodePage ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert< WCHAR, char, AllocT >( dest, src, cchSrcActual, false, &hasLoss, pErrorCode ); +} + +template< typename AllocT > +inline size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR ** dest, DWORD * pErrorCode ) +{ + srcCodePage = ExpandSpecialCP( srcCodePage ); + EncodingConverter cvt( CP_UTF16, srcCodePage ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert< WCHAR, char, AllocT >( dest, src, cchSrcActual, true, &hasLoss, pErrorCode ); +} + +template< typename AllocT > +inline size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char ** dest, bool * pHasDataLoss, DWORD * pErrorCode ) +{ + destCodePage = ExpandSpecialCP( destCodePage ); + EncodingConverter cvt( destCodePage, CP_UTF16 ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+mplat_wcslen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert< char, WCHAR, AllocT >( dest, src, cchSrcActual, false, &hasLoss, pErrorCode ); +} + + +// MPLAT_UNIX ---------------------------------------------------------------- +#else +// ! MPLAT_UNIX ---------------------------------------------------------------- + + +inline const SystemLocale SystemLocale::Singleton() +{ + // On Windows, Localization is an empty class so creation of this + // should be optimized away. Empty classes have a sizeof 1 so there's + // something to take the address of. + C_ASSERT( 1 == sizeof(SystemLocale) ); + return SystemLocale(); +} + +inline DWORD SystemLocale::CurrentTimeZoneBias( LONG * offsetInMinutes, DWORD * pTZInfo ) const +{ + TIME_ZONE_INFORMATION tzi; + DWORD tzInfo; + if ( NULL == offsetInMinutes ) + return ERROR_INVALID_PARAMETER; + else if ( TIME_ZONE_ID_INVALID == (tzInfo = GetTimeZoneInformation(&tzi)) ) + return GetLastError(); + else + { + *offsetInMinutes = tzi.Bias; + if ( NULL != pTZInfo ) + *pTZInfo = tzInfo; + + return ERROR_SUCCESS; + } +} + +inline DWORD SystemLocale::CurrentLocalTime( LPSYSTEMTIME pTime ) +{ + GetLocalTime( pTime ); + return ERROR_SUCCESS; +} + +inline UINT SystemLocale::AnsiCP() const +{ + return GetACP(); +} + +inline UINT SystemLocale::OemCP() const +{ + return GetOEMCP(); +} + +inline UINT SystemLocale::MaxCharCchSize( UINT codepage ) +{ + CPINFO cpinfo; + BOOL rc = GetCPInfo( codepage, &cpinfo ); + return (rc ? cpinfo.MaxCharSize : 0); +} + +inline size_t SystemLocale::ToLower( const char * src, SSIZE_T cchSrc, char * dest, size_t cchDest, DWORD * pErrorCode ) const +{ + // Windows API takes 'int' sized parameters + if ( cchSrc < -1 || 0x7FFFFFF < cchSrc || 0x7FFFFFF < cchDest ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + +OACR_WARNING_PUSH +OACR_WARNING_DISABLE(SYSTEM_LOCALE_MISUSE , " INTERNATIONALIZATION BASELINE AT KATMAI RTM. FUTURE ANALYSIS INTENDED. ") +OACR_WARNING_DISABLE(ANSI_APICALL, " Keeping the ANSI API for now. ") + int cch = LCMapStringA( + LOCALE_SYSTEM_DEFAULT, + LCMAP_LOWERCASE, + src, + (int)cchSrc, + dest, + (int)cchDest ); +OACR_WARNING_POP + + return ReturnCchResult( cch, pErrorCode ); +} + +inline int SystemLocale::CompareWithFlags( DWORD flags, const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) +{ + // Windows API takes 'int' sized parameters + if ( cchLeft < -1 || 0x7FFFFFF < cchLeft || cchRight < -1 || 0x7FFFFFF < cchRight ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + +OACR_WARNING_PUSH +OACR_WARNING_DISABLE(SYSTEM_LOCALE_MISUSE , " INTERNATIONALIZATION BASELINE AT KATMAI RTM. FUTURE ANALYSIS INTENDED. ") + int cmp = CompareStringA( LOCALE_SYSTEM_DEFAULT, flags, left, (int)cchLeft, right, (int)cchRight ); +OACR_WARNING_POP + if ( NULL != pErrorCode ) + { + *pErrorCode = (CSTR_ERROR == cmp ? GetLastError() : ERROR_SUCCESS); + } + return cmp; +} + +inline int SystemLocale::Compare( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + return CompareWithFlags( 0, left, cchLeft, right, cchRight, pErrorCode ); +} + +inline int SystemLocale::CompareIgnoreCase( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + return CompareWithFlags( NORM_IGNORECASE, left, cchLeft, right, cchRight, pErrorCode ); +} + +inline int SystemLocale::CompareIgnoreCaseAndWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + return CompareWithFlags( NORM_IGNORECASE|NORM_IGNOREWIDTH, left, cchLeft, right, cchRight, pErrorCode ); +} + +inline int SystemLocale::CompareIgnoreWidth( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + return CompareWithFlags( NORM_IGNOREWIDTH, left, cchLeft, right, cchRight, pErrorCode ); +} + +inline char * SystemLocale::NextChar( UINT codepage, const char * start ) +{ + return CharNextExA( (WORD)codepage, start, 0 ); +} + +inline size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode ) +{ + return FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, dest, cchDest, pErrorCode ); +} + +inline size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode ) +{ + return FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, dest, cchDest, pErrorCode, true ); +} + +inline size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode ) +{ + BOOL dataloss = FALSE; + size_t cchCvt = FastAsciiWideCharToMultiByte( destCodePage, src, cchSrc, dest, cchDest, &dataloss, pErrorCode ); + if ( NULL != pHasDataLoss ) + { + *pHasDataLoss = (FALSE != dataloss); + } + return cchCvt; +} + +template< typename AllocT > +inline size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR ** dest, DWORD * pErrorCode ) +{ + size_t cchCvt = FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, NULL, 0, pErrorCode ); + if ( 0 < cchCvt ) + { + AutoArray< WCHAR, AllocT > newDestBuffer( cchCvt ); + cchCvt = FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, newDestBuffer.m_ptr, cchCvt, pErrorCode ); + if ( 0 < cchCvt ) + *dest = newDestBuffer.Detach(); + } + return cchCvt; +} + +template< typename AllocT > +inline size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR ** dest, DWORD * pErrorCode ) +{ + size_t cchCvt = FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, NULL, 0, pErrorCode, true ); + if ( 0 < cchCvt ) + { + AutoArray< WCHAR, AllocT > newDestBuffer( cchCvt ); + cchCvt = FastAsciiMultiByteToWideChar( srcCodePage, src, cchSrc, newDestBuffer.m_ptr, cchCvt, pErrorCode, true ); + if ( 0 < cchCvt ) + *dest = newDestBuffer.Detach(); + } + return cchCvt; +} + +template< typename AllocT > +inline size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char ** dest, bool * pHasDataLoss, DWORD * pErrorCode ) +{ + BOOL dataloss = FALSE; + size_t cchCvt = FastAsciiWideCharToMultiByte( destCodePage, src, cchSrc, NULL, 0, &dataloss, pErrorCode ); + if ( 0 < cchCvt ) + { + AutoArray< char, AllocT > newDestBuffer( cchCvt ); + cchCvt = FastAsciiWideCharToMultiByte( destCodePage, src, cchSrc, newDestBuffer.m_ptr, cchCvt, &dataloss, pErrorCode ); + if ( 0 < cchCvt ) + *dest = newDestBuffer.Detach(); + } + if ( NULL != pHasDataLoss ) + { + *pHasDataLoss = (FALSE != dataloss); + } + return cchCvt; +} + +// ! MPLAT_UNIX ---------------------------------------------------------------- +#endif + +#endif // __LOCALIZATION_HPP__ diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp new file mode 100644 index 000000000..eb66d34a5 --- /dev/null +++ b/source/shared/localizationimpl.cpp @@ -0,0 +1,1043 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: LocalizationImpl.hpp +// +// Contents: Contains non-inline code for the SystemLocale class +// Must be included in one c/cpp file per binary +// A build error will occur if this inclusion policy is not followed +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "localization.hpp" + +#include "globalization.h" +#include "StringFunctions.h" + +struct cp_iconv +{ + UINT CodePage; + const char * IConvEncoding; + + static const cp_iconv g_cp_iconv[]; + static const size_t g_cp_iconv_count; + + static int GetIndex( UINT codepage ) + { + for ( size_t idx = 0; idx < g_cp_iconv_count; ++idx ) + { + if ( g_cp_iconv[idx].CodePage == codepage ) + return (int)idx; + } + // Should never be an unknown code page + assert( false ); + return -1; + } +}; + +// Array of CodePage-to-IConvEncoding mappings +// First few elements are most commonly used +// CodePage 2 corresponds to binary. If the attribute PDO::SQLSRV_ENCODING_BINARY +// is set, GetIndex() above hits the assert(false) directive unless we include +// CodePage 2 below and assign an empty string to it. +const cp_iconv cp_iconv::g_cp_iconv[] = { + { 65001, "UTF-8" }, + { 1200, "UTF-16LE" }, + { 3, "UTF-8" }, + { 2, "" }, + { 1252, "CP1252//TRANSLIT" }, + { 850, "CP850//TRANSLIT" }, + { 437, "CP437//TRANSLIT" }, + { 874, "CP874//TRANSLIT" }, + { 932, "CP932//TRANSLIT" }, + { 936, "CP936//TRANSLIT" }, + { 949, "CP949//TRANSLIT" }, + { 950, "CP950//TRANSLIT" }, + { 1250, "CP1250//TRANSLIT" }, + { 1251, "CP1251//TRANSLIT" }, + { 1253, "CP1253//TRANSLIT" }, + { 1254, "CP1254//TRANSLIT" }, + { 1255, "CP1255//TRANSLIT" }, + { 1256, "CP1256//TRANSLIT" }, + { 1257, "CP1257//TRANSLIT" }, + { 1258, "CP1258//TRANSLIT" }, + { 12000, "UTF-32LE" } +}; +const size_t cp_iconv::g_cp_iconv_count = ARRAYSIZE(cp_iconv::g_cp_iconv); + +#ifdef MPLAT_UNIX + +class IConvCachePool +{ + SLIST_HEADER m_Pool[cp_iconv::g_cp_iconv_count][cp_iconv::g_cp_iconv_count]; + + IConvCachePool( const IConvCachePool & ); + IConvCachePool & operator=( const IConvCachePool & ); + + // This bool indicates that the iconv pool is no longer available. + // For the driver,lis flag indicates the pool can no longer be used. + // Global destructors will be called by a single thread so this flag does not + // need thread synch protection. + static bool s_PoolDestroyed; + + IConvCachePool() + { + for ( int dstIdx = 0; dstIdx < cp_iconv::g_cp_iconv_count; ++dstIdx ) + { + for ( int srcIdx = 0; srcIdx < cp_iconv::g_cp_iconv_count; ++srcIdx ) + { + InitializeSListHead( &m_Pool[dstIdx][srcIdx] ); + } + } + } + + ~IConvCachePool() + { + IConvCachePool::s_PoolDestroyed = true; + + // Clean up remaining nodes + for ( int dstIdx = 0; dstIdx < cp_iconv::g_cp_iconv_count; ++dstIdx ) + { + for ( int srcIdx = 0; srcIdx < cp_iconv::g_cp_iconv_count; ++srcIdx ) + { + IConvCache * pNode = static_cast( InterlockedFlushSList(&m_Pool[dstIdx][srcIdx]) ); + while ( NULL != pNode ) + { + IConvCache * pNext = static_cast( pNode->Next ); + delete pNode; + pNode = pNext; + } + } + } + } + + USHORT Depth( int dstIdx, int srcIdx ) + { + assert( 0 <= dstIdx && dstIdx < cp_iconv::g_cp_iconv_count ); + assert( 0 <= srcIdx && srcIdx < cp_iconv::g_cp_iconv_count ); + return QueryDepthSList( &m_Pool[dstIdx][srcIdx] ); + } + + // If this returns NULL, then caller must allocate their own iconv_t. + // It will return NULL if allocation for a new instance failed (out of memory). + const IConvCache * Borrow( int dstIdx, int srcIdx ) + { + assert( 0 <= dstIdx && dstIdx < cp_iconv::g_cp_iconv_count ); + assert( 0 <= srcIdx && srcIdx < cp_iconv::g_cp_iconv_count ); + + const IConvCache * pCache = static_cast( InterlockedPopEntrySList(&m_Pool[dstIdx][srcIdx]) ); + if ( NULL == pCache ) + { + const IConvCache * pNewCache = new IConvCache( dstIdx, srcIdx ); + if ( NULL != pNewCache ) + { + if ( INVALID_ICONV != pNewCache->GetIConv() ) + pCache = pNewCache; + else + delete pNewCache; + } + } + return pCache; + } + + void Return( const IConvCache * pCache, int dstIdx, int srcIdx ) + { + assert( pCache ); + assert( 0 <= dstIdx && dstIdx < cp_iconv::g_cp_iconv_count ); + assert( 0 <= srcIdx && srcIdx < cp_iconv::g_cp_iconv_count ); + + // Setting an arbitrary limit to prevent unbounded memory use by the pool. + // Want this to be large enough for a substantial number of concurrent threads. + const USHORT MAX_POOL_SIZE = 1024; + + if ( INVALID_ICONV != pCache->GetIConv() && Depth(dstIdx, srcIdx) < MAX_POOL_SIZE ) + { + SLIST_ENTRY * pNode = const_cast( pCache ); + InterlockedPushEntrySList( &m_Pool[dstIdx][srcIdx], pNode ); + } + else + { + delete pCache; + } + } + + static IConvCachePool & Singleton() + { + // GCC ensures that function scoped static initializers are threadsafe + // We must not use the -fno-threadsafe-statics compiler option +#if !defined(__GNUC__) || defined(NO_THREADSAFE_STATICS) + #error "Relying on GCC's threadsafe initialization of local statics." +#endif + static IConvCachePool s_Pool; + return s_Pool; + } + +public: + static const IConvCache * BorrowCache( UINT dstCP, UINT srcCP ) + { + int dstIdx = cp_iconv::GetIndex(dstCP); + int srcIdx = cp_iconv::GetIndex(srcCP); + + if ( -1 == dstIdx || -1 == srcIdx ) + return NULL; + else if ( !s_PoolDestroyed ) + return Singleton().Borrow( dstIdx, srcIdx ); + else + return new IConvCache( dstIdx, srcIdx ); + } + + static void ReturnCache( const IConvCache * pCache, UINT dstCP, UINT srcCP ) + { + int dstIdx = cp_iconv::GetIndex(dstCP); + int srcIdx = cp_iconv::GetIndex(srcCP); + + if ( -1 != dstIdx && -1 != srcIdx && !s_PoolDestroyed ) + Singleton().Return( pCache, dstIdx, srcIdx ); + else + delete pCache; + } + + static USHORT Depth( UINT dstCP, UINT srcCP ) + { + if ( IConvCachePool::s_PoolDestroyed ) + return 0; + else + { + int dstIdx = cp_iconv::GetIndex(dstCP); + int srcIdx = cp_iconv::GetIndex(srcCP); + + if ( -1 == dstIdx || -1 == srcIdx ) + return 0; + else + return Singleton().Depth( dstIdx, srcIdx ); + } + } +}; + + +bool IConvCachePool::s_PoolDestroyed = false; + +#ifdef DEBUG +// This is only used by unit tests. +// Product code should directly use IConvCachePool::Depth from +// within this translation unit. +USHORT GetIConvCachePoolDepth( UINT dstCP, UINT srcCP ) +{ + return IConvCachePool::Depth( dstCP, srcCP ); +} +#endif // DEBUG + +IConvCache::IConvCache( int dstIdx, int srcIdx ) + : m_iconv( iconv_open( + cp_iconv::g_cp_iconv[dstIdx].IConvEncoding, + cp_iconv::g_cp_iconv[srcIdx].IConvEncoding) ) +{ +} + +IConvCache::~IConvCache() +{ + if ( INVALID_ICONV != m_iconv ) + iconv_close( m_iconv ); +} + +#endif // MPLAT_UNIX + +EncodingConverter::EncodingConverter( UINT dstCodePage, UINT srcCodePage ) + : m_dstCodePage( dstCodePage ), + m_srcCodePage( srcCodePage ) +#ifdef MPLAT_UNIX + , m_pCvtCache( NULL ) +#endif +{ +} + +EncodingConverter::~EncodingConverter() +{ +#ifdef MPLAT_UNIX + if ( NULL != m_pCvtCache ) + { + IConvCachePool::ReturnCache( m_pCvtCache, m_dstCodePage, m_srcCodePage ); + } +#endif +} + +bool EncodingConverter::Initialize() +{ +#if defined(MPLAT_UNIX) + if ( !IsValidIConv() ) + { + m_pCvtCache = IConvCachePool::BorrowCache( m_dstCodePage, m_srcCodePage ); + } + return IsValidIConv(); +#elif defined(MPLAT_WWOWH) + return true; +#endif +} + +//#endif + +#ifdef MPLAT_UNIX +// MPLAT_UNIX ---------------------------------------------------------------- +#include + +using namespace std; + + + +SystemLocale::SystemLocale( const char * localeName ) + : m_pLocale( new std::locale(localeName) ) +{ +} + +SystemLocale::~SystemLocale() +{ + delete m_pLocale; +} + +const SystemLocale & SystemLocale::Singleton() +{ + // GCC ensures that function scoped static initializers are threadsafe + // We must not use the -fno-threadsafe-statics compiler option +#if !defined(__GNUC__) || defined(NO_THREADSAFE_STATICS) + #error "Relying on GCC's threadsafe initialization of local statics." +#endif + static const SystemLocale s_Default( "en_US.utf8" ); + return s_Default; +} + +int SystemLocale::GetResourcePath( char * buffer, size_t cchBuffer ) const +{ + // XPLAT_ODBC_TODO: VSTS 718708 Localization + // Also need to use AdjustLCID logic when handling more locales + return snprintf( buffer, cchBuffer, "/opt/microsoft/msodbcsql/share/resources/en_US/"); +} + +DWORD SystemLocale::CurrentTimeZoneBias( LONG * offsetInMinutes, DWORD * tzinfo ) const +{ + if ( NULL == offsetInMinutes ) + return ERROR_INVALID_PARAMETER; + + time_t now = time( NULL ); + if ( (time_t)(-1) == now ) + return ERROR_NOT_SUPPORTED; + + struct tm utc, local; + if ( NULL == gmtime_r(&now, &utc) || NULL == localtime_r(&now, &local) ) + return ERROR_INVALID_DATA; + + *offsetInMinutes = BiasInMinutes( utc, local ); + + if ( NULL != tzinfo ) + { + *tzinfo = (0 == local.tm_isdst ? TIME_ZONE_ID_STANDARD : (0 < local.tm_isdst ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_UNKNOWN)); + } + + return ERROR_SUCCESS; +} + +DWORD SystemLocale::CurrentLocalTime( LPSYSTEMTIME pTime ) +{ + if ( NULL == pTime ) + return ERROR_INVALID_PARAMETER; + + memset( pTime, 0, sizeof(SYSTEMTIME) ); + + time_t now = time( NULL ); + if ( (time_t)(-1) == now ) + return ERROR_NOT_SUPPORTED; + + struct tm local; + if ( NULL == localtime_r(&now, &local) ) + return ERROR_INVALID_DATA; + + pTime->wYear = local.tm_year + 1900; + pTime->wMonth = local.tm_mon + 1; + pTime->wDay = local.tm_mday; + pTime->wHour = local.tm_hour; + pTime->wMinute = local.tm_min; + pTime->wSecond = local.tm_sec; + pTime->wMilliseconds = 0; + pTime->wDayOfWeek = local.tm_wday; + + return ERROR_SUCCESS; +} + +size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode ) +{ + srcCodePage = ExpandSpecialCP( srcCodePage ); + EncodingConverter cvt( CP_UTF16, srcCodePage ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert( dest, cchDest, src, cchSrcActual, false, &hasLoss, pErrorCode ); +} + +size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode ) +{ + srcCodePage = ExpandSpecialCP( srcCodePage ); + EncodingConverter cvt( CP_UTF16, srcCodePage ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert( dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode ); +} + +size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode ) +{ + destCodePage = ExpandSpecialCP( destCodePage ); + EncodingConverter cvt( destCodePage, CP_UTF16 ); + if ( !cvt.Initialize() ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1+mplat_wcslen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert( dest, cchDest, src, cchSrcActual, false, &hasLoss, pErrorCode ); +} + +size_t SystemLocale::FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode) +{ + destCodePage = ExpandSpecialCP(destCodePage); + EncodingConverter cvt(destCodePage, CP_UTF16); + if (!cvt.Initialize()) + { + if (NULL != pErrorCode) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + size_t cchSrcActual = (cchSrc < 0 ? (1 + mplat_wcslen(src)) : cchSrc); + bool hasLoss; + return cvt.Convert(dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode); +} + +size_t SystemLocale::ToLower( const char * src, SSIZE_T cchSrc, char * dest, size_t cchDest, DWORD * pErrorCode ) const +{ + size_t cchSrcActual = (cchSrc < 0 ? (1+strlen(src)) : cchSrc); + if ( 0 == cchSrcActual ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return 0; + } + if ( 0 == cchDest ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return cchSrcActual; + } + else if ( cchDest < cchSrcActual ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + memcpy_s( dest, cchSrcActual, src, cchSrcActual ); + + use_facet< ctype< char > >(*m_pLocale).tolower( dest, dest+cchSrcActual ); + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return cchSrcActual; +} + +int SystemLocale::Compare( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + if ( NULL == left || NULL == right || 0 == cchLeft || 0 == cchRight ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return CSTR_ERROR; + } + + size_t cchLeftActual = (cchLeft < 0 ? strlen(left) : cchLeft); + size_t cchRightActual = (cchRight < 0 ? strlen(right) : cchRight); + + int cmp = strncmp( left, right, min(cchLeftActual, cchRightActual) ); + if ( 0 == cmp ) + { + if ( cchLeftActual < cchRightActual ) + cmp = -1; + else if ( cchLeftActual > cchRightActual ) + cmp = 1; + } + else if ( cmp < 0 ) + cmp = 1; // CompareString is inverse of strcmp + else + cmp = -1; // CompareString is inverse of strcmp + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return cmp+2; +} + +int SystemLocale::CompareIgnoreCase( const char * left, SSIZE_T cchLeft, const char * right, SSIZE_T cchRight, DWORD * pErrorCode ) const +{ + if ( NULL == left || NULL == right || 0 == cchLeft || 0 == cchRight ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + return CSTR_ERROR; + } + + size_t cchLeftActual = (cchLeft < 0 ? strlen(left) : cchLeft); + size_t cchRightActual = (cchRight < 0 ? strlen(right) : cchRight); + + int cmp = strncasecmp( left, right, min(cchLeftActual, cchRightActual) ); + if ( 0 == cmp ) + { + if ( cchLeftActual < cchRightActual ) + cmp = -1; + else if ( cchLeftActual > cchRightActual ) + cmp = 1; + } + else if ( cmp < 0 ) + cmp = 1; // CompareString is inverse of strcmp + else + cmp = -1; // CompareString is inverse of strcmp + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return cmp+2; +} + +char * SystemLocale::NextChar( UINT codepage, const char * start, size_t cchBytesLeft ) +{ + if ( NULL == start || '\0' == *start || 0 == cchBytesLeft ) + return const_cast( start ); + + char first = *start; + codepage = ExpandSpecialCP( codepage ); + if ( CP_UTF8 != codepage ) + { + if ( !IsDBCSLeadByteEx(codepage, first) || '\0' == *(start+1) ) + return const_cast( start+1 ); // single byte char or truncated double byte char + else + return const_cast( start+2 ); // double byte char + } + + // CP_UTF8 + // MB utf8 sequences have this format + // Lead byte starts with 2 set bits, '11' + // Rest of bytes start with one set and one not, '10' + + // ASCII or not first of utf8 sequence + // If this isn't the first byte of a utf8 sequence, just move one byte at a time + // since we don't know where the correct boundary is located. + if ( (char)0 == (first & (char)0x80) || !SystemLocale::IsUtf8LeadByte((BYTE)first) ) + return const_cast( start+1 ); + else + { + // Initial char tells us how many bytes are supposed to be in this sequence + UINT cchExpectedSize = SystemLocale::CchUtf8CodePt( (BYTE)first ); + + // Skip lead bye + ++start; + --cchExpectedSize; + --cchBytesLeft; + + // Proceed to end of utf8 sequence, null term, or end of expected size + while ( 0 < cchExpectedSize && 0 < cchBytesLeft && (char)0x80 == (*start & (char)0xC0) ) + { + ++start; + --cchExpectedSize; + --cchBytesLeft; + } + return const_cast( start ); + } +} + +char * SystemLocale::NextChar( UINT codepage, const char * start ) +{ + // Just assume some large max buffer size since caller is saying + // start is null terminated. + return NextChar( codepage, start, DWORD_MAX ); +} + +// MPLAT_UNIX ---------------------------------------------------------------- +#else +// !MPLAT_UNIX ---------------------------------------------------------------- + +//----------------------------------------------------------------------------------- +// IsW2CZeroFlagCodePage +// +// @func Does this code page need special handling for WideCharToMultiByte or +// MultiByteToWideChar to avoid error code as ERROR_INVALID_PARAMETER to be returned +// +// @rdesc bool +// @flag TRUE | needs special handling +// @flag FALSE | doesn't need special handling +//----------------------------------------------------------------------------------- + +#define IsW2CZeroFlagCodePage(codePage) (((codePage) < 50220) ? FALSE : _IsW2CZeroFlagCodePage(codePage)) + +inline BOOL _IsW2CZeroFlagCodePage +( + UINT CodePage +) +{ + assert(CodePage >= 50220); + + // According to MSDN, these code pages need special handling + // during WideCharToMultiByte call w/r its parameter flags + if (CodePage == 50220 || + CodePage == 50221 || + CodePage == 50222 || + CodePage == 50225 || + CodePage == 50227 || + CodePage == 50229 || + CodePage == 52936 || + CodePage == 54936 || + CodePage == 65000 || + CodePage == 65001 || + CodePage >= 57002 && CodePage <= 57011) + { + return TRUE; + } + + return FALSE; +} + +//------------------------------------------------------------------- +// Custom version of MultiByteToWideChar (faster for all ASCII strings) +// Convert ASCII data (0x00-0x7f) until first non-ASCII data, +// calling OS MultiByteToWideChar in that case. +// +size_t SystemLocale::FastAsciiMultiByteToWideChar( + UINT CodePage, + __in_ecount(cch) const char *pch, // IN | source string + SSIZE_T cch, // IN | count of characters or -1 + __out_ecount_opt(cwch) PWCHAR pwch, // IN | Result string + size_t cwch, // IN | counter of wcharacters of result buffer or 0 + DWORD* pErrorCode, // OUT | optional pointer to return error code + bool bStrict // IN | Return error if invalid chars in src +) +{ + if ( 0 == cch || cch < -1 || NULL == pch ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + + const char *pchStart = pch; + + // Divide into + // Case 1a: cch, do convert + // Case 1b: cch, just count + // Case 2a: null-term, do convert + // Case 2b: null-term, just count + if (-1 != cch) + { + // 0 <= cch + // + // Case 1: We have counter of characters + if (0 != cch) + { + if (0 != cwch) + { + // Case 1a: Have to convert, not just calculate necessary space + + // Optimization: When converting first cwch characters, it's not + // necessary to check for buffer overflow. Also, loop is unrolled. + size_t cquads = min((size_t)cch, cwch) >> 2; + + while (0 != cquads) + { + unsigned quad = *(unsigned UNALIGNED *)pch; + + if (quad & 0x80808080) + goto general; + + OACR_WARNING_SUPPRESS ( INCORRECT_VALIDATION, "Due to performance, we suppress this PREFast warning" ); + *(unsigned UNALIGNED *)pwch = (quad & 0x7F) | ((quad & 0x7F00) << 8); + + quad >>= 16; + + OACR_WARNING_SUPPRESS ( POTENTIAL_BUFFER_OVERFLOW_HIGH_PRIORITY, "PREFast incorrectly warns of buffer overrun for cwch < 4, which won't enter this loop." ); + *(unsigned UNALIGNED *)(pwch+2) = (quad & 0x7F) | ((quad & 0x7F00) << 8); + + pch += 4; + pwch += 4; + cch -= 4; + cquads --; + } + + // Convert end of string - slower, but the loop will be executed 3 times max + if (0 != cch) + { + const char *pchEnd = pchStart + cwch; + + do + { + unsigned ch = (unsigned)*pch; + + if (pch == pchEnd) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; // Not enough space + } + + if (ch > 0x7F) + goto general; + + *(pwch++) = (WCHAR)ch; + + pch++; + cch--; + } while (0 != cch); + + } + } + else + { + // Case 1b: Have to calculate necessary space only + if (SystemLocale::MaxCharCchSize(CodePage) == 1) // SBCS code pages 1char = 1 unc char + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(cch); + } + + do + { + if ((unsigned)*pch > 0x7F) + goto general; + + pch++; + } while (0 != --cch); + } + } + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pch - pchStart); + } + else + { + // Case 2: zero-terminated string + if (0 != cwch) + { + // Case 2a: Have to convert, not just calculate necessary space + const char *pchEnd = pch + cwch; + + do + { + unsigned ch = (unsigned)*pch; + + if (ch > 0x7F) + goto general; + else + { + *pwch = (WCHAR)ch; + pch ++; + if (0 == ch) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pch - pchStart); + } + pwch ++; + } + } while (pch != pchEnd); + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; // Not enough space + } + else + { + // Case 2b: Have to calculate necessary space + unsigned ch; + + do + { + ch = (unsigned)*pch; + + if (ch > 0x7F) + goto general; + pch ++; + } while (0 != ch); + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pch - pchStart); + } + } + + // Have to call Win32 API +general: + { + size_t cwchConverted; + size_t cwchUnicode; + + cwchConverted = (pch - pchStart); + + if ( cwch > cwchConverted ) + cwch -= cwchConverted; + else + cwch = 0; + + // Windows MBtoWC takes int inputs + if ( INT32_MAX < cch || INT32_MAX < cwch ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + + cwchUnicode = (UINT)MultiByteToWideChar( + CodePage, + (IsW2CZeroFlagCodePage(CodePage) ? 0 : MB_PRECOMPOSED) + | (bStrict ? MB_ERR_INVALID_CHARS : 0), + pch, + (int)cch, + pwch, + (int)cwch); + + if ( 0 == cwchUnicode ) + { + if ( NULL != pErrorCode ) + *pErrorCode = GetLastError(); + return 0; + } + else + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return (cwchConverted + cwchUnicode); + } + } +} + +//------------------------------------------------------------------- +// Custom version of WideCharToMultiByte (faster for all ASCII strings) +// Convert ASCII data (0x00-0x7f) until first non-ASCII data, +// calling OS WideCharToMultiByte in that case. +size_t SystemLocale::FastAsciiWideCharToMultiByte +( + UINT CodePage, + const WCHAR *pwch, // IN | source string + SSIZE_T cwch, // IN | count of characters or -1 + __out_ecount(cch) char *pch, // IN | Result string + size_t cch, // IN | Length of result buffer or 0 + BOOL *pfDataLoss, // IN | True if there was data loss during CP conversion + DWORD *pErrorCode +) +{ + if ( 0 == cwch || NULL == pwch || cwch < -1 ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + + const WCHAR *pwchStart = pwch; + const char *pchStart = pch; + + // Divide into + // Case 1a: cwch, do convert + // Case 1b: cwch, just count + // Case 2a: null-term, do convert + // Case 2b: null-term, just count + if (-1 != cwch) + { + // Case 1: We have counter of characters + if (0 != cwch) + { + if (0 != cch) + { + // Case 1a: Have to convert, not just calculate necessary space + + // Optimization: When converting first cch characters, it's not + // necessary to check for buffer overflow. Also, loop is unrolled. + size_t cquads = cch >> 2; + + while (0 != cquads && 4 <= cwch) + { + unsigned pairLo = *(unsigned UNALIGNED *)pwch; + unsigned pairHi = *(unsigned UNALIGNED *)(pwch+2); + + if ((pairLo | pairHi) & 0xFF80FF80) + goto general; + + *(unsigned UNALIGNED *)pch = (pairLo & 0x7F) | + ((pairLo >> 8) & 0x7F00) | + ((pairHi & 0x7F) << 16) | + ((pairHi & 0x7F0000) << 8); + pch += 4; + pwch += 4; + cwch -= 4; + cquads --; + } + // Convert end of string - slower, but the loop will be executed 3 times max + if (0 != cwch) + { + const char *pchEnd = pchStart + cch; + + do + { + unsigned wch = (unsigned)*pwch; + + if (pch == pchEnd) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; // Not enough space + } + + if ((unsigned)*pwch > 0x7F) + goto general; + + *(pch ++) = (char) wch; + pwch ++; + cwch --; + } while (0 != cwch); + } + } + else + { + // Case 1b: Have to calculate necessary space + do + { + if ((unsigned)*pwch > 0x7F) + goto general; + + pwch ++; + cwch --; + } while (0 != cwch); + } + } + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pwch - pwchStart); + } + else + { + // Case 2: zero-terminated string + if (0 != cch) + { + // Case 2a: Have to convert, not just calculate necessary space + const char *pchEnd = pch + cch; + + do + { + unsigned wch = (unsigned)*pwch; + + if (wch > 0x7F) + goto general; + else + { + *pch = (char) wch; + pwch ++; + if (0 == wch) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pwch - pwchStart); + } + pch ++; + } + } while (pch != pchEnd); + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; // Not enough space + } + else + { + // Case 2b: Have to calculate necessary space + unsigned wch; + + do + { + wch = (unsigned)*pwch; + if (wch > 0x7F) + goto general; + pwch ++; + } while (0 != wch); + + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return static_cast(pwch - pwchStart); + } + } + + // Have to call Win32 API +general: + { + size_t cchConverted; + size_t cchUnicode; + + // initialize output param if any + if (pfDataLoss) + *pfDataLoss = FALSE; + + cchConverted = (pwch - pwchStart); + + if ( cch > cchConverted ) + cch -= cchConverted; + else + cch = 0; + + // Windows MBtoWC takes int inputs + if ( INT32_MAX < cch || INT32_MAX < cwch ) + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_INVALID_PARAMETER; + + return 0; + } + + cchUnicode = (UINT)WideCharToMultiByte ( + CodePage, + 0, + pwch, + (int)cwch, + pch, + (int)cch, + NULL, + IsW2CZeroFlagCodePage(CodePage) ? NULL : pfDataLoss); + + if ( 0 == cchUnicode ) + { + if ( NULL != pErrorCode ) + *pErrorCode = GetLastError(); + return 0; + } + else + { + if ( NULL != pErrorCode ) + *pErrorCode = ERROR_SUCCESS; + return (cchConverted + cchUnicode); + } + } +} + +// !MPLAT_UNIX ---------------------------------------------------------------- +#endif + diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h new file mode 100644 index 000000000..278a86175 --- /dev/null +++ b/source/shared/msodbcsql.h @@ -0,0 +1,426 @@ +#ifndef __msodbcsql_h__ +#define __msodbcsql_h__ + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: msodbcsql.h +// +// Contents: Routines that use statement handles +// +// Contents: This SDK is not supported under any Microsoft standard support +// program or service. The information is provided AS IS without +// warranty of any kind. Microsoft disclaims all implied +// warranties including, without limitation, any implied +// warranties of merchantability or of fitness for a particular +// purpose. The entire risk arising out of the use of this SDK +// remains with you. In no event shall Microsoft, its authors, or +// anyone else involved in the creation, production, or delivery +// of this SDK be liable for any damages whatsoever (including, +// without limitation, damages for loss of business profits, +// business interruption, loss of business information, or other +// pecuniary loss) arising out of the use of or inability to use +// this SDK, even if Microsoft has been advised of the possibility +// of such damages. +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + + +#if !defined(SQLODBC_VER) +#define SQLODBC_VER 1100 +#endif + +#if SQLODBC_VER >= 1300 + +#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 13 for SQL Server" +#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server" +#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 13 for SQL Server" +#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server" + +#endif /* SQLODBC_VER >= 1300 */ + +#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI +#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI +#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI +#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI + +#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER + +/* max SQL Server identifier length */ +#define SQL_MAX_SQLSERVERNAME 128 + +/* + * SQLSetConnectAttr driver specific defines. + * Microsoft has 1200 thru 1249 reserved for Microsoft ODBC Driver for SQL Server usage. + * Connection attributes + */ +#define SQL_COPT_SS_BASE 1200 +#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) /* Force integrated security on login */ +#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) /* Perform code page translation */ +#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) /* Allow strong encryption for data */ +#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) /* Multiple active result set per connection */ +#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) /* Used to set/get any driver-specific or ODBC-defined TXN iso level */ +#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) /* Trust server certificate */ +/* + * SQLSetStmtAttr Microsoft ODBC Driver for SQL Server specific defines. + * Statement attributes + */ +#define SQL_SOPT_SS_BASE 1225 +#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) /* Text pointer logging */ +#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) /* Set NOBROWSETABLE option */ +#define SQL_SOPT_SS_COLUMN_ENCRYPTION (SQL_SOPT_SS_BASE+13)/* Sets the column encryption mode */ +/* Define old names */ +#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING +#define SQL_COPT_SS_BASE_EX 1240 +#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) /* Issues warning when data from the server had a loss during code page conversion. */ +#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) /* dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check */ +#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) /* Application Intent */ +#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) /* Multi-subnet Failover */ +#define SQL_COPT_SS_TNIR (SQL_COPT_SS_BASE_EX+9) /* Transparent Network IP Resolution */ +#define SQL_COPT_SS_COLUMN_ENCRYPTION (SQL_COPT_SS_BASE_EX+10) /* Always Encrypted Enabled or Disabled */ +#define SQL_COPT_SS_AEKEYSTOREPROVIDER (SQL_COPT_SS_BASE_EX+11) /* Load a keystore provider or read the list of loaded keystore providers */ +#define SQL_COPT_SS_AEKEYSTOREDATA (SQL_COPT_SS_BASE_EX+12) /* Communicate with a loaded keystore provider */ +#define SQL_COPT_SS_AETRUSTEDCMKPATHS (SQL_COPT_SS_BASE_EX+13) /* List of trusted CMK paths */ +#define SQL_COPT_SS_AECEKCACHETTL (SQL_COPT_SS_BASE_EX+14)// Symmetric Key Cache TTL +/* + * SQLColAttributes driver specific defines. + * SQLSetDescField/SQLGetDescField driver specific defines. + * Microsoft has 1200 thru 1249 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_CA_SS_BASE 1200 +#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) /* dbcoltype/dbalttype */ +#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) /* dbcolutype/dbaltutype */ +#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) /* dbnumorders */ +#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) /* dbordercol */ +#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) /* dbvarylen */ +#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) /* dbnumcompute */ +#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) /* dbnextrow status return */ +#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) /* dbbylist */ +#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) /* dbaltcolid */ +#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) /* dbaltop */ +#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) /* dbcollen */ +#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) /* Column is hidden (FOR BROWSE) */ +#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) /* Column is key column (FOR BROWSE) */ +#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) /* Column collation (only for chars) */ +#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15) +#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16) +#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17) + +/* XML, CLR UDT, and table valued parameter related metadata */ +#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) /* UDT catalog name */ +#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) /* UDT schema name */ +#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) /* UDT type name */ +#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) /* Name of the catalog that contains XML Schema collection */ +#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) /* Name of the schema that contains XML Schema collection */ +#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) /* Name of the XML Schema collection */ +#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) /* Catalog name */ +#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) /* Schema name */ +#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) /* Type name */ + +/* table valued parameter related metadata */ +#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) /* column is computed */ +#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) /* column is part of a unique key */ +#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) /* column sort order */ +#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) /* column sort ordinal */ +#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) /* column has default value for all rows of the table valued parameter */ + +/* sparse column related metadata */ +#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) /* column is a column-set column for sparse columns */ + +/* Legacy datetime related metadata */ +#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) /* column type to send on the wire for datetime types */ + +/* force column encryption */ +#define SQL_CA_SS_FORCE_ENCRYPT (SQL_CA_SS_BASE+36) /* indicate mandatory encryption for this parameter */ + +/* Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only */ +#define SQL_IS_OFF 0L /* Integrated security isn't used */ +#define SQL_IS_ON 1L /* Integrated security is used */ +#define SQL_IS_DEFAULT SQL_IS_OFF +/* Defines for use with SQL_COPT_SS_TRANSLATE */ +#define SQL_XL_OFF 0L /* Code page translation is not performed */ +#define SQL_XL_ON 1L /* Code page translation is performed */ +#define SQL_XL_DEFAULT SQL_XL_ON +/* Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING */ +#define SQL_TL_OFF 0L /* No logging on text pointer ops */ +#define SQL_TL_ON 1L /* Logging occurs on text pointer ops */ +#define SQL_TL_DEFAULT SQL_TL_ON +/* Defines for use with SQL_SOPT_SS_NOBROWSETABLE */ +#define SQL_NB_OFF 0L /* NO_BROWSETABLE is off */ +#define SQL_NB_ON 1L /* NO_BROWSETABLE is on */ +#define SQL_NB_DEFAULT SQL_NB_OFF +/* Defines for use with SQL_SOPT_SS_COLUMN_ENCRYPTION */ +#define SQL_CE_DISABLED 0L /* Disabled */ +#define SQL_CE_RESULTSETONLY 1L /* Decryption Only (resultsets and return values) */ +#define SQL_CE_ENABLED 3L /* Enabled (both encryption and decryption) */ + +/* SQL_COPT_SS_ENCRYPT */ +#define SQL_EN_OFF 0L +#define SQL_EN_ON 1L +/* SQL_COPT_SS_TRUST_SERVER_CERTIFICATE */ +#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L +#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L +/* SQL_COPT_SS_WARN_ON_CP_ERROR */ +#define SQL_WARN_NO 0L +#define SQL_WARN_YES 1L +/* SQL_COPT_SS_MARS_ENABLED */ +#define SQL_MARS_ENABLED_NO 0L +#define SQL_MARS_ENABLED_YES 1L +/* SQL_TXN_ISOLATION_OPTION bitmasks */ +#define SQL_TXN_SS_SNAPSHOT 0x00000020L +/* SQL_COPT_SS_COLUMN_ENCRYPTION */ +#define SQL_COLUMN_ENCRYPTION_DISABLE 0L +#define SQL_COLUMN_ENCRYPTION_ENABLE 1L +#define SQL_COLUMN_ENCRYPTION_DEFAULT SQL_COLUMN_ENCRYPTION_DISABLE +// Defines for use with SQL_COPT_SS_AECEKCACHETTL +#define SQL_AECEKCACHETTL_DEFAULT 7200L // TTL value in seconds (2 hours) + +/* The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER */ +#define SQL_SS_ORDER_UNSPECIFIED 0L +#define SQL_SS_DESCENDING_ORDER 1L +#define SQL_SS_ASCENDING_ORDER 2L +#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED + +/* + * Driver specific SQL data type defines. + * Microsoft has -150 thru -199 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_SS_VARIANT (-150) +#define SQL_SS_UDT (-151) +#define SQL_SS_XML (-152) +#define SQL_SS_TABLE (-153) +#define SQL_SS_TIME2 (-154) +#define SQL_SS_TIMESTAMPOFFSET (-155) + +/* Local types to be used with SQL_CA_SS_SERVER_TYPE */ +#define SQL_SS_TYPE_DEFAULT 0L +#define SQL_SS_TYPE_SMALLDATETIME 1L +#define SQL_SS_TYPE_DATETIME 2L + +/* Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager. */ +#define SQL_C_TYPES_EXTENDED 0x04000L + +/* + * SQL_SS_LENGTH_UNLIMITED is used to describe the max length of + * VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns + */ +#define SQL_SS_LENGTH_UNLIMITED 0 + +/* + * User Data Type definitions. + * Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE. + */ +#define SQLudtBINARY 3 +#define SQLudtBIT 16 +#define SQLudtBITN 0 +#define SQLudtCHAR 1 +#define SQLudtDATETIM4 22 +#define SQLudtDATETIME 12 +#define SQLudtDATETIMN 15 +#define SQLudtDECML 24 +#define SQLudtDECMLN 26 +#define SQLudtFLT4 23 +#define SQLudtFLT8 8 +#define SQLudtFLTN 14 +#define SQLudtIMAGE 20 +#define SQLudtINT1 5 +#define SQLudtINT2 6 +#define SQLudtINT4 7 +#define SQLudtINTN 13 +#define SQLudtMONEY 11 +#define SQLudtMONEY4 21 +#define SQLudtMONEYN 17 +#define SQLudtNUM 10 +#define SQLudtNUMN 25 +#define SQLudtSYSNAME 18 +#define SQLudtTEXT 19 +#define SQLudtTIMESTAMP 80 +#define SQLudtUNIQUEIDENTIFIER 0 +#define SQLudtVARBINARY 4 +#define SQLudtVARCHAR 2 +#define MIN_USER_DATATYPE 256 +/* + * Aggregate operator types. + * Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP. + */ +#define SQLAOPSTDEV 0x30 /* Standard deviation */ +#define SQLAOPSTDEVP 0x31 /* Standard deviation population */ +#define SQLAOPVAR 0x32 /* Variance */ +#define SQLAOPVARP 0x33 /* Variance population */ +#define SQLAOPCNT 0x4b /* Count */ +#define SQLAOPSUM 0x4d /* Sum */ +#define SQLAOPAVG 0x4f /* Average */ +#define SQLAOPMIN 0x51 /* Min */ +#define SQLAOPMAX 0x52 /* Max */ +#define SQLAOPANY 0x53 /* Any */ +#define SQLAOPNOOP 0x56 /* None */ +/* + * SQLGetDiagField driver specific defines. + * Microsoft has -1150 thru -1199 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_DIAG_SS_BASE (-1150) +#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE) +#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1) +#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2) +#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3) +#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4) +/* + * SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines. + * Microsoft has -200 thru -299 reserved for Microsoft ODBC Driver for SQL Server usage. + */ +#define SQL_DIAG_DFC_SS_BASE (-200) +#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0) +#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1) +#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2) +#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3) +#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4) +#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5) +#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6) +#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7) +#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8) +#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9) +#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10) +#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11) +#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12) +#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13) +#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14) +#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15) +#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16) +#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17) +#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18) +#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19) +#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) +#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) +#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21) +#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) +#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) +#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23) +#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24) +#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25) +#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26) +#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26) +#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) +#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) +#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28) +#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) +#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) +#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30) +#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31) +#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32) +#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33) +#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34) +#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35) +#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36) +#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37) +#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38) +#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39) +#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40) +#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41) +#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42) +#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43) +#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44) +#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45) +#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46) +#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47) +#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48) +#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49) +#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50) +#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51) +#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52) +#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53) +#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54) +#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55) +#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56) + +/* Severity codes for SQL_DIAG_SS_SEVERITY */ +#define EX_ANY 0 +#define EX_INFO 10 +#define EX_MAXISEVERITY EX_INFO +#define EX_MISSING 11 +#define EX_TYPE 12 +#define EX_DEADLOCK 13 +#define EX_PERMIT 14 +#define EX_SYNTAX 15 +#define EX_USER 16 +#define EX_RESOURCE 17 +#define EX_INTOK 18 +#define MAXUSEVERITY EX_INTOK +#define EX_LIMIT 19 +#define EX_CMDFATAL 20 +#define MINFATALERR EX_CMDFATAL +#define EX_DBFATAL 21 +#define EX_TABCORRUPT 22 +#define EX_DBCORRUPT 23 +#define EX_HARDWARE 24 +#define EX_CONTROL 25 + +/* Keystore Provider interface definition */ + +typedef void errFunc(void *ctx, const wchar_t *msg, ...); + +#define IDS_MSG(x) ((const wchar_t*)(x)) + +typedef struct AEKeystoreProvider +{ + wchar_t *Name; + int (*Init)(void *ctx, errFunc *onError); + int (*Read)(void *ctx, errFunc *onError, void *data, unsigned int *len); + int (*Write)(void *ctx, errFunc *onError, void *data, unsigned int len); + int (*DecryptCEK)( + void *ctx, + errFunc *onError, + const wchar_t *keyPath, + const wchar_t *alg, + unsigned char *ecek, + unsigned short ecek_len, + unsigned char **cek_out, + unsigned short *cek_len); + void (*Free)(); +} AEKEYSTOREPROVIDER; + +/* Data is defined to be past the end of the structure header. + This is accepted by MSVC, GCC, and C99 standard but former emits + unnecessary warning, hence it has to be disabled. +*/ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4200) +#endif + +typedef struct AEKeystoreData +{ + wchar_t *Name; + unsigned int dataSize; + char Data[]; +} AEKEYSTOREPROVIDERDATA; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +/* The following constants are for the Azure Key Vault configuration interface */ +#define AKV_CONFIG_FLAGS 0 + #define AKVCFG_USECLIENTID 0x00000001 + #define AKVCFG_AUTORENEW 0x00000002 + +#define AKV_CONFIG_CLIENTID 1 +#define AKV_CONFIG_CLIENTKEY 2 + +#define AKV_CONFIG_ACCESSTOKEN 3 +#define AKV_CONFIG_TOKENEXPIRY 4 + +#define AKV_CONFIG_MAXRETRIES 5 +#define AKV_CONFIG_RETRYTIMEOUT 6 +#define AKV_CONFIG_RETRYWAIT 7 + +#endif /* __msodbcsql_h__ */ + diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h new file mode 100644 index 000000000..b51b7f2a0 --- /dev/null +++ b/source/shared/sal_def.h @@ -0,0 +1,796 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: sal_def.h +// +// Contents: Contains the minimal definitions to build on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef XPLAT_SAL_DEFINED +#define XPLAT_SAL_DEFINED + +#define __allocator +#define __analysis_assert(e) +#define __analysis_assume(e) +#define __bcount(size) +#define __bcount_opt(size) +#define __blocksOn(resource) +#define __bound +#define __callback +#define __checkReturn +#define __control_entrypoint(category) +#define __data_entrypoint(category) +#define __deref +#define __deref_bcount(size) +#define __deref_bcount_opt(size) +#define __deref_ecount(size) +#define __deref_ecount_opt(size) +#define __deref_in +#define __deref_in +#define __deref_in_bcount(size) +#define __deref_in_bcount_opt(size) +#define __deref_in_ecount(size) +#define __deref_in_ecount_opt(size) +#define __deref_in_opt +#define __deref_in_range(lb,ub) +#define __deref_in_xcount(size) +#define __deref_in_xcount_opt(size) +#define __deref_inout +#define __deref_inout_bcount(size) +#define __deref_inout_bcount_full(size) +#define __deref_inout_bcount_full_opt(size) +#define __deref_inout_bcount_nz(size) +#define __deref_inout_bcount_nz_opt(size) +#define __deref_inout_bcount_opt(size) +#define __deref_inout_bcount_part(size,length) +#define __deref_inout_bcount_part_opt(size,length) +#define __deref_inout_bcount_z(size) +#define __deref_inout_bcount_z_opt(size) +#define __deref_inout_ecount(size) +#define __deref_inout_ecount_full(size) +#define __deref_inout_ecount_full_opt(size) +#define __deref_inout_ecount_nz(size) +#define __deref_inout_ecount_nz_opt(size) +#define __deref_inout_ecount_opt(size) +#define __deref_inout_ecount_part(size,length) +#define __deref_inout_ecount_part_opt(size,length) +#define __deref_inout_ecount_z(size) +#define __deref_inout_ecount_z_opt(size) +#define __deref_inout_nz +#define __deref_inout_nz_opt +#define __deref_inout_opt +#define __deref_inout_xcount(size) +#define __deref_inout_xcount_full(size) +#define __deref_inout_xcount_full_opt(size) +#define __deref_inout_xcount_opt(size) +#define __deref_inout_xcount_part(size,length) +#define __deref_inout_xcount_part_opt(size,length) +#define __deref_inout_z +#define __deref_inout_z_opt +#define __deref_opt_bcount(size) +#define __deref_opt_bcount_opt(size) +#define __deref_opt_ecount(size) +#define __deref_opt_ecount_opt(size) +#define __deref_opt_in +#define __deref_opt_in_bcount(size) +#define __deref_opt_in_bcount_opt(size) +#define __deref_opt_in_ecount(size) +#define __deref_opt_in_ecount_opt(size) +#define __deref_opt_in_opt +#define __deref_opt_in_xcount(size) +#define __deref_opt_in_xcount_opt(size) +#define __deref_opt_inout +#define __deref_opt_inout_bcount(size) +#define __deref_opt_inout_bcount_full(size) +#define __deref_opt_inout_bcount_full_opt(size) +#define __deref_opt_inout_bcount_nz(size) +#define __deref_opt_inout_bcount_nz_opt(size) +#define __deref_opt_inout_bcount_opt(size) +#define __deref_opt_inout_bcount_part(size,length) +#define __deref_opt_inout_bcount_part_opt(size,length) +#define __deref_opt_inout_bcount_z(size) +#define __deref_opt_inout_bcount_z_opt(size) +#define __deref_opt_inout_ecount(size) +#define __deref_opt_inout_ecount_full(size) +#define __deref_opt_inout_ecount_full_opt(size) +#define __deref_opt_inout_ecount_nz(size) +#define __deref_opt_inout_ecount_nz_opt(size) +#define __deref_opt_inout_ecount_opt(size) +#define __deref_opt_inout_ecount_part(size,length) +#define __deref_opt_inout_ecount_part_opt(size,length) +#define __deref_opt_inout_ecount_z(size) +#define __deref_opt_inout_ecount_z_opt(size) +#define __deref_opt_inout_nz +#define __deref_opt_inout_nz_opt +#define __deref_opt_inout_opt +#define __deref_opt_inout_xcount(size) +#define __deref_opt_inout_xcount_full(size) +#define __deref_opt_inout_xcount_full_opt(size) +#define __deref_opt_inout_xcount_opt(size) +#define __deref_opt_inout_xcount_part(size,length) +#define __deref_opt_inout_xcount_part_opt(size,length) +#define __deref_opt_inout_z +#define __deref_opt_inout_z_opt +#define __deref_opt_out +#define __deref_opt_out_bcount(size) +#define __deref_opt_out_bcount_full(size) +#define __deref_opt_out_bcount_full_opt(size) +#define __deref_opt_out_bcount_nz_opt(size) +#define __deref_opt_out_bcount_opt(size) +#define __deref_opt_out_bcount_part(size,length) +#define __deref_opt_out_bcount_part_opt(size,length) +#define __deref_opt_out_bcount_z_opt(size) +#define __deref_opt_out_ecount(size) +#define __deref_opt_out_ecount_full(size) +#define __deref_opt_out_ecount_full_opt(size) +#define __deref_opt_out_ecount_nz_opt(size) +#define __deref_opt_out_ecount_opt(size) +#define __deref_opt_out_ecount_part(size,length) +#define __deref_opt_out_ecount_part_opt(size,length) +#define __deref_opt_out_ecount_z_opt(size) +#define __deref_opt_out_nz_opt +#define __deref_opt_out_opt +#define __deref_opt_out_xcount(size) +#define __deref_opt_out_xcount_full(size) +#define __deref_opt_out_xcount_full_opt(size) +#define __deref_opt_out_xcount_opt(size) +#define __deref_opt_out_xcount_part(size,length) +#define __deref_opt_out_xcount_part_opt(size,length) +#define __deref_opt_out_z +#define __deref_opt_out_z_opt +#define __deref_opt_xcount(size) +#define __deref_opt_xcount_opt(size) +#define __deref_out +#define __deref_out_bcount(size) +#define __deref_out_bcount_full(size) +#define __deref_out_bcount_full_opt(size) +#define __deref_out_bcount_nz(size) +#define __deref_out_bcount_nz_opt(size) +#define __deref_out_bcount_opt(size) +#define __deref_out_bcount_part(size,length) +#define __deref_out_bcount_part_opt(size,length) +#define __deref_out_bcount_z(size) +#define __deref_out_bcount_z_opt(size) +#define __deref_out_bound +#define __deref_out_ecount(size) +#define __deref_out_ecount_full(size) +#define __deref_out_ecount_full_opt(size) +#define __deref_out_ecount_nz(size) +#define __deref_out_ecount_nz_opt(size) +#define __deref_out_ecount_opt(size) +#define __deref_out_ecount_part(size,length) +#define __deref_out_ecount_part_opt(size,length) +#define __deref_out_ecount_z(size) +#define __deref_out_ecount_z_opt(size) +#define __deref_out_nz +#define __deref_out_nz_opt +#define __deref_out_opt +#define __deref_out_range(lb,ub) +#define __deref_out_xcount(size) +#define __deref_out_xcount_full(size) +#define __deref_out_xcount_full_opt(size) +#define __deref_out_xcount_opt(size) +#define __deref_out_xcount_part(size,length) +#define __deref_out_xcount_part_opt(size,length) +#define __deref_out_z +#define __deref_out_z_opt +#define __deref_xcount(size) +#define __deref_xcount_opt(size) +#define __ecount(size) +#define __ecount_opt(size) +#define __fallthrough +#define __field_bcount(size) +#define __field_bcount_full(size) +#define __field_bcount_full_opt(size) +#define __field_bcount_opt(size) +#define __field_bcount_part(size,init) +#define __field_bcount_part_opt(size,init) +#define __field_data_source(src_sym) +#define __field_ecount(size) +#define __field_ecount_full(size) +#define __field_ecount_full_opt(size) +#define __field_ecount_opt(size) +#define __field_ecount_part(size,init) +#define __field_ecount_part_opt(size,init) +#define __field_range(lb,ub) +#define __field_xcount(size) +#define __field_xcount_full(size) +#define __field_xcount_full_opt(size) +#define __field_xcount_opt(size) +#define __field_xcount_part(size,init) +#define __field_xcount_part_opt(size,init) +#define __format_string +#define __inn +#define __in_bcount(size) +#define __in_bcount_nz(size) +#define __in_bcount_nz_opt(size) +#define __in_bcount_opt(size) +#define __in_bcount_z(size) +#define __in_bcount_z_opt(size) +#define __in_bound +#define __in_ecount(size) +#define __in_ecount_nz(size) +#define __in_ecount_nz_opt(size) +#define __in_ecount_opt(size) +#define __in_ecount_z(size) +#define __in_ecount_z_opt(size) +#define __in_nz +#define __in_nz_opt +#define __in_opt +#define __in_range(lb,ub) +#define __in_xcount(size) +#define __in_xcount_opt(size) +#define __in_z +#define __in_z_opt +#define __inexpressible_readableTo(size) +#define __inexpressible_writableTo(size) +#define __inner_allocator +#define __inner_assume_bound(i) +#define __inner_assume_bound_dec +#define __inner_bound +#define __inner_range(lb,ub) +#define __inout +#define __inout_bcount(size) +#define __inout_bcount_full(size) +#define __inout_bcount_full_opt(size) +#define __inout_bcount_nz(size) +#define __inout_bcount_nz_opt(size) +#define __inout_bcount_opt(size) +#define __inout_bcount_part(size,length) +#define __inout_bcount_part_opt(size,length) +#define __inout_bcount_z(size) +#define __inout_bcount_z_opt(size) +#define __inout_ecount(size) +#define __inout_ecount_full(size) +#define __inout_ecount_full_opt(size) +#define __inout_ecount_nz(size) +#define __inout_ecount_nz_opt(size) +#define __inout_ecount_opt(size) +#define __inout_ecount_part(size,length) +#define __inout_ecount_part_opt(size,length) +#define __inout_ecount_z(size) +#define __inout_ecount_z_opt(size) +#define __inout_nz +#define __inout_nz_opt +#define __inout_opt +#define __inout_xcount(size) +#define __inout_xcount_full(size) +#define __inout_xcount_full_opt(size) +#define __inout_xcount_opt(size) +#define __inout_xcount_opt(size) +#define __inout_xcount_part(size,length) +#define __inout_xcount_part_opt(size,length) +#define __inout_z +#define __inout_z_opt +#define __nullnullterminated +#define __nullterminated +#define __outt +#define __out_bcount(size) +#define __out_bcount_full(size) +#define __out_bcount_full_opt(size) +#define __out_bcount_full_z(size) +#define __out_bcount_full_z_opt(size) +#define __out_bcount_nz(size) +#define __out_bcount_nz_opt(size) +#define __out_bcount_opt(size) +#define __out_bcount_part(size,length) +#define __out_bcount_part_opt(size,length) +#define __out_bcount_part_z(size,length) +#define __out_bcount_part_z_opt(size,length) +#define __out_bcount_z(size) +#define __out_bcount_z_opt(size) +#define __out_bound +#define __out_ecount(size) +#define __out_ecount_full(size) +#define __out_ecount_full_opt(size) +#define __out_ecount_full_z(size) +#define __out_ecount_full_z_opt(size) +#define __out_ecount_nz(size) +#define __out_ecount_nz_opt(size) +#define __out_ecount_opt(size) +#define __out_ecount_part(size,length) +#define __out_ecount_part_opt(size,length) +#define __out_ecount_part_z(size,length) +#define __out_ecount_part_z_opt(size,length) +#define __out_ecount_z(size) +#define __out_ecount_z_opt(size) +#define __out_nz +#define __out_nz_opt +#define __out_opt +#define __out_range(lb,ub) +#define __out_xcount(size) +#define __out_xcount_full(size) +#define __out_xcount_full_opt(size) +#define __out_xcount_opt(size) +#define __out_xcount_part(size,length) +#define __out_xcount_part_opt(size,length) +#define __out_z +#define __out_z_opt +#define __override +#define __range(lb,ub) +#define __reserved +#define __sql_escaped_and_delimited_right_bracket +#define __struct_bcount(size) +#define __struct_xcount(size) +#define __success(expr) +#define __transfer(formal) +#define __typefix(ctype) +#define __xcount(size) +#define __xcount_opt(size) +#define _Analysis_assume_ +#define _Check_return_ +#define _Check_return_ +#define _Deref +#define _Deref_in_bound_ +#define _Deref_in_range_(lb,ub) +#define _Deref_inout_bound_ +#define _Deref_inout_z_ +#define _Deref_inout_z_bytecap_c_(size) +#define _Deref_inout_z_cap_c_(size) +#define _Deref_opt_out_ +#define _Deref_opt_out_opt_ +#define _Deref_opt_out_opt_z_ +#define _Deref_opt_out_z_ +#define _Deref_out_ +#define _Deref_out_bound_ +#define _Deref_out_opt_ +#define _Deref_out_opt_z_ +#define _Deref_out_range_(lb,ub) +#define _Deref_out_z_ +#define _Deref_out_z_bytecap_c_(size) +#define _Deref_out_z_cap_c_(size) +#define _Deref_post_bytecap_(size) +#define _Deref_post_bytecap_c_(size) +#define _Deref_post_bytecap_x_(size) +#define _Deref_post_bytecount_(size) +#define _Deref_post_bytecount_c_(size) +#define _Deref_post_bytecount_x_(size) +#define _Deref_post_cap_(size) +#define _Deref_post_cap_c_(size) +#define _Deref_post_cap_x_(size) +#define _Deref_post_count_(size) +#define _Deref_post_count_c_(size) +#define _Deref_post_count_x_(size) +#define _Deref_post_maybenull_ +#define _Deref_post_notnull_ +#define _Deref_post_null_ +#define _Deref_post_opt_bytecap_(size) +#define _Deref_post_opt_bytecap_c_(size) +#define _Deref_post_opt_bytecap_x_(size) +#define _Deref_post_opt_bytecount_(size) +#define _Deref_post_opt_bytecount_c_(size) +#define _Deref_post_opt_bytecount_x_(size) +#define _Deref_post_opt_cap_(size) +#define _Deref_post_opt_cap_c_(size) +#define _Deref_post_opt_cap_x_(size) +#define _Deref_post_opt_count_(size) +#define _Deref_post_opt_count_c_(size) +#define _Deref_post_opt_count_x_(size) +#define _Deref_post_opt_valid_ +#define _Deref_post_opt_valid_bytecap_(size) +#define _Deref_post_opt_valid_bytecap_c_(size) +#define _Deref_post_opt_valid_bytecap_x_(size) +#define _Deref_post_opt_valid_cap_(size) +#define _Deref_post_opt_valid_cap_c_(size) +#define _Deref_post_opt_valid_cap_x_(size) +#define _Deref_post_opt_z_ +#define _Deref_post_opt_z_bytecap_(size) +#define _Deref_post_opt_z_bytecap_c_(size) +#define _Deref_post_opt_z_bytecap_x_(size) +#define _Deref_post_opt_z_cap_(size) +#define _Deref_post_opt_z_cap_c_(size) +#define _Deref_post_opt_z_cap_x_(size) +#define _Deref_post_valid_ +#define _Deref_post_valid_bytecap_(size) +#define _Deref_post_valid_bytecap_c_(size) +#define _Deref_post_valid_bytecap_x_(size) +#define _Deref_post_valid_cap_(size) +#define _Deref_post_valid_cap_c_(size) +#define _Deref_post_valid_cap_x_(size) +#define _Deref_post_z_ +#define _Deref_post_z_ +#define _Deref_post_z_bytecap_(size) +#define _Deref_post_z_bytecap_c_(size) +#define _Deref_post_z_bytecap_x_(size) +#define _Deref_post_z_cap_(size) +#define _Deref_post_z_cap_c_(size) +#define _Deref_post_z_cap_x_(size) +#define _Deref_pre_bytecap_(size) +#define _Deref_pre_bytecap_c_(size) +#define _Deref_pre_bytecap_x_(size) +#define _Deref_pre_bytecount_(size) +#define _Deref_pre_bytecount_c_(size) +#define _Deref_pre_bytecount_x_(size) +#define _Deref_pre_cap_(size) +#define _Deref_pre_cap_c_(size) +#define _Deref_pre_cap_x_(size) +#define _Deref_pre_count_(size) +#define _Deref_pre_count_c_(size) +#define _Deref_pre_count_x_(size) +#define _Deref_pre_invalid_ +#define _Deref_pre_maybenull_ +#define _Deref_pre_notnull_ +#define _Deref_pre_null_ +#define _Deref_pre_opt_bytecap_(size) +#define _Deref_pre_opt_bytecap_c_(size) +#define _Deref_pre_opt_bytecap_x_(size) +#define _Deref_pre_opt_bytecount_(size) +#define _Deref_pre_opt_bytecount_c_(size) +#define _Deref_pre_opt_bytecount_x_(size) +#define _Deref_pre_opt_cap_(size) +#define _Deref_pre_opt_cap_c_(size) +#define _Deref_pre_opt_cap_x_(size) +#define _Deref_pre_opt_count_(size) +#define _Deref_pre_opt_count_c_(size) +#define _Deref_pre_opt_count_x_(size) +#define _Deref_pre_opt_valid_ +#define _Deref_pre_opt_valid_bytecap_(size) +#define _Deref_pre_opt_valid_bytecap_c_(size) +#define _Deref_pre_opt_valid_bytecap_x_(size) +#define _Deref_pre_opt_valid_cap_(size) +#define _Deref_pre_opt_valid_cap_c_(size) +#define _Deref_pre_opt_valid_cap_x_(size) +#define _Deref_pre_opt_z_ +#define _Deref_pre_opt_z_bytecap_(size) +#define _Deref_pre_opt_z_bytecap_c_(size) +#define _Deref_pre_opt_z_bytecap_x_(size) +#define _Deref_pre_opt_z_cap_(size) +#define _Deref_pre_opt_z_cap_c_(size) +#define _Deref_pre_opt_z_cap_x_(size) +#define _Deref_pre_readonly_ +#define _Deref_pre_valid_ +#define _Deref_pre_valid_bytecap_(size) +#define _Deref_pre_valid_bytecap_c_(size) +#define _Deref_pre_valid_bytecap_x_(size) +#define _Deref_pre_valid_cap_(size) +#define _Deref_pre_valid_cap_c_(size) +#define _Deref_pre_valid_cap_x_(size) +#define _Deref_pre_writeonly_ +#define _Deref_pre_z_ +#define _Deref_pre_z_bytecap_(size) +#define _Deref_pre_z_bytecap_c_(size) +#define _Deref_pre_z_bytecap_x_(size) +#define _Deref_pre_z_cap_(size) +#define _Deref_pre_z_cap_c_(size) +#define _Deref_pre_z_cap_x_(size) +#define _Deref_prepost_bytecap_(size) +#define _Deref_prepost_bytecap_x_(size) +#define _Deref_prepost_bytecount_(size) +#define _Deref_prepost_bytecount_x_(size) +#define _Deref_prepost_cap_(size) +#define _Deref_prepost_cap_x_(size) +#define _Deref_prepost_count_(size) +#define _Deref_prepost_count_x_(size) +#define _Deref_prepost_opt_bytecap_(size) +#define _Deref_prepost_opt_bytecap_x_(size) +#define _Deref_prepost_opt_bytecount_(size) +#define _Deref_prepost_opt_bytecount_x_(size) +#define _Deref_prepost_opt_cap_(size) +#define _Deref_prepost_opt_cap_x_(size) +#define _Deref_prepost_opt_count_(size) +#define _Deref_prepost_opt_count_x_(size) +#define _Deref_prepost_opt_valid_ +#define _Deref_prepost_opt_valid_bytecap_(size) +#define _Deref_prepost_opt_valid_bytecap_x_(size) +#define _Deref_prepost_opt_valid_cap_(size) +#define _Deref_prepost_opt_valid_cap_x_(size) +#define _Deref_prepost_opt_z_ +#define _Deref_prepost_opt_z_bytecap_(size) +#define _Deref_prepost_opt_z_cap_(size) +#define _Deref_prepost_valid_ +#define _Deref_prepost_valid_bytecap_(size) +#define _Deref_prepost_valid_bytecap_x_(size) +#define _Deref_prepost_valid_cap_(size) +#define _Deref_prepost_valid_cap_x_(size) +#define _Deref_prepost_z_ +#define _Deref_prepost_z_bytecap_(size) +#define _Deref_prepost_z_cap_(size) +#define _Deref_ret_bound_ +#define _Deref_ret_opt_z_ +#define _Deref_ret_range_(lb,ub) +#define _Deref_ret_z_ +#define _FormatMessage_format_string_ +#define _In_ +#define _In_bound_ +#define _In_bytecount_(size) +#define _In_bytecount_c_(size) +#define _In_bytecount_x_(size) +#define _In_count_(size) +#define _In_count_c_(size) +#define _In_count_x_(size) +#define _In_opt_ +#define _In_opt_ +#define _In_opt_bytecount_(size) +#define _In_opt_bytecount_c_(size) +#define _In_opt_bytecount_x_(size) +#define _In_opt_count_(size) +#define _In_opt_count_c_(size) +#define _In_opt_count_x_(size) +#define _In_opt_ptrdiff_count_(size) +#define _In_opt_z_ +#define _In_opt_z_bytecount_(size) +#define _In_opt_z_bytecount_c_(size) +#define _In_opt_z_count_(size) +#define _In_opt_z_count_c_(size) +#define _In_ptrdiff_count_(size) +#define _In_range_(lb,ub) +#define _In_reads_bytes_(size) +#define _In_z_ +#define _In_z_bytecount_(size) +#define _In_z_bytecount_c_(size) +#define _In_z_count_(size) +#define _In_z_count_c_(size) +#define _Inout_ +#define _Inout_bytecap_(size) +#define _Inout_bytecap_c_(size) +#define _Inout_bytecap_x_(size) +#define _Inout_bytecount_(size) +#define _Inout_bytecount_c_(size) +#define _Inout_bytecount_x_(size) +#define _Inout_cap_(size) +#define _Inout_cap_c_(size) +#define _Inout_cap_x_(size) +#define _Inout_count_(size) +#define _Inout_count_c_(size) +#define _Inout_count_x_(size) +#define _Inout_opt_ +#define _Inout_opt_bytecap_(size) +#define _Inout_opt_bytecap_c_(size) +#define _Inout_opt_bytecap_x_(size) +#define _Inout_opt_bytecount_(size) +#define _Inout_opt_bytecount_c_(size) +#define _Inout_opt_bytecount_x_(size) +#define _Inout_opt_cap_(size) +#define _Inout_opt_cap_c_(size) +#define _Inout_opt_cap_x_(size) +#define _Inout_opt_count_(size) +#define _Inout_opt_count_c_(size) +#define _Inout_opt_count_x_(size) +#define _Inout_opt_ptrdiff_count_(size) +#define _Inout_opt_z_ +#define _Inout_opt_z_bytecap_(size) +#define _Inout_opt_z_bytecap_c_(size) +#define _Inout_opt_z_bytecap_x_(size) +#define _Inout_opt_z_bytecount_(size) +#define _Inout_opt_z_bytecount_c_(size) +#define _Inout_opt_z_cap_(size) +#define _Inout_opt_z_cap_c_(size) +#define _Inout_opt_z_cap_x_(size) +#define _Inout_opt_z_count_(size) +#define _Inout_opt_z_count_c_(size) +#define _Inout_ptrdiff_count_(size) +#define _Inout_z_ +#define _Inout_z_bytecap_(size) +#define _Inout_z_bytecap_c_(size) +#define _Inout_z_bytecap_x_(size) +#define _Inout_z_bytecount_(size) +#define _Inout_z_bytecount_c_(size) +#define _Inout_z_cap_(size) +#define _Inout_z_cap_c_(size) +#define _Inout_z_cap_x_(size) +#define _Inout_z_count_(size) +#define _Inout_z_count_c_(size) +#define _Out_ +#define _Out_bound_ +#define _Out_bytecap_(size) +#define _Out_bytecap_c_(size) +#define _Out_bytecap_post_bytecount_(cap,count) +#define _Out_bytecap_x_(size) +#define _Out_bytecapcount_(capcount) +#define _Out_bytecapcount_x_(capcount) +#define _Out_cap_(size) +#define _Out_cap_c_(size) +#define _Out_cap_m_(mult,size) +#define _Out_cap_post_count_(cap,count) +#define _Out_cap_x_(size) +#define _Out_capcount_(capcount) +#define _Out_capcount_x_(capcount) +#define _Out_opt_ +#define _Out_opt_bytecap_(size) +#define _Out_opt_bytecap_c_(size) +#define _Out_opt_bytecap_post_bytecount_(cap,count) +#define _Out_opt_bytecap_x_(size) +#define _Out_opt_bytecapcount_(capcount) +#define _Out_opt_bytecapcount_x_(capcount) +#define _Out_opt_cap_(size) +#define _Out_opt_cap_c_(size) +#define _Out_opt_cap_m_(mult,size) +#define _Out_opt_cap_post_count_(cap,count) +#define _Out_opt_cap_x_(size) +#define _Out_opt_capcount_(capcount) +#define _Out_opt_capcount_x_(capcount) +#define _Out_opt_ptrdiff_cap_(size) +#define _Out_opt_z_bytecap_(size) +#define _Out_opt_z_bytecap_c_(size) +#define _Out_opt_z_bytecap_post_bytecount_(cap,count) +#define _Out_opt_z_bytecap_x_(size) +#define _Out_opt_z_bytecapcount_(capcount) +#define _Out_opt_z_cap_(size) +#define _Out_opt_z_cap_c_(size) +#define _Out_opt_z_cap_m_(mult,size) +#define _Out_opt_z_cap_post_count_(cap,count) +#define _Out_opt_z_cap_x_(size) +#define _Out_opt_z_capcount_(capcount) +#define _Out_ptrdiff_cap_(size) +#define _Out_range_(lb,ub) +#define _Out_writes_(size) +#define _Out_writes_bytes_(count) +#define _Out_z_bytecap_(size) +#define _Out_z_bytecap_c_(size) +#define _Out_z_bytecap_post_bytecount_(cap,count) +#define _Out_z_bytecap_x_(size) +#define _Out_z_bytecapcount_(capcount) +#define _Out_z_cap_(size) +#define _Out_z_cap_c_(size) +#define _Out_z_cap_m_(mult,size) +#define _Out_z_cap_post_count_(cap,count) +#define _Out_z_cap_x_(size) +#define _Out_z_capcount_(capcount) +#define _Post_bytecap_(size) +#define _Post_bytecount_(size) +#define _Post_bytecount_c_(size) +#define _Post_bytecount_x_(size) +#define _Post_cap_(size) +#define _Post_count_(size) +#define _Post_count_c_(size) +#define _Post_count_x_(size) +#define _Post_invalid_ +#define _Post_maybez_ +#define _Post_notnull_ +#define _Post_ptr_invalid_ +#define _Post_valid_ +#define _Post_z_ +#define _Post_z_bytecount_(size) +#define _Post_z_bytecount_c_(size) +#define _Post_z_bytecount_x_(size) +#define _Post_z_count_(size) +#define _Post_z_count_c_(size) +#define _Post_z_count_x_(size) +#define _Pre_bytecap_(size) +#define _Pre_bytecap_c_(size) +#define _Pre_bytecap_x_(size) +#define _Pre_bytecount_(size) +#define _Pre_bytecount_c_(size) +#define _Pre_bytecount_x_(size) +#define _Pre_cap_(size) +#define _Pre_cap_c_(size) +#define _Pre_cap_for_(param) +#define _Pre_cap_m_(mult,size) +#define _Pre_cap_x_(size) +#define _Pre_count_(size) +#define _Pre_count_c_(size) +#define _Pre_count_x_(size) +#define _Pre_invalid_ +#define _Pre_maybenull_ +#define _Pre_notnull_ +#define _Pre_null_ +#define _Pre_opt_bytecap_(size) +#define _Pre_opt_bytecap_c_(size) +#define _Pre_opt_bytecap_x_(size) +#define _Pre_opt_bytecount_(size) +#define _Pre_opt_bytecount_c_(size) +#define _Pre_opt_bytecount_x_(size) +#define _Pre_opt_cap_(size) +#define _Pre_opt_cap_c_(size) +#define _Pre_opt_cap_for_(param) +#define _Pre_opt_cap_m_(mult,size) +#define _Pre_opt_cap_x_(size) +#define _Pre_opt_count_(size) +#define _Pre_opt_count_c_(size) +#define _Pre_opt_count_x_(size) +#define _Pre_opt_ptrdiff_cap_(ptr) +#define _Pre_opt_ptrdiff_count_(ptr) +#define _Pre_opt_valid_ +#define _Pre_opt_valid_bytecap_(size) +#define _Pre_opt_valid_bytecap_c_(size) +#define _Pre_opt_valid_bytecap_x_(size) +#define _Pre_opt_valid_cap_(size) +#define _Pre_opt_valid_cap_c_(size) +#define _Pre_opt_valid_cap_x_(size) +#define _Pre_opt_z_ +#define _Pre_opt_z_bytecap_(size) +#define _Pre_opt_z_bytecap_c_(size) +#define _Pre_opt_z_bytecap_x_(size) +#define _Pre_opt_z_cap_(size) +#define _Pre_opt_z_cap_c_(size) +#define _Pre_opt_z_cap_x_(size) +#define _Pre_ptrdiff_cap_(ptr) +#define _Pre_ptrdiff_count_(ptr) +#define _Pre_readonly_ +#define _Pre_valid_ +#define _Pre_valid_bytecap_(size) +#define _Pre_valid_bytecap_c_(size) +#define _Pre_valid_bytecap_x_(size) +#define _Pre_valid_cap_(size) +#define _Pre_valid_cap_c_(size) +#define _Pre_valid_cap_x_(size) +#define _Pre_writeonly_ +#define _Pre_z_ +#define _Pre_z_bytecap_(size) +#define _Pre_z_bytecap_c_(size) +#define _Pre_z_bytecap_x_(size) +#define _Pre_z_cap_(size) +#define _Pre_z_cap_c_(size) +#define _Pre_z_cap_x_(size) +#define _Prepost_bytecount_(size) +#define _Prepost_bytecount_c_(size) +#define _Prepost_bytecount_x_(size) +#define _Prepost_count_(size) +#define _Prepost_count_c_(size) +#define _Prepost_count_x_(size) +#define _Prepost_opt_bytecount_(size) +#define _Prepost_opt_bytecount_c_(size) +#define _Prepost_opt_bytecount_x_(size) +#define _Prepost_opt_count_(size) +#define _Prepost_opt_count_c_(size) +#define _Prepost_opt_count_x_(size) +#define _Prepost_opt_valid_ +#define _Prepost_opt_z_ +#define _Prepost_valid_ +#define _Prepost_z_ +#define _Printf_format_string_ +#define _Ret_ +#define _Ret_bound_ +#define _Ret_bytecap_(size) +#define _Ret_bytecap_c_(size) +#define _Ret_bytecap_x_(size) +#define _Ret_bytecount_(size) +#define _Ret_bytecount_c_(size) +#define _Ret_bytecount_x_(size) +#define _Ret_cap_(size) +#define _Ret_cap_c_(size) +#define _Ret_cap_x_(size) +#define _Ret_count_(size) +#define _Ret_count_c_(size) +#define _Ret_count_x_(size) +#define _Ret_maybenull_ +#define _Ret_notnull_ +#define _Ret_null_ +#define _Ret_opt_ +#define _Ret_opt_ +#define _Ret_opt_bytecap_(size) +#define _Ret_opt_bytecap_c_(size) +#define _Ret_opt_bytecap_x_(size) +#define _Ret_opt_bytecount_(size) +#define _Ret_opt_bytecount_c_(size) +#define _Ret_opt_bytecount_x_(size) +#define _Ret_opt_cap_(size) +#define _Ret_opt_cap_c_(size) +#define _Ret_opt_cap_x_(size) +#define _Ret_opt_count_(size) +#define _Ret_opt_count_c_(size) +#define _Ret_opt_count_x_(size) +#define _Ret_opt_valid_ +#define _Ret_opt_z_ +#define _Ret_opt_z_ +#define _Ret_opt_z_bytecap_(size) +#define _Ret_opt_z_bytecount_(size) +#define _Ret_opt_z_cap_(size) +#define _Ret_opt_z_count_(size) +#define _Ret_range_(lb,ub) +#define _Ret_valid_ +#define _Ret_z_ +#define _Ret_z_ +#define _Ret_z_bytecap_(size) +#define _Ret_z_bytecount_(size) +#define _Ret_z_cap_(size) +#define _Ret_z_count_(size) +#define _Scanf_format_string_ +#define _Scanf_s_format_string_ +#define _Success_(expr) +#define _Outptr_ +#define _Notnull_ + +#endif // XPLAT_SAL_DEFINED + diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h new file mode 100644 index 000000000..45798b863 --- /dev/null +++ b/source/shared/typedefs_for_linux.h @@ -0,0 +1,103 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: typedefs_for_linux.h +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __linux_typedefs__ +#define __linux_typedefs__ + +#define MPLAT_UNIX + +#include "xplat.h" +#include "interlockedslist.h" + +#define CP_OEMCP 1 // default to OEM code page +#define CP_MACCP 2 // default to MAC code page +#define CP_THREAD_ACP 3 // current thread's ANSI code page +#define CP_SYMBOL 42 // SYMBOL translations + +#define CP_UTF7 65000 // UTF-7 translation + +#define MAKELANGID(p, s) ((((WORD )(s)) << 10) | (WORD )(p)) +#define LANG_NEUTRAL 0x00 +#define SUBLANG_DEFAULT 0x01 // user default + +DWORD FormatMessageA( + DWORD dwFlags, + LPCVOID lpSource, + DWORD dwMessageId, + DWORD dwLanguageId, + LPTSTR lpBuffer, + DWORD nSize, + va_list *Arguments + ); + +DWORD FormatMessageW( + DWORD dwFlags, + LPCVOID lpSource, + DWORD dwMessageId, + DWORD dwLanguageId, + LPWSTR lpBuffer, + DWORD nSize, + va_list *Arguments + ); + +#ifndef _WIN32 +#define FormatMessage FormatMessageA +#else +#define FormatMessage FormatMessageW +#endif // !_WIN32 + +#define FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100 +#define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 +#define FORMAT_MESSAGE_FROM_STRING 0x00000400 +#define FORMAT_MESSAGE_FROM_HMODULE 0x00000800 +#define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 +#define FORMAT_MESSAGE_ARGUMENT_ARRAY 0x00002000 +#define FORMAT_MESSAGE_MAX_WIDTH_MASK 0x000000FF + +#define ERROR_NO_UNICODE_TRANSLATION 1113L +#define ERROR_SUCCESS 0L +#define ERROR_INVALID_DATA 13L + +typedef int errno_t; +int mplat_snwprintf_s(WCHAR *str, size_t sizeOfBuffer, size_t count, const WCHAR *format, ...); +int mplat_vsnwprintf( WCHAR * buffer, size_t count, const WCHAR * format, va_list args ); +int mplat_snprintf_s(char *str, size_t sizeOfBuffer, size_t count, const char *format, ...); +int mplat_vsnprintf( char * buffer, size_t count, const char * format, va_list args ); +errno_t mplat_wctomb_s(int *pRetValue, char *mbchar, size_t sizeInBytes, WCHAR wchar); +WCHAR * mplat_wcscpy(WCHAR * _Dst, const WCHAR * _Src); +char * mplat_cscpy(char * _Dst, const char * _Src); +BOOL IsDBCSLeadByteEx(__inn UINT CodePage, __inn BYTE TestChar); + +typedef struct _SYSTEMTIME { + WORD wYear; + WORD wMonth; + WORD wDayOfWeek; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; +} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME; + +typedef HINSTANCE HMODULE; /* HMODULEs can be used in place of HINSTANCEs */ + +//From sqlext.h +#define SQL_GUID (-11) + +size_t mplat_wcslen( const WCHAR * ); + +#endif // __linux_typedefs__ diff --git a/sqlsrv/version.h b/source/shared/version.h similarity index 70% rename from sqlsrv/version.h rename to source/shared/version.h index 93bb6d47f..c6c5a0c91 100644 --- a/sqlsrv/version.h +++ b/source/shared/version.h @@ -1,27 +1,37 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: version.h -// Contents: Version number constants -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#define VER_FILEVERSION_STR "4.1.0.0" -#define _FILEVERSION 4,1,0,0 -#define SQLVERSION_MAJOR 4 -#define SQLVERSION_MINOR 1 -#define SQLVERSION_MMDD 0 -#define SQLVERSION_REVISION 0 - - - +#ifndef VERSION_H +#define VERSION_H +//--------------------------------------------------------------------------------------------------------------------------------- +// File: version.h +// Contents: Version number constants +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +// helper macros to stringify the a macro value +#define STRINGIFY(a) TOSTRING(a) +#define TOSTRING(a) #a + +#define SQLVERSION_MAJOR 4 +#define SQLVERSION_MINOR 1 +#define SQLVERSION_RELEASE 6 +#define SQLVERSION_BUILD 0 + +#define VER_FILEVERSION_STR STRINGIFY( SQLVERSION_MAJOR ) "." STRINGIFY( SQLVERSION_MINOR ) "." STRINGIFY( SQLVERSION_RELEASE ) "." STRINGIFY( SQLVERSION_BUILD ) +#define _FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_RELEASE,SQLVERSION_BUILD +#define PHP_SQLSRV_VERSION STRINGIFY( SQLVERSION_MAJOR ) "." STRINGIFY( SQLVERSION_MINOR ) "." STRINGIFY( SQLVERSION_RELEASE ) +#define PHP_PDO_SQLSRV_VERSION PHP_SQLSRV_VERSION + +#endif // VERSION_H + + diff --git a/source/shared/xplat.h b/source/shared/xplat.h new file mode 100644 index 000000000..7bc12faa0 --- /dev/null +++ b/source/shared/xplat.h @@ -0,0 +1,490 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: xplat.h +// +// Contents: include for definition of Windows types for non-Windows platforms +// +// Microsoft Drivers 4.0 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef __XPLAT_H__ +#define __XPLAT_H__ + +#ifndef _WCHART_DEFINED +#define _WCHART_DEFINED +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +// Turned on all warnings in WwoWH projects +// These warnings need to be disabled to be build warning free +// Note that some of these should be enabled and the code fixed +#pragma warning( disable : 4668 ) // preprocessor macro not defined +#pragma warning( disable : 4820 ) // padding after data member +#pragma warning( disable : 4201 ) // nonstandard: nameless union +#pragma warning( disable : 4100 ) // unreferenced formal parameter +#pragma warning( disable : 4514 ) // unreferenced inline function +#pragma warning( disable : 4505 ) // unreferenced inline function +#pragma warning( disable : 4710 ) // function not inlined +#pragma warning( disable : 4191 ) // unsafe conversion +#pragma warning( disable : 4365 ) // signed/unsigned argument conversion +#pragma warning( disable : 4245 ) // signed/unsigned assignment conversion +#pragma warning( disable : 4389 ) // signed/unsigned == +#pragma warning( disable : 4987 ) // nonstandard: throw(...) +#pragma warning( disable : 4510 ) // default ctor could not be generated +#pragma warning( disable : 4512 ) // operator= could not be generated +#pragma warning( disable : 4626 ) // operator= could not be generated +#pragma warning( disable : 4625 ) // copy ctor could not be generated or accessed +#pragma warning( disable : 4189 ) // unused initialized local variable +#pragma warning( disable : 4127 ) // constant conditional test +#pragma warning( disable : 4061 ) // Unused enum values in switch +#pragma warning( disable : 4062 ) // Unused enum values in switch +#pragma warning( disable : 4706 ) // assignment within conditional +#pragma warning( disable : 4610 ) // can never be instantiated +#pragma warning( disable : 4244 ) // possible loss of data in conversion +#pragma warning( disable : 4701 ) // possible use of uninitialized variable +#pragma warning( disable : 4918 ) // invalid pragma optimization parameter +#pragma warning( disable : 4702 ) // unreachable code +#pragma warning( disable : 4265 ) // class with virtual fxns has non-virtual dtor +#pragma warning( disable : 4238 ) // nonstandard: class rvalue used as lvalue +#pragma warning( disable : 4310 ) // cast truncates constant value +#pragma warning( disable : 4946 ) // reinterpret_cast between related classes +#pragma warning( disable : 4264 ) // no matching override, hides base fxn +#pragma warning( disable : 4242 ) // conversion: possible loss of data +#pragma warning( disable : 4820 ) // added padding bytes +#endif + +// Compiler specific items +#define _cdecl +#define __cdecl +#define __fastcall +#define _inline inline +#define __inline inline +#define __forceinline inline +#define __stdcall + +#if !defined(_MSC_VER) +#define __declspec__noinline __attribute__((noinline)) +#define __declspec__selectany +#define __declspec(a) __declspec__##a +#define __FUNCTION__ __func__ + +#define __int8 char +#define __int32 int + +// __int64 +// This type must be defined in a way that allows "unsigned __int64" as a valid type declaration. +// That precludes using the obvious "int64_t" from stdint.h, because "unsigned int64_t" is not allowed +// (one should use "uint64_t" for unsigned 64-bit integers). As a result, we must use compiler-specific +// types such as GCC's "long long" instead +#if defined(_LP64) +#define __int64 long +#elif defined(__GNUC__) +#define __int64 long long +#else +#error "Compiler-specific definition required for __int64 in 32-bit builds" +#endif +#endif + +// GCC-specific definitions +#if defined(__GNUC__) +#define MPLAT_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif // defined(__GNUC__) + +// For compilers that don't support cross-module inlining (part of whole-program/link-time +// optimization), such as the current MPLAT compilers (GCC 4.1.2 for RHEL5 and GCC 4.4 for RHEL6), +// we must force the generation of out-of-line definitions for functions that otherwise +// are only defined inline when those functions are called from other translation units. +// There are a handful of instances of these in SNI code as well as ODBC code. +// +// To force the compiler to emit an out-of-line definition for a function, just add an otherwise +// unused global (external linkage) non-const pointer pointing to the function: +// +// #if defined(MPLAT_NO_LTO) +// void (* g_pfnMyFunctionUnused)(MyFunctionArguments *) = MyFunction; +// #endif // defined(MPLAT_NO_LTO) +// +// This works because, absent whole-program optimization, the compiler cannot determine that the +// pointers are never called through, and the out-of-line definition cannot be optimized out, +// giving calling translation units something to link to. +// +// GCC adds LTO as of version 4.5 +//JL - TODO: this version check doesn't work in Ubuntu +//#if defined(__GNUC__) && MPLAT_GCC_VERSION < 40500 +#define MPLAT_NO_LTO +//#endif + +#ifdef MPLAT_UNIX + +// Needed to use the standard library min and max +#include +using std::min; +using std::max; + +#elif MPLAT_WWOWH + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#endif // MPLAT_WWOWH + +// Deal with differences between Windows and *nix interpretations of the C/C++ 'long' data type. +// +// On 64-bit Windows, 'long' is 32 bits. On 64-bit Linux, 'long' is 64 bits. Assuming the Windows code +// depends on it being 32 bits, use a definition that provides a guaranteed 32-bit type definition. +// +// Similarly, because 'long long' (and its cousin 'unsigned long long') are not portable across +// Linux/UNIX platforms and compilers, provide common definitions for 64-bit types as well. +// +// These types are used in this file primarily to define common Windows types (DWORD, LONG, etc.) +// Cross-platform code should use either the Windows types or appropriate types from . +#if defined(_MSC_VER) // WwoWH +typedef long windowsLong_t; +typedef unsigned long windowsULong_t; +typedef __int64 windowsLongLong_t; +typedef unsigned __int64 windowsULongLong_t; +#else // *nix (!WwoWH) +#include // Use standard bit-specific types (signed/unsigned integrated) +typedef int32_t windowsLong_t; +typedef uint32_t windowsULong_t; +typedef int64_t windowsLongLong_t; +typedef uint64_t windowsULongLong_t; +#endif +typedef windowsLong_t LONG, *PLONG, *LPLONG; +typedef windowsLongLong_t LONGLONG; +typedef windowsULongLong_t ULONGLONG; + +#include +#include +#include "xplat_intsafe.h" + +// Exclude these headers in Windows machines (just for building on Windows w/o Windows headers) +#define SPECSTRINGS_H // specstrings.h +#define ASOSHOST_DEFINED // asoshost.h +#define _WINDOWS_ // windows.h +#define _INC_WINDOWSX // windowsx.h +#define _WINBASE_ // winbase.h +#define _WINNLS_ // winnls.h +#define _WINERROR_ // winerror.h +#define NETCONS_INCLUDED // lmcons.h +#define __WINCRYPT_H__ // wincrypt.h +#define _INC_TCHAR // tchar.h +#define _INC_FCNTL // fcntl.h +#define _INC_SHARE // share.h +#define _INC_IO // io.h +#define _INC_TYPES // sys/types.h +#define _INC_STAT // sys/stat.h +#define _INC_TIMEB // sys/timeb.h +#define __unknwn_h__ // unknwn.h +#define __objidl_h__ // objidl.h +#define _OBJBASE_H_ // objbase.h +#define __RPC_H__ // rpc.h +#define __RPCNDR_H__ // rpcndr.h +#define _NP_HPP_ // np.hpp (no named pipes) +#define _SM_HPP_ // sm.hpp (no shared memory) +#define VIA_HEADER // via.hpp (no via) +#define _WINUSER_ // winuser.h + +#define interface struct + +// What we need from dlgattr.h +#define OPTIONON L"Yes" +#define OPTIONOFF L"No" + + +//----------------------------------------------------------------------------- +// Definitions for UnixODBC Driver Manager + +// Define this to enable driver code to conditionalize around UnixODBC Driver +// Manager "quirks"... +#ifndef MPLAT_WWOWH +#define UNIXODBC +#endif + +/* can be defined in php sources */ +#ifdef ODBCVER +#undef ODBCVER +#endif + +// Build the mplat driver as an ODBC 3.8 driver, so that all of the +// source code shared with Windows SNAC (which is ODBC 3.8) compiles. +#define ODBCVER 0x0380 + + +// Define this to indicate that we provide our own definitions for Windows types +#define ALLREADY_HAVE_WINDOWS_TYPE + +// Definitions not otherwise provided in sqltypes.h, given that we define our own Windows types +#define SQL_API +typedef signed char SCHAR; +typedef SCHAR SQLSCHAR; +typedef int SDWORD; +typedef unsigned int UDWORD; +typedef signed short int SWORD; +typedef signed short SSHORT; +typedef double SDOUBLE; +typedef double LDOUBLE; +typedef float SFLOAT; +typedef void* PTR; +typedef signed short RETCODE; +typedef void* SQLHWND; + +// Definitions missing from sql.h +#define SQL_PARAM_DATA_AVAILABLE 101 +#define SQL_APD_TYPE (-100) + +// Bid control bit, only for xplat +// It traces everything we current enabled for bid. +// The correlated tracing feature is not enabled. +#define DEFAULT_BID_CORT_BIT 0xFFFFBFFFF + +// End definitions for UnixODBC SQL headers +// ---------------------------------------------------------------------------- + +#define UNREFERENCED_PARAMETER(arg) + +// From share.h +#define _SH_DENYNO 0x40 /* deny none mode */ + + +// WinNT.h +#define CONST const +#define VOID void +#define DLL_PROCESS_ATTACH 1 +#define DLL_THREAD_ATTACH 2 +#define DLL_THREAD_DETACH 3 +#define DLL_PROCESS_DETACH 0 +#define VER_GREATER_EQUAL 3 +#define VER_MINORVERSION 0x0000001 +#define VER_MAJORVERSION 0x0000002 +#define VER_SERVICEPACKMINOR 0x0000010 +#define VER_SERVICEPACKMAJOR 0x0000020 +#define VER_SET_CONDITION(_m_,_t_,_c_) \ + ((_m_)=VerSetConditionMask((_m_),(_t_),(_c_))) + + +// Predeclared types from windef needed for remaining WinNT types +// to break circular dependency between WinNT.h and windef.h types. +//typedef ULONG DWORD; +typedef unsigned char BYTE; +typedef unsigned char UCHAR; +typedef UCHAR *PUCHAR; + +typedef DWORD LCID; +typedef LONG HRESULT; +typedef char CHAR; +typedef CHAR *LPSTR, *PSTR; +typedef CHAR *PCHAR, *LPCH, *PCH; +typedef CONST CHAR *LPCCH, *PCCH; +#ifdef SQL_WCHART_CONVERT +typedef wchar_t WCHAR; +#else +typedef unsigned short WCHAR; +#endif +typedef WCHAR *LPWSTR; +typedef WCHAR *PWSTR; +typedef CONST WCHAR *LPCWSTR; +typedef CONST WCHAR *PCWSTR; +typedef CONST CHAR *LPCSTR, *PCSTR; +typedef void *PVOID; +typedef PVOID HANDLE; +typedef BYTE BOOLEAN; +typedef BOOLEAN *PBOOLEAN; +typedef HANDLE *PHANDLE; +typedef WCHAR *PWCHAR, *LPWCH, *PWCH; +typedef CONST WCHAR *LPCWCH, *PCWCH; +typedef int HFILE; + +typedef short SHORT; +typedef CONST CHAR *LPCCH, *PCCH; + +typedef unsigned short WORD; + +#define RTL_NUMBER_OF_V1(A) (sizeof(A)/sizeof((A)[0])) +#define ARRAYSIZE(A) RTL_NUMBER_OF_V1(A) +#define STATUS_STACK_OVERFLOW ((DWORD )0xC00000FDL) +typedef union _LARGE_INTEGER { + struct { + DWORD LowPart; + LONG HighPart; + }; + struct { + DWORD LowPart; + LONG HighPart; + } u; + LONGLONG QuadPart; +} LARGE_INTEGER; +typedef LARGE_INTEGER *PLARGE_INTEGER; +typedef void * RPC_IF_HANDLE; + +typedef WORD LANGID; + +typedef enum _HEAP_INFORMATION_CLASS { + + HeapCompatibilityInformation, + HeapEnableTerminationOnCorruption + + +} HEAP_INFORMATION_CLASS; + + +#define REG_SZ ( 1 ) // Unicode nul terminated string +#define REG_DWORD ( 4 ) // 32-bit number + +#define RTL_NUMBER_OF_V1(A) (sizeof(A)/sizeof((A)[0])) +#define RTL_NUMBER_OF(A) RTL_NUMBER_OF_V1(A) + + +// windef.h +typedef VOID *LPVOID; +typedef CONST void *LPCVOID; +typedef int INT; +typedef int *LPINT; +typedef unsigned int UINT; +typedef ULONGLONG UINT64; +typedef unsigned int *PUINT; +typedef unsigned char BYTE; +typedef BYTE *PBYTE; +typedef BYTE *LPBYTE; +typedef const BYTE *LPCBYTE; +#define _LPCBYTE_DEFINED +//typedef int BOOL; +typedef BOOL * LPBOOL; +typedef unsigned short WORD; +typedef WORD * LPWORD; +typedef WORD UWORD; +typedef DWORD * LPDWORD; +typedef DWORD * PDWORD; +typedef unsigned short USHORT; +#define CDECL // TODO _cdecl and cdecl not portable? +#define WINAPI // TODO __stdcall not portable? +#define MAX_PATH 260 +typedef HANDLE HINSTANCE; +typedef HANDLE HGLOBAL; +typedef ULONGLONG DWORDLONG; +typedef DWORDLONG *PDWORDLONG; +typedef float FLOAT; + +typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME, *PFILETIME, *LPFILETIME; +typedef double DOUBLE; +#define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16)) + +// INT_PTR - http://msdn.microsoft.com/en-us/library/aa384154(VS.85).aspx +#ifdef _WIN64 +typedef __int64 INT_PTR; +#else +typedef int INT_PTR; +#endif + +typedef INT_PTR (*FARPROC)(); +typedef INT_PTR (*NEARPROC)(); +typedef INT_PTR (*PROC)(); + + +DWORD GetFileSize( + __inn HANDLE hFile, + __out_opt LPDWORD lpFileSizeHigh +); + +typedef union _ULARGE_INTEGER { + struct { + DWORD LowPart; + DWORD HighPart; + }; + struct { + DWORD LowPart; + DWORD HighPart; + } u; + ULONGLONG QuadPart; +} ULARGE_INTEGER; + +typedef ULARGE_INTEGER *PULARGE_INTEGER; + +#ifndef IN +#define IN +#endif + +#ifndef OUT +#define OUT +#endif + +#ifndef OPTIONAL +#define OPTIONAL +#endif + + +ULONGLONG +VerSetConditionMask( + IN ULONGLONG ConditionMask, + IN DWORD TypeMask, + IN BYTE Condition + ); + + +//// ntdef.h +#define __unaligned +#ifndef UNALIGNED +#define UNALIGNED +#endif +//typedef __nullterminated WCHAR UNALIGNED *LPUWSTR; + +//// crtdefs.h +//#if !defined(_TRUNCATE) +//#define _TRUNCATE ((size_t)-1) +//#endif + +//// ?? +//typedef ULONG_PTR DWORD_PTR; +#define FALSE ((BOOL)0) +#define TRUE ((BOOL)1) + + +//// asoshost.h (excluded above) +//struct ISOSHost_MemObj; +//struct ISOSHost; +//extern ISOSHost_MemObj *g_pMO; +//extern ISOSHost *g_pISOSHost; +//inline HRESULT CreateSQLSOSHostInterface() { return 0; } +//inline HRESULT CreateGlobalSOSHostInterface() { return 0; } + +//// These are temporary solution versions of the real files that contain the minimal declarations +//// needed to compile for non-Windows platforms. See the special include path for the +//// location of these files. + +#include "xplat_winerror.h" + +//#define LMEM_FIXED 0 +typedef void * HLOCAL; +HLOCAL LocalAlloc(UINT uFlags, SIZE_T uBytes); +//HLOCAL LocalReAlloc(HLOCAL hMem, SIZE_T uBytes, UINT uFlags); +HLOCAL LocalFree(HLOCAL hMem); + +// End of xplat.h +#endif //__XPLAT_H__ diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h new file mode 100644 index 000000000..e0cdd76b6 --- /dev/null +++ b/source/shared/xplat_intsafe.h @@ -0,0 +1,7635 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: xplat_intsafe.h +// +// Contents: This module defines helper functions to prevent +// integer overflow bugs. +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + + +#ifndef XPLAT_INTSAFE_H +#define XPLAT_INTSAFE_H + +#if (_MSC_VER > 1000) +#pragma once +#endif + +#if !defined(_W64) +#if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && (_MSC_VER >= 1300) +#define _W64 __w64 +#else +#define _W64 +#endif +#endif + +#include "sal_def.h" +#include + +// +// typedefs +// +typedef char CHAR; +typedef signed char INT8; +typedef unsigned char UCHAR; +typedef unsigned char UINT8; +typedef unsigned char BYTE; +typedef short SHORT; +typedef signed short INT16; +typedef unsigned short USHORT; +typedef unsigned short UINT16; +typedef unsigned short WORD; +typedef int INT; +typedef signed int INT32; +typedef unsigned int UINT; +typedef unsigned int UINT32; +typedef windowsLong_t LONG; +typedef windowsULong_t DWORD; +typedef windowsLongLong_t LONGLONG; +typedef windowsLongLong_t LONG64; +typedef windowsLongLong_t INT64; +typedef windowsULongLong_t ULONGLONG; +typedef windowsULongLong_t DWORDLONG; +typedef windowsULongLong_t ULONG64; +typedef windowsULongLong_t DWORD64; +typedef windowsULongLong_t UINT64; + +#if (__midl > 501) +typedef [public] __int3264 INT_PTR; +typedef [public] unsigned __int3264 UINT_PTR; +typedef [public] __int3264 LONG_PTR; +typedef [public] unsigned __int3264 ULONG_PTR; +#else +#ifdef _WIN64 +typedef __int64 INT_PTR, *PINT_PTR; +typedef unsigned __int64 UINT_PTR, *PUINT_PTR; +typedef __int64 LONG_PTR, *PLONG_PTR; +typedef unsigned __int64 ULONG_PTR, *PULONG_PTR; +#else +typedef _W64 int INT_PTR, *PINT_PTR; +typedef _W64 unsigned int UINT_PTR, *PUINT_PTR; +typedef _W64 windowsLong_t LONG_PTR, *PLONG_PTR; +typedef _W64 windowsULong_t ULONG_PTR, *PULONG_PTR; +#endif // WIN64 +#endif // (__midl > 501) + +typedef ULONG_PTR DWORD_PTR; +typedef LONG_PTR SSIZE_T; +typedef ULONG_PTR SIZE_T; + +#if defined(_AMD64_) +#ifdef __cplusplus +extern "C" { +#endif + +#define UnsignedMultiply128 _umul128 + +ULONG64 +UnsignedMultiply128 ( + __inn ULONG64 Multiplier, + __inn ULONG64 Multiplicand, + __outt __deref_out_range(==,Multiplier * Multiplicand) ULONG64 *HighProduct + ); +#pragma intrinsic(_umul128) + +#ifdef __cplusplus +} +#endif +#endif // _AMD64_ + + + +typedef __success(return >= 0) windowsLong_t HRESULT; + +#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0) +#define FAILED(hr) (((HRESULT)(hr)) < 0) + +#define S_OK ((HRESULT)0L) + +#define INTSAFE_E_ARITHMETIC_OVERFLOW ((HRESULT)0x80070216L) // 0x216 = 534 = ERROR_ARITHMETIC_OVERFLOW +#ifndef SORTPP_PASS +// compiletime asserts (failure results in error C2118: negative subscript) +#define C_ASSERT(e) typedef char __C_ASSERT__[(e)?1:-1] +#else +#define C_ASSERT(e) +#endif + +// +// UInt32x32To64 macro +// +#define UInt32x32To64(a, b) ((windowsULongLong_t)(((windowsULongLong_t)(a)) * ((windowsULong_t)(b)))) + +// +// Min/Max type values +// +//#define INT8_MIN (-127 - 1) +#define SHORT_MIN (-32768) +//#define INT16_MIN (-32767 - 1) +//#define INT_MIN (-2147483647 - 1) +//#define INT32_MIN (-2147483647l - 1) +//#define LONG_MIN (-2147483647L - 1) +#define LONGLONG_MIN (-9223372036854775807ll - 1) +#define LONG64_MIN (-9223372036854775807ll - 1) +//#define INT64_MIN (-9223372036854775807ll - 1) +#define INT128_MIN (-170141183460469231731687303715884105727i128 - 1) + +#ifdef _WIN64 +#define INT_PTR_MIN (-9223372036854775807ll - 1) +#define LONG_PTR_MIN (-9223372036854775807ll - 1) +#define PTRDIFF_T_MIN (-9223372036854775807ll - 1) +#define SSIZE_T_MIN (-9223372036854775807ll - 1) +#else +#define INT_PTR_MIN (-2147483647 - 1) +#define LONG_PTR_MIN (-2147483647L - 1) +#define PTRDIFF_T_MIN (-2147483647 - 1) +#define SSIZE_T_MIN (-2147483647L - 1) +#endif + +//#define INT8_MAX 127 +//#define UINT8_MAX 0xff +#define BYTE_MAX 0xff +#define SHORT_MAX 32767 +//#define INT16_MAX 32767 +#define USHORT_MAX 0xffff +//#define UINT16_MAX 0xffff +#define WORD_MAX 0xffff +//#define INT_MAX 2147483647 +//#define INT32_MAX 2147483647l +//#define UINT_MAX 0xffffffff +//#define UINT32_MAX 0xfffffffful +//#define LONG_MAX 2147483647L +//#define ULONG_MAX 0xffffffffUL +#define DWORD_MAX 0xffffffffUL +#define LONGLONG_MAX 9223372036854775807ll +#define LONG64_MAX 9223372036854775807ll +//#define INT64_MAX 9223372036854775807ll +#define ULONGLONG_MAX 0xffffffffffffffffull +#define DWORDLONG_MAX 0xffffffffffffffffull +#define ULONG64_MAX 0xffffffffffffffffull +#define DWORD64_MAX 0xffffffffffffffffull +//#define UINT64_MAX 0xffffffffffffffffull +#define INT128_MAX 170141183460469231731687303715884105727i128 +#define UINT128_MAX 0xffffffffffffffffffffffffffffffffui128 + +#ifndef _I64_MIN +#define _I64_MIN INT64_MIN +#define _I64_MAX INT64_MAX +#define _UI64_MIN UINT64_MIN +#define _UI64_MAX UINT64_MAX +#endif + +#undef SIZE_T_MAX + +#ifdef _WIN64 +#define INT_PTR_MAX 9223372036854775807ll +#define UINT_PTR_MAX 0xffffffffffffffffull +#define LONG_PTR_MAX 9223372036854775807ll +#define ULONG_PTR_MAX 0xffffffffffffffffull +#define DWORD_PTR_MAX 0xffffffffffffffffull +#define PTRDIFF_T_MAX 9223372036854775807ll +#define SIZE_T_MAX 0xffffffffffffffffull +#define SSIZE_T_MAX 9223372036854775807ll +#define _SIZE_T_MAX 0xffffffffffffffffull +#else +#define INT_PTR_MAX 2147483647 +#define UINT_PTR_MAX 0xffffffff +#define LONG_PTR_MAX 2147483647L +#define ULONG_PTR_MAX 0xffffffffUL +#define DWORD_PTR_MAX 0xffffffffUL +#define PTRDIFF_T_MAX 2147483647 +#define SIZE_T_MAX 0xffffffff +#define SSIZE_T_MAX 2147483647L +#define _SIZE_T_MAX 0xffffffffUL +#endif + + +// +// It is common for -1 to be used as an error value +// +#define INT8_ERROR (-1) +#define UINT8_ERROR 0xff +#define BYTE_ERROR 0xff +#define SHORT_ERROR (-1) +#define INT16_ERROR (-1) +#define USHORT_ERROR 0xffff +#define UINT16_ERROR 0xffff +#define WORD_ERROR 0xffff +#define INT_ERROR (-1) +#define INT32_ERROR (-1l) +#define UINT_ERROR 0xffffffff +#define UINT32_ERROR 0xfffffffful +#define LONG_ERROR (-1L) +#define ULONG_ERROR 0xffffffffUL +#define DWORD_ERROR 0xffffffffUL +#define LONGLONG_ERROR (-1ll) +#define LONG64_ERROR (-1ll) +#define INT64_ERROR (-1ll) +#define ULONGLONG_ERROR 0xffffffffffffffffull +#define DWORDLONG_ERROR 0xffffffffffffffffull +#define ULONG64_ERROR 0xffffffffffffffffull +#define UINT64_ERROR 0xffffffffffffffffull + +#ifdef _WIN64 +#define INT_PTR_ERROR (-1ll) +#define UINT_PTR_ERROR 0xffffffffffffffffull +#define LONG_PTR_ERROR (-1ll) +#define ULONG_PTR_ERROR 0xffffffffffffffffull +#define DWORD_PTR_ERROR 0xffffffffffffffffull +#define PTRDIFF_T_ERROR (-1ll) +#define SIZE_T_ERROR 0xffffffffffffffffull +#define SSIZE_T_ERROR (-1ll) +#define _SIZE_T_ERROR 0xffffffffffffffffull +#else +#define INT_PTR_ERROR (-1) +#define UINT_PTR_ERROR 0xffffffff +#define LONG_PTR_ERROR (-1L) +#define ULONG_PTR_ERROR 0xffffffffUL +#define DWORD_PTR_ERROR 0xffffffffUL +#define PTRDIFF_T_ERROR (-1) +#define SIZE_T_ERROR 0xffffffff +#define SSIZE_T_ERROR (-1L) +#define _SIZE_T_ERROR 0xffffffffUL +#endif + + +// +// We make some assumptions about the sizes of various types. Let's be +// explicit about those assumptions and check them. +// +C_ASSERT(sizeof(USHORT) == 2); +C_ASSERT(sizeof(INT) == 4); +C_ASSERT(sizeof(UINT) == 4); +C_ASSERT(sizeof(LONG) == 4); +C_ASSERT(sizeof(ULONG) == 8); +C_ASSERT(sizeof(UINT_PTR) == sizeof(ULONG_PTR)); + + +//============================================================================= +// Conversion functions +// +// There are three reasons for having conversion functions: +// +// 1. We are converting from a signed type to an unsigned type of the same +// size, or vice-versa. +// +// Since we only have unsigned math functions, this allows people to convert +// to unsigned, do the math, and then convert back to signed. +// +// 2. We are converting to a smaller type, and we could therefore possibly +// overflow. +// +// 3. We are converting to a bigger type, and we are signed and the type we are +// converting to is unsigned. +// +//============================================================================= + + +// +// INT8 -> UCHAR conversion +// +__inline +HRESULT +Int8ToUChar( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) UCHAR* pch) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pch = (UCHAR)i8Operand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> UINT8 conversion +// +__inline +HRESULT +Int8ToUInt8( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) UINT8* pu8Result) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pu8Result = (UINT8)i8Operand; + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> BYTE conversion +// +#define Int8ToByte Int8ToUInt8 + +// +// INT8 -> USHORT conversion +// +__inline +HRESULT +Int8ToUShort( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) USHORT* pusResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pusResult = (USHORT)i8Operand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> UINT16 conversion +// +#define Int8ToUInt16 Int8ToUShort + +// +// INT8 -> WORD conversion +// +#define Int8ToWord Int8ToUShort + +// +// INT8 -> UINT conversion +// +__inline +HRESULT +Int8ToUInt( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) UINT* puResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *puResult = (UINT)i8Operand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> UINT32 conversion +// +#define Int8ToUInt32 Int8ToUInt + +// +// INT8 -> UINT_PTR conversion +// +__inline +HRESULT +Int8ToUIntPtr( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) UINT_PTR* puResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *puResult = (UINT_PTR)i8Operand; + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> ULONG conversion +// +__inline +HRESULT +Int8ToULong( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) ULONG* pulResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pulResult = (ULONG)i8Operand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> ULONG_PTR conversion +// +__inline +HRESULT +Int8ToULongPtr( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pulResult = (ULONG_PTR)i8Operand; + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> DWORD conversion +// +#define Int8ToDWord Int8ToULong + +// +// INT8 -> DWORD_PTR conversion +// +#define Int8ToDWordPtr Int8ToULongPtr + +// +// INT8 -> ULONGLONG conversion +// +__inline +HRESULT +Int8ToULongLong( + __inn INT8 i8Operand, + __outt __deref_out_range(==,i8Operand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (i8Operand >= 0) + { + *pullResult = (ULONGLONG)i8Operand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT8 -> DWORDLONG conversion +// +#define Int8ToDWordLong Int8ToULongLong + +// +// INT8 -> ULONG64 conversion +// +#define Int8ToULong64 Int8ToULongLong + +// +// INT8 -> DWORD64 conversion +// +#define Int8ToDWord64 Int8ToULongLong + +// +// INT8 -> UINT64 conversion +// +#define Int8ToUInt64 Int8ToULongLong + +// +// INT8 -> size_t conversion +// +#define Int8ToSizeT Int8ToUIntPtr + +// +// INT8 -> SIZE_T conversion +// +#define Int8ToSIZET Int8ToULongPtr + +// +// UINT8 -> INT8 conversion +// +__inline +HRESULT +UInt8ToInt8( + __inn UINT8 u8Operand, + __outt __deref_out_range(==,u8Operand) INT8* pi8Result) +{ + HRESULT hr; + + if (u8Operand <= INT8_MAX) + { + *pi8Result = (INT8)u8Operand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT8 -> CHAR conversion +// +__forceinline +HRESULT +UInt8ToChar( + __inn UINT8 u8Operand, + __outt __deref_out_range(==,u8Operand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + *pch = (CHAR)u8Operand; + return S_OK; +#else + return UInt8ToInt8(u8Operand, (INT8*)pch); +#endif +} + +// +// BYTE -> INT8 conversion +// +__inline +HRESULT +ByteToInt8( + __inn BYTE bOperand, + __outt __deref_out_range(==,bOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (bOperand <= INT8_MAX) + { + *pi8Result = (INT8)bOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// BYTE -> CHAR conversion +// +__forceinline +HRESULT +ByteToChar( + __inn BYTE bOperand, + __outt __deref_out_range(==,bOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + *pch = (CHAR)bOperand; + return S_OK; +#else + return ByteToInt8(bOperand, (INT8*)pch); +#endif +} + +// +// SHORT -> INT8 conversion +// +__inline +HRESULT +ShortToInt8( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((sOperand >= INT8_MIN) && (sOperand <= INT8_MAX)) + { + *pi8Result = (INT8)sOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> UCHAR conversion +// +__inline +HRESULT +ShortToUChar( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((sOperand >= 0) && (sOperand <= 255)) + { + *pch = (UCHAR)sOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> CHAR conversion +// +__forceinline +HRESULT +ShortToChar( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return ShortToUChar(sOperand, (UCHAR*)pch); +#else + return ShortToInt8(sOperand, (INT8*)pch); +#endif // _CHAR_UNSIGNED +} + +// +// SHORT -> UINT8 conversion +// +__inline +HRESULT +ShortToUInt8( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if ((sOperand >= 0) && (sOperand <= UINT8_MAX)) + { + *pui8Result = (UINT8)sOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> BYTE conversion +// +#define ShortToByte ShortToUInt8 + +// +// SHORT -> USHORT conversion +// +__inline +HRESULT +ShortToUShort( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *pusResult = (USHORT)sOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> UINT16 conversion +// +#define ShortToUInt16 ShortToUShort + +// +// SHORT -> WORD conversion +// +#define ShortToWord ShortToUShort + +// +// SHORT -> UINT conversion +// +__inline +HRESULT +ShortToUInt( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) UINT* puResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *puResult = (UINT)sOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> UINT32 conversion +// +#define ShortToUInt32 ShortToUInt + +// +// SHORT -> UINT_PTR conversion +// +__inline +HRESULT +ShortToUIntPtr( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) UINT_PTR* puResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *puResult = (UINT_PTR)sOperand; + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> ULONG conversion +// +__inline +HRESULT +ShortToULong( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *pulResult = (ULONG)sOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> ULONG_PTR conversion +// +__inline +HRESULT +ShortToULongPtr( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *pulResult = (ULONG_PTR)sOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> DWORD conversion +// +#define ShortToDWord ShortToULong + +// +// SHORT -> DWORD_PTR conversion +// +__inline +HRESULT +ShortToDWordPtr( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) DWORD_PTR* pdwResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *pdwResult = (DWORD_PTR)sOperand; + hr = S_OK; + } + else + { + *pdwResult = DWORD_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> ULONGLONG conversion +// +__inline +HRESULT +ShortToULongLong( + __inn SHORT sOperand, + __outt __deref_out_range(==,sOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (sOperand >= 0) + { + *pullResult = (ULONGLONG)sOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SHORT -> DWORDLONG conversion +// +#define ShortToDWordLong ShortToULongLong + +// +// SHORT -> ULONG64 conversion +// +#define ShortToULong64 ShortToULongLong + +// +// SHORT -> DWORD64 conversion +// +#define ShortToDWord64 ShortToULongLong + +// +// SHORT -> UINT64 conversion +// +#define ShortToUInt64 ShortToULongLong + +// +// SHORT -> size_t conversion +// +#define ShortToSizeT ShortToUIntPtr + +// +// SHORT -> SIZE_T conversion +// +#define ShortToSIZET ShortToULongPtr + +// +// INT16 -> CHAR conversion +// +#define Int16ToChar ShortToChar + +// +// INT16 -> INT8 conversion +// +#define Int16ToInt8 ShortToInt8 + +// +// INT16 -> UCHAR conversion +// +#define Int16ToUChar ShortToUChar + +// +// INT16 -> UINT8 conversion +// +#define Int16ToUInt8 ShortToUInt8 + +// +// INT16 -> BYTE conversion +// +#define Int16ToByte ShortToUInt8 + +// +// INT16 -> USHORT conversion +// +#define Int16ToUShort ShortToUShort + +// +// INT16 -> UINT16 conversion +// +#define Int16ToUInt16 ShortToUShort + +// +// INT16 -> WORD conversion +// +#define Int16ToWord ShortToUShort + +// +// INT16 -> UINT conversion +// +#define Int16ToUInt ShortToUInt + +// +// INT16 -> UINT32 conversion +// +#define Int16ToUInt32 ShortToUInt + +// +// INT16 -> UINT_PTR conversion +// +#define Int16ToUIntPtr ShortToUIntPtr + +// +// INT16 -> ULONG conversion +// +#define Int16ToULong ShortToULong + +// +// INT16 -> ULONG_PTR conversion +// +#define Int16ToULongPtr ShortToULongPtr + +// +// INT16 -> DWORD conversion +// +#define Int16ToDWord ShortToULong + +// +// INT16 -> DWORD_PTR conversion +// +#define Int16ToDWordPtr ShortToULongPtr + +// +// INT16 -> ULONGLONG conversion +// +#define Int16ToULongLong ShortToULongLong + +// +// INT16 -> DWORDLONG conversion +// +#define Int16ToDWordLong ShortToULongLong + +// +// INT16 -> ULONG64 conversion +// +#define Int16ToULong64 ShortToULongLong + +// +// INT16 -> DWORD64 conversion +// +#define Int16ToDWord64 ShortToULongLong + +// +// INT16 -> UINT64 conversion +// +#define Int16ToUInt64 ShortToULongLong + +// +// INT16 -> size_t conversion +// +#define Int16ToSizeT ShortToUIntPtr + +// +// INT16 -> SIZE_T conversion +// +#define Int16ToSIZET ShortToULongPtr + +// +// USHORT -> INT8 conversion +// +__inline +HRESULT +UShortToInt8( + __inn USHORT usOperand, + __outt __deref_out_range(==,usOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (usOperand <= INT8_MAX) + { + *pi8Result = (INT8)usOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT -> UCHAR conversion +// +__inline +HRESULT +UShortToUChar( + __inn USHORT usOperand, + __outt __deref_out_range(==,usOperand) UCHAR* pch) +{ + HRESULT hr; + + if (usOperand <= 255) + { + *pch = (UCHAR)usOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT -> CHAR conversion +// +__forceinline +HRESULT +UShortToChar( + __inn USHORT usOperand, + __outt __deref_out_range(==,usOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return UShortToUChar(usOperand, (UCHAR*)pch); +#else + return UShortToInt8(usOperand, (INT8*)pch); +#endif // _CHAR_UNSIGNED +} + +// +// USHORT -> UINT8 conversion +// +__inline +HRESULT +UShortToUInt8( + __inn USHORT usOperand, + __outt __deref_out_range(==,usOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if (usOperand <= UINT8_MAX) + { + *pui8Result = (UINT8)usOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT -> BYTE conversion +// +#define UShortToByte UShortToUInt8 + +// +// USHORT -> SHORT conversion +// +__inline +HRESULT +UShortToShort( + __inn USHORT usOperand, + __outt __deref_out_range(==,usOperand) SHORT* psResult) +{ + HRESULT hr; + + if (usOperand <= SHORT_MAX) + { + *psResult = (SHORT)usOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT -> INT16 conversion +// +#define UShortToInt16 UShortToShort + +// +// UINT16 -> CHAR conversion +// +#define UInt16ToChar UShortToChar + +// +// UINT16 -> INT8 conversion +// +#define UInt16ToInt8 UShortToInt8 + +// +// UINT16 -> UCHAR conversion +// +#define UInt16ToUChar UShortToUChar + +// +// UINT16 -> UINT8 conversion +// +#define UInt16ToUInt8 UShortToUInt8 + +// +// UINT16 -> BYTE conversion +// +#define UInt16ToByte UShortToUInt8 + +// +// UINT16 -> SHORT conversion +// +#define UInt16ToShort UShortToShort + +// +// UINT16 -> INT16 conversion +// +#define UInt16ToInt16 UShortToShort + +// +// WORD -> INT8 conversion +// +#define WordToInt8 UShortToInt8 + +// +// WORD -> CHAR conversion +// +#define WordToChar UShortToChar + +// +// WORD -> UCHAR conversion +// +#define WordToUChar UShortToUChar + +// +// WORD -> UINT8 conversion +// +#define WordToUInt8 UShortToUInt8 + +// +// WORD -> BYTE conversion +// +#define WordToByte UShortToUInt8 + +// +// WORD -> SHORT conversion +// +#define WordToShort UShortToShort + +// +// WORD -> INT16 conversion +// +#define WordToInt16 UShortToShort + +// +// INT -> INT8 conversion +// +__inline +HRESULT +IntToInt8( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((iOperand >= INT8_MIN) && (iOperand <= INT8_MAX)) + { + *pi8Result = (INT8)iOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> UCHAR conversion +// +__inline +HRESULT +IntToUChar( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= 255)) + { + *pch = (UCHAR)iOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> CHAR conversion +// +__forceinline +HRESULT +IntToChar( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return IntToUChar(iOperand, (UCHAR*)pch); +#else + return IntToInt8(iOperand, (INT8*)pch); +#endif // _CHAR_UNSIGNED +} + +// +// INT -> BYTE conversion +// +#define IntToByte IntToUInt8 + +// +// INT -> UINT8 conversion +// +__inline +HRESULT +IntToUInt8( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= UINT8_MAX)) + { + *pui8Result = (UINT8)iOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> SHORT conversion +// +__inline +HRESULT +IntToShort( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) SHORT* psResult) +{ + HRESULT hr; + + if ((iOperand >= SHORT_MIN) && (iOperand <= SHORT_MAX)) + { + *psResult = (SHORT)iOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> INT16 conversion +// +#define IntToInt16 IntToShort + +// +// INT -> USHORT conversion +// +__inline +HRESULT +IntToUShort( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) USHORT* pusResult) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= USHORT_MAX)) + { + *pusResult = (USHORT)iOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> UINT16 conversion +// +#define IntToUInt16 IntToUShort + +// +// INT -> WORD conversion +// +#define IntToWord IntToUShort + +// +// INT -> UINT conversion +// +__inline +HRESULT +IntToUInt( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) UINT* puResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *puResult = (UINT)iOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> UINT_PTR conversion +// +#ifdef _WIN64 +#define IntToUIntPtr IntToULongLong +#else +#define IntToUIntPtr IntToUInt +#endif + +// +// INT -> ULONG conversion +// +__inline +HRESULT +IntToULong( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *pulResult = (ULONG)iOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> ULONG_PTR conversion +// +#ifdef _WIN64 +#define IntToULongPtr IntToULongLong +#else +#define IntToULongPtr IntToULong +#endif + +// +// INT -> DWORD conversion +// +#define IntToDWord IntToULong + +// +// INT -> DWORD_PTR conversion +// +#define IntToDWordPtr IntToULongPtr + +// +// INT -> ULONGLONG conversion +// +__inline +HRESULT +IntToULongLong( + __inn INT iOperand, + __outt __deref_out_range(==,iOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *pullResult = (ULONGLONG)iOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT -> DWORDLONG conversion +// +#define IntToDWordLong IntToULongLong + +// +// INT -> ULONG64 conversion +// +#define IntToULong64 IntToULongLong + +// +// INT -> DWORD64 conversion +// +#define IntToDWord64 IntToULongLong + +// +// INT -> UINT64 conversion +// +#define IntToUInt64 IntToULongLong + +// +// INT -> size_t conversion +// +#define IntToSizeT IntToUIntPtr + +// +// INT -> SIZE_T conversion +// +#define IntToSIZET IntToULongPtr + +// +// INT32 -> CHAR conversion +// +#define Int32ToChar IntToChar + +// +// INT32 -> INT328 conversion +// +#define Int32ToInt8 IntToInt8 + +// +// INT32 -> UCHAR conversion +// +#define Int32ToUChar IntToUChar + +// +// INT32 -> BYTE conversion +// +#define Int32ToByte IntToUInt8 + +// +// INT32 -> UINT8 conversion +// +#define Int32ToUInt8 IntToUInt8 + +// +// INT32 -> SHORT conversion +// +#define Int32ToShort IntToShort + +// +// INT32 -> INT16 conversion +// +#define Int32ToInt16 IntToShort + +// +// INT32 -> USHORT conversion +// +#define Int32ToUShort IntToUShort + +// +// INT32 -> UINT16 conversion +// +#define Int32ToUInt16 IntToUShort + +// +// INT32 -> WORD conversion +// +#define Int32ToWord IntToUShort + +// +// INT32 -> UINT conversion +// +#define Int32ToUInt IntToUInt + +// +// INT32 -> UINT32 conversion +// +#define Int32ToUInt32 IntToUInt + +// +// INT32 -> UINT_PTR conversion +// +#define Int32ToUIntPtr IntToUIntPtr + +// +// INT32 -> ULONG conversion +// +#define Int32ToULong IntToULong + +// +// INT32 -> ULONG_PTR conversion +// +#define Int32ToULongPtr IntToULongPtr + +// +// INT32 -> DWORD conversion +// +#define Int32ToDWord IntToULong + +// +// INT32 -> DWORD_PTR conversion +// +#define Int32ToDWordPtr IntToULongPtr + +// +// INT32 -> ULONGLONG conversion +// +#define Int32ToULongLong IntToULongLong + +// +// INT32 -> DWORDLONG conversion +// +#define Int32ToDWordLong IntToULongLong + +// +// INT32 -> ULONG64 conversion +// +#define Int32ToULong64 IntToULongLong + +// +// INT32 -> DWORD64 conversion +// +#define Int32ToDWord64 IntToULongLong + +// +// INT32 -> UINT64 conversion +// +#define Int32ToUInt64 IntToULongLong + +// +// INT32 -> size_t conversion +// +#define Int32ToSizeT IntToUIntPtr + +// +// INT32 -> SIZE_T conversion +// +#define Int32ToSIZET IntToULongPtr + +// +// INT_PTR -> INT8 conversion +// +__inline +HRESULT +IntPtrToInt8( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((iOperand >= INT8_MIN) && (iOperand <= INT8_MAX)) + { + *pi8Result = (INT8)iOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT_PTR -> UCHAR conversion +// +__inline +HRESULT +IntPtrToUChar( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= 255)) + { + *pch = (UCHAR)iOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT_PTR -> CHAR conversion +// +__forceinline +HRESULT +IntPtrToChar( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return IntPtrToUChar(iOperand, (UCHAR*)pch); +#else + return IntPtrToInt8(iOperand, (INT8*)pch); +#endif // _CHAR_UNSIGNED +} + +// +// INT_PTR -> UINT8 conversion +// +__inline +HRESULT +IntPtrToUInt8( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= UINT8_MAX)) + { + *pui8Result = (UINT8)iOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT_PTR -> BYTE conversion +// +#define IntPtrToByte IntPtrToUInt8 + +// +// INT_PTR -> SHORT conversion +// +__inline +HRESULT +IntPtrToShort( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) SHORT* psResult) +{ + HRESULT hr; + + if ((iOperand >= SHORT_MIN) && (iOperand <= SHORT_MAX)) + { + *psResult = (SHORT)iOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT_PTR -> INT16 conversion +// +#define IntPtrToInt16 IntPtrToShort + +// +// INT_PTR -> USHORT conversion +// +__inline +HRESULT +IntPtrToUShort( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) USHORT* pusResult) +{ + HRESULT hr; + + if ((iOperand >= 0) && (iOperand <= USHORT_MAX)) + { + *pusResult = (USHORT)iOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// INT_PTR -> UINT16 conversion +// +#define IntPtrToUInt16 IntPtrToUShort + +// +// INT_PTR -> WORD conversion +// +#define IntPtrToWord IntPtrToUShort + +// +// INT_PTR -> INT conversion +// +#ifdef _WIN64 +#define IntPtrToInt LongLongToInt +#else +__inline +HRESULT +IntPtrToInt( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) INT* piResult) +{ + *piResult = (INT)iOperand; + return S_OK; +} +#endif + +// +// INT_PTR -> INT32 conversion +// +#define IntPtrToInt32 IntPtrToInt + +// +// INT_PTR -> UINT conversion +// +#ifdef _WIN64 +#define IntPtrToUInt LongLongToUInt +#else +__inline +HRESULT +IntPtrToUInt( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) UINT* puResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *puResult = (UINT)iOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// INT_PTR -> UINT32 conversion +// +#define IntPtrToUInt32 IntPtrToUInt + +// +// INT_PTR -> UINT_PTR conversion +// +#ifdef _WIN64 +#define IntPtrToUIntPtr LongLongToULongLong +#else +__inline +HRESULT +IntPtrToUIntPtr( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) UINT_PTR* puResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *puResult = (UINT_PTR)iOperand; + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// INT_PTR -> LONG conversion +// +#ifdef _WIN64 +#define IntPtrToLong LongLongToLong +#else +__inline +HRESULT +IntPtrToLong( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) LONG* plResult) +{ + *plResult = (LONG)iOperand; + return S_OK; +} +#endif + +// +// INT_PTR -> LONG_PTR conversion +// +__inline +HRESULT +IntPtrToLongPtr( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) LONG_PTR* plResult) +{ + *plResult = (LONG_PTR)iOperand; + return S_OK; +} + +// +// INT_PTR -> ULONG conversion +// +#ifdef _WIN64 +#define IntPtrToULong LongLongToULong +#else +__inline +HRESULT +IntPtrToULong( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *pulResult = (ULONG)iOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// INT_PTR -> ULONG_PTR conversion +// +#ifdef _WIN64 +#define IntPtrToULongPtr LongLongToULongLong +#else +__inline +HRESULT +IntPtrToULongPtr( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *pulResult = (ULONG_PTR)iOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// INT_PTR -> DWORD conversion +// +#define IntPtrToDWord IntPtrToULong + +// +// INT_PTR -> DWORD_PTR conversion +// +#define IntPtrToDWordPtr IntPtrToULongPtr + +// +// INT_PTR -> ULONGLONG conversion +// +#ifdef _WIN64 +#define IntPtrToULongLong LongLongToULongLong +#else +__inline +HRESULT +IntPtrToULongLong( + __inn INT_PTR iOperand, + __outt __deref_out_range(==,iOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (iOperand >= 0) + { + *pullResult = (ULONGLONG)iOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// INT_PTR -> DWORDLONG conversion +// +#define IntPtrToDWordLong IntPtrToULongLong + +// +// INT_PTR -> ULONG64 conversion +// +#define IntPtrToULong64 IntPtrToULongLong + +// +// INT_PTR -> DWORD64 conversion +// +#define IntPtrToDWord64 IntPtrToULongLong + +// +// INT_PTR -> UINT64 conversion +// +#define IntPtrToUInt64 IntPtrToULongLong + +// +// INT_PTR -> size_t conversion +// +#define IntPtrToSizeT IntPtrToUIntPtr + +// +// INT_PTR -> SIZE_T conversion +// +#define IntPtrToSIZET IntPtrToULongPtr + +// +// UINT -> INT8 conversion +// +__inline +HRESULT +UIntToInt8( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (uOperand <= INT8_MAX) + { + *pi8Result = (INT8)uOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> UCHAR conversion +// +__inline +HRESULT +UIntToUChar( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) UCHAR* pch) +{ + HRESULT hr; + + if (uOperand <= 255) + { + *pch = (UCHAR)uOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> CHAR conversion +// +__forceinline +HRESULT +UIntToChar( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return UIntToUChar(uOperand, (UCHAR*)pch); +#else + return UIntToInt8(uOperand, (INT8*)pch); +#endif +} + +// +// UINT -> UINT8 conversion +// +__inline +HRESULT +UIntToUInt8( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if (uOperand <= UINT8_MAX) + { + *pui8Result = (UINT8)uOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> BYTE conversion +// +#define UIntToByte UIntToUInt8 + +// +// UINT -> SHORT conversion +// +__inline +HRESULT +UIntToShort( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) SHORT* psResult) +{ + HRESULT hr; + + if (uOperand <= SHORT_MAX) + { + *psResult = (SHORT)uOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> INT16 conversion +// +#define UIntToInt16 UIntToShort + +// +// UINT -> USHORT conversion +// +__inline +HRESULT +UIntToUShort( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (uOperand <= USHORT_MAX) + { + *pusResult = (USHORT)uOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> UINT16 conversion +// +#define UIntToUInt16 UIntToUShort + +// +// UINT -> WORD conversion +// +#define UIntToWord UIntToUShort + +// +// UINT -> INT conversion +// +__inline +HRESULT +UIntToInt( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) INT* piResult) +{ + HRESULT hr; + + if (uOperand <= INT_MAX) + { + *piResult = (INT)uOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> INT32 conversion +// +#define UIntToInt32 UIntToInt + +// +// UINT -> INT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +UIntToIntPtr( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) INT_PTR* piResult) +{ + *piResult = uOperand; + return S_OK; +} +#else +#define UIntToIntPtr UIntToInt +#endif + +// +// UINT -> LONG conversion +// +__inline +HRESULT +UIntToLong( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) LONG* plResult) +{ + HRESULT hr; + + if (uOperand <= INT32_MAX) + { + *plResult = (LONG)uOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT -> LONG_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +UIntToLongPtr( + __inn UINT uOperand, + __outt __deref_out_range(==,uOperand) LONG_PTR* plResult) +{ + *plResult = uOperand; + return S_OK; +} +#else +#define UIntToLongPtr UIntToLong +#endif + +// +// UINT -> ptrdiff_t conversion +// +#define UIntToPtrdiffT UIntToIntPtr + +// +// UINT -> SSIZE_T conversion +// +#define UIntToSSIZET UIntToLongPtr + +// +// UINT32 -> CHAR conversion +// +#define UInt32ToChar UIntToChar + +// +// UINT32 -> INT8 conversion +// +#define UInt32ToInt8 UIntToInt8 + +// +// UINT32 -> UCHAR conversion +// +#define UInt32ToUChar UIntToUChar + +// +// UINT32 -> UINT8 conversion +// +#define UInt32ToUInt8 UIntToUInt8 + +// +// UINT32 -> BYTE conversion +// +#define UInt32ToByte UInt32ToUInt8 + +// +// UINT32 -> SHORT conversion +// +#define UInt32ToShort UIntToShort + +// +// UINT32 -> INT16 conversion +// +#define UInt32ToInt16 UIntToShort + +// +// UINT32 -> USHORT conversion +// +#define UInt32ToUShort UIntToUShort + +// +// UINT32 -> UINT16 conversion +// +#define UInt32ToUInt16 UIntToUShort + +// +// UINT32 -> WORD conversion +// +#define UInt32ToWord UIntToUShort + +// +// UINT32 -> INT conversion +// +#define UInt32ToInt UIntToInt + +// +// UINT32 -> INT_PTR conversion +// +#define UInt32ToIntPtr UIntToIntPtr + +// +// UINT32 -> INT32 conversion +// +#define UInt32ToInt32 UIntToInt + +// +// UINT32 -> LONG conversion +// +#define UInt32ToLong UIntToLong + +// +// UINT32 -> LONG_PTR conversion +// +#define UInt32ToLongPtr UIntToLongPtr + +// +// UINT32 -> ptrdiff_t conversion +// +#define UInt32ToPtrdiffT UIntToPtrdiffT + +// +// UINT32 -> SSIZE_T conversion +// +#define UInt32ToSSIZET UIntToSSIZET + +// +// UINT_PTR -> INT8 conversion +// +__inline +HRESULT +UIntPtrToInt8( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (uOperand <= INT8_MAX) + { + *pi8Result = (INT8)uOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> UCHAR conversion +// +__inline +HRESULT +UIntPtrToUChar( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) UCHAR* pch) +{ + HRESULT hr; + + if (uOperand <= 255) + { + *pch = (UCHAR)uOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> CHAR conversion +// +__forceinline +HRESULT +UIntPtrToChar( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return UIntPtrToUChar(uOperand, (UCHAR*)pch); +#else + return UIntPtrToInt8(uOperand, (INT8*)pch); +#endif +} + +// +// UINT_PTR -> UINT8 conversion +// +__inline +HRESULT +UIntPtrToUInt8( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) UINT8* pu8Result) +{ + HRESULT hr; + + if (uOperand <= UINT8_MAX) + { + *pu8Result = (UINT8)uOperand; + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> BYTE conversion +// +#define UIntPtrToByte UIntPtrToUInt8 + +// +// UINT_PTR -> SHORT conversion +// +__inline +HRESULT +UIntPtrToShort( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) SHORT* psResult) +{ + HRESULT hr; + + if (uOperand <= SHORT_MAX) + { + *psResult = (SHORT)uOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> INT16 conversion +// +__inline +HRESULT +UIntPtrToInt16( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) INT16* pi16Result) +{ + HRESULT hr; + + if (uOperand <= INT16_MAX) + { + *pi16Result = (INT16)uOperand; + hr = S_OK; + } + else + { + *pi16Result = INT16_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> USHORT conversion +// +__inline +HRESULT +UIntPtrToUShort( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (uOperand <= USHORT_MAX) + { + *pusResult = (USHORT)uOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> UINT16 conversion +// +__inline +HRESULT +UIntPtrToUInt16( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) UINT16* pu16Result) +{ + HRESULT hr; + + if (uOperand <= UINT16_MAX) + { + *pu16Result = (UINT16)uOperand; + hr = S_OK; + } + else + { + *pu16Result = UINT16_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> WORD conversion +// +#define UIntPtrToWord UIntPtrToUShort + +// +// UINT_PTR -> INT conversion +// +__inline +HRESULT +UIntPtrToInt( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) INT* piResult) +{ + HRESULT hr; + + if (uOperand <= INT_MAX) + { + *piResult = (INT)uOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> INT32 conversion +// +#define UIntPtrToInt32 UIntPtrToInt + +// +// UINT_PTR -> INT_PTR conversion +// +__inline +HRESULT +UIntPtrToIntPtr( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) INT_PTR* piResult) +{ + HRESULT hr; + + if (uOperand <= INT_PTR_MAX) + { + *piResult = (INT_PTR)uOperand; + hr = S_OK; + } + else + { + *piResult = INT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> UINT conversion +// +#ifdef _WIN64 +#define UIntPtrToUInt ULongLongToUInt +#else +__inline +HRESULT +UIntPtrToUInt( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) UINT* puResult) +{ + *puResult = (UINT)uOperand; + return S_OK; +} +#endif + +// +// UINT_PTR -> UINT32 conversion +// +#define UIntPtrToUInt32 UIntPtrToUInt + +// +// UINT_PTR -> LONG conversion +// +__inline +HRESULT +UIntPtrToLong( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) LONG* plResult) +{ + HRESULT hr; + + if (uOperand <= INT32_MAX) + { + *plResult = (LONG)uOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> LONG_PTR conversion +// +__inline +HRESULT +UIntPtrToLongPtr( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) LONG_PTR* plResult) +{ + HRESULT hr; + + if (uOperand <= LONG_PTR_MAX) + { + *plResult = (LONG_PTR)uOperand; + hr = S_OK; + } + else + { + *plResult = LONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT_PTR -> ULONG conversion +// +#ifdef _WIN64 +#define UIntPtrToULong ULongLongToULong +#else +__inline +HRESULT +UIntPtrToULong( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) ULONG* pulResult) +{ + *pulResult = (ULONG)uOperand; + return S_OK; +} +#endif + +// +// UINT_PTR -> DWORD conversion +// +#define UIntPtrToDWord UIntPtrToULong + +// +// UINT_PTR -> LONGLONG conversion +// +#ifdef _WIN64 +#define UIntPtrToLongLong ULongLongToLongLong +#else +__inline +HRESULT +UIntPtrToLongLong( + __inn UINT_PTR uOperand, + __outt __deref_out_range(==,uOperand) LONGLONG* pllResult) +{ + *pllResult = (LONGLONG)uOperand; + return S_OK; +} +#endif + +// +// UINT_PTR -> LONG64 conversion +// +#define UIntPtrToLong64 UIntPtrToLongLong + +// +// UINT_PTR -> INT64 conversion +// +#define UIntPtrToInt64 UIntPtrToLongLong + +// +// UINT_PTR -> ptrdiff_t conversion +// +#define UIntPtrToPtrdiffT UIntPtrToIntPtr + +// +// UINT_PTR -> SSIZE_T conversion +// +#define UIntPtrToSSIZET UIntPtrToLongPtr + +// +// LONG -> INT8 conversion +// +__inline +HRESULT +LongToInt8( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((lOperand >= INT8_MIN) && (lOperand <= INT8_MAX)) + { + *pi8Result = (INT8)lOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> UCHAR conversion +// +__inline +HRESULT +LongToUChar( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= 255)) + { + *pch = (UCHAR)lOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> CHAR conversion +// +__forceinline +HRESULT +LongToChar( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return LongToUChar(lOperand, (UCHAR*)pch); +#else + return LongToInt8(lOperand, (INT8*)pch); +#endif +} + +// +// LONG -> UINT8 conversion +// +__inline +HRESULT +LongToUInt8( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= UINT8_MAX)) + { + *pui8Result = (UINT8)lOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> BYTE conversion +// +#define LongToByte LongToUInt8 + +// +// LONG -> SHORT conversion +// +__inline +HRESULT +LongToShort( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) SHORT* psResult) +{ + HRESULT hr; + + if ((lOperand >= SHORT_MIN) && (lOperand <= SHORT_MAX)) + { + *psResult = (SHORT)lOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> INT16 conversion +// +#define LongToInt16 LongToShort + +// +// LONG -> USHORT conversion +// +__inline +HRESULT +LongToUShort( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) USHORT* pusResult) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= USHORT_MAX)) + { + *pusResult = (USHORT)lOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> UINT16 conversion +// +#define LongToUInt16 LongToUShort + +// +// LONG -> WORD conversion +// +#define LongToWord LongToUShort + +// +// LONG -> INT conversion +// +__inline +HRESULT +LongToInt( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) INT* piResult) +{ + C_ASSERT(sizeof(INT) == sizeof(LONG)); + *piResult = (INT)lOperand; + return S_OK; +} + +// +// LONG -> INT32 conversion +// +#define LongToInt32 LongToInt + +// +// LONG -> INT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +LongToIntPtr( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) INT_PTR* piResult) +{ + *piResult = lOperand; + return S_OK; +} +#else +#define LongToIntPtr LongToInt +#endif + +// +// LONG -> UINT conversion +// +__inline +HRESULT +LongToUInt( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) UINT* puResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *puResult = (UINT)lOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> UINT32 conversion +// +#define LongToUInt32 LongToUInt + +// +// LONG -> UINT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +LongToUIntPtr( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) UINT_PTR* puResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *puResult = (UINT_PTR)lOperand; + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#else +#define LongToUIntPtr LongToUInt +#endif + +// +// LONG -> ULONG conversion +// +__inline +HRESULT +LongToULong( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pulResult = (ULONG)lOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> ULONG_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +LongToULongPtr( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pulResult = (ULONG_PTR)lOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#else +#define LongToULongPtr LongToULong +#endif + +// +// LONG -> DWORD conversion +// +#define LongToDWord LongToULong + +// +// LONG -> DWORD_PTR conversion +// +#define LongToDWordPtr LongToULongPtr + +// +// LONG -> ULONGLONG conversion +// +__inline +HRESULT +LongToULongLong( + __inn LONG lOperand, + __outt __deref_out_range(==,lOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pullResult = (ULONGLONG)lOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG -> DWORDLONG conversion +// +#define LongToDWordLong LongToULongLong + +// +// LONG -> ULONG64 conversion +// +#define LongToULong64 LongToULongLong + +// +// LONG -> DWORD64 conversion +// +#define LongToDWord64 LongToULongLong + +// +// LONG -> UINT64 conversion +// +#define LongToUInt64 LongToULongLong + +// +// LONG -> ptrdiff_t conversion +// +#define LongToPtrdiffT LongToIntPtr + +// +// LONG -> size_t conversion +// +#define LongToSizeT LongToUIntPtr + +// +// LONG -> SIZE_T conversion +// +#define LongToSIZET LongToULongPtr + +// +// LONG_PTR -> INT8 conversion +// +__inline +HRESULT +LongPtrToInt8( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((lOperand >= INT8_MIN) && (lOperand <= INT8_MAX)) + { + *pi8Result = (INT8)lOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> UCHAR conversion +// +__inline +HRESULT +LongPtrToUChar( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= 255)) + { + *pch = (UCHAR)lOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> CHAR conversion +// +__forceinline +HRESULT +LongPtrToChar( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return LongPtrToUChar(lOperand, (UCHAR*)pch); +#else + return LongPtrToInt8(lOperand, (INT8*)pch); +#endif +} + +// +// LONG_PTR -> UINT8 conversion +// +__inline +HRESULT +LongPtrToUInt8( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= UINT8_MAX)) + { + *pui8Result = (UINT8)lOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> BYTE conversion +// +#define LongPtrToByte LongPtrToUInt8 + +// +// LONG_PTR -> SHORT conversion +// +__inline +HRESULT +LongPtrToShort( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) SHORT* psResult) +{ + HRESULT hr; + + if ((lOperand >= SHORT_MIN) && (lOperand <= SHORT_MAX)) + { + *psResult = (SHORT)lOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> INT16 conversion +// +#define LongPtrToInt16 LongPtrToShort + +// +// LONG_PTR -> USHORT conversion +// +__inline +HRESULT +LongPtrToUShort( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) USHORT* pusResult) +{ + HRESULT hr; + + if ((lOperand >= 0) && (lOperand <= USHORT_MAX)) + { + *pusResult = (USHORT)lOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> UINT16 conversion +// +#define LongPtrToUInt16 LongPtrToUShort + +// +// LONG_PTR -> WORD conversion +// +#define LongPtrToWord LongPtrToUShort + +// +// LONG_PTR -> INT conversion +// +#ifdef _WIN64 +#define LongPtrToInt LongLongToInt +#else +__inline +HRESULT +LongPtrToInt( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) INT* piResult) +{ + C_ASSERT(sizeof(INT) == sizeof(LONG_PTR)); + *piResult = (INT)lOperand; + return S_OK; +} +#endif + +// +// LONG_PTR -> INT32 conversion +// +#define LongPtrToInt32 LongPtrToInt + +// +// LONG_PTR -> INT_PTR conversion +// +__inline +HRESULT +LongPtrToIntPtr( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) INT_PTR* piResult) +{ + C_ASSERT(sizeof(LONG_PTR) == sizeof(INT_PTR)); + *piResult = (INT_PTR)lOperand; + return S_OK; +} + +// +// LONG_PTR -> UINT conversion +// +#ifdef _WIN64 +#define LongPtrToUInt LongLongToUInt +#else +__inline +HRESULT +LongPtrToUInt( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) UINT* puResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *puResult = (UINT)lOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// LONG_PTR -> UINT32 conversion +// +#define LongPtrToUInt32 LongPtrToUInt + +// +// LONG_PTR -> UINT_PTR conversion +// +__inline +HRESULT +LongPtrToUIntPtr( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) UINT_PTR* puResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *puResult = (UINT_PTR)lOperand; + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> LONG conversion +// +#ifdef _WIN64 +#define LongPtrToLong LongLongToLong +#else +__inline +HRESULT +LongPtrToLong( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) LONG* plResult) +{ + *plResult = (LONG)lOperand; + return S_OK; +} +#endif + +// +// LONG_PTR -> ULONG conversion +// +#ifdef _WIN64 +#define LongPtrToULong LongLongToULong +#else +__inline +HRESULT +LongPtrToULong( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pulResult = (ULONG)lOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif + +// +// LONG_PTR -> ULONG_PTR conversion +// +__inline +HRESULT +LongPtrToULongPtr( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pulResult = (ULONG_PTR)lOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> DWORD conversion +// +#define LongPtrToDWord LongPtrToULong + +// +// LONG_PTR -> DWORD_PTR conversion +// +#define LongPtrToDWordPtr LongPtrToULongPtr + +// +// LONG_PTR -> ULONGLONG conversion +// +__inline +HRESULT +LongPtrToULongLong( + __inn LONG_PTR lOperand, + __outt __deref_out_range(==,lOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (lOperand >= 0) + { + *pullResult = (ULONGLONG)lOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONG_PTR -> DWORDLONG conversion +// +#define LongPtrToDWordLong LongPtrToULongLong + +// +// LONG_PTR -> ULONG64 conversion +// +#define LongPtrToULong64 LongPtrToULongLong + +// +// LONG_PTR -> DWORD64 conversion +// +#define LongPtrToDWord64 LongPtrToULongLong + +// +// LONG_PTR -> UINT64 conversion +// +#define LongPtrToUInt64 LongPtrToULongLong + +// +// LONG_PTR -> size_t conversion +// +#define LongPtrToSizeT LongPtrToUIntPtr + +// +// LONG_PTR -> SIZE_T conversion +// +#define LongPtrToSIZET LongPtrToULongPtr + +// +// ULONG -> INT8 conversion +// +__inline +HRESULT +ULongToInt8( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (ulOperand <= INT8_MAX) + { + *pi8Result = (INT8)ulOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> UCHAR conversion +// +__inline +HRESULT +ULongToUChar( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) UCHAR* pch) +{ + HRESULT hr; + + if (ulOperand <= 255) + { + *pch = (UCHAR)ulOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> CHAR conversion +// +__forceinline +HRESULT +ULongToChar( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return ULongToUChar(ulOperand, (UCHAR*)pch); +#else + return ULongToInt8(ulOperand, (INT8*)pch); +#endif +} + +// +// ULONG -> UINT8 conversion +// +__inline +HRESULT +ULongToUInt8( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if (ulOperand <= UINT8_MAX) + { + *pui8Result = (UINT8)ulOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> BYTE conversion +// +#define ULongToByte ULongToUInt8 + +// +// ULONG -> SHORT conversion +// +__inline +HRESULT +ULongToShort( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) SHORT* psResult) +{ + HRESULT hr; + + if (ulOperand <= SHORT_MAX) + { + *psResult = (SHORT)ulOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> INT16 conversion +// +#define ULongToInt16 ULongToShort + +// +// ULONG -> USHORT conversion +// +__inline +HRESULT +ULongToUShort( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (ulOperand <= USHORT_MAX) + { + *pusResult = (USHORT)ulOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> UINT16 conversion +// +#define ULongToUInt16 ULongToUShort + +// +// ULONG -> WORD conversion +// +#define ULongToWord ULongToUShort + +// +// ULONG -> INT conversion +// +__inline +HRESULT +ULongToInt( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) INT* piResult) +{ + HRESULT hr; + + if (ulOperand <= INT_MAX) + { + *piResult = (INT)ulOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> INT32 conversion +// +#define ULongToInt32 ULongToInt + +// +// ULONG -> INT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +ULongToIntPtr( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) INT_PTR* piResult) +{ + *piResult = (INT_PTR)ulOperand; + return S_OK; +} +#else +#define ULongToIntPtr ULongToInt +#endif + +// +// ULONG -> UINT conversion +// +__inline +HRESULT +ULongToUInt( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) UINT* puResult) +{ + //C_ASSERT(sizeof(ULONG) == sizeof(UINT)); + *puResult = (UINT)ulOperand; + return S_OK; +} + +// +// ULONG -> UINT32 conversion +// +#define ULongToUInt32 ULongToUInt + +// +// ULONG -> UINT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +ULongToUIntPtr( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) UINT_PTR* puiResult) +{ + C_ASSERT(sizeof(UINT_PTR) > sizeof(ULONG)); + *puiResult = (UINT_PTR)ulOperand; + return S_OK; +} +#else +#define ULongToUIntPtr ULongToUInt +#endif + +// +// ULONG -> LONG conversion +// +__inline +HRESULT +ULongToLong( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) LONG* plResult) +{ + HRESULT hr; + + if (ulOperand <= INT32_MAX) + { + *plResult = (LONG)ulOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG -> LONG_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +ULongToLongPtr( + __inn ULONG ulOperand, + __outt __deref_out_range(==,ulOperand) LONG_PTR* plResult) +{ + C_ASSERT(sizeof(LONG_PTR) > sizeof(ULONG)); + *plResult = (LONG_PTR)ulOperand; + return S_OK; +} +#else +#define ULongToLongPtr ULongToLong +#endif + +// +// ULONG -> ptrdiff_t conversion +// +#define ULongToPtrdiffT ULongToIntPtr + +// +// ULONG -> SSIZE_T conversion +// +#define ULongToSSIZET ULongToLongPtr + +// +// ULONG_PTR -> INT8 conversion +// +__inline +HRESULT +ULongPtrToInt8( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (ulOperand <= INT8_MAX) + { + *pi8Result = (INT8)ulOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> UCHAR conversion +// +__inline +HRESULT +ULongPtrToUChar( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) UCHAR* pch) +{ + HRESULT hr; + + if (ulOperand <= 255) + { + *pch = (UCHAR)ulOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> CHAR conversion +// +__forceinline +HRESULT +ULongPtrToChar( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return ULongPtrToUChar(ulOperand, (UCHAR*)pch); +#else + return ULongPtrToInt8(ulOperand, (INT8*)pch); +#endif +} + +// +// ULONG_PTR -> UINT8 conversion +// +__inline +HRESULT +ULongPtrToUInt8( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) UINT8* pui8Result) +{ + HRESULT hr; + + if (ulOperand <= UINT8_MAX) + { + *pui8Result = (UINT8)ulOperand; + hr = S_OK; + } + else + { + *pui8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> BYTE conversion +// +#define ULongPtrToByte ULongPtrToUInt8 + +// +// ULONG_PTR -> SHORT conversion +// +__inline +HRESULT +ULongPtrToShort( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) SHORT* psResult) +{ + HRESULT hr; + + if (ulOperand <= SHORT_MAX) + { + *psResult = (SHORT)ulOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> INT16 conversion +// +#define ULongPtrToInt16 ULongPtrToShort + +// +// ULONG_PTR -> USHORT conversion +// +__inline +HRESULT +ULongPtrToUShort( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (ulOperand <= USHORT_MAX) + { + *pusResult = (USHORT)ulOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> UINT16 conversion +// +#define ULongPtrToUInt16 ULongPtrToUShort + +// +// ULONG_PTR -> WORD conversion +// +#define ULongPtrToWord ULongPtrToUShort + +// +// ULONG_PTR -> INT conversion +// +__inline +HRESULT +ULongPtrToInt( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) INT* piResult) +{ + HRESULT hr; + + if (ulOperand <= INT_MAX) + { + *piResult = (INT)ulOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> INT32 conversion +// +#define ULongPtrToInt32 ULongPtrToInt + +// +// ULONG_PTR -> INT_PTR conversion +// +__inline +HRESULT +ULongPtrToIntPtr( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) INT_PTR* piResult) +{ + HRESULT hr; + + if (ulOperand <= INT_PTR_MAX) + { + *piResult = (INT_PTR)ulOperand; + hr = S_OK; + } + else + { + *piResult = INT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> UINT conversion +// +#ifdef _WIN64 +#define ULongPtrToUInt ULongLongToUInt +#else +__inline +HRESULT +ULongPtrToUInt( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) UINT* puResult) +{ + C_ASSERT(sizeof(ULONG_PTR) == sizeof(UINT)); + *puResult = (UINT)ulOperand; + return S_OK; +} +#endif + +// +// ULONG_PTR -> UINT32 conversion +// +#define ULongPtrToUInt32 ULongPtrToUInt + +// +// ULONG_PTR -> UINT_PTR conversion +// +__inline +HRESULT +ULongPtrToUIntPtr( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) UINT_PTR* puResult) +{ + *puResult = (UINT_PTR)ulOperand; + return S_OK; +} + +// +// ULONG_PTR -> LONG conversion +// +__inline +HRESULT +ULongPtrToLong( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) LONG* plResult) +{ + HRESULT hr; + + if (ulOperand <= INT32_MAX) + { + *plResult = (LONG)ulOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> LONG_PTR conversion +// +__inline +HRESULT +ULongPtrToLongPtr( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) LONG_PTR* plResult) +{ + HRESULT hr; + + if (ulOperand <= LONG_PTR_MAX) + { + *plResult = (LONG_PTR)ulOperand; + hr = S_OK; + } + else + { + *plResult = LONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR -> ULONG conversion +// +#ifdef _WIN64 +#define ULongPtrToULong ULongLongToULong +#else +__inline +HRESULT +ULongPtrToULong( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) ULONG* pulResult) +{ + *pulResult = (ULONG)ulOperand; + return S_OK; +} +#endif + +// +// ULONG_PTR -> DWORD conversion +// +#define ULongPtrToDWord ULongPtrToULong + +// +// ULONG_PTR -> LONGLONG conversion +// +#ifdef _WIN64 +#define ULongPtrToLongLong ULongLongToLongLong +#else +__inline +HRESULT +ULongPtrToLongLong( + __inn ULONG_PTR ulOperand, + __outt __deref_out_range(==,ulOperand) LONGLONG* pllResult) +{ + *pllResult = (LONGLONG)ulOperand; + return S_OK; +} +#endif + +// +// ULONG_PTR -> LONG64 conversion +// +#define ULongPtrToLong64 ULongPtrToLongLong + +// +// ULONG_PTR -> INT64 +// +#define ULongPtrToInt64 ULongPtrToLongLong + +// +// ULONG_PTR -> ptrdiff_t conversion +// +#define ULongPtrToPtrdiffT ULongPtrToIntPtr + +// +// ULONG_PTR -> SSIZE_T conversion +// +#define ULongPtrToSSIZET ULongPtrToLongPtr + +// +// DWORD -> INT8 conversion +// +#define DWordToInt8 ULongToInt8 + +// +// DWORD -> CHAR conversion +// +#define DWordToChar ULongToChar + +// +// DWORD -> UCHAR conversion +// +#define DWordToUChar ULongToUChar + +// +// DWORD -> UINT8 conversion +// +#define DWordToUInt8 ULongToUInt8 + +// +// DWORD -> BYTE conversion +// +#define DWordToByte ULongToUInt8 + +// +// DWORD -> SHORT conversion +// +#define DWordToShort ULongToShort + +// +// DWORD -> INT16 conversion +// +#define DWordToInt16 ULongToShort + +// +// DWORD -> USHORT conversion +// +#define DWordToUShort ULongToUShort + +// +// DWORD -> UINT16 conversion +// +#define DWordToUInt16 ULongToUShort + +// +// DWORD -> WORD conversion +// +#define DWordToWord ULongToUShort + +// +// DWORD -> INT conversion +// +#define DWordToInt ULongToInt + +// +// DWORD -> INT32 conversion +// +#define DWordToInt32 ULongToInt + +// +// DWORD -> INT_PTR conversion +// +#define DWordToIntPtr ULongToIntPtr + +// +// DWORD -> UINT conversion +// +#define DWordToUInt ULongToUInt + +// +// DWORD -> UINT32 conversion +// +#define DWordToUInt32 ULongToUInt + +// +// DWORD -> UINT_PTR conversion +// +#define DWordToUIntPtr ULongToUIntPtr + +// +// DWORD -> LONG conversion +// +#define DWordToLong ULongToLong + +// +// DWORD -> LONG_PTR conversion +// +#define DWordToLongPtr ULongToLongPtr + +// +// DWORD -> ptrdiff_t conversion +// +#define DWordToPtrdiffT ULongToIntPtr + +// +// DWORD -> SSIZE_T conversion +// +#define DWordToSSIZET ULongToLongPtr + +// +// DWORD_PTR -> INT8 conversion +// +#define DWordPtrToInt8 ULongPtrToInt8 + +// +// DWORD_PTR -> UCHAR conversion +// +#define DWordPtrToUChar ULongPtrToUChar + +// +// DWORD_PTR -> CHAR conversion +// +#define DWordPtrToChar ULongPtrToChar + +// +// DWORD_PTR -> UINT8 conversion +// +#define DWordPtrToUInt8 ULongPtrToUInt8 + +// +// DWORD_PTR -> BYTE conversion +// +#define DWordPtrToByte ULongPtrToUInt8 + +// +// DWORD_PTR -> SHORT conversion +// +#define DWordPtrToShort ULongPtrToShort + +// +// DWORD_PTR -> INT16 conversion +// +#define DWordPtrToInt16 ULongPtrToShort + +// +// DWORD_PTR -> USHORT conversion +// +#define DWordPtrToUShort ULongPtrToUShort + +// +// DWORD_PTR -> UINT16 conversion +// +#define DWordPtrToUInt16 ULongPtrToUShort + +// +// DWORD_PTR -> WORD conversion +// +#define DWordPtrToWord ULongPtrToUShort + +// +// DWORD_PTR -> INT conversion +// +#define DWordPtrToInt ULongPtrToInt + +// +// DWORD_PTR -> INT32 conversion +// +#define DWordPtrToInt32 ULongPtrToInt + +// +// DWORD_PTR -> INT_PTR conversion +// +#define DWordPtrToIntPtr ULongPtrToIntPtr + +// +// DWORD_PTR -> UINT conversion +// +#define DWordPtrToUInt ULongPtrToUInt + +// +// DWORD_PTR -> UINT32 conversion +// +#define DWordPtrToUInt32 ULongPtrToUInt + +// +// DWODR_PTR -> UINT_PTR conversion +// +#define DWordPtrToUIntPtr ULongPtrToUIntPtr + +// +// DWORD_PTR -> LONG conversion +// +#define DWordPtrToLong ULongPtrToLong + +// +// DWORD_PTR -> LONG_PTR conversion +// +#define DWordPtrToLongPtr ULongPtrToLongPtr + +// +// DWORD_PTR -> ULONG conversion +// +#define DWordPtrToULong ULongPtrToULong + +// +// DWORD_PTR -> DWORD conversion +// +#define DWordPtrToDWord ULongPtrToULong + +// +// DWORD_PTR -> LONGLONG conversion +// +#define DWordPtrToLongLong ULongPtrToLongLong + +// +// DWORD_PTR -> LONG64 conversion +// +#define DWordPtrToLong64 ULongPtrToLongLong + +// +// DWORD_PTR -> INT64 conversion +// +#define DWordPtrToInt64 ULongPtrToLongLong + +// +// DWORD_PTR -> ptrdiff_t conversion +// +#define DWordPtrToPtrdiffT ULongPtrToIntPtr + +// +// DWORD_PTR -> SSIZE_T conversion +// +#define DWordPtrToSSIZET ULongPtrToLongPtr + +// +// LONGLONG -> INT8 conversion +// +__inline +HRESULT +LongLongToInt8( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) INT8* pi8Result) +{ + HRESULT hr; + + if ((llOperand >= INT8_MIN) && (llOperand <= INT8_MAX)) + { + *pi8Result = (INT8)llOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> UCHAR conversion +// +__inline +HRESULT +LongLongToUChar( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) UCHAR* pch) +{ + HRESULT hr; + + if ((llOperand >= 0) && (llOperand <= 255)) + { + *pch = (UCHAR)llOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> CHAR conversion +// +__forceinline +HRESULT +LongLongToChar( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return LongLongToUChar(llOperand, (UCHAR*)pch); +#else + return LongLongToInt8(llOperand, (INT8*)pch); +#endif +} + +// +// LONGLONG -> UINT8 conversion +// +__inline +HRESULT +LongLongToUInt8( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) UINT8* pu8Result) +{ + HRESULT hr; + + if ((llOperand >= 0) && (llOperand <= UINT8_MAX)) + { + *pu8Result = (UINT8)llOperand; + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> BYTE conversion +// +#define LongLongToByte LongLongToUInt8 + +// +// LONGLONG -> SHORT conversion +// +__inline +HRESULT +LongLongToShort( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) SHORT* psResult) +{ + HRESULT hr; + + if ((llOperand >= SHORT_MIN) && (llOperand <= SHORT_MAX)) + { + *psResult = (SHORT)llOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> INT16 conversion +// +#define LongLongToInt16 LongLongToShort + +// +// LONGLONG -> USHORT conversion +// +__inline +HRESULT +LongLongToUShort( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) USHORT* pusResult) +{ + HRESULT hr; + + if ((llOperand >= 0) && (llOperand <= USHORT_MAX)) + { + *pusResult = (USHORT)llOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> UINT16 conversion +// +#define LongLongToUInt16 LongLongToUShort + +// +// LONGLONG -> WORD conversion +// +#define LongLongToWord LongLongToUShort + +// +// LONGLONG -> INT conversion +// +__inline +HRESULT +LongLongToInt( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) INT* piResult) +{ + HRESULT hr; + + if ((llOperand >= INT_MIN) && (llOperand <= INT_MAX)) + { + *piResult = (INT)llOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> INT32 conversion +// +#define LongLongToInt32 LongLongToInt + +// +// LONGLONG -> INT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +LongLongToIntPtr( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) INT_PTR* piResult) +{ + *piResult = llOperand; + return S_OK; +} +#else +#define LongLongToIntPtr LongLongToInt +#endif + +// +// LONGLONG -> UINT conversion +// +__inline +HRESULT +LongLongToUInt( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) UINT* puResult) +{ + HRESULT hr; + + if ((llOperand >= 0) && (llOperand <= UINT_MAX)) + { + *puResult = (UINT)llOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> UINT32 conversion +// +#define LongLongToUInt32 LongLongToUInt + +// +// LONGLONG -> UINT_PTR conversion +// +#ifdef _WIN64 +#define LongLongToUIntPtr LongLongToULongLong +#else +#define LongLongToUIntPtr LongLongToUInt +#endif + +// +// LONGLONG -> LONG conversion +// +__inline +HRESULT +LongLongToLong( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) LONG* plResult) +{ + HRESULT hr; + + if ((llOperand >= INT32_MIN) && (llOperand <= INT32_MAX)) + { + *plResult = (LONG)llOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> LONG_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +LongLongToLongPtr( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) LONG_PTR* plResult) +{ + *plResult = (LONG_PTR)llOperand; + return S_OK; +} +#else +#define LongLongToLongPtr LongLongToLong +#endif + +// +// LONGLONG -> ULONG conversion +// +__inline +HRESULT +LongLongToULong( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) ULONG* pulResult) +{ + HRESULT hr; + + if ((llOperand >= 0) && (llOperand <= (LONGLONG)(ULONGLONG)UINT32_MAX)) + { + *pulResult = (ULONG)llOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> ULONG_PTR conversion +// +#ifdef _WIN64 +#define LongLongToULongPtr LongLongToULongLong +#else +#define LongLongToULongPtr LongLongToULong +#endif + +// +// LONGLONG -> DWORD conversion +// +#define LongLongToDWord LongLongToULong + +// +// LONGLONG -> DWORD_PTR conversion +// +#define LongLongToDWordPtr LongLongToULongPtr + +// +// LONGLONG -> ULONGLONG conversion +// +__inline +HRESULT +LongLongToULongLong( + __inn LONGLONG llOperand, + __outt __deref_out_range(==,llOperand) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (llOperand >= 0) + { + *pullResult = (ULONGLONG)llOperand; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// LONGLONG -> DWORDLONG conversion +// +#define LongLongToDWordLong LongLongToULongLong + +// +// LONGLONG -> ULONG64 conversion +// +#define LongLongToULong64 LongLongToULongLong + +// +// LONGLONG -> DWORD64 conversion +// +#define LongLongToDWord64 LongLongToULongLong + +// +// LONGLONG -> UINT64 conversion +// +#define LongLongToUInt64 LongLongToULongLong + +// +// LONGLONG -> ptrdiff_t conversion +// +#define LongLongToPtrdiffT LongLongToIntPtr + +// +// LONGLONG -> size_t conversion +// +#define LongLongToSizeT LongLongToUIntPtr + +// +// LONGLONG -> SSIZE_T conversion +// +#define LongLongToSSIZET LongLongToLongPtr + +// +// LONGLONG -> SIZE_T conversion +// +#define LongLongToSIZET LongLongToULongPtr + +// +// LONG64 -> CHAR conversion +// +#define Long64ToChar LongLongToChar + +// +// LONG64 -> INT8 conversion +// +#define Long64ToInt8 LongLongToInt8 + +// +// LONG64 -> UCHAR conversion +// +#define Long64ToUChar LongLongToUChar + +// +// LONG64 -> UINT8 conversion +// +#define Long64ToUInt8 LongLongToUInt8 + +// +// LONG64 -> BYTE conversion +// +#define Long64ToByte LongLongToUInt8 + +// +// LONG64 -> SHORT conversion +// +#define Long64ToShort LongLongToShort + +// +// LONG64 -> INT16 conversion +// +#define Long64ToInt16 LongLongToShort + +// +// LONG64 -> USHORT conversion +// +#define Long64ToUShort LongLongToUShort + +// +// LONG64 -> UINT16 conversion +// +#define Long64ToUInt16 LongLongToUShort + +// +// LONG64 -> WORD conversion +// +#define Long64ToWord LongLongToUShort + +// +// LONG64 -> INT conversion +// +#define Long64ToInt LongLongToInt + +// +// LONG64 -> INT32 conversion +// +#define Long64ToInt32 LongLongToInt + +// +// LONG64 -> INT_PTR conversion +// +#define Long64ToIntPtr LongLongToIntPtr + +// +// LONG64 -> UINT conversion +// +#define Long64ToUInt LongLongToUInt + +// +// LONG64 -> UINT32 conversion +// +#define Long64ToUInt32 LongLongToUInt + +// +// LONG64 -> UINT_PTR conversion +// +#define Long64ToUIntPtr LongLongToUIntPtr + +// +// LONG64 -> LONG conversion +// +#define Long64ToLong LongLongToLong + +// +// LONG64 -> LONG_PTR conversion +// +#define Long64ToLongPtr LongLongToLongPtr + +// +// LONG64 -> ULONG conversion +// +#define Long64ToULong LongLongToULong + +// +// LONG64 -> ULONG_PTR conversion +// +#define Long64ToULongPtr LongLongToULongPtr + +// +// LONG64 -> DWORD conversion +// +#define Long64ToDWord LongLongToULong + +// +// LONG64 -> DWORD_PTR conversion +// +#define Long64ToDWordPtr LongLongToULongPtr + +// +// LONG64 -> ULONGLONG conversion +// +#define Long64ToULongLong LongLongToULongLong + +// +// LONG64 -> ptrdiff_t conversion +// +#define Long64ToPtrdiffT LongLongToIntPtr + +// +// LONG64 -> size_t conversion +// +#define Long64ToSizeT LongLongToUIntPtr + +// +// LONG64 -> SSIZE_T conversion +// +#define Long64ToSSIZET LongLongToLongPtr + +// +// LONG64 -> SIZE_T conversion +// +#define Long64ToSIZET LongLongToULongPtr + +// +// INT64 -> CHAR conversion +// +#define Int64ToChar LongLongToChar + +// +// INT64 -> INT8 conversion +// +#define Int64ToInt8 LongLongToInt8 + +// +// INT64 -> UCHAR conversion +// +#define Int64ToUChar LongLongToUChar + +// +// INT64 -> UINT8 conversion +// +#define Int64ToUInt8 LongLongToUInt8 + +// +// INT64 -> BYTE conversion +// +#define Int64ToByte LongLongToUInt8 + +// +// INT64 -> SHORT conversion +// +#define Int64ToShort LongLongToShort + +// +// INT64 -> INT16 conversion +// +#define Int64ToInt16 LongLongToShort + +// +// INT64 -> USHORT conversion +// +#define Int64ToUShort LongLongToUShort + +// +// INT64 -> UINT16 conversion +// +#define Int64ToUInt16 LongLongToUShort + +// +// INT64 -> WORD conversion +// +#define Int64ToWord LongLongToUShort + +// +// INT64 -> INT conversion +// +#define Int64ToInt LongLongToInt + +// +// INT64 -> INT32 conversion +// +#define Int64ToInt32 LongLongToInt + +// +// INT64 -> INT_PTR conversion +// +#define Int64ToIntPtr LongLongToIntPtr + +// +// INT64 -> UINT conversion +// +#define Int64ToUInt LongLongToUInt + +// +// INT64 -> UINT32 conversion +// +#define Int64ToUInt32 LongLongToUInt + +// +// INT64 -> UINT_PTR conversion +// +#define Int64ToUIntPtr LongLongToUIntPtr + +// +// INT64 -> LONG conversion +// +#define Int64ToLong LongLongToLong + +// +// INT64 -> LONG_PTR conversion +// +#define Int64ToLongPtr LongLongToLongPtr + +// +// INT64 -> ULONG conversion +// +#define Int64ToULong LongLongToULong + +// +// INT64 -> ULONG_PTR conversion +// +#define Int64ToULongPtr LongLongToULongPtr + +// +// INT64 -> DWORD conversion +// +#define Int64ToDWord LongLongToULong + +// +// INT64 -> DWORD_PTR conversion +// +#define Int64ToDWordPtr LongLongToULongPtr + +// +// INT64 -> ULONGLONG conversion +// +#define Int64ToULongLong LongLongToULongLong + +// +// INT64 -> DWORDLONG conversion +// +#define Int64ToDWordLong LongLongToULongLong + +// +// INT64 -> ULONG64 conversion +// +#define Int64ToULong64 LongLongToULongLong + +// +// INT64 -> DWORD64 conversion +// +#define Int64ToDWord64 LongLongToULongLong + +// +// INT64 -> UINT64 conversion +// +#define Int64ToUInt64 LongLongToULongLong + +// +// INT64 -> ptrdiff_t conversion +// +#define Int64ToPtrdiffT LongLongToIntPtr + +// +// INT64 -> size_t conversion +// +#define Int64ToSizeT LongLongToUIntPtr + +// +// INT64 -> SSIZE_T conversion +// +#define Int64ToSSIZET LongLongToLongPtr + +// +// INT64 -> SIZE_T conversion +// +#define Int64ToSIZET LongLongToULongPtr + +// +// ULONGLONG -> INT8 conversion +// +__inline +HRESULT +ULongLongToInt8( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) INT8* pi8Result) +{ + HRESULT hr; + + if (ullOperand <= INT8_MAX) + { + *pi8Result = (INT8)ullOperand; + hr = S_OK; + } + else + { + *pi8Result = INT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> UCHAR conversion +// +__inline +HRESULT +ULongLongToUChar( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) UCHAR* pch) +{ + HRESULT hr; + + if (ullOperand <= 255) + { + *pch = (UCHAR)ullOperand; + hr = S_OK; + } + else + { + *pch = '\0'; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> CHAR conversion +// +__forceinline +HRESULT +ULongLongToChar( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) CHAR* pch) +{ +#ifdef _CHAR_UNSIGNED + return ULongLongToUChar(ullOperand, (UCHAR*)pch); +#else + return ULongLongToInt8(ullOperand, (INT8*)pch); +#endif +} + +// +// ULONGLONG -> UINT8 conversion +// +__inline +HRESULT +ULongLongToUInt8( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) UINT8* pu8Result) +{ + HRESULT hr; + + if (ullOperand <= UINT8_MAX) + { + *pu8Result = (UINT8)ullOperand; + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> BYTE conversion +// +#define ULongLongToByte ULongLongToUInt8 + +// +// ULONGLONG -> SHORT conversion +// +__inline +HRESULT +ULongLongToShort( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) SHORT* psResult) +{ + HRESULT hr; + + if (ullOperand <= SHORT_MAX) + { + *psResult = (SHORT)ullOperand; + hr = S_OK; + } + else + { + *psResult = SHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> INT16 conversion +// +#define ULongLongToInt16 ULongLongToShort + +// +// ULONGLONG -> USHORT conversion +// +__inline +HRESULT +ULongLongToUShort( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) USHORT* pusResult) +{ + HRESULT hr; + + if (ullOperand <= USHORT_MAX) + { + *pusResult = (USHORT)ullOperand; + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> UINT16 conversion +// +#define ULongLongToUInt16 ULongLongToUShort + +// +// ULONGLONG -> WORD conversion +// +#define ULongLongToWord ULongLongToUShort + +// +// ULONGLONG -> INT conversion +// +__inline +HRESULT +ULongLongToInt( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) INT* piResult) +{ + HRESULT hr; + + if (ullOperand <= INT_MAX) + { + *piResult = (INT)ullOperand; + hr = S_OK; + } + else + { + *piResult = INT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> INT32 conversion +// +#define ULongLongToInt32 ULongLongToInt + +// +// ULONGLONG -> INT_PTR conversion +// +#ifdef _WIN64 +#define ULongLongToIntPtr ULongLongToLongLong +#else +#define ULongLongToIntPtr ULongLongToInt +#endif + +// +// ULONGLONG -> UINT conversion +// +__inline +HRESULT +ULongLongToUInt( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) UINT* puResult) +{ + HRESULT hr; + + if (ullOperand <= UINT_MAX) + { + *puResult = (UINT)ullOperand; + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> UINT32 conversion +// +#define ULongLongToUInt32 ULongLongToUInt + +// +// ULONGLONG -> UINT_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +ULongLongToUIntPtr( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) UINT_PTR* puResult) +{ + *puResult = ullOperand; + return S_OK; +} +#else +#define ULongLongToUIntPtr ULongLongToUInt +#endif + +// +// ULONGLONG -> LONG conversion +// +__inline +HRESULT +ULongLongToLong( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) LONG* plResult) +{ + HRESULT hr; + + if (ullOperand <= INT32_MAX) + { + *plResult = (LONG)ullOperand; + hr = S_OK; + } + else + { + *plResult = LONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> LONG_PTR conversion +// +__inline +HRESULT +ULongLongToLongPtr( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) LONG_PTR* plResult) +{ + HRESULT hr; + + if (ullOperand <= LONG_PTR_MAX) + { + *plResult = (LONG_PTR)ullOperand; + hr = S_OK; + } + else + { + *plResult = LONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> ULONG conversion +// +__inline +HRESULT +ULongLongToULong( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) ULONG* pulResult) +{ + HRESULT hr; + + if (ullOperand <= UINT32_MAX) + { + *pulResult = (ULONG)ullOperand; + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> ULONG_PTR conversion +// +#ifdef _WIN64 +__inline +HRESULT +ULongLongToULongPtr( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) ULONG_PTR* pulResult) +{ + *pulResult = ullOperand; + return S_OK; +} +#else +#define ULongLongToULongPtr ULongLongToULong +#endif + +// +// ULONGLONG -> DWORD conversion +// +#define ULongLongToDWord ULongLongToULong + +// +// ULONGLONG -> DWORD_PTR conversion +// +#define ULongLongToDWordPtr ULongLongToULongPtr + +// +// ULONGLONG -> LONGLONG conversion +// +__inline +HRESULT +ULongLongToLongLong( + __inn ULONGLONG ullOperand, + __outt __deref_out_range(==,ullOperand) LONGLONG* pllResult) +{ + HRESULT hr; + + if (ullOperand <= LONGLONG_MAX) + { + *pllResult = (LONGLONG)ullOperand; + hr = S_OK; + } + else + { + *pllResult = LONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONGLONG -> INT64 conversion +// +#define ULongLongToInt64 ULongLongToLongLong + +// +// ULONGLONG -> LONG64 conversion +// +#define ULongLongToLong64 ULongLongToLongLong + +// +// ULONGLONG -> ptrdiff_t conversion +// +#define ULongLongToPtrdiffT ULongLongToIntPtr + +// +// ULONGLONG -> size_t conversion +// +#define ULongLongToSizeT ULongLongToUIntPtr + +// +// ULONGLONG -> SSIZE_T conversion +// +#define ULongLongToSSIZET ULongLongToLongPtr + +// +// ULONGLONG -> SIZE_T conversion +// +#define ULongLongToSIZET ULongLongToULongPtr + +// +// DWORDLONG -> CHAR conversion +// +#define DWordLongToChar ULongLongToChar + +// +// DWORDLONG -> INT8 conversion +// +#define DWordLongToInt8 ULongLongToInt8 + +// +// DWORDLONG -> UCHAR conversion +// +#define DWordLongToUChar ULongLongToUChar + +// +// DWORDLONG -> UINT8 conversion +// +#define DWordLongToUInt8 ULongLongToUInt8 + +// +// DWORDLONG -> BYTE conversion +// +#define DWordLongToByte ULongLongToUInt8 + +// +// DWORDLONG -> SHORT conversion +// +#define DWordLongToShort ULongLongToShort + +// +// DWORDLONG -> INT16 conversion +// +#define DWordLongToInt16 ULongLongToShort + +// +// DWORDLONG -> USHORT conversion +// +#define DWordLongToUShort ULongLongToUShort + +// +// DWORDLONG -> UINT16 conversion +// +#define DWordLongToUInt16 ULongLongToUShort + +// +// DWORDLONG -> WORD conversion +// +#define DWordLongToWord ULongLongToUShort + +// +// DWORDLONG -> INT conversion +// +#define DWordLongToInt ULongLongToInt + +// +// DWORDLONG -> INT32 conversion +// +#define DWordLongToInt32 ULongLongToInt + +// +// DWORDLONG -> INT_PTR conversion +// +#define DWordLongToIntPtr ULongLongToIntPtr + +// +// DWORDLONG -> UINT conversion +// +#define DWordLongToUInt ULongLongToUInt + +// +// DWORDLONG -> UINT32 conversion +// +#define DWordLongToUInt32 ULongLongToUInt + +// +// DWORDLONG -> UINT_PTR conversion +// +#define DWordLongToUIntPtr ULongLongToUIntPtr + +// +// DWORDLONG -> LONG conversion +// +#define DWordLongToLong ULongLongToLong + +// +// DWORDLONG -> LONG_PTR conversion +// +#define DWordLongToLongPtr ULongLongToLongPtr + +// +// DWORDLONG -> ULONG conversion +// +#define DWordLongToULong ULongLongToULong + +// +// DWORDLONG -> ULONG_PTR conversion +// +#define DWordLongToULongPtr ULongLongToULongPtr + +// +// DWORDLONG -> DWORD conversion +// +#define DWordLongToDWord ULongLongToULong + +// +// DWORDLONG -> DWORD_PTR conversion +// +#define DWordLongToDWordPtr ULongLongToULongPtr + +// +// DWORDLONG -> LONGLONG conversion +// +#define DWordLongToLongLong ULongLongToLongLong + +// +// DWORDLONG -> LONG64 conversion +// +#define DWordLongToLong64 ULongLongToLongLong + +// +// DWORDLONG -> INT64 conversion +// +#define DWordLongToInt64 ULongLongToLongLong + +// +// DWORDLONG -> ptrdiff_t conversion +// +#define DWordLongToPtrdiffT ULongLongToIntPtr + +// +// DWORDLONG -> size_t conversion +// +#define DWordLongToSizeT ULongLongToUIntPtr + +// +// DWORDLONG -> SSIZE_T conversion +// +#define DWordLongToSSIZET ULongLongToLongPtr + +// +// DWORDLONG -> SIZE_T conversion +// +#define DWordLongToSIZET ULongLongToULongPtr + +// +// ULONG64 -> CHAR conversion +// +#define ULong64ToChar ULongLongToChar + +// +// ULONG64 -> INT8 conversion +// +#define ULong64ToInt8 ULongLongToInt8 + +// +// ULONG64 -> UCHAR conversion +// +#define ULong64ToUChar ULongLongToUChar + +// +// ULONG64 -> UINT8 conversion +// +#define ULong64ToUInt8 ULongLongToUInt8 + +// +// ULONG64 -> BYTE conversion +// +#define ULong64ToByte ULongLongToUInt8 + +// +// ULONG64 -> SHORT conversion +// +#define ULong64ToShort ULongLongToShort + +// +// ULONG64 -> INT16 conversion +// +#define ULong64ToInt16 ULongLongToShort + +// +// ULONG64 -> USHORT conversion +// +#define ULong64ToUShort ULongLongToUShort + +// +// ULONG64 -> UINT16 conversion +// +#define ULong64ToUInt16 ULongLongToUShort + +// +// ULONG64 -> WORD conversion +// +#define ULong64ToWord ULongLongToUShort + +// +// ULONG64 -> INT conversion +// +#define ULong64ToInt ULongLongToInt + +// +// ULONG64 -> INT32 conversion +// +#define ULong64ToInt32 ULongLongToInt + +// +// ULONG64 -> INT_PTR conversion +// +#define ULong64ToIntPtr ULongLongToIntPtr + +// +// ULONG64 -> UINT conversion +// +#define ULong64ToUInt ULongLongToUInt + +// +// ULONG64 -> UINT32 conversion +// +#define ULong64ToUInt32 ULongLongToUInt + +// +// ULONG64 -> UINT_PTR conversion +// +#define ULong64ToUIntPtr ULongLongToUIntPtr + +// +// ULONG64 -> LONG conversion +// +#define ULong64ToLong ULongLongToLong + +// +// ULONG64 -> LONG_PTR conversion +// +#define ULong64ToLongPtr ULongLongToLongPtr + +// +// ULONG64 -> ULONG conversion +// +#define ULong64ToULong ULongLongToULong + +// +// ULONG64 -> ULONG_PTR conversion +// +#define ULong64ToULongPtr ULongLongToULongPtr + +// +// ULONG64 -> DWORD conversion +// +#define ULong64ToDWord ULongLongToULong + +// +// ULONG64 -> DWORD_PTR conversion +// +#define ULong64ToDWordPtr ULongLongToULongPtr + +// +// ULONG64 -> LONGLONG conversion +// +#define ULong64ToLongLong ULongLongToLongLong + +// +// ULONG64 -> LONG64 conversion +// +#define ULong64ToLong64 ULongLongToLongLong + +// +// ULONG64 -> INT64 conversion +// +#define ULong64ToInt64 ULongLongToLongLong + +// +// ULONG64 -> ptrdiff_t conversion +// +#define ULong64ToPtrdiffT ULongLongToIntPtr + +// +// ULONG64 -> size_t conversion +// +#define ULong64ToSizeT ULongLongToUIntPtr + +// +// ULONG64 -> SSIZE_T conversion +// +#define ULong64ToSSIZET ULongLongToLongPtr + +// +// ULONG64 -> SIZE_T conversion +// +#define ULong64ToSIZET ULongLongToULongPtr + +// +// DWORD64 -> CHAR conversion +// +#define DWord64ToChar ULongLongToChar + +// +// DWORD64 -> INT8 conversion +// +#define DWord64ToInt8 ULongLongToInt8 + +// +// DWORD64 -> UCHAR conversion +// +#define DWord64ToUChar ULongLongToUChar + +// +// DWORD64 -> UINT8 conversion +// +#define DWord64ToUInt8 ULongLongToUInt8 + +// +// DWORD64 -> BYTE conversion +// +#define DWord64ToByte ULongLongToUInt8 + +// +// DWORD64 -> SHORT conversion +// +#define DWord64ToShort ULongLongToShort + +// +// DWORD64 -> INT16 conversion +// +#define DWord64ToInt16 ULongLongToShort + +// +// DWORD64 -> USHORT conversion +// +#define DWord64ToUShort ULongLongToUShort + +// +// DWORD64 -> UINT16 conversion +// +#define DWord64ToUInt16 ULongLongToUShort + +// +// DWORD64 -> WORD conversion +// +#define DWord64ToWord ULongLongToUShort + +// +// DWORD64 -> INT conversion +// +#define DWord64ToInt ULongLongToInt + +// +// DWORD64 -> INT32 conversion +// +#define DWord64ToInt32 ULongLongToInt + +// +// DWORD64 -> INT_PTR conversion +// +#define DWord64ToIntPtr ULongLongToIntPtr + +// +// DWORD64 -> UINT conversion +// +#define DWord64ToUInt ULongLongToUInt + +// +// DWORD64 -> UINT32 conversion +// +#define DWord64ToUInt32 ULongLongToUInt + +// +// DWORD64 -> UINT_PTR conversion +// +#define DWord64ToUIntPtr ULongLongToUIntPtr + +// +// DWORD64 -> LONG conversion +// +#define DWord64ToLong ULongLongToLong + +// +// DWORD64 -> LONG_PTR conversion +// +#define DWord64ToLongPtr ULongLongToLongPtr + +// +// DWORD64 -> ULONG conversion +// +#define DWord64ToULong ULongLongToULong + +// +// DWORD64 -> ULONG_PTR conversion +// +#define DWord64ToULongPtr ULongLongToULongPtr + +// +// DWORD64 -> DWORD conversion +// +#define DWord64ToDWord ULongLongToULong + +// +// DWORD64 -> DWORD_PTR conversion +// +#define DWord64ToDWordPtr ULongLongToULongPtr + +// +// DWORD64 -> LONGLONG conversion +// +#define DWord64ToLongLong ULongLongToLongLong + +// +// DWORD64 -> LONG64 conversion +// +#define DWord64ToLong64 ULongLongToLongLong + +// +// DWORD64 -> INT64 conversion +// +#define DWord64ToInt64 ULongLongToLongLong + +// +// DWORD64 -> ptrdiff_t conversion +// +#define DWord64ToPtrdiffT ULongLongToIntPtr + +// +// DWORD64 -> size_t conversion +// +#define DWord64ToSizeT ULongLongToUIntPtr + +// +// DWORD64 -> SSIZE_T conversion +// +#define DWord64ToSSIZET ULongLongToLongPtr + +// +// DWORD64 -> SIZE_T conversion +// +#define DWord64ToSIZET ULongLongToULongPtr + +// +// UINT64 -> CHAR conversion +// +#define UInt64ToChar ULongLongToChar + +// +// UINT64 -> INT8 conversion +// +#define UInt64ToInt8 ULongLongToInt8 + +// +// UINT64 -> UCHAR conversion +// +#define UInt64ToUChar ULongLongToUChar + +// +// UINT64 -> UINT8 conversion +// +#define UInt64ToUInt8 ULongLongToUInt8 + +// +// UINT64 -> BYTE conversion +// +#define UInt64ToByte ULongLongToUInt8 + +// +// UINT64 -> SHORT conversion +// +#define UInt64ToShort ULongLongToShort + +// +// UINT64 -> INT16 conversion +// +// +#define UInt64ToInt16 ULongLongToShort + +// +// UINT64 -> USHORT conversion +// +#define UInt64ToUShort ULongLongToUShort + +// +// UINT64 -> UINT16 conversion +// +#define UInt64ToUInt16 ULongLongToUShort + +// +// UINT64 -> WORD conversion +// +#define UInt64ToWord ULongLongToUShort + +// +// UINT64 -> INT conversion +// +#define UInt64ToInt ULongLongToInt + +// +// UINT64 -> INT32 conversion +// +#define UInt64ToInt32 ULongLongToInt + +// +// UINT64 -> INT_PTR conversion +// +#define UInt64ToIntPtr ULongLongToIntPtr + +// +// UINT64 -> UINT conversion +// +#define UInt64ToUInt ULongLongToUInt + +// +// UINT64 -> UINT32 conversion +// +#define UInt64ToUInt32 ULongLongToUInt + +// +// UINT64 -> UINT_PTR conversion +// +#define UInt64ToUIntPtr ULongLongToUIntPtr + +// +// UINT64 -> LONG conversion +// +#define UInt64ToLong ULongLongToLong + +// +// UINT64 -> LONG_PTR conversion +// +#define UInt64ToLongPtr ULongLongToLongPtr + +// +// UINT64 -> ULONG conversion +// +#define UInt64ToULong ULongLongToULong + +// +// UINT64 -> ULONG_PTR conversion +// +#define UInt64ToULongPtr ULongLongToULongPtr + +// +// UINT64 -> DWORD conversion +// +#define UInt64ToDWord ULongLongToULong + +// +// UINT64 -> DWORD_PTR conversion +// +#define UInt64ToDWordPtr ULongLongToULongPtr + +// +// UINT64 -> LONGLONG conversion +// +#define UInt64ToLongLong ULongLongToLongLong + +// +// UINT64 -> LONG64 conversion +// +#define UInt64ToLong64 ULongLongToLongLong + +// +// UINT64 -> INT64 conversion +// +#define UInt64ToInt64 ULongLongToLongLong + +// +// UINT64 -> ptrdiff_t conversion +// +#define UInt64ToPtrdiffT ULongLongToIntPtr + +// +// UINT64 -> size_t conversion +// +#define UInt64ToSizeT ULongLongToUIntPtr + +// +// UINT64 -> SSIZE_T conversion +// +#define UInt64ToSSIZET ULongLongToLongPtr + +// +// UINT64 -> SIZE_T conversion +// +#define UInt64ToSIZET ULongLongToULongPtr + +// +// ptrdiff_t -> CHAR conversion +// +#define PtrdiffTToChar IntPtrToChar + +// +// ptrdiff_t -> INT8 conversion +// +#define PtrdiffTToInt8 IntPtrToInt8 + +// +// ptrdiff_t -> UCHAR conversion +// +#define PtrdiffTToUChar IntPtrToUChar + +// +// ptrdiff_t -> UINT8 conversion +// +#define PtrdiffTToUInt8 IntPtrToUInt8 + +// +// ptrdiff_t -> BYTE conversion +// +#define PtrdiffTToByte IntPtrToUInt8 + +// +// ptrdiff_t -> SHORT conversion +// +#define PtrdiffTToShort IntPtrToShort + +// +// ptrdiff_t -> INT16 conversion +// +#define PtrdiffTToInt16 IntPtrToShort + +// +// ptrdiff_t -> USHORT conversion +// +#define PtrdiffTToUShort IntPtrToUShort + +// +// ptrdiff_t -> UINT16 conversion +// +#define PtrdiffTToUInt16 IntPtrToUShort + +// +// ptrdiff_t -> WORD conversion +// +#define PtrdiffTToWord IntPtrToUShort + +// +// ptrdiff_t -> INT conversion +// +#define PtrdiffTToInt IntPtrToInt + +// +// ptrdiff_t -> INT32 conversion +// +#define PtrdiffTToInt32 IntPtrToInt + +// +// ptrdiff_t -> UINT conversion +// +#define PtrdiffTToUInt IntPtrToUInt + +// +// ptrdiff_t -> UINT32 conversion +// +#define PtrdiffTToUInt32 IntPtrToUInt + +// +// ptrdiff_t -> UINT_PTR conversion +// +#define PtrdiffTToUIntPtr IntPtrToUIntPtr + +// +// ptrdiff_t -> LONG conversion +// +#define PtrdiffTToLong IntPtrToLong + +// +// ptrdiff_t -> LONG_PTR conversion +// +#define PtrdiffTToLongPtr IntPtrToLongPtr + +// +// ptrdiff_t -> ULONG conversion +// +#define PtrdiffTToULong IntPtrToULong + +// +// ptrdiff_t -> ULONG_PTR conversion +// +#define PtrdiffTToULongPtr IntPtrToULongPtr + +// +// ptrdiff_t -> DWORD conversion +// +#define PtrdiffTToDWord IntPtrToULong + +// +// ptrdiff_t -> DWORD_PTR conversion +// +#define PtrdiffTToDWordPtr IntPtrToULongPtr + +// +// ptrdiff_t -> ULONGLONG conversion +// +#define PtrdiffTToULongLong IntPtrToULongLong + +// +// ptrdiff_t -> DWORDLONG conversion +// +#define PtrdiffTToDWordLong IntPtrToULongLong + +// +// ptrdiff_t -> ULONG64 conversion +// +#define PtrdiffTToULong64 IntPtrToULongLong + +// +// ptrdiff_t -> DWORD64 conversion +// +#define PtrdiffTToDWord64 IntPtrToULongLong + +// +// ptrdiff_t -> UINT64 conversion +// +#define PtrdiffTToUInt64 IntPtrToULongLong + +// +// ptrdiff_t -> size_t conversion +// +#define PtrdiffTToSizeT IntPtrToUIntPtr + +// +// ptrdiff_t -> SIZE_T conversion +// +#define PtrdiffTToSIZET IntPtrToULongPtr + +// +// size_t -> INT8 conversion +// +#define SizeTToInt8 UIntPtrToInt8 + +// +// size_t -> UCHAR conversion +// +#define SizeTToUChar UIntPtrToUChar + +// +// size_t -> CHAR conversion +// +#define SizeTToChar UIntPtrToChar + +// +// size_t -> UINT8 conversion +// +#define SizeTToUInt8 UIntPtrToUInt8 + +// +// size_t -> BYTE conversion +// +#define SizeTToByte UIntPtrToUInt8 + +// +// size_t -> SHORT conversion +// +#define SizeTToShort UIntPtrToShort + +// +// size_t -> INT16 conversion +// +#define SizeTToInt16 UIntPtrToShort + +// +// size_t -> USHORT conversion +// +#define SizeTToUShort UIntPtrToUShort + +// +// size_t -> UINT16 conversion +// +#define SizeTToUInt16 UIntPtrToUShort + +// +// size_t -> WORD +// +#define SizeTToWord UIntPtrToUShort + +// +// size_t -> INT conversion +// +#define SizeTToInt UIntPtrToInt + +// +// size_t -> INT32 conversion +// +#define SizeTToInt32 UIntPtrToInt + +// +// size_t -> INT_PTR conversion +// +#define SizeTToIntPtr UIntPtrToIntPtr + +// +// size_t -> UINT conversion +// +#define SizeTToUInt UIntPtrToUInt + +// +// size_t -> UINT32 conversion +// +#define SizeTToUInt32 UIntPtrToUInt + +// +// size_t -> LONG conversion +// +#define SizeTToLong UIntPtrToLong + +// +// size_t -> LONG_PTR conversion +// +#define SizeTToLongPtr UIntPtrToLongPtr + +// +// size_t -> ULONG conversion +// +#define SizeTToULong UIntPtrToULong + +// +// size_t -> DWORD conversion +// +#define SizeTToDWord UIntPtrToULong + +// +// size_t -> LONGLONG conversion +// +#define SizeTToLongLong UIntPtrToLongLong + +// +// size_t -> LONG64 conversion +// +#define SizeTToLong64 UIntPtrToLongLong + +// +// size_t -> INT64 +// +#define SizeTToInt64 UIntPtrToLongLong + +// +// size_t -> ptrdiff_t conversion +// +#define SizeTToPtrdiffT UIntPtrToIntPtr + +// +// size_t -> SSIZE_T conversion +// +#define SizeTToSSIZET UIntPtrToLongPtr + +// +// SSIZE_T -> INT8 conversion +// +#define SSIZETToInt8 LongPtrToInt8 + +// +// SSIZE_T -> UCHAR conversion +// +#define SSIZETToUChar LongPtrToUChar + +// +// SSIZE_T -> CHAR conversion +// +#define SSIZETToChar LongPtrToChar + +// +// SSIZE_T -> UINT8 conversion +// +#define SSIZETToUInt8 LongPtrToUInt8 + +// +// SSIZE_T -> BYTE conversion +// +#define SSIZETToByte LongPtrToUInt8 + +// +// SSIZE_T -> SHORT conversion +// +#define SSIZETToShort LongPtrToShort + +// +// SSIZE_T -> INT16 conversion +// +#define SSIZETToInt16 LongPtrToShort + +// +// SSIZE_T -> USHORT conversion +// +#define SSIZETToUShort LongPtrToUShort + +// +// SSIZE_T -> UINT16 conversion +// +#define SSIZETToUInt16 LongPtrToUShort + +// +// SSIZE_T -> WORD conversion +// +#define SSIZETToWord LongPtrToUShort + +// +// SSIZE_T -> INT conversion +// +#define SSIZETToInt LongPtrToInt + +// +// SSIZE_T -> INT32 conversion +// +#define SSIZETToInt32 LongPtrToInt + +// +// SSIZE_T -> INT_PTR conversion +// +#define SSIZETToIntPtr LongPtrToIntPtr + +// +// SSIZE_T -> UINT conversion +// +#define SSIZETToUInt LongPtrToUInt + +// +// SSIZE_T -> UINT32 conversion +// +#define SSIZETToUInt32 LongPtrToUInt + +// +// SSIZE_T -> UINT_PTR conversion +// +#define SSIZETToUIntPtr LongPtrToUIntPtr + +// +// SSIZE_T -> LONG conversion +// +#define SSIZETToLong LongPtrToLong + +// +// SSIZE_T -> ULONG conversion +// +#define SSIZETToULong LongPtrToULong + +// +// SSIZE_T -> ULONG_PTR conversion +// +#define SSIZETToULongPtr LongPtrToULongPtr + +// +// SSIZE_T -> DWORD conversion +// +#define SSIZETToDWord LongPtrToULong + +// +// SSIZE_T -> DWORD_PTR conversion +// +#define SSIZETToDWordPtr LongPtrToULongPtr + +// +// SSIZE_T -> ULONGLONG conversion +// +#define SSIZETToULongLong LongPtrToULongLong + +// +// SSIZE_T -> DWORDLONG conversion +// +#define SSIZETToDWordLong LongPtrToULongLong + +// +// SSIZE_T -> ULONG64 conversion +// +#define SSIZETToULong64 LongPtrToULongLong + +// +// SSIZE_T -> DWORD64 conversion +// +#define SSIZETToDWord64 LongPtrToULongLong + +// +// SSIZE_T -> UINT64 conversion +// +#define SSIZETToUInt64 LongPtrToULongLong + +// +// SSIZE_T -> size_t conversion +// +#define SSIZETToSizeT LongPtrToUIntPtr + +// +// SSIZE_T -> SIZE_T conversion +// +#define SSIZETToSIZET LongPtrToULongPtr + +// +// SIZE_T -> INT8 conversion +// +#define SIZETToInt8 ULongPtrToInt8 + +// +// SIZE_T -> UCHAR conversion +// +#define SIZETToUChar ULongPtrToUChar + +// +// SIZE_T -> CHAR conversion +// +#define SIZETToChar ULongPtrToChar + +// +// SIZE_T -> UINT8 conversion +// +#define SIZETToUInt8 ULongPtrToUInt8 + +// +// SIZE_T -> BYTE conversion +// +#define SIZETToByte ULongPtrToUInt8 + +// +// SIZE_T -> SHORT conversion +// +#define SIZETToShort ULongPtrToShort + +// +// SIZE_T -> INT16 conversion +// +#define SIZETToInt16 ULongPtrToShort + +// +// SIZE_T -> USHORT conversion +// +#define SIZETToUShort ULongPtrToUShort + +// +// SIZE_T -> UINT16 conversion +// +#define SIZETToUInt16 ULongPtrToUShort + +// +// SIZE_T -> WORD +// +#define SIZETToWord ULongPtrToUShort + +// +// SIZE_T -> INT conversion +// +#define SIZETToInt ULongPtrToInt + +// +// SIZE_T -> INT32 conversion +// +#define SIZETToInt32 ULongPtrToInt + +// +// SIZE_T -> INT_PTR conversion +// +#define SIZETToIntPtr ULongPtrToIntPtr + +// +// SIZE_T -> UINT conversion +// +#define SIZETToUInt ULongPtrToUInt + +// +// SIZE_T -> UINT32 conversion +// +#define SIZETToUInt32 ULongPtrToUInt + +// +// SIZE_T -> UINT_PTR conversion +// +#define SIZETToUIntPtr ULongPtrToUIntPtr + +// +// SIZE_T -> LONG conversion +// +#define SIZETToLong ULongPtrToLong + +// +// SIZE_T -> LONG_PTR conversion +// +#define SIZETToLongPtr ULongPtrToLongPtr + +// +// SIZE_T -> ULONG conversion +// +#define SIZETToULong ULongPtrToULong + +// +// SIZE_T -> DWORD conversion +// +#define SIZETToDWord ULongPtrToULong + +// +// SIZE_T -> LONGLONG conversion +// +#define SIZETToLongLong ULongPtrToLongLong + +// +// SIZE_T -> LONG64 conversion +// +#define SIZETToLong64 ULongPtrToLongLong + +// +// SIZE_T -> INT64 +// +#define SIZETToInt64 ULongPtrToLongLong + +// +// SIZE_T -> ptrdiff_t conversion +// +#define SIZETToPtrdiffT ULongPtrToIntPtr + +// +// SIZE_T -> SSIZE_T conversion +// +#define SIZETToSSIZET ULongPtrToLongPtr + + +//============================================================================= +// Addition functions +//============================================================================= + +// +// UINT8 addition +// +__inline +HRESULT +UInt8Add( + __inn UINT8 u8Augend, + __inn UINT8 u8Addend, + __outt __deref_out_range(==,u8Augend + u8Addend) UINT8* pu8Result) +{ + HRESULT hr; + + if (((UINT8)(u8Augend + u8Addend)) >= u8Augend) + { + *pu8Result = (UINT8)(u8Augend + u8Addend); + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT addition +// +__inline +HRESULT +UShortAdd( + __inn USHORT usAugend, + __inn USHORT usAddend, + __outt __deref_out_range(==,usAugend + usAddend) USHORT* pusResult) +{ + HRESULT hr; + + if (((USHORT)(usAugend + usAddend)) >= usAugend) + { + *pusResult = (USHORT)(usAugend + usAddend); + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT16 addition +// +#define UInt16Add UShortAdd + +// +// WORD addtition +// +#define WordAdd UShortAdd + +// +// UINT addition +// +__inline +HRESULT +UIntAdd( + __inn UINT uAugend, + __inn UINT uAddend, + __outt __deref_out_range(==,uAugend + uAddend) UINT* puResult) +{ + HRESULT hr; + + if ((uAugend + uAddend) >= uAugend) + { + *puResult = (uAugend + uAddend); + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT32 addition +// +#define UInt32Add UIntAdd + +// +// UINT_PTR addition +// +#ifdef _WIN64 +#define UIntPtrAdd ULongLongAdd +#else +__inline +HRESULT +UIntPtrAdd( + __inn UINT_PTR uAugend, + __inn UINT_PTR uAddend, + __outt __deref_out_range(==,uAugend + uAddend) UINT_PTR* puResult) +{ + HRESULT hr; + + if ((uAugend + uAddend) >= uAugend) + { + *puResult = (uAugend + uAddend); + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// ULONG addition +// +__inline +HRESULT +ULongAdd( + __inn ULONG ulAugend, + __inn ULONG ulAddend, + __outt __deref_out_range(==,ulAugend + ulAddend) ULONG* pulResult) +{ + HRESULT hr; + + if ((ulAugend + ulAddend) >= ulAugend) + { + *pulResult = (ulAugend + ulAddend); + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR addition +// +#ifdef _WIN64 +#define ULongPtrAdd ULongLongAdd +#else +__inline +HRESULT +ULongPtrAdd( + __inn ULONG_PTR ulAugend, + __inn ULONG_PTR ulAddend, + __outt __deref_out_range(==,ulAugend + ulAddend) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if ((ulAugend + ulAddend) >= ulAugend) + { + *pulResult = (ulAugend + ulAddend); + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// DWORD addition +// +#define DWordAdd ULongAdd + +// +// DWORD_PTR addition +// +#ifdef _WIN64 +#define DWordPtrAdd ULongLongAdd +#else +__inline +HRESULT +DWordPtrAdd( + __inn DWORD_PTR dwAugend, + __inn DWORD_PTR dwAddend, + __outt __deref_out_range(==,dwAugend + dwAddend) DWORD_PTR* pdwResult) +{ + HRESULT hr; + + if ((dwAugend + dwAddend) >= dwAugend) + { + *pdwResult = (dwAugend + dwAddend); + hr = S_OK; + } + else + { + *pdwResult = DWORD_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// size_t addition +// +__inline +HRESULT +SizeTAdd( + __inn size_t Augend, + __inn size_t Addend, + __outt __deref_out_range(==,Augend + Addend) size_t* pResult) +{ + HRESULT hr; + + if ((Augend + Addend) >= Augend) + { + *pResult = (Augend + Addend); + hr = S_OK; + } + else + { + *pResult = SIZE_T_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SIZE_T addition +// +#ifdef _WIN64 +#define SIZETAdd ULongLongAdd +#else +__inline +HRESULT +SIZETAdd( + __inn SIZE_T Augend, + __inn SIZE_T Addend, + __outt __deref_out_range(==,Augend + Addend) SIZE_T* pResult) +{ + HRESULT hr; + + if ((Augend + Addend) >= Augend) + { + *pResult = (Augend + Addend); + hr = S_OK; + } + else + { + *pResult = _SIZE_T_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// ULONGLONG addition +// +__inline +HRESULT +ULongLongAdd( + __inn ULONGLONG ullAugend, + __inn ULONGLONG ullAddend, + __outt __deref_out_range(==,ullAugend + ullAddend) ULONGLONG* pullResult) +{ + HRESULT hr; + + if ((ullAugend + ullAddend) >= ullAugend) + { + *pullResult = (ullAugend + ullAddend); + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// DWORDLONG addition +// +#define DWordLongAdd ULongLongAdd + +// +// ULONG64 addition +// +#define ULong64Add ULongLongAdd + +// +// DWORD64 addition +// +#define DWord64Add ULongLongAdd + +// +// UINT64 addition +// +#define UInt64Add ULongLongAdd + + +//============================================================================= +// Subtraction functions +//============================================================================= + +// +// UINT8 subtraction +// +__inline +HRESULT +UInt8Sub( + __inn UINT8 u8Minuend, + __inn UINT8 u8Subtrahend, + __outt __deref_out_range(==,u8Minuend - u8Subtrahend) UINT8* pu8Result) +{ + HRESULT hr; + + if (u8Minuend >= u8Subtrahend) + { + *pu8Result = (UINT8)(u8Minuend - u8Subtrahend); + hr = S_OK; + } + else + { + *pu8Result = UINT8_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// USHORT subtraction +// +__inline +HRESULT +UShortSub( + __inn USHORT usMinuend, + __inn USHORT usSubtrahend, + __outt __deref_out_range(==,usMinuend - usSubtrahend) USHORT* pusResult) +{ + HRESULT hr; + + if (usMinuend >= usSubtrahend) + { + *pusResult = (USHORT)(usMinuend - usSubtrahend); + hr = S_OK; + } + else + { + *pusResult = USHORT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT16 subtraction +// +#define UInt16Sub UShortSub + +// +// WORD subtraction +// +#define WordSub UShortSub + + +// +// UINT subtraction +// +__inline +HRESULT +UIntSub( + __inn UINT uMinuend, + __inn UINT uSubtrahend, + __outt __deref_out_range(==,uMinuend - uSubtrahend) UINT* puResult) +{ + HRESULT hr; + + if (uMinuend >= uSubtrahend) + { + *puResult = (uMinuend - uSubtrahend); + hr = S_OK; + } + else + { + *puResult = UINT_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// UINT32 subtraction +// +#define UInt32Sub UIntSub + +// +// UINT_PTR subtraction +// +#ifdef _WIN64 +#define UIntPtrSub ULongLongSub +#else +__inline +HRESULT +UIntPtrSub( + __inn UINT_PTR uMinuend, + __inn UINT_PTR uSubtrahend, + __outt __deref_out_range(==,uMinuend - uSubtrahend) UINT_PTR* puResult) +{ + HRESULT hr; + + if (uMinuend >= uSubtrahend) + { + *puResult = (uMinuend - uSubtrahend); + hr = S_OK; + } + else + { + *puResult = UINT_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// ULONG subtraction +// +__inline +HRESULT +ULongSub( + __inn ULONG ulMinuend, + __inn ULONG ulSubtrahend, + __outt __deref_out_range(==,ulMinuend - ulSubtrahend) ULONG* pulResult) +{ + HRESULT hr; + + if (ulMinuend >= ulSubtrahend) + { + *pulResult = (ulMinuend - ulSubtrahend); + hr = S_OK; + } + else + { + *pulResult = ULONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// ULONG_PTR subtraction +// +#ifdef _WIN64 +#define ULongPtrSub ULongLongSub +#else +__inline +HRESULT +ULongPtrSub( + __inn ULONG_PTR ulMinuend, + __inn ULONG_PTR ulSubtrahend, + __outt __deref_out_range(==,ulMinuend - ulSubtrahend) ULONG_PTR* pulResult) +{ + HRESULT hr; + + if (ulMinuend >= ulSubtrahend) + { + *pulResult = (ulMinuend - ulSubtrahend); + hr = S_OK; + } + else + { + *pulResult = ULONG_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + + +// +// DWORD subtraction +// +#define DWordSub ULongSub + +// +// DWORD_PTR subtraction +// +#ifdef _WIN64 +#define DWordPtrSub ULongLongSub +#else +__inline +HRESULT +DWordPtrSub( + __inn DWORD_PTR dwMinuend, + __inn DWORD_PTR dwSubtrahend, + __outt __deref_out_range(==,dwMinuend - dwSubtrahend) DWORD_PTR* pdwResult) +{ + HRESULT hr; + + if (dwMinuend >= dwSubtrahend) + { + *pdwResult = (dwMinuend - dwSubtrahend); + hr = S_OK; + } + else + { + *pdwResult = DWORD_PTR_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// size_t subtraction +// +__inline +HRESULT +SizeTSub( + __inn size_t Minuend, + __inn size_t Subtrahend, + __outt __deref_out_range(==,Minuend - Subtrahend) size_t* pResult) +{ + HRESULT hr; + + if (Minuend >= Subtrahend) + { + *pResult = (Minuend - Subtrahend); + hr = S_OK; + } + else + { + *pResult = SIZE_T_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// SIZE_T subtraction +// +#ifdef _WIN64 +#define SIZETSub ULongLongSub +#else +__inline +HRESULT +SIZETSub( + __inn SIZE_T Minuend, + __inn SIZE_T Subtrahend, + __outt __deref_out_range(==,Minuend - Subtrahend) SIZE_T* pResult) +{ + HRESULT hr; + + if (Minuend >= Subtrahend) + { + *pResult = (Minuend - Subtrahend); + hr = S_OK; + } + else + { + *pResult = _SIZE_T_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} +#endif // _WIN64 + +// +// ULONGLONG subtraction +// +__inline +HRESULT +ULongLongSub( + __inn ULONGLONG ullMinuend, + __inn ULONGLONG ullSubtrahend, + __outt __deref_out_range(==,ullMinuend - ullSubtrahend) ULONGLONG* pullResult) +{ + HRESULT hr; + + if (ullMinuend >= ullSubtrahend) + { + *pullResult = (ullMinuend - ullSubtrahend); + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } + + return hr; +} + +// +// DWORDLONG subtraction +// +#define DWordLongSub ULongLongSub + +// +// ULONG64 subtraction +// +#define ULong64Sub ULongLongSub + +// +// DWORD64 subtraction +// +#define DWord64Sub ULongLongSub + +// +// UINT64 subtraction +// +#define UInt64Sub ULongLongSub + + +//============================================================================= +// Multiplication functions +//============================================================================= + +// +// UINT8 multiplication +// +__inline +HRESULT +UInt8Mult( + __inn UINT8 u8Multiplicand, + __inn UINT8 u8Multiplier, + __outt __deref_out_range(==,u8Multiplier * u8Multiplicand) UINT8* pu8Result) +{ + UINT uResult = ((UINT)u8Multiplicand) * ((UINT)u8Multiplier); + + return UIntToUInt8(uResult, pu8Result); +} + +// +// USHORT multiplication +// +__inline +HRESULT +UShortMult( + __inn USHORT usMultiplicand, + __inn USHORT usMultiplier, + __outt __deref_out_range(==,usMultiplier * usMultiplicand)USHORT* pusResult) +{ + ULONG ulResult = ((ULONG)usMultiplicand) * ((ULONG)usMultiplier); + + return ULongToUShort(ulResult, pusResult); +} + +// +// UINT16 multiplication +// +#define UInt16Mult UShortMult + +// +// WORD multiplication +// +#define WordMult UShortMult + +// +// UINT multiplication +// +__inline +HRESULT +UIntMult( + __inn UINT uMultiplicand, + __inn UINT uMultiplier, + __outt __deref_out_range(==,uMultiplier * uMultiplicand) UINT* puResult) +{ + ULONGLONG ull64Result = UInt32x32To64(uMultiplicand, uMultiplier); + + return ULongLongToUInt(ull64Result, puResult); +} + +// +// UINT32 multiplication +// +#define UInt32Mult UIntMult + +// +// UINT_PTR multiplication +// +#ifdef _WIN64 +#define UIntPtrMult ULongLongMult +#else +__inline +HRESULT +UIntPtrMult( + __inn UINT_PTR uMultiplicand, + __inn UINT_PTR uMultiplier, + __outt __deref_out_range(==,uMultiplier * uMultiplicand) UINT_PTR* puResult) +{ + ULONGLONG ull64Result = UInt32x32To64(uMultiplicand, uMultiplier); + + return ULongLongToUIntPtr(ull64Result, puResult); +} +#endif // _WIN64 + +// +// ULONG multiplication +// +__inline +HRESULT +ULongMult( + __inn ULONG ulMultiplicand, + __inn ULONG ulMultiplier, + __outt __deref_out_range(==,ulMultiplier * ulMultiplicand) ULONG* pulResult) +{ + ULONGLONG ull64Result = UInt32x32To64(ulMultiplicand, ulMultiplier); + + return ULongLongToULong(ull64Result, pulResult); +} + +// +// ULONG_PTR multiplication +// +#ifdef _WIN64 +#define ULongPtrMult ULongLongMult +#else +__inline +HRESULT +ULongPtrMult( + __inn ULONG_PTR ulMultiplicand, + __inn ULONG_PTR ulMultiplier, + __outt __deref_out_range(==,ulMultiplier * ulMultiplicand) ULONG_PTR* pulResult) +{ + ULONGLONG ull64Result = UInt32x32To64(ulMultiplicand, ulMultiplier); + + return ULongLongToULongPtr(ull64Result, (unsigned long *)pulResult); +} +#endif // _WIN64 + +// +// DWORD multiplication +// +#define DWordMult ULongMult + +// +// DWORD_PTR multiplication +// +#ifdef _WIN64 +#define DWordPtrMult ULongLongMult +#else +__inline +HRESULT +DWordPtrMult( + __inn DWORD_PTR dwMultiplicand, + __inn DWORD_PTR dwMultiplier, + __outt __deref_out_range(==,dwMultiplier * dwMultiplicand) DWORD_PTR* pdwResult) +{ + ULONGLONG ull64Result = UInt32x32To64(dwMultiplicand, dwMultiplier); + + return ULongLongToDWordPtr(ull64Result, (unsigned long *)pdwResult); +} +#endif // _WIN64 + +// +// size_t multiplication +// + +#ifdef _WIN64 +#define SizeTMult ULongLongMult +#else +__inline +HRESULT +SizeTMult( + __inn size_t Multiplicand, + __inn size_t Multiplier, + __outt __deref_out_range(==,Multiplier * Multiplicand) UINT* pResult) +{ + ULONGLONG ull64Result = UInt32x32To64(Multiplicand, Multiplier); + + return ULongLongToSizeT(ull64Result, pResult); +} +#endif // _WIN64 + +// +// SIZE_T multiplication +// +#ifdef _WIN64 +#define SIZETMult ULongLongMult +#else +__inline +HRESULT +SIZETMult( + __inn SIZE_T Multiplicand, + __inn SIZE_T Multiplier, + __outt __deref_out_range(==,Multiplier * Multiplicand) SIZE_T* pResult) +{ + ULONGLONG ull64Result = UInt32x32To64(Multiplicand, Multiplier); + + return ULongLongToSIZET(ull64Result, (unsigned long *)pResult); +} +#endif // _WIN64 + +// +// ULONGLONG multiplication +// +__inline +HRESULT +ULongLongMult( + __inn ULONGLONG ullMultiplicand, + __inn ULONGLONG ullMultiplier, + __outt __deref_out_range(==,ullMultiplier * ullMultiplicand) ULONGLONG* pullResult) +{ + HRESULT hr; +#ifdef _AMD64_ + ULONGLONG u64ResultHigh; + ULONGLONG u64ResultLow; + + u64ResultLow = UnsignedMultiply128(ullMultiplicand, ullMultiplier, &u64ResultHigh); + if (u64ResultHigh == 0) + { + *pullResult = u64ResultLow; + hr = S_OK; + } + else + { + *pullResult = ULONGLONG_ERROR; + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + } +#else + // 64x64 into 128 is like 32.32 x 32.32. + // + // a.b * c.d = a*(c.d) + .b*(c.d) = a*c + a*.d + .b*c + .b*.d + // back in non-decimal notation where A=a*2^32 and C=c*2^32: + // A*C + A*d + b*C + b*d + // So there are four components to add together. + // result = (a*c*2^64) + (a*d*2^32) + (b*c*2^32) + (b*d) + // + // a * c must be 0 or there would be bits in the high 64-bits + // a * d must be less than 2^32 or there would be bits in the high 64-bits + // b * c must be less than 2^32 or there would be bits in the high 64-bits + // then there must be no overflow of the resulting values summed up. + + ULONG dw_a; + ULONG dw_b; + ULONG dw_c; + ULONG dw_d; + ULONGLONG ad = 0; + ULONGLONG bc = 0; + ULONGLONG bd = 0; + ULONGLONG ullResult = 0; + + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + + dw_a = (ULONG)(ullMultiplicand >> 32); + dw_c = (ULONG)(ullMultiplier >> 32); + + // common case -- if high dwords are both zero, no chance for overflow + if ((dw_a == 0) && (dw_c == 0)) + { + dw_b = (DWORD)ullMultiplicand; + dw_d = (DWORD)ullMultiplier; + + *pullResult = (((ULONGLONG)dw_b) * (ULONGLONG)dw_d); + hr = S_OK; + } + else + { + // a * c must be 0 or there would be bits set in the high 64-bits + if ((dw_a == 0) || + (dw_c == 0)) + { + dw_d = (DWORD)ullMultiplier; + + // a * d must be less than 2^32 or there would be bits set in the high 64-bits + ad = (((ULONGLONG)dw_a) * (ULONGLONG)dw_d); + if ((ad & 0xffffffff00000000LL) == 0) + { + dw_b = (DWORD)ullMultiplicand; + + // b * c must be less than 2^32 or there would be bits set in the high 64-bits + bc = (((ULONGLONG)dw_b) * (ULONGLONG)dw_c); + if ((bc & 0xffffffff00000000LL) == 0) + { + // now sum them all up checking for overflow. + // shifting is safe because we already checked for overflow above + if (SUCCEEDED(ULongLongAdd(bc << 32, ad << 32, &ullResult))) + { + // b * d + bd = (((ULONGLONG)dw_b) * (ULONGLONG)dw_d); + + if (SUCCEEDED(ULongLongAdd(ullResult, bd, &ullResult))) + { + *pullResult = ullResult; + hr = S_OK; + } + } + } + } + } + } + + if (FAILED(hr)) + { + *pullResult = ULONGLONG_ERROR; + } +#endif // _AMD64_ + + return hr; +} + +// +// DWORDLONG multiplication +// +#define DWordLongMult ULongLongMult + +// +// ULONG64 multiplication +// +#define ULong64Mult ULongLongMult + +// +// DWORD64 multiplication +// +#define DWord64Mult ULongLongMult + +// +// UINT64 multiplication +// +#define UInt64Mult ULongLongMult + +// +// Macros that are no longer used in this header but which clients may +// depend on being defined here. +// +#define LOWORD(_dw) ((WORD)(((DWORD_PTR)(_dw)) & 0xffff)) +#define HIWORD(_dw) ((WORD)((((DWORD_PTR)(_dw)) >> 16) & 0xffff)) +#define LODWORD(_qw) ((DWORD)(_qw)) +#define HIDWORD(_qw) ((DWORD)(((_qw) >> 32) & 0xffffffff)) + +#endif // XPLAT_INTSAFE_H diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h new file mode 100644 index 000000000..f7cd17163 --- /dev/null +++ b/source/shared/xplat_winerror.h @@ -0,0 +1,132 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: xplat_winerror.h +// +// Contents: Contains the minimal definitions to build on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef XPLAT_WINERROR_H +#define XPLAT_WINERROR_H + +#define NOERROR 0 +#define WAIT_TIMEOUT 258L // dderror +#define S_OK ((HRESULT)0L) +#define S_FALSE ((HRESULT)1L) +#define E_NOTIMPL ((HRESULT) 0x80004001L) +#define E_FAIL ((HRESULT) 0x80004005L) +#define E_ABORT _HRESULT_TYPEDEF_(0x80004004L) +#define ERROR_HANDLE_EOF 38L +#define E_UNEXPECTED ((HRESULT) 0x8000FFFFL) +#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0) +#define FAILED(hr) (((HRESULT)(hr)) < 0) +#define ERROR_SUCCESS 0L +#define ERROR_ACCESS_DENIED 5L +#define ERROR_TIMEOUT 1460L +#define E_POINTER _HRESULT_TYPEDEF_(0x80004003L) +#define _HRESULT_TYPEDEF_(_sc) ((HRESULT)_sc) +#define E_OUTOFMEMORY _HRESULT_TYPEDEF_(0x8007000EL) +#define NO_ERROR 0L +#define ERROR_CANCELLED 1223L +#define E_INVALIDARG _HRESULT_TYPEDEF_(0x80070057L) +#define DISP_E_TYPEMISMATCH _HRESULT_TYPEDEF_(0x80020005L) +#define DISP_E_OVERFLOW _HRESULT_TYPEDEF_(0x8002000AL) +#define ERROR_INSUFFICIENT_BUFFER 122L // dderror +#define FACILITY_WIN32 7 +#define __HRESULT_FROM_WIN32(x) ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : ((HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000))) +#define HRESULT_FROM_WIN32(x) __HRESULT_FROM_WIN32(x) +#define SEVERITY_ERROR 1 +#define FACILITY_ITF 4 +#define MAKE_HRESULT(sev,fac,code) \ + ((HRESULT) (((windowsULong_t)(sev)<<31) | ((windowsULong_t)(fac)<<16) | ((windowsULong_t)(code))) ) +#define ERROR_INVALID_DATA 13L +#define ERROR_INVALID_PARAMETER 87L // dderror +#define ERROR_POSSIBLE_DEADLOCK 1131L +#define ERROR_INVALID_FLAGS 1004L +#define ERROR_NO_UNICODE_TRANSLATION 1113L + +#define ERROR_FILE_NOT_FOUND 2L +#define ERROR_PATH_NOT_FOUND 3L +#define ERROR_TOO_MANY_OPEN_FILES 4L +#define E_NOINTERFACE _HRESULT_TYPEDEF_(0x80004002L) + +#define ERROR_MOD_NOT_FOUND 126L +#define ERROR_NO_MORE_FILES 18L +#define ERROR_FILE_EXISTS 80L +#define ERROR_ALREADY_EXISTS 183L +#define ERROR_SHARING_VIOLATION 32L +#define SCODE_CODE(sc) ((sc) & 0xFFFF) +#define ERROR_READ_FAULT 30L +#define ERROR_INTERNAL_ERROR 1359L +//---------------------------------------------------------------------------- +// Error codes used by SNI +// +#define ERROR_NOT_ENOUGH_MEMORY 8L // dderror +#define ERROR_IO_PENDING 997L +#define WSA_IO_PENDING (ERROR_IO_PENDING) +#define WSAHOST_NOT_FOUND 11001L +#define WSATRY_AGAIN 11002L +#define WSANO_RECOVERY 11003L +#define WSANO_DATA 11004L +#define WSATYPE_NOT_FOUND 10109L +#define WSA_NOT_ENOUGH_MEMORY 8L +#define WSAEINTR 10004L +#define WSAEACCES 10013L +#define WSAEFAULT 10014L +#define WSAEINVAL 10022L +#define WSAEMFILE 10024L +#define WSAEWOULDBLOCK 10035L +#define WSAEALREADY 10037L +#define WSAENOTSOCK 10038L +#define WSAEMSGSIZE 10040L +#define WSAENOPROTOOPT 10042L +#define WSAEPROTONOSUPPORT 10043L +#define WSAESOCKTNOSUPPORT 10044L +#define WSAEOPNOTSUPP 10045L +#define WSAEAFNOSUPPORT 10047L +#define WSAEADDRINUSE 10048L +#define WSAEADDRNOTAVAIL 10049L +#define WSAENETUNREACH 10051L +#define WSAECONNRESET 10054L +#define WSAENOBUFS 10055L +#define WSAEISCONN 10056L +#define WSAENOTCONN 10057L +#define WSAETIMEDOUT 10060L +#define WSAECONNREFUSED 10061L +#define WSANOTINITIALISED 10093L +#define ERROR_OUTOFMEMORY 14L +#define ERROR_NOT_SUPPORTED 50L +#define ERROR_BUFFER_OVERFLOW 111L +#define ERROR_MAX_THRDS_REACHED 164L +#define ERROR_INVALID_OPERATION 4317L +#define ERROR_INVALID_STATE 5023L +#define SEC_E_BAD_BINDINGS _HRESULT_TYPEDEF_(0x80090346L) +#define ERROR_MORE_DATA 234L // dderror +#define ERROR_ARITHMETIC_OVERFLOW 534L +#define SEC_E_INCOMPLETE_MESSAGE _HRESULT_TYPEDEF_(0x80090318L) +#define ERROR_OPERATION_ABORTED 995L +#define ERROR_CONNECTION_REFUSED 1225L +#define SEC_E_OK ((HRESULT)0x00000000L) +#define SEC_E_UNSUPPORTED_FUNCTION _HRESULT_TYPEDEF_(0x80090302L) +#define SEC_E_TARGET_UNKNOWN _HRESULT_TYPEDEF_(0x80090303L) +#define SEC_E_OUT_OF_SEQUENCE _HRESULT_TYPEDEF_(0x80090310L) +#define SEC_E_INVALID_TOKEN _HRESULT_TYPEDEF_(0x80090308L) +#define SEC_I_CONTINUE_NEEDED _HRESULT_TYPEDEF_(0x00090312L) +#define ERROR_INVALID_FUNCTION 1L // dderror +#define TRUST_E_TIME_STAMP _HRESULT_TYPEDEF_(0x80096005L) +#define CRYPT_E_NOT_FOUND _HRESULT_TYPEDEF_(0x80092004L) +#define WAIT_TIMEOUT 258L // dderror + + +#endif // XPLAT_WINERROR_H diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h new file mode 100644 index 000000000..0f21a44aa --- /dev/null +++ b/source/shared/xplat_winnls.h @@ -0,0 +1,144 @@ +//--------------------------------------------------------------------------------------------------------------------------------- +// File: xplat_winnls.h +// +// Contents: Contains the minimal definitions to build on non-Windows platforms +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifndef XPLAT_WINNLS_H +#define XPLAT_WINNLS_H + +#include +#include "typedefs_for_linux.h" + +struct threadlocaleinfostruct; +struct threadmbcinfostruct; +typedef struct threadlocaleinfostruct * pthreadlocinfo; +typedef struct threadmbcinfostruct * pthreadmbcinfo; + +typedef struct localeinfo_struct +{ + pthreadlocinfo locinfo; + pthreadmbcinfo mbcinfo; +} _locale_tstruct, *_locale_t; + +#define LOCALE_SDECIMAL 0x0000000E // decimal separator +#define LOCALE_SCURRENCY 0x00000014 // local monetary symbol +#define LOCALE_SMONDECIMALSEP 0x00000016 // monetary decimal separator +#define LOCALE_SMONTHOUSANDSEP 0x00000017 // monetary thousand separator +#define LOCALE_SMONGROUPING 0x00000018 // monetary grouping +#define LOCALE_ILDATE 0x00000022 // long date format ordering (derived from LOCALE_SLONGDATE, use that instead) +#define LOCALE_ITIME 0x00000023 // time format specifier (derived from LOCALE_STIMEFORMAT, use that instead) +#define LOCALE_SABBREVMONTHNAME1 0x00000044 // abbreviated name for January + +#define LOCALE_IDEFAULTLANGUAGE 0x00000009 // default language id +#define LOCALE_IDEFAULTCOUNTRY 0x0000000A // default country/region code, deprecated +#define LOCALE_IDEFAULTCODEPAGE 0x0000000B // default oem code page +#define LOCALE_IDEFAULTANSICODEPAGE 0x00001004 // default ansi code page +#define LOCALE_IDEFAULTMACCODEPAGE 0x00001011 // default mac code page + +#define LOCALE_STIMEFORMAT 0x00001003 // time format string, eg "HH:mm:ss" + +typedef DWORD LCTYPE; + +#define NORM_IGNORECASE 0x00000001 // ignore case +#define NORM_IGNORENONSPACE 0x00000002 // ignore nonspacing chars +#define NORM_IGNORESYMBOLS 0x00000004 // ignore symbols + +#define LINGUISTIC_IGNORECASE 0x00000010 // linguistically appropriate 'ignore case' +#define LINGUISTIC_IGNOREDIACRITIC 0x00000020 // linguistically appropriate 'ignore nonspace' + +#define NORM_IGNOREKANATYPE 0x00010000 // ignore kanatype +#define NORM_IGNOREWIDTH 0x00020000 // ignore width +#define NORM_LINGUISTIC_CASING 0x08000000 // use linguistic rules for casing + + +#define NORM_IGNORECASE 0x00000001 // ignore case + +#define MB_PRECOMPOSED 0x00000001 // use precomposed chars +#define MB_COMPOSITE 0x00000002 // use composite chars +#define MB_USEGLYPHCHARS 0x00000004 // use glyph chars, not ctrl chars +#define MB_ERR_INVALID_CHARS 0x00000008 // error for invalid chars + +#define WC_COMPOSITECHECK 0x00000200 // convert composite to precomposed +#define WC_DISCARDNS 0x00000010 // discard non-spacing chars +#define WC_SEPCHARS 0x00000020 // generate separate chars +#define WC_DEFAULTCHAR 0x00000040 // replace w/ default char + + +typedef WORD LANGID; + + + + +#define NLS_VALID_LOCALE_MASK 0x000fffff + +#define MAKELANGID(p, s) ((((WORD )(s)) << 10) | (WORD )(p)) +#define MAKELCID(lgid, srtid) ((DWORD)((((DWORD)((WORD )(srtid))) << 16) | \ + ((DWORD)((WORD )(lgid))))) +#define LANG_NEUTRAL 0x00 +#define SUBLANG_DEFAULT 0x01 // user default +#define SUBLANG_SYS_DEFAULT 0x02 // system default +#define SORT_DEFAULT 0x0 // sorting default +#define LANG_USER_DEFAULT (MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)) +#define LOCALE_USER_DEFAULT (MAKELCID(LANG_USER_DEFAULT, SORT_DEFAULT)) +#define SUBLANG_ENGLISH_US 0x01 // English (USA) +#define LANG_ENGLISH 0x09 +#define LOCALE_ENGLISH_US MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) +#define LANG_SYSTEM_DEFAULT (MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT)) +#define LOCALE_SYSTEM_DEFAULT (MAKELCID(LANG_SYSTEM_DEFAULT, SORT_DEFAULT)) + +BOOL +WINAPI +IsDBCSLeadByte( + __inn BYTE TestChar); + + +#ifdef MPLAT_UNIX +// XPLAT_ODBC_TODO: VSTS 718708 Localization +// Find way to remove this +LCID GetUserDefaultLCID(); +#endif + + +BOOL IsValidCodePage(UINT CodePage); + +#define HIGH_SURROGATE_START 0xd800 +#define HIGH_SURROGATE_END 0xdbff +#define LOW_SURROGATE_START 0xdc00 +#define LOW_SURROGATE_END 0xdfff +#define IS_HIGH_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && ((wch) <= HIGH_SURROGATE_END)) +#define IS_LOW_SURROGATE(wch) (((wch) >= LOW_SURROGATE_START) && ((wch) <= LOW_SURROGATE_END)) + +int +GetLocaleInfoA( + __inn LCID Locale, + __inn LCTYPE LCType, + __out_ecount_opt(cchData) LPSTR lpLCData, + __inn int cchData); +int +GetLocaleInfoW( + __inn LCID Locale, + __inn LCTYPE LCType, + __out_ecount_opt(cchData) LPWSTR lpLCData, + __inn int cchData); +#ifdef UNICODE +#define GetLocaleInfo GetLocaleInfoW +#else +#define GetLocaleInfo GetLocaleInfoA +#endif // !UNICODE + + +#endif // XPLAT_WINNLS_H diff --git a/source/sqlsrv/CREDITS b/source/sqlsrv/CREDITS new file mode 100644 index 000000000..442116644 --- /dev/null +++ b/source/sqlsrv/CREDITS @@ -0,0 +1 @@ +Microsoft Drivers for PHP for SQL Server diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 new file mode 100644 index 000000000..255470d46 --- /dev/null +++ b/source/sqlsrv/config.m4 @@ -0,0 +1,42 @@ +PHP_ARG_ENABLE(sqlsrv, whether to enable sqlsrv functions, +[ --disable-sqlsrv Disable sqlsrv functions], yes) + +if test "$PHP_SQLSRV" != "no"; then + sqlsrv_src_class="\ + conn.cpp \ + util.cpp \ + init.cpp \ + stmt.cpp \ + " + shared_src_class="\ + shared/core_conn.cpp \ + shared/core_results.cpp \ + shared/core_stream.cpp \ + shared/core_init.cpp \ + shared/core_stmt.cpp \ + shared/core_util.cpp \ + shared/FormattedPrint.cpp \ + shared/localizationimpl.cpp \ + shared/StringFunctions.cpp \ + " + AC_MSG_CHECKING([for SQLSRV headers]) + if test -f $srcdir/ext/sqlsrv/shared/core_sqlsrv.h; then + sqlsrv_inc_path=$srcdir/ext/sqlsrv/shared/ + elif test -f $srcdir/shared/core_sqlsrv.h; then + sqlsrv_inc_path=$srcdir/shared/ + else + AC_MSG_ERROR([Cannot find SQLSRV headers]) + fi + AC_MSG_RESULT($sqlsrv_inc_path) + + CXXFLAGS="$CXXFLAGS -std=c++11" + PHP_REQUIRE_CXX() + PHP_ADD_LIBRARY(stdc++, 1, SQLSRV_SHARED_LIBADD) + PHP_ADD_LIBRARY(odbc, 1, SQLSRV_SHARED_LIBADD) + PHP_ADD_LIBRARY(odbcinst, 1, SQLSRV_SHARED_LIBADD) + PHP_SUBST(SQLSRV_SHARED_LIBADD) + AC_DEFINE(HAVE_SQLSRV, 1, [ ]) + PHP_ADD_INCLUDE([$sqlsrv_inc_path]) + PHP_NEW_EXTENSION(sqlsrv, $sqlsrv_src_class $shared_src_class, $ext_shared,,-std=c++11) + PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1) +fi diff --git a/sqlsrv/config.w32 b/source/sqlsrv/config.w32 similarity index 59% rename from sqlsrv/config.w32 rename to source/sqlsrv/config.w32 index 1fb9fa5b0..2b534aef6 100644 --- a/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -1,39 +1,42 @@ -//---------------------------------------------------------------------------------------------------------------------------------- -// File: config.w32 -// -// Contents: JScript build configuration used by buildconf.bat -// -// Microsoft Drivers 4.0 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -ARG_ENABLE("sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (SQLSRV driver)", "no"); - -if( PHP_SQLSRV != "no" ) { - - sqlsrv_src = "conn.cpp init.cpp stmt.cpp util.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp"; - - if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") && - CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")) { - - EXTENSION("sqlsrv", sqlsrv_src, PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1") - - CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_SQLSRV_ODBC'); - CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_SQLSRV_ODBC'); - ADD_FLAG( 'LDFLAGS_SQLSRV', '/NXCOMPAT /DYNAMICBASE /debug' ); - ADD_FLAG( 'CFLAGS_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' ); - ADD_FLAG( 'CFLAGS_SQLSRV', '/EHsc' ); - ADD_FLAG( 'CFLAGS_SQLSRV', '/GS' ); - ADD_FLAG( 'CFLAGS_SQLSRV', '/Zi' ); - } -} +//---------------------------------------------------------------------------------------------------------------------------------- +// File: config.w32 +// +// Contents: JScript build configuration used by buildconf.bat +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +ARG_ENABLE("sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (SQLSRV driver)", "no"); + +if( PHP_SQLSRV != "no" ) { + + sqlsrv_src_class = " conn.cpp init.cpp stmt.cpp util.cpp "; + shared_src_class = " core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp "; + + if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") && + CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")&& + CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_SQLSRV", configure_module_dirname + "\\shared")) { + ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "sqlsrv" ); + CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_SQLSRV_ODBC"); + CHECK_HEADER_ADD_INCLUDE("sqlext.h", "CFLAGS_SQLSRV_ODBC"); + ADD_FLAG( "LDFLAGS_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug" ); + ADD_FLAG( "CFLAGS_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" ); + ADD_FLAG( "CFLAGS_SQLSRV", "/EHsc" ); + ADD_FLAG( "CFLAGS_SQLSRV", "/GS" ); + ADD_FLAG( "CFLAGS_SQLSRV", "/Zi" ); + EXTENSION("sqlsrv", sqlsrv_src_class , PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); + } else { + WARNING("sqlsrv not enabled; libraries and headers not found"); + } +} diff --git a/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp similarity index 95% rename from sqlsrv/conn.cpp rename to source/sqlsrv/conn.cpp index 2e7f1b2b7..59a2479f5 100644 --- a/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -1,1303 +1,1299 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: conn.cpp -// -// Contents: Routines that use connection handles -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "php_sqlsrv.h" -#include -#include -#include - -#include -#include - -// *** internal variables and constants *** - -namespace { - -// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros -unsigned int current_log_subsystem = LOG_CONN; - -struct date_as_string_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - TSRMLS_C; // show as used to avoid a warning - - ss_sqlsrv_conn* ss_conn = static_cast( conn ); - - if( zend_is_true( value )) { - ss_conn->date_as_string = true; - } - else { - ss_conn->date_as_string = false; - } - } -}; - -struct conn_char_set_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - convert_to_string( value ); - const char* encoding = Z_STRVAL_P( value ); - size_t encoding_len = Z_STRLEN_P( value ); - - zend_ulong index = -1; - zend_string* key = NULL; - void* ss_encoding_temp = NULL; - - ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, ss_encoding_temp ) { - sqlsrv_encoding* ss_encoding = reinterpret_cast( ss_encoding_temp ); - ss_encoding_temp = NULL; - if (!strnicmp( encoding, ss_encoding->iana, encoding_len )) { - - if ( ss_encoding->not_for_connection ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); - } - - conn->set_encoding( static_cast(ss_encoding->code_page )); - return; - } - } ZEND_HASH_FOREACH_END(); - - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); - } -}; - -struct bool_conn_str_func { - - static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ) - { - TSRMLS_C; - char const* val_str; - if( zend_is_true( value )) { - val_str = "yes"; - } - else { - val_str = "no"; - } - conn_str += option->odbc_name; - conn_str += "={"; - conn_str += val_str; - conn_str += "};"; - } -}; - -template -struct int_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_LVAL_P( value )), SQL_IS_UINTEGER TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } - } -}; - -template -struct bool_conn_attr_func { - - static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) - { - try { - core::SQLSetConnectAttr(conn, Attr, reinterpret_cast((zend_long)zend_is_true(value)), - SQL_IS_UINTEGER TSRMLS_CC); - - } - catch( core::CoreException& ) { - throw; - } - - } -}; - -//// *** internal functions *** - -void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ); -void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ char** uid, _Out_ char** pwd, - _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ); -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ); -void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, - HashTable* options_ht, zval* data TSRMLS_DC ); -void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, HashTable* options_ht, zval* data TSRMLS_DC ); -int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC ); -int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ); - -} - -// constants for parameters used by process_params function(s) -int ss_sqlsrv_conn::descriptor; -char* ss_sqlsrv_conn::resource_name = "ss_sqlsrv_conn"; - -// connection specific parameter proccessing. Use the generic function specialised to return a connection -// resource. -#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \ - rsrc = process_params( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, __VA_ARGS__ ); \ - if( rsrc == NULL ) { \ - RETURN_FALSE; \ - } - -namespace SSStmtOptionNames { - const char QUERY_TIMEOUT[]= "QueryTimeout"; - const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec"; - const char SCROLLABLE[] = "Scrollable"; - const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT; -} - -namespace SSConnOptionNames { - -// most of these strings are the same for both the sqlsrv_connect connection option -// and the name put into the connection string. MARS is the only one that's different. -const char APP[] = "APP"; -const char ApplicationIntent[] = "ApplicationIntent"; -const char AttachDBFileName[] = "AttachDbFileName"; -const char CharacterSet[] = "CharacterSet"; -const char ConnectionPooling[] = "ConnectionPooling"; -const char Database[] = "Database"; -const char DateAsString[] = "ReturnDatesAsStrings"; -const char Encrypt[] = "Encrypt"; -const char Failover_Partner[] = "Failover_Partner"; -const char LoginTimeout[] = "LoginTimeout"; -const char MARS_Option[] = "MultipleActiveResultSets"; -const char MultiSubnetFailover[] = "MultiSubnetFailover"; -const char PWD[] = "PWD"; -const char QuotedId[] = "QuotedId"; -const char TraceFile[] = "TraceFile"; -const char TraceOn[] = "TraceOn"; -const char TrustServerCertificate[] = "TrustServerCertificate"; -const char TransactionIsolation[] = "TransactionIsolation"; -const char UID[] = "UID"; -const char WSID[] = "WSID"; - -} - -enum SS_CONN_OPTIONS { - - SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC, -}; - -//List of all statement options supported by this driver -const stmt_option SS_STMT_OPTS[] = { - { - SSStmtOptionNames::QUERY_TIMEOUT, - sizeof( SSStmtOptionNames::QUERY_TIMEOUT ), - SQLSRV_STMT_OPTION_QUERY_TIMEOUT, - std::unique_ptr( new stmt_option_query_timeout ) - }, - { - SSStmtOptionNames::SEND_STREAMS_AT_EXEC, - sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ), - SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, - std::unique_ptr( new stmt_option_send_at_exec ) - }, - { - SSStmtOptionNames::SCROLLABLE, - sizeof( SSStmtOptionNames::SCROLLABLE ), - SQLSRV_STMT_OPTION_SCROLLABLE, - std::unique_ptr( new stmt_option_scrollable ) - }, - { - SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE, - sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ), - SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, - std::unique_ptr( new stmt_option_buffered_query_limit ) - }, - { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, -}; - - -// List of all connection options supported by this driver. -const connection_option SS_CONN_OPTS[] = { - { - SSConnOptionNames::APP, - sizeof( SSConnOptionNames::APP ), - SQLSRV_CONN_OPTION_APP, - ODBCConnOptions::APP, - sizeof( ODBCConnOptions::APP ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::ApplicationIntent, - sizeof( SSConnOptionNames::ApplicationIntent ), - SQLSRV_CONN_OPTION_APPLICATION_INTENT, - ODBCConnOptions::ApplicationIntent, - sizeof( ODBCConnOptions::ApplicationIntent ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::AttachDBFileName, - sizeof( SSConnOptionNames::AttachDBFileName ), - SQLSRV_CONN_OPTION_ATTACHDBFILENAME, - ODBCConnOptions::AttachDBFileName, - sizeof( ODBCConnOptions::AttachDBFileName ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::CharacterSet, - sizeof( SSConnOptionNames::CharacterSet ), - SQLSRV_CONN_OPTION_CHARACTERSET, - ODBCConnOptions::CharacterSet, - sizeof( ODBCConnOptions::CharacterSet ), - CONN_ATTR_STRING, - conn_char_set_func::func - }, - { - SSConnOptionNames::ConnectionPooling, - sizeof( SSConnOptionNames::ConnectionPooling ), - SQLSRV_CONN_OPTION_CONN_POOLING, - ODBCConnOptions::ConnectionPooling, - sizeof( ODBCConnOptions::ConnectionPooling ), - CONN_ATTR_BOOL, - conn_null_func::func - }, - { - SSConnOptionNames::Database, - sizeof( SSConnOptionNames::Database ), - SQLSRV_CONN_OPTION_DATABASE, - ODBCConnOptions::Database, - sizeof( ODBCConnOptions::Database ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::Encrypt, - sizeof( SSConnOptionNames::Encrypt ), - SQLSRV_CONN_OPTION_ENCRYPT, - ODBCConnOptions::Encrypt, - sizeof( ODBCConnOptions::Encrypt ), - CONN_ATTR_BOOL, - bool_conn_str_func::func - }, - { - SSConnOptionNames::Failover_Partner, - sizeof( SSConnOptionNames::Failover_Partner ), - SQLSRV_CONN_OPTION_FAILOVER_PARTNER, - ODBCConnOptions::Failover_Partner, - sizeof( ODBCConnOptions::Failover_Partner ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::LoginTimeout, - sizeof( SSConnOptionNames::LoginTimeout ), - SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, - ODBCConnOptions::LoginTimeout, - sizeof( ODBCConnOptions::LoginTimeout ), - CONN_ATTR_INT, - int_conn_attr_func::func - }, - { - SSConnOptionNames::MARS_Option, - sizeof( SSConnOptionNames::MARS_Option ), - SQLSRV_CONN_OPTION_MARS, - ODBCConnOptions::MARS_ODBC, - sizeof( ODBCConnOptions::MARS_ODBC ), - CONN_ATTR_BOOL, - bool_conn_str_func::func - }, - { - SSConnOptionNames::MultiSubnetFailover, - sizeof( SSConnOptionNames::MultiSubnetFailover ), - SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, - ODBCConnOptions::MultiSubnetFailover, - sizeof( ODBCConnOptions::MultiSubnetFailover ), - CONN_ATTR_BOOL, - bool_conn_str_func::func - }, - { - SSConnOptionNames::QuotedId, - sizeof( SSConnOptionNames::QuotedId ), - SQLSRV_CONN_OPTION_QUOTED_ID, - ODBCConnOptions::QuotedId, - sizeof( ODBCConnOptions::QuotedId ), - CONN_ATTR_BOOL, - bool_conn_str_func::func - }, - { - SSConnOptionNames::TraceFile, - sizeof( SSConnOptionNames::TraceFile ), - SQLSRV_CONN_OPTION_TRACE_FILE, - ODBCConnOptions::TraceFile, - sizeof( ODBCConnOptions::TraceFile ), - CONN_ATTR_STRING, - str_conn_attr_func::func - }, - { - SSConnOptionNames::TraceOn, - sizeof( SSConnOptionNames::TraceOn ), - SQLSRV_CONN_OPTION_TRACE_ON, - ODBCConnOptions::TraceOn, - sizeof( ODBCConnOptions::TraceOn ), - CONN_ATTR_BOOL, - bool_conn_attr_func::func - }, - { - SSConnOptionNames::TransactionIsolation, - sizeof( SSConnOptionNames::TransactionIsolation ), - SQLSRV_CONN_OPTION_TRANS_ISOLATION, - ODBCConnOptions::TransactionIsolation, - sizeof( ODBCConnOptions::TransactionIsolation ), - CONN_ATTR_INT, - int_conn_attr_func::func - }, - { - SSConnOptionNames::TrustServerCertificate, - sizeof( SSConnOptionNames::TrustServerCertificate ), - SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, - ODBCConnOptions::TrustServerCertificate, - sizeof( ODBCConnOptions::TrustServerCertificate ), - CONN_ATTR_BOOL, - bool_conn_str_func::func - }, - { - SSConnOptionNames::WSID, - sizeof( SSConnOptionNames::WSID ), - SQLSRV_CONN_OPTION_WSID, - ODBCConnOptions::WSID, - sizeof( ODBCConnOptions::WSID ), - CONN_ATTR_STRING, - conn_str_append_func::func - }, - { - SSConnOptionNames::DateAsString, - sizeof( SSConnOptionNames::DateAsString ), - SS_CONN_OPTION_DATE_AS_STRING, - SSConnOptionNames::DateAsString, - sizeof( SSConnOptionNames::DateAsString ), - CONN_ATTR_BOOL, - date_as_string_func::func - }, - { NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table -}; - -// sqlsrv_connect( string $serverName [, array $connectionInfo]) -// -// Creates a connection resource and opens a connection. By default, the -// connection is attempted using Windows Authentication. -// -// Parameters -// $serverName: A string specifying the name of the server to which a connection -// is being established. An instance name (for example, "myServer\instanceName") -// or port number (for example, "myServer, 1521") can be included as part of -// this string. For a complete description of the options available for this -// parameter, see the Server keyword in the ODBC Driver Connection String -// Keywords section of Using Connection String Keywords with ODBC Driver 11 for SQL Server. -// -// $connectionInfo [OPTIONAL]: An associative array that contains connection -// attributes (for example, array("Database" => "AdventureWorks")). -// -// Return Value -// A PHP connection resource. If a connection cannot be successfully created and -// opened, false is returned - -PHP_FUNCTION ( sqlsrv_connect ) -{ - - LOG_FUNCTION( "sqlsrv_connect" ); - SET_FUNCTION_NAME( *g_henv_cp ); - SET_FUNCTION_NAME( *g_henv_ncp ); - - reset_errors( TSRMLS_C ); - - const char* server = NULL; - zval* options_z = NULL; - char* uid = NULL; - char* pwd = NULL; - size_t server_len = 0; - zval conn_z; - ZVAL_UNDEF(&conn_z); - // get the server name and connection options - int result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &server, &server_len, &options_z ); - - CHECK_CUSTOM_ERROR(( result == FAILURE ), *g_henv_cp, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, "sqlsrv_connect" ) { - RETURN_FALSE; - } - - hash_auto_ptr ss_conn_options_ht; - hash_auto_ptr stmts; - ss_sqlsrv_conn* conn = NULL; - - try { - - // Initialize the options array to be passed to the core layer - ALLOC_HASHTABLE( ss_conn_options_ht ); - - core::sqlsrv_zend_hash_init( *g_henv_cp, ss_conn_options_ht, 10 /* # of buckets */, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); - - // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. - ::validate_conn_options( *g_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht TSRMLS_CC ); - - // call the core connect function - conn = static_cast( core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, &core::allocate_conn, - server, uid, pwd, ss_conn_options_ht, ss_error_handler, - SS_CONN_OPTS, NULL, "sqlsrv_connect" TSRMLS_CC )); - - SQLSRV_ASSERT( conn != NULL, "sqlsrv_connect: Invalid connection returned. Exception should have been thrown." ); - - // create a bunch of statements - ALLOC_HASHTABLE( stmts ); - - core::sqlsrv_zend_hash_init( *g_henv_cp, stmts, 5, NULL /* dtor */, 0 /* persistent */ TSRMLS_CC ); - - // register the connection with the PHP runtime - - ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC); - - conn->stmts = stmts; - stmts.transferred(); - RETURN_RES( Z_RES(conn_z) ); - } - - catch( core::CoreException& ) { - - if( conn != NULL ) { - - conn->invalidate(); - } - - RETURN_FALSE; - } - - catch( ... ) { - - DIE("sqlsrv_connect: Unknown exception caught."); - } -} - -// sqlsrv_begin_transaction( resource $conn ) -// -// Begins a transaction on a specified connection. The current transaction -// includes all statements on the specified connection that were executed after -// the call to sqlsrv_begin_transaction and before any calls to sqlsrv_rollback -// or sqlsrv_commit. -// -// The SQLSRV driver is in auto-commit mode by default. This means that all -// queries are automatically committed upon success unless they have been -// designated as part of an explicit transaction by using -// sqlsrv_begin_transaction. -// -// If sqlsrv_begin_transaction is called after a transaction has already been -// initiated on the connection but not completed by calling either sqlsrv_commit -// or sqlsrv_rollback, the call returns false and an Already in Transaction -// error is added to the error collection. -// -// Parameters -// $conn: The connection with which the transaction is associated. -// -// Return Value -// A Boolean value: true if the transaction was successfully begun. Otherwise, false. - -PHP_FUNCTION( sqlsrv_begin_transaction ) -{ - LOG_FUNCTION( "sqlsrv_begin_transaction" ); - - - ss_sqlsrv_conn* conn = NULL; - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - // Return false if already in transaction - CHECK_CUSTOM_ERROR(( conn->in_transaction == true ), *conn, SS_SQLSRV_ERROR_ALREADY_IN_TXN ) { - RETURN_FALSE; - } - - try { - - core_sqlsrv_begin_transaction( conn TSRMLS_CC ); - conn->in_transaction = true; - RETURN_TRUE; - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE("sqlsrv_begin_transaction: Unknown exception caught."); - } -} - - -// sqlsrv_close( resource $conn ) -// Closes the specified connection and releases associated resources. -// -// Parameters -// $conn: The connection to be closed. Null is a valid value parameter for this -// parameter. This allows the function to be called multiple times in a -// script. For example, if you close a connection in an error condition and -// close it again at the end of the script, the second call to sqlsrv_close will -// return true because the first call to sqlsrv_close (in the error condition) -// sets the connection resource to null. -// -// Return Value -// The Boolean value true unless the function is called with an invalid -// parameter. If the function is called with an invalid parameter, false is -// returned. - -PHP_FUNCTION( sqlsrv_close ) -{ - LOG_FUNCTION( "sqlsrv_close" ); - - zval* conn_r = NULL; - ss_sqlsrv_conn* conn = NULL; - sqlsrv_context_auto_ptr error_ctx; - - reset_errors( TSRMLS_C ); - - try { - - // dummy context to pass to the error handler - error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); - - if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r) == FAILURE ) { - - // Check if it was a zval - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &conn_r ); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - throw ss::SSException(); - } - - // if sqlsrv_close was called on a non-existent connection then we just return success. - if( Z_TYPE_P( conn_r ) == IS_NULL ) { - RETURN_TRUE; - } - else { - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - } - - conn = static_cast( zend_fetch_resource( Z_RES_P( conn_r ) TSRMLS_CC, ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor )); - - // if sqlsrv_close was called on an already closed connection then we just return success. - if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) { - RETURN_TRUE; - } - - CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - SET_FUNCTION_NAME( *conn ); - - // cause any variables still holding a reference to this to be invalid so they cause - // an error when passed to a sqlsrv function. There's nothing we can do if the - // removal fails, so we just log it and move on. - if( zend_list_close( Z_RES_P( conn_r ) ) == FAILURE ) { - LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P( conn_r )); - } - - ZVAL_NULL( conn_r ); - - RETURN_TRUE; - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_close: Unknown exception caught." ); - } -} - -void __cdecl sqlsrv_conn_dtor( zend_resource *rsrc TSRMLS_DC ) -{ - LOG_FUNCTION( "sqlsrv_conn_dtor" ); - - // get the structure - ss_sqlsrv_conn *conn = static_cast( rsrc->ptr ); - SQLSRV_ASSERT( conn != NULL, "sqlsrv_conn_dtor: connection was null"); - - SET_FUNCTION_NAME( *conn ); - - // close all statements associated with the connection. - sqlsrv_conn_close_stmts( conn TSRMLS_CC ); - - // close the connection itself. - core_sqlsrv_close( conn TSRMLS_CC ); - - rsrc->ptr = NULL; -} - -// sqlsrv_commit( resource $conn ) -// -// Commits the current transaction on the specified connection and returns the -// connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or -// sqlsrv_commit. - -// The SQLSRV driver is in auto-commit mode by -// default. This means that all queries are automatically committed upon success -// unless they have been designated as part of an explicit transaction by using -// sqlsrv_begin_transaction. If sqlsrv_commit is called on a connection that is -// not in an active transaction and that was initiated with -// sqlsrv_begin_transaction, the call returns false and a Not in Transaction -// error is added to the error collection. -// -// Parameters -// $conn: The connection on which the transaction is active. -// -// Return Value -// A Boolean value: true if the transaction was successfully committed. Otherwise, false. - -PHP_FUNCTION( sqlsrv_commit ) -{ - LOG_FUNCTION( "sqlsrv_commit" ); - - ss_sqlsrv_conn* conn = NULL; - - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - // Return false if not in transaction - CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) { - RETURN_FALSE; - } - - try { - - conn->in_transaction = false; - core_sqlsrv_commit( conn TSRMLS_CC ); - RETURN_TRUE; - - } - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_commit: Unknown exception caught." ); - } -} - -// sqlsrv_rollback( resource $conn ) -// -// Rolls back the current transaction on the specified connection and returns -// the connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or -// sqlsrv_commit. -// -// The SQLSRV driver is in auto-commit mode by default. This -// means that all queries are automatically committed upon success unless they -// have been designated as part of an explicit transaction by using -// sqlsrv_begin_transaction. -// -// If sqlsrv_rollback is called on a connection that is not in an active -// transaction that was initiated with sqlsrv_begin_transaction, the call -// returns false and a Not in Transaction error is added to the error -// collection. -// -// Parameters -// $conn: The connection on which the transaction is active. -// -// Return Value -// A Boolean value: true if the transaction was successfully rolled back. Otherwise, false. - -PHP_FUNCTION( sqlsrv_rollback ) -{ - LOG_FUNCTION( "sqlsrv_rollback" ); - - - ss_sqlsrv_conn* conn = NULL; - - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - // Return false if not in transaction - CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) { - RETURN_FALSE; - } - - try { - - conn->in_transaction = false; - core_sqlsrv_rollback( conn TSRMLS_CC ); - RETURN_TRUE; - } - catch( core::CoreException& ){ - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_rollback: Unknown exception caught." ); - } -} - -// sqlsrv_client_info -// Returns the ODBC driver's dll name, version and the ODBC version. Also returns -// the version of this extension. -// Parameters: -// $conn - The connection resource by which the client and server are connected. - -PHP_FUNCTION( sqlsrv_client_info ) -{ - - LOG_FUNCTION( "sqlsrv_client_info" ); - ss_sqlsrv_conn* conn = NULL; - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - try { - - core_sqlsrv_get_client_info( conn, return_value TSRMLS_CC ); - - // Add the sqlsrv driver's file version - core::sqlsrv_add_assoc_string( *conn, return_value, "ExtensionVer", VER_FILEVERSION_STR, 1 /*duplicate*/ TSRMLS_CC ); - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_client_info: Unknown exception caught." ); - } -} - -// sqlsrv_server_info( resource $conn ) -// -// Returns information about the server. -// -// Parameters -// $conn: The connection resource by which the client and server are connected. -// -// Return Value -// An associative array with the following keys: -// CurrentDatabase -// The database currently being targeted. -// SQLServerVersion -// The version of SQL Server. -// SQLServerName -// The name of the server. - -PHP_FUNCTION( sqlsrv_server_info ) -{ - try { - - LOG_FUNCTION( "sqlsrv_server_info" ); - ss_sqlsrv_conn* conn = NULL; - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - core_sqlsrv_get_server_info( conn, return_value TSRMLS_CC ); - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_server_info: Unknown exception caught." ); - } -} - - -// sqlsrv_prepare( resource $conn, string $tsql [, array $params [, array $options]]) -// -// Creates a statement resource associated with the specified connection. A statement -// resource returned by sqlsrv_prepare may be executed multiple times by sqlsrv_execute. -// In between each execution, the values may be updated by changing the value of the -// variables bound. Output parameters cannot be relied upon to contain their results until -// all rows are processed. -// -// Parameters -// $conn: The connection resource associated with the created statement. -// -// $tsql: The Transact-SQL expression that corresponds to the created statement. -// -// $params [OPTIONAL]: An array of values that correspond to parameters in a -// parameterized query. Each parameter may be specified as: -// $value | array($value [, $direction [, $phpType [, $sqlType]]]) -// When given just a $value, the direction is default input, and phptype is the value -// given, with the sql type inferred from the php type. -// -// $options [OPTIONAL]: An associative array that sets query properties. The -// table below lists the supported keys and corresponding values: -// QueryTimeout -// Sets the query timeout in seconds. By default, the driver will wait -// indefinitely for results. -// SendStreamParamsAtExec -// Configures the driver to send all stream data at execution (true), or to -// send stream data in chunks (false). By default, the value is set to -// true. For more information, see sqlsrv_send_stream_data. -// -// Return Value -// A statement resource. If the statement resource cannot be created, false is returned. - -PHP_FUNCTION( sqlsrv_prepare ) -{ - - LOG_FUNCTION( "sqlsrv_prepare" ); - - sqlsrv_malloc_auto_ptr stmt; - ss_sqlsrv_conn* conn = NULL; - char *sql = NULL; - zend_long sql_len = 0; - zval* params_z = NULL; - zval* options_z = NULL; - hash_auto_ptr ss_stmt_options_ht; - zval stmt_z; - ZVAL_UNDEF(&stmt_z); - - PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z ); - - try { - - if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) { - - // Initialize the options array to be passed to the core layer - ALLOC_HASHTABLE( ss_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); - - validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); - - } - - if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - if( sql == NULL ) { - - DIE( "sqlsrv_prepare: sql string was null." ); - } - - stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, - ss_stmt_options_ht, SS_STMT_OPTS, - ss_error_handler, NULL TSRMLS_CC ) ); - - core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC ); - - //mark_params_by_reference( stmt, params_z TSRMLS_CC ); - if (params_z) { - stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); - ZVAL_COPY(stmt->params_z, params_z); - } - - stmt->prepared = true; - - // register the statement with the PHP runtime - ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC ); - - // store the resource id with the connection so the connection - // can release this statement when it closes. - zend_long next_index = zend_hash_next_free_element( conn->stmts ); - - core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); - - stmt->conn_index = next_index; - - // the statement is now registered with EG( regular_list ) - stmt.transferred(); - - RETURN_RES(Z_RES(stmt_z)); - - } - catch( core::CoreException& ) { - - if( stmt ) { - - stmt->conn = NULL; - stmt->~ss_sqlsrv_stmt(); - } - if (!Z_ISUNDEF(stmt_z)) { - free_stmt_resource(&stmt_z TSRMLS_CC); - } - - RETURN_FALSE; - } - - catch( ... ) { - - DIE( "sqlsrv_prepare: Unknown exception caught." ); - } -} - -// sqlsrv_query( resource $conn, string $tsql [, array $params [, array $options]]) -// -// Creates a statement resource associated with the specified connection. The statement -// is immediately executed and may not be executed again using sqlsrv_execute. -// -// Parameters -// $conn: The connection resource associated with the created statement. -// -// $tsql: The Transact-SQL expression that corresponds to the created statement. -// -// $params [OPTIONAL]: An array of values that correspond to parameters in a -// parameterized query. Each parameter may be specified as: -// $value | array($value [, $direction [, $phpType [, $sqlType]]]) -// When given just a $value, the direction is default input, and phptype is the value -// given, with the sql type inferred from the php type. -// -// $options [OPTIONAL]: An associative array that sets query properties. The -// table below lists the supported keys and corresponding values: -// QueryTimeout -// Sets the query timeout in seconds. By default, the driver will wait -// indefinitely for results. -// SendStreamParamsAtExec -// Configures the driver to send all stream data at execution (true), or to -// send stream data in chunks (false). By default, the value is set to -// true. For more information, see sqlsrv_send_stream_data. -// -// Return Value -// A statement resource. If the statement resource cannot be created, false is returned. - -PHP_FUNCTION( sqlsrv_query ) -{ - - LOG_FUNCTION( "sqlsrv_query" ); - - ss_sqlsrv_conn* conn = NULL; - sqlsrv_malloc_auto_ptr stmt; - char* sql = NULL; - hash_auto_ptr ss_stmt_options_ht; - int sql_len = 0; - zval* options_z = NULL; - zval* params_z = NULL; - zval stmt_z; - ZVAL_UNDEF(&stmt_z); - - PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z ); - - try { - - // check for statement options - if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) { - - // Initialize the options array to be passed to the core layer - ALLOC_HASHTABLE( ss_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, ZVAL_PTR_DTOR, - 0 /*persistent*/ TSRMLS_CC ); - - validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); - } - - if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) { - THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - if( sql == NULL ) { - - DIE( "sqlsrv_query: sql string was null." ); - } - - stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, - ss_stmt_options_ht, SS_STMT_OPTS, - ss_error_handler, NULL TSRMLS_CC ) ); - - if( params_z ) { - stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); - ZVAL_COPY(stmt->params_z, params_z); - } - - stmt->set_func( "sqlsrv_query" ); - - bind_params( stmt TSRMLS_CC ); - - // execute the statement - core_sqlsrv_execute( stmt TSRMLS_CC, sql, sql_len ); - - // register the statement with the PHP runtime - ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC); - // store the resource id with the connection so the connection - // can release this statement when it closes. - zend_ulong next_index = zend_hash_next_free_element( conn->stmts ); - - core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); - stmt->conn_index = next_index; - stmt.transferred(); - - RETURN_RES(Z_RES(stmt_z)); - } - - catch( core::CoreException& ) { - - if( stmt ) { - - stmt->conn = NULL; // tell the statement that it isn't part of the connection so it doesn't try to remove itself - stmt->~ss_sqlsrv_stmt(); - } - if (!Z_ISUNDEF(stmt_z)) { - free_stmt_resource(&stmt_z TSRMLS_CC); - } - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_query: Unknown exception caught." ); - } -} - -void free_stmt_resource( zval* stmt_z TSRMLS_DC ) -{ - if( FAILURE == zend_list_close( Z_RES_P( stmt_z ))) { - LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z)); - } - ZVAL_NULL( stmt_z ); - zval_ptr_dtor(stmt_z); -} - -// internal connection functions - -namespace { - -// must close all statement handles opened by this connection before closing the connection -// no errors are returned, since close should always succeed - -void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ) -{ - //pre-condition check - SQLSRV_ASSERT(( conn->handle() != NULL ), "sqlsrv_conn_close_stmts: Connection handle is NULL. Trying to destroy an " - "already destroyed connection."); - SQLSRV_ASSERT(( conn->stmts ), "sqlsrv_conn_close_stmts: Connection doesn't contain a statement array." ); - - // loop through the stmts hash table and destroy each stmt resource so we can close the - // ODBC connection - - zval* rsrc_ptr = NULL; - ZEND_HASH_FOREACH_VAL( conn->stmts, rsrc_ptr ) { - try { - int zr = ( rsrc_ptr ) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, *conn, SQLSRV_ERROR_ZEND_HASH ) { - throw core::CoreException(); - } - } - catch( core::CoreException& ) { - DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" ); - } - // see if the statement is still valid, and if not skip to the next one - // presumably this should never happen because if it's in the list, it should still be valid - // by virtue that a statement resource should remove itself from its connection when it is - // destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource - // and move to the next one. - ss_sqlsrv_stmt* stmt = NULL; - stmt = static_cast( Z_RES_VAL_P( rsrc_ptr )); - if( stmt == NULL || Z_RES_TYPE_P( rsrc_ptr ) != ss_sqlsrv_stmt::descriptor ) { - LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves" - " from the connection so this shouldn't be out of sync." ); - continue; - } - // delete the statement by deleting it from Zend's resource list, which will force its destruction - stmt->conn = NULL; - - try { - // this would call the destructor on the statement. - int zr = zend_list_close(Z_RES_P(rsrc_ptr)); - } - catch( core::CoreException& ) { - LOG(SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", Z_RES_HANDLE_P(rsrc_ptr)); - } - } ZEND_HASH_FOREACH_END(); - - zend_hash_destroy( conn->stmts ); - FREE_HASHTABLE( conn->stmts ); - conn->stmts = NULL; -} - -int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC ) -{ - for( int i=0; SS_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) - { - if( key_len == SS_CONN_OPTS[ i ].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[ i ].sqlsrv_name )) { - - - switch( SS_CONN_OPTS[ i ].value_type ) { - - case CONN_ATTR_BOOL: - // bool attributes can be either strings to be appended to the connection string - // as yes or no or integral connection attributes. This will have to be reworked - // if we ever introduce a boolean connection option that maps to a string connection - // attribute. - break; - case CONN_ATTR_INT: - { - CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - SS_CONN_OPTS[ i ].sqlsrv_name ) - { - throw ss::SSException(); - } - break; - } - case CONN_ATTR_STRING: - { - CHECK_CUSTOM_ERROR( Z_TYPE_P( value_z ) != IS_STRING, ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - SS_CONN_OPTS[ i ].sqlsrv_name ) { - - throw ss::SSException(); - } - - char* value = Z_STRVAL_P( value_z ); - size_t value_len = Z_STRLEN_P( value_z ); - bool escaped = core_is_conn_opt_value_escaped( value, value_len ); - - CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[ i ].sqlsrv_name ) { - - throw ss::SSException(); - } - break; - } - } - - return SS_CONN_OPTS[ i ].conn_option_key; - } - } - return SQLSRV_CONN_OPTION_INVALID; -} - -int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ) -{ - for( int i = 0; SS_STMT_OPTS[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) - { - if( key_len == SS_STMT_OPTS[ i ].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[ i ].name )) { - return SS_STMT_OPTS[ i ].key; - } - } - return SQLSRV_STMT_OPTION_INVALID; -} - -void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, - HashTable* options_ht, zval* data TSRMLS_DC ) -{ - int option_key = ::get_stmt_option_key( key, key_len TSRMLS_CC ); - - CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, ZSTR_VAL( key ) ) { - - throw ss::SSException(); - } - - Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. - core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); -} - -void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, - HashTable* options_ht, zval* data TSRMLS_DC ) -{ - int option_key = ::get_conn_option_key( ctx, key, key_len, data TSRMLS_CC ); - CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key ) { - - throw ss::SSException(); - } - - Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. - core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); -} - -// Iterates through the list of statement options provided by the user and validates them -// against the list of supported statement options by this driver. After validation -// creates a Hashtable of statement options to be sent to the core layer for processing. - -void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ) -{ - try { - if( stmt_options ) { - - HashTable* options_ht = Z_ARRVAL_P( stmt_options ); - zend_ulong int_key = -1; - zend_string *key = NULL; - zval* data = NULL; - ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - size_t key_len = 0; - zval* conn_opt = NULL; - int result = 0; - - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - if (type != HASH_KEY_IS_STRING) { - CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) { - throw core::CoreException(); - } - } - key_len = ZSTR_LEN(key) + 1; - add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - } - } - catch( core::CoreException& ) { - - throw; - } -} - -// Iterates through the list of connection options provided by the user and validates them -// against the predefined list of supported connection options by this driver. After validation -// creates a Hashtable of connection options to be sent to the core layer for processing. - -void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ char** uid, _Out_ char** pwd, _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ) -{ - try { - - if( user_options_z ) { - - HashTable* options_ht = Z_ARRVAL_P( user_options_z ); - zend_ulong int_key = -1; - zend_string *key = NULL; - zval* data = NULL; - ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - CHECK_CUSTOM_ERROR(( Z_TYPE_P( data ) == IS_NULL || Z_TYPE_P( data ) == IS_UNDEF ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key) { - throw ss::SSException(); - } - - CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) { - throw ss::SSException(); - } - - // Length of the key string does not include the null terminator in PHP7, +1 has to be added - size_t key_len = ZSTR_LEN(key) + 1; - if( key_len == sizeof(SSConnOptionNames::UID) && !stricmp(ZSTR_VAL(key), SSConnOptionNames::UID )) { - - *uid = Z_STRVAL_P( data ); - } - - else if( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::PWD )) { - - *pwd = Z_STRVAL_P( data ); - } - else { - - ::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC ); - } - } ZEND_HASH_FOREACH_END(); - } - } - catch( core::CoreException& ) { - throw; - } -} - -} // namespace +//--------------------------------------------------------------------------------------------------------------------------------- +// File: conn.cpp +// +// Contents: Routines that use connection handles +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_sqlsrv.h" + +#include +#include + +// *** internal variables and constants *** + +namespace { + +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros +unsigned int current_log_subsystem = LOG_CONN; + +struct date_as_string_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + TSRMLS_C; // show as used to avoid a warning + + ss_sqlsrv_conn* ss_conn = static_cast( conn ); + + if( zend_is_true( value )) { + ss_conn->date_as_string = true; + } + else { + ss_conn->date_as_string = false; + } + } +}; + +struct conn_char_set_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + convert_to_string( value ); + const char* encoding = Z_STRVAL_P( value ); + size_t encoding_len = Z_STRLEN_P( value ); + + zend_ulong index = -1; + zend_string* key = NULL; + void* ss_encoding_temp = NULL; + + ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, ss_encoding_temp ) { + sqlsrv_encoding* ss_encoding = reinterpret_cast( ss_encoding_temp ); + ss_encoding_temp = NULL; + if (!strnicmp( encoding, ss_encoding->iana, encoding_len )) { + + if ( ss_encoding->not_for_connection ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); + } + + conn->set_encoding( static_cast(ss_encoding->code_page )); + return; + } + } ZEND_HASH_FOREACH_END(); + + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding ); + } +}; + +struct bool_conn_str_func { + + static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str TSRMLS_DC ) + { + TSRMLS_C; + char const* val_str; + if( zend_is_true( value )) { + val_str = "yes"; + } + else { + val_str = "no"; + } + conn_str += option->odbc_name; + conn_str += "={"; + conn_str += val_str; + conn_str += "};"; + } +}; + +template +struct int_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_LVAL_P( value )), SQL_IS_UINTEGER TSRMLS_CC ); + } + catch( core::CoreException& ) { + throw; + } + } +}; + +template +struct bool_conn_attr_func { + + static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + { + try { + core::SQLSetConnectAttr(conn, Attr, reinterpret_cast((zend_long)zend_is_true(value)), + SQL_IS_UINTEGER TSRMLS_CC); + + } + catch( core::CoreException& ) { + throw; + } + + } +}; + +//// *** internal functions *** + +void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ); +void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ char** uid, _Out_ char** pwd, + _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ); +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ); +void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, + HashTable* options_ht, zval* data TSRMLS_DC ); +void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, HashTable* options_ht, zval* data TSRMLS_DC ); +int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC ); +int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ); + +} + +// constants for parameters used by process_params function(s) +int ss_sqlsrv_conn::descriptor; +char* ss_sqlsrv_conn::resource_name = static_cast("ss_sqlsrv_conn"); + +// connection specific parameter proccessing. Use the generic function specialised to return a connection +// resource. +#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \ + rsrc = process_params( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, ##__VA_ARGS__ );\ + if( rsrc == NULL ) { \ + RETURN_FALSE; \ + } + +namespace SSStmtOptionNames { + const char QUERY_TIMEOUT[]= "QueryTimeout"; + const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec"; + const char SCROLLABLE[] = "Scrollable"; + const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT; +} + +namespace SSConnOptionNames { + +// most of these strings are the same for both the sqlsrv_connect connection option +// and the name put into the connection string. MARS is the only one that's different. +const char APP[] = "APP"; +const char ApplicationIntent[] = "ApplicationIntent"; +const char AttachDBFileName[] = "AttachDbFileName"; +const char CharacterSet[] = "CharacterSet"; +const char ConnectionPooling[] = "ConnectionPooling"; +const char Database[] = "Database"; +const char DateAsString[] = "ReturnDatesAsStrings"; +const char Encrypt[] = "Encrypt"; +const char Failover_Partner[] = "Failover_Partner"; +const char LoginTimeout[] = "LoginTimeout"; +const char MARS_Option[] = "MultipleActiveResultSets"; +const char MultiSubnetFailover[] = "MultiSubnetFailover"; +const char PWD[] = "PWD"; +const char QuotedId[] = "QuotedId"; +const char TraceFile[] = "TraceFile"; +const char TraceOn[] = "TraceOn"; +const char TrustServerCertificate[] = "TrustServerCertificate"; +const char TransactionIsolation[] = "TransactionIsolation"; +const char UID[] = "UID"; +const char WSID[] = "WSID"; + +} + +enum SS_CONN_OPTIONS { + + SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC, +}; + +//List of all statement options supported by this driver +const stmt_option SS_STMT_OPTS[] = { + { + SSStmtOptionNames::QUERY_TIMEOUT, + sizeof( SSStmtOptionNames::QUERY_TIMEOUT ), + SQLSRV_STMT_OPTION_QUERY_TIMEOUT, + std::unique_ptr( new stmt_option_query_timeout ) + }, + { + SSStmtOptionNames::SEND_STREAMS_AT_EXEC, + sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ), + SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, + std::unique_ptr( new stmt_option_send_at_exec ) + }, + { + SSStmtOptionNames::SCROLLABLE, + sizeof( SSStmtOptionNames::SCROLLABLE ), + SQLSRV_STMT_OPTION_SCROLLABLE, + std::unique_ptr( new stmt_option_scrollable ) + }, + { + SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE, + sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ), + SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, + std::unique_ptr( new stmt_option_buffered_query_limit ) + }, + { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, +}; + + +// List of all connection options supported by this driver. +const connection_option SS_CONN_OPTS[] = { + { + SSConnOptionNames::APP, + sizeof( SSConnOptionNames::APP ), + SQLSRV_CONN_OPTION_APP, + ODBCConnOptions::APP, + sizeof( ODBCConnOptions::APP ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::ApplicationIntent, + sizeof( SSConnOptionNames::ApplicationIntent ), + SQLSRV_CONN_OPTION_APPLICATION_INTENT, + ODBCConnOptions::ApplicationIntent, + sizeof( ODBCConnOptions::ApplicationIntent ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::AttachDBFileName, + sizeof( SSConnOptionNames::AttachDBFileName ), + SQLSRV_CONN_OPTION_ATTACHDBFILENAME, + ODBCConnOptions::AttachDBFileName, + sizeof( ODBCConnOptions::AttachDBFileName ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::CharacterSet, + sizeof( SSConnOptionNames::CharacterSet ), + SQLSRV_CONN_OPTION_CHARACTERSET, + ODBCConnOptions::CharacterSet, + sizeof( ODBCConnOptions::CharacterSet ), + CONN_ATTR_STRING, + conn_char_set_func::func + }, + { + SSConnOptionNames::ConnectionPooling, + sizeof( SSConnOptionNames::ConnectionPooling ), + SQLSRV_CONN_OPTION_CONN_POOLING, + ODBCConnOptions::ConnectionPooling, + sizeof( ODBCConnOptions::ConnectionPooling ), + CONN_ATTR_BOOL, + conn_null_func::func + }, + { + SSConnOptionNames::Database, + sizeof( SSConnOptionNames::Database ), + SQLSRV_CONN_OPTION_DATABASE, + ODBCConnOptions::Database, + sizeof( ODBCConnOptions::Database ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::Encrypt, + sizeof( SSConnOptionNames::Encrypt ), + SQLSRV_CONN_OPTION_ENCRYPT, + ODBCConnOptions::Encrypt, + sizeof( ODBCConnOptions::Encrypt ), + CONN_ATTR_BOOL, + bool_conn_str_func::func + }, + { + SSConnOptionNames::Failover_Partner, + sizeof( SSConnOptionNames::Failover_Partner ), + SQLSRV_CONN_OPTION_FAILOVER_PARTNER, + ODBCConnOptions::Failover_Partner, + sizeof( ODBCConnOptions::Failover_Partner ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::LoginTimeout, + sizeof( SSConnOptionNames::LoginTimeout ), + SQLSRV_CONN_OPTION_LOGIN_TIMEOUT, + ODBCConnOptions::LoginTimeout, + sizeof( ODBCConnOptions::LoginTimeout ), + CONN_ATTR_INT, + int_conn_attr_func::func + }, + { + SSConnOptionNames::MARS_Option, + sizeof( SSConnOptionNames::MARS_Option ), + SQLSRV_CONN_OPTION_MARS, + ODBCConnOptions::MARS_ODBC, + sizeof( ODBCConnOptions::MARS_ODBC ), + CONN_ATTR_BOOL, + bool_conn_str_func::func + }, + { + SSConnOptionNames::MultiSubnetFailover, + sizeof( SSConnOptionNames::MultiSubnetFailover ), + SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, + ODBCConnOptions::MultiSubnetFailover, + sizeof( ODBCConnOptions::MultiSubnetFailover ), + CONN_ATTR_BOOL, + bool_conn_str_func::func + }, + { + SSConnOptionNames::QuotedId, + sizeof( SSConnOptionNames::QuotedId ), + SQLSRV_CONN_OPTION_QUOTED_ID, + ODBCConnOptions::QuotedId, + sizeof( ODBCConnOptions::QuotedId ), + CONN_ATTR_BOOL, + bool_conn_str_func::func + }, + { + SSConnOptionNames::TraceFile, + sizeof( SSConnOptionNames::TraceFile ), + SQLSRV_CONN_OPTION_TRACE_FILE, + ODBCConnOptions::TraceFile, + sizeof( ODBCConnOptions::TraceFile ), + CONN_ATTR_STRING, + str_conn_attr_func::func + }, + { + SSConnOptionNames::TraceOn, + sizeof( SSConnOptionNames::TraceOn ), + SQLSRV_CONN_OPTION_TRACE_ON, + ODBCConnOptions::TraceOn, + sizeof( ODBCConnOptions::TraceOn ), + CONN_ATTR_BOOL, + bool_conn_attr_func::func + }, + { + SSConnOptionNames::TransactionIsolation, + sizeof( SSConnOptionNames::TransactionIsolation ), + SQLSRV_CONN_OPTION_TRANS_ISOLATION, + ODBCConnOptions::TransactionIsolation, + sizeof( ODBCConnOptions::TransactionIsolation ), + CONN_ATTR_INT, + int_conn_attr_func::func + }, + { + SSConnOptionNames::TrustServerCertificate, + sizeof( SSConnOptionNames::TrustServerCertificate ), + SQLSRV_CONN_OPTION_TRUST_SERVER_CERT, + ODBCConnOptions::TrustServerCertificate, + sizeof( ODBCConnOptions::TrustServerCertificate ), + CONN_ATTR_BOOL, + bool_conn_str_func::func + }, + { + SSConnOptionNames::WSID, + sizeof( SSConnOptionNames::WSID ), + SQLSRV_CONN_OPTION_WSID, + ODBCConnOptions::WSID, + sizeof( ODBCConnOptions::WSID ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, + { + SSConnOptionNames::DateAsString, + sizeof( SSConnOptionNames::DateAsString ), + SS_CONN_OPTION_DATE_AS_STRING, + SSConnOptionNames::DateAsString, + sizeof( SSConnOptionNames::DateAsString ), + CONN_ATTR_BOOL, + date_as_string_func::func + }, + { NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table +}; + +// sqlsrv_connect( string $serverName [, array $connectionInfo]) +// +// Creates a connection resource and opens a connection. By default, the +// connection is attempted using Windows Authentication. +// +// Parameters +// $serverName: A string specifying the name of the server to which a connection +// is being established. An instance name (for example, "myServer\instanceName") +// or port number (for example, "myServer, 1521") can be included as part of +// this string. For a complete description of the options available for this +// parameter, see the Server keyword in the ODBC Driver Connection String +// Keywords section of Using Connection String Keywords with ODBC Driver 11 for SQL Server. +// +// $connectionInfo [OPTIONAL]: An associative array that contains connection +// attributes (for example, array("Database" => "AdventureWorks")). +// +// Return Value +// A PHP connection resource. If a connection cannot be successfully created and +// opened, false is returned + +PHP_FUNCTION ( sqlsrv_connect ) +{ + + LOG_FUNCTION( "sqlsrv_connect" ); + SET_FUNCTION_NAME( *g_henv_cp ); + SET_FUNCTION_NAME( *g_henv_ncp ); + + reset_errors( TSRMLS_C ); + + const char* server = NULL; + zval* options_z = NULL; + char* uid = NULL; + char* pwd = NULL; + size_t server_len = 0; + zval conn_z; + ZVAL_UNDEF(&conn_z); + // get the server name and connection options + int result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &server, &server_len, &options_z ); + + CHECK_CUSTOM_ERROR(( result == FAILURE ), *g_henv_cp, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, "sqlsrv_connect" ) { + RETURN_FALSE; + } + + hash_auto_ptr ss_conn_options_ht; + hash_auto_ptr stmts; + ss_sqlsrv_conn* conn = NULL; + + try { + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( ss_conn_options_ht ); + + core::sqlsrv_zend_hash_init( *g_henv_cp, ss_conn_options_ht, 10 /* # of buckets */, + ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + + // Either of g_henv_cp or g_henv_ncp can be used to propogate the error. + ::validate_conn_options( *g_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht TSRMLS_CC ); + + // call the core connect function + conn = static_cast( core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, &core::allocate_conn, + server, uid, pwd, ss_conn_options_ht, ss_error_handler, + SS_CONN_OPTS, NULL, "sqlsrv_connect" TSRMLS_CC )); + + SQLSRV_ASSERT( conn != NULL, "sqlsrv_connect: Invalid connection returned. Exception should have been thrown." ); + + // create a bunch of statements + ALLOC_HASHTABLE( stmts ); + + core::sqlsrv_zend_hash_init( *g_henv_cp, stmts, 5, NULL /* dtor */, 0 /* persistent */ TSRMLS_CC ); + + // register the connection with the PHP runtime + + ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC); + + conn->stmts = stmts; + stmts.transferred(); + RETURN_RES( Z_RES(conn_z) ); + } + + catch( core::CoreException& ) { + + if( conn != NULL ) { + + conn->invalidate(); + } + + RETURN_FALSE; + } + + catch( ... ) { + + DIE("sqlsrv_connect: Unknown exception caught."); + } +} + +// sqlsrv_begin_transaction( resource $conn ) +// +// Begins a transaction on a specified connection. The current transaction +// includes all statements on the specified connection that were executed after +// the call to sqlsrv_begin_transaction and before any calls to sqlsrv_rollback +// or sqlsrv_commit. +// +// The SQLSRV driver is in auto-commit mode by default. This means that all +// queries are automatically committed upon success unless they have been +// designated as part of an explicit transaction by using +// sqlsrv_begin_transaction. +// +// If sqlsrv_begin_transaction is called after a transaction has already been +// initiated on the connection but not completed by calling either sqlsrv_commit +// or sqlsrv_rollback, the call returns false and an Already in Transaction +// error is added to the error collection. +// +// Parameters +// $conn: The connection with which the transaction is associated. +// +// Return Value +// A Boolean value: true if the transaction was successfully begun. Otherwise, false. + +PHP_FUNCTION( sqlsrv_begin_transaction ) +{ + LOG_FUNCTION( "sqlsrv_begin_transaction" ); + + + ss_sqlsrv_conn* conn = NULL; + PROCESS_PARAMS( conn, "r", _FN_, 0 ); + + // Return false if already in transaction + CHECK_CUSTOM_ERROR(( conn->in_transaction == true ), *conn, SS_SQLSRV_ERROR_ALREADY_IN_TXN ) { + RETURN_FALSE; + } + + try { + + core_sqlsrv_begin_transaction( conn TSRMLS_CC ); + conn->in_transaction = true; + RETURN_TRUE; + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE("sqlsrv_begin_transaction: Unknown exception caught."); + } +} + + +// sqlsrv_close( resource $conn ) +// Closes the specified connection and releases associated resources. +// +// Parameters +// $conn: The connection to be closed. Null is a valid value parameter for this +// parameter. This allows the function to be called multiple times in a +// script. For example, if you close a connection in an error condition and +// close it again at the end of the script, the second call to sqlsrv_close will +// return true because the first call to sqlsrv_close (in the error condition) +// sets the connection resource to null. +// +// Return Value +// The Boolean value true unless the function is called with an invalid +// parameter. If the function is called with an invalid parameter, false is +// returned. + +PHP_FUNCTION( sqlsrv_close ) +{ + LOG_FUNCTION( "sqlsrv_close" ); + + zval* conn_r = NULL; + ss_sqlsrv_conn* conn = NULL; + sqlsrv_context_auto_ptr error_ctx; + + reset_errors( TSRMLS_C ); + + try { + + // dummy context to pass to the error handler + error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); + SET_FUNCTION_NAME( *error_ctx ); + + if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r) == FAILURE ) { + + // Check if it was a zval + int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &conn_r ); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + throw ss::SSException(); + } + + // if sqlsrv_close was called on a non-existent connection then we just return success. + if( Z_TYPE_P( conn_r ) == IS_NULL ) { + RETURN_TRUE; + } + else { + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + } + + conn = static_cast( zend_fetch_resource( Z_RES_P( conn_r ) TSRMLS_CC, ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor )); + + // if sqlsrv_close was called on an already closed connection then we just return success. + if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) { + RETURN_TRUE; + } + + CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + SET_FUNCTION_NAME( *conn ); + + // cause any variables still holding a reference to this to be invalid so they cause + // an error when passed to a sqlsrv function. There's nothing we can do if the + // removal fails, so we just log it and move on. + if( zend_list_close( Z_RES_P( conn_r ) ) == FAILURE ) { + LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P( conn_r )); + } + + ZVAL_NULL( conn_r ); + + RETURN_TRUE; + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_close: Unknown exception caught." ); + } +} + +void __cdecl sqlsrv_conn_dtor( zend_resource *rsrc TSRMLS_DC ) +{ + LOG_FUNCTION( "sqlsrv_conn_dtor" ); + + // get the structure + ss_sqlsrv_conn *conn = static_cast( rsrc->ptr ); + SQLSRV_ASSERT( conn != NULL, "sqlsrv_conn_dtor: connection was null"); + + SET_FUNCTION_NAME( *conn ); + + // close all statements associated with the connection. + sqlsrv_conn_close_stmts( conn TSRMLS_CC ); + + // close the connection itself. + core_sqlsrv_close( conn TSRMLS_CC ); + + rsrc->ptr = NULL; +} + +// sqlsrv_commit( resource $conn ) +// +// Commits the current transaction on the specified connection and returns the +// connection to the auto-commit mode. The current transaction includes all +// statements on the specified connection that were executed after the call to +// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or +// sqlsrv_commit. + +// The SQLSRV driver is in auto-commit mode by +// default. This means that all queries are automatically committed upon success +// unless they have been designated as part of an explicit transaction by using +// sqlsrv_begin_transaction. If sqlsrv_commit is called on a connection that is +// not in an active transaction and that was initiated with +// sqlsrv_begin_transaction, the call returns false and a Not in Transaction +// error is added to the error collection. +// +// Parameters +// $conn: The connection on which the transaction is active. +// +// Return Value +// A Boolean value: true if the transaction was successfully committed. Otherwise, false. + +PHP_FUNCTION( sqlsrv_commit ) +{ + LOG_FUNCTION( "sqlsrv_commit" ); + + ss_sqlsrv_conn* conn = NULL; + + PROCESS_PARAMS( conn, "r", _FN_, 0 ); + + // Return false if not in transaction + CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) { + RETURN_FALSE; + } + + try { + + conn->in_transaction = false; + core_sqlsrv_commit( conn TSRMLS_CC ); + RETURN_TRUE; + + } + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_commit: Unknown exception caught." ); + } +} + +// sqlsrv_rollback( resource $conn ) +// +// Rolls back the current transaction on the specified connection and returns +// the connection to the auto-commit mode. The current transaction includes all +// statements on the specified connection that were executed after the call to +// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or +// sqlsrv_commit. +// +// The SQLSRV driver is in auto-commit mode by default. This +// means that all queries are automatically committed upon success unless they +// have been designated as part of an explicit transaction by using +// sqlsrv_begin_transaction. +// +// If sqlsrv_rollback is called on a connection that is not in an active +// transaction that was initiated with sqlsrv_begin_transaction, the call +// returns false and a Not in Transaction error is added to the error +// collection. +// +// Parameters +// $conn: The connection on which the transaction is active. +// +// Return Value +// A Boolean value: true if the transaction was successfully rolled back. Otherwise, false. + +PHP_FUNCTION( sqlsrv_rollback ) +{ + LOG_FUNCTION( "sqlsrv_rollback" ); + + + ss_sqlsrv_conn* conn = NULL; + + PROCESS_PARAMS( conn, "r", _FN_, 0 ); + + // Return false if not in transaction + CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) { + RETURN_FALSE; + } + + try { + + conn->in_transaction = false; + core_sqlsrv_rollback( conn TSRMLS_CC ); + RETURN_TRUE; + } + catch( core::CoreException& ){ + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_rollback: Unknown exception caught." ); + } +} + +// sqlsrv_client_info +// Returns the ODBC driver's dll name, version and the ODBC version. Also returns +// the version of this extension. +// Parameters: +// $conn - The connection resource by which the client and server are connected. + +PHP_FUNCTION( sqlsrv_client_info ) +{ + + LOG_FUNCTION( "sqlsrv_client_info" ); + ss_sqlsrv_conn* conn = NULL; + PROCESS_PARAMS( conn, "r", _FN_, 0 ); + + try { + + core_sqlsrv_get_client_info( conn, return_value TSRMLS_CC ); + + // Add the sqlsrv driver's file version + //Declarations below eliminate compiler warnings about string constant to char* conversions + const char* extver = "ExtensionVer"; + std::string filever = VER_FILEVERSION_STR; + core::sqlsrv_add_assoc_string( *conn, return_value, extver, &filever[0], 1 /*duplicate*/ TSRMLS_CC ); + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_client_info: Unknown exception caught." ); + } +} + +// sqlsrv_server_info( resource $conn ) +// +// Returns information about the server. +// +// Parameters +// $conn: The connection resource by which the client and server are connected. +// +// Return Value +// An associative array with the following keys: +// CurrentDatabase +// The database currently being targeted. +// SQLServerVersion +// The version of SQL Server. +// SQLServerName +// The name of the server. + +PHP_FUNCTION( sqlsrv_server_info ) +{ + try { + + LOG_FUNCTION( "sqlsrv_server_info" ); + ss_sqlsrv_conn* conn = NULL; + PROCESS_PARAMS( conn, "r", _FN_, 0 ); + + core_sqlsrv_get_server_info( conn, return_value TSRMLS_CC ); + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_server_info: Unknown exception caught." ); + } +} + + +// sqlsrv_prepare( resource $conn, string $tsql [, array $params [, array $options]]) +// +// Creates a statement resource associated with the specified connection. A statement +// resource returned by sqlsrv_prepare may be executed multiple times by sqlsrv_execute. +// In between each execution, the values may be updated by changing the value of the +// variables bound. Output parameters cannot be relied upon to contain their results until +// all rows are processed. +// +// Parameters +// $conn: The connection resource associated with the created statement. +// +// $tsql: The Transact-SQL expression that corresponds to the created statement. +// +// $params [OPTIONAL]: An array of values that correspond to parameters in a +// parameterized query. Each parameter may be specified as: +// $value | array($value [, $direction [, $phpType [, $sqlType]]]) +// When given just a $value, the direction is default input, and phptype is the value +// given, with the sql type inferred from the php type. +// +// $options [OPTIONAL]: An associative array that sets query properties. The +// table below lists the supported keys and corresponding values: +// QueryTimeout +// Sets the query timeout in seconds. By default, the driver will wait +// indefinitely for results. +// SendStreamParamsAtExec +// Configures the driver to send all stream data at execution (true), or to +// send stream data in chunks (false). By default, the value is set to +// true. For more information, see sqlsrv_send_stream_data. +// +// Return Value +// A statement resource. If the statement resource cannot be created, false is returned. + +PHP_FUNCTION( sqlsrv_prepare ) +{ + + LOG_FUNCTION( "sqlsrv_prepare" ); + + sqlsrv_malloc_auto_ptr stmt; + ss_sqlsrv_conn* conn = NULL; + char *sql = NULL; + zend_long sql_len = 0; + zval* params_z = NULL; + zval* options_z = NULL; + hash_auto_ptr ss_stmt_options_ht; + zval stmt_z; + ZVAL_UNDEF(&stmt_z); + + PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z ); + + try { + + if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) { + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( ss_stmt_options_ht ); + core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, + ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + + validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); + + } + + if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + if( sql == NULL ) { + + DIE( "sqlsrv_prepare: sql string was null." ); + } + + stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, + ss_stmt_options_ht, SS_STMT_OPTS, + ss_error_handler, NULL TSRMLS_CC ) ); + + core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC ); + + //mark_params_by_reference( stmt, params_z TSRMLS_CC ); + if (params_z) { + stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); + ZVAL_COPY(stmt->params_z, params_z); + } + + stmt->prepared = true; + + // register the statement with the PHP runtime + ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC ); + + // store the resource id with the connection so the connection + // can release this statement when it closes. + zend_long next_index = zend_hash_next_free_element( conn->stmts ); + + core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); + + stmt->conn_index = next_index; + + // the statement is now registered with EG( regular_list ) + stmt.transferred(); + + RETURN_RES(Z_RES(stmt_z)); + + } + catch( core::CoreException& ) { + + if( stmt ) { + + stmt->conn = NULL; + stmt->~ss_sqlsrv_stmt(); + } + if (!Z_ISUNDEF(stmt_z)) { + free_stmt_resource(&stmt_z TSRMLS_CC); + } + + RETURN_FALSE; + } + + catch( ... ) { + + DIE( "sqlsrv_prepare: Unknown exception caught." ); + } +} + +// sqlsrv_query( resource $conn, string $tsql [, array $params [, array $options]]) +// +// Creates a statement resource associated with the specified connection. The statement +// is immediately executed and may not be executed again using sqlsrv_execute. +// +// Parameters +// $conn: The connection resource associated with the created statement. +// +// $tsql: The Transact-SQL expression that corresponds to the created statement. +// +// $params [OPTIONAL]: An array of values that correspond to parameters in a +// parameterized query. Each parameter may be specified as: +// $value | array($value [, $direction [, $phpType [, $sqlType]]]) +// When given just a $value, the direction is default input, and phptype is the value +// given, with the sql type inferred from the php type. +// +// $options [OPTIONAL]: An associative array that sets query properties. The +// table below lists the supported keys and corresponding values: +// QueryTimeout +// Sets the query timeout in seconds. By default, the driver will wait +// indefinitely for results. +// SendStreamParamsAtExec +// Configures the driver to send all stream data at execution (true), or to +// send stream data in chunks (false). By default, the value is set to +// true. For more information, see sqlsrv_send_stream_data. +// +// Return Value +// A statement resource. If the statement resource cannot be created, false is returned. + +PHP_FUNCTION( sqlsrv_query ) +{ + + LOG_FUNCTION( "sqlsrv_query" ); + + ss_sqlsrv_conn* conn = NULL; + sqlsrv_malloc_auto_ptr stmt; + char* sql = NULL; + hash_auto_ptr ss_stmt_options_ht; + size_t sql_len = 0; + zval* options_z = NULL; + zval* params_z = NULL; + zval stmt_z; + ZVAL_UNDEF(&stmt_z); + + PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z ); + + try { + + // check for statement options + if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) { + + // Initialize the options array to be passed to the core layer + ALLOC_HASHTABLE( ss_stmt_options_ht ); + core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, ZVAL_PTR_DTOR, + 0 /*persistent*/ TSRMLS_CC ); + + validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); + } + + if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) { + THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + if( sql == NULL ) { + + DIE( "sqlsrv_query: sql string was null." ); + } + + stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, + ss_stmt_options_ht, SS_STMT_OPTS, + ss_error_handler, NULL TSRMLS_CC ) ); + + if( params_z ) { + stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); + ZVAL_COPY(stmt->params_z, params_z); + } + + stmt->set_func( "sqlsrv_query" ); + + bind_params( stmt TSRMLS_CC ); + + // execute the statement + core_sqlsrv_execute( stmt TSRMLS_CC, sql, static_cast( sql_len ) ); + + // register the statement with the PHP runtime + ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC); + // store the resource id with the connection so the connection + // can release this statement when it closes. + zend_ulong next_index = zend_hash_next_free_element( conn->stmts ); + + core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); + stmt->conn_index = next_index; + stmt.transferred(); + + RETURN_RES(Z_RES(stmt_z)); + } + + catch( core::CoreException& ) { + + if( stmt ) { + + stmt->conn = NULL; // tell the statement that it isn't part of the connection so it doesn't try to remove itself + stmt->~ss_sqlsrv_stmt(); + } + if (!Z_ISUNDEF(stmt_z)) { + free_stmt_resource(&stmt_z TSRMLS_CC); + } + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_query: Unknown exception caught." ); + } +} + +void free_stmt_resource( zval* stmt_z TSRMLS_DC ) +{ + if( FAILURE == zend_list_close( Z_RES_P( stmt_z ))) { + LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z)); + } + ZVAL_NULL( stmt_z ); + zval_ptr_dtor(stmt_z); +} + +// internal connection functions + +namespace { + +// must close all statement handles opened by this connection before closing the connection +// no errors are returned, since close should always succeed + +void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC ) +{ + //pre-condition check + SQLSRV_ASSERT(( conn->handle() != NULL ), "sqlsrv_conn_close_stmts: Connection handle is NULL. Trying to destroy an " + "already destroyed connection."); + SQLSRV_ASSERT(( conn->stmts ), "sqlsrv_conn_close_stmts: Connection doesn't contain a statement array." ); + + // loop through the stmts hash table and destroy each stmt resource so we can close the + // ODBC connection + + zval* rsrc_ptr = NULL; + ZEND_HASH_FOREACH_VAL( conn->stmts, rsrc_ptr ) { + try { + int zr = ( rsrc_ptr ) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, *conn, SQLSRV_ERROR_ZEND_HASH ) { + throw core::CoreException(); + } + } + catch( core::CoreException& ) { + DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" ); + } + // see if the statement is still valid, and if not skip to the next one + // presumably this should never happen because if it's in the list, it should still be valid + // by virtue that a statement resource should remove itself from its connection when it is + // destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource + // and move to the next one. + ss_sqlsrv_stmt* stmt = NULL; + stmt = static_cast( Z_RES_VAL_P( rsrc_ptr )); + if( stmt == NULL || Z_RES_TYPE_P( rsrc_ptr ) != ss_sqlsrv_stmt::descriptor ) { + LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves" + " from the connection so this shouldn't be out of sync." ); + continue; + } + // delete the statement by deleting it from Zend's resource list, which will force its destruction + stmt->conn = NULL; + + // this would call the destructor on the statement. + // There's nothing we can do if the removal fails, so we just log it and move on. + if( zend_list_close( Z_RES_P(rsrc_ptr) ) == FAILURE ) { + LOG(SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", Z_RES_HANDLE_P(rsrc_ptr)); + } + } ZEND_HASH_FOREACH_END(); + + zend_hash_destroy( conn->stmts ); + FREE_HASHTABLE( conn->stmts ); + conn->stmts = NULL; +} + +int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC ) +{ + for( int i=0; SS_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) + { + if( key_len == SS_CONN_OPTS[ i ].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[ i ].sqlsrv_name )) { + + + switch( SS_CONN_OPTS[ i ].value_type ) { + + case CONN_ATTR_BOOL: + // bool attributes can be either strings to be appended to the connection string + // as yes or no or integral connection attributes. This will have to be reworked + // if we ever introduce a boolean connection option that maps to a string connection + // attribute. + break; + case CONN_ATTR_INT: + { + CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, + SS_CONN_OPTS[ i ].sqlsrv_name ) + { + throw ss::SSException(); + } + break; + } + case CONN_ATTR_STRING: + { + CHECK_CUSTOM_ERROR( Z_TYPE_P( value_z ) != IS_STRING, ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, + SS_CONN_OPTS[ i ].sqlsrv_name ) { + + throw ss::SSException(); + } + + char* value = Z_STRVAL_P( value_z ); + size_t value_len = Z_STRLEN_P( value_z ); + bool escaped = core_is_conn_opt_value_escaped( value, value_len ); + + CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[ i ].sqlsrv_name ) { + + throw ss::SSException(); + } + break; + } + } + + return SS_CONN_OPTS[ i ].conn_option_key; + } + } + return SQLSRV_CONN_OPTION_INVALID; +} + +int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC ) +{ + for( int i = 0; SS_STMT_OPTS[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) + { + if( key_len == SS_STMT_OPTS[ i ].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[ i ].name )) { + return SS_STMT_OPTS[ i ].key; + } + } + return SQLSRV_STMT_OPTION_INVALID; +} + +void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, + HashTable* options_ht, zval* data TSRMLS_DC ) +{ + int option_key = ::get_stmt_option_key( key, key_len TSRMLS_CC ); + + CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, ZSTR_VAL( key ) ) { + + throw ss::SSException(); + } + + Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. + core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); +} + +void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, + HashTable* options_ht, zval* data TSRMLS_DC ) +{ + int option_key = ::get_conn_option_key( ctx, key, key_len, data TSRMLS_CC ); + CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, ZSTR_VAL( key ) ) { + + throw ss::SSException(); + } + + Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. + core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); +} + +// Iterates through the list of statement options provided by the user and validates them +// against the list of supported statement options by this driver. After validation +// creates a Hashtable of statement options to be sent to the core layer for processing. + +void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ) +{ + try { + if( stmt_options ) { + + HashTable* options_ht = Z_ARRVAL_P( stmt_options ); + zend_ulong int_key = -1; + zend_string *key = NULL; + zval* data = NULL; + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + size_t key_len = 0; + + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + if (type != HASH_KEY_IS_STRING) { + CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) { + throw core::CoreException(); + } + } + key_len = ZSTR_LEN(key) + 1; + add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); + } + } + catch( core::CoreException& ) { + + throw; + } +} + +// Iterates through the list of connection options provided by the user and validates them +// against the predefined list of supported connection options by this driver. After validation +// creates a Hashtable of connection options to be sent to the core layer for processing. + +void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, _Out_ char** uid, _Out_ char** pwd, _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ) +{ + try { + + if( user_options_z ) { + + HashTable* options_ht = Z_ARRVAL_P( user_options_z ); + zend_ulong int_key = -1; + zend_string *key = NULL; + zval* data = NULL; + ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { + int type = HASH_KEY_NON_EXISTENT; + type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + CHECK_CUSTOM_ERROR(( Z_TYPE_P( data ) == IS_NULL || Z_TYPE_P( data ) == IS_UNDEF ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key) { + throw ss::SSException(); + } + + CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) { + throw ss::SSException(); + } + + // Length of the key string does not include the null terminator in PHP7, +1 has to be added + size_t key_len = ZSTR_LEN(key) + 1; + if( key_len == sizeof(SSConnOptionNames::UID) && !stricmp(ZSTR_VAL(key), SSConnOptionNames::UID )) { + + *uid = Z_STRVAL_P( data ); + } + + else if( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::PWD )) { + + *pwd = Z_STRVAL_P( data ); + } + else { + + ::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC ); + } + } ZEND_HASH_FOREACH_END(); + } + } + catch( core::CoreException& ) { + throw; + } +} + +} // namespace diff --git a/sqlsrv/init.cpp b/source/sqlsrv/init.cpp similarity index 93% rename from sqlsrv/init.cpp rename to source/sqlsrv/init.cpp index e9c0473b5..5f047f4b4 100644 --- a/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -1,660 +1,680 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: init.cpp -// Contents: initialization routines for the extension -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "php_sqlsrv.h" - -#ifdef ZTS -ZEND_TSRMLS_CACHE_DEFINE(); -#endif -ZEND_GET_MODULE(g_sqlsrv) - -extern "C" { - -ZEND_DECLARE_MODULE_GLOBALS(sqlsrv); - -} - -// module global variables (initialized in minit and freed in mshutdown) -HashTable* g_ss_errors_ht = NULL; -// special list of warnings to ignore even if warnings are treated as errors -HashTable* g_ss_warnings_to_ignore_ht = NULL; -// encodings we understand -HashTable* g_ss_encodings_ht = NULL; - -// Destructors called by Zend for each element in the hashtable -void sqlsrv_error_const_dtor( zval* element ); -void sqlsrv_encoding_dtor( zval* element ); - -// henv context for creating connections -sqlsrv_context* g_henv_cp; -sqlsrv_context* g_henv_ncp; - -namespace { - -// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros -unsigned int current_log_subsystem = LOG_INIT; -} - -// argument info structures for functions, arranged alphabetically. -// see zend_API.h in the PHP sources for more information about these macros -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_begin_transaction_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, conn ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_cancel_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_close_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, conn ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_client_info_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, conn ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_commit_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, conn ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_configure_arginfo, 0, 0, 2 ) - ZEND_ARG_INFO( 0, setting ) - ZEND_ARG_INFO( 0, value ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_connect_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, server_name ) - ZEND_ARG_ARRAY_INFO( 0, connection_info, 0 ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_errors_arginfo, 0, 1, 0 ) - ZEND_ARG_INFO( 0, errors_and_or_warnings ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_execute_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 1, 1 ) - ZEND_ARG_INFO( 0, stmt ) - ZEND_ARG_INFO( 0, fetch_type ) - ZEND_ARG_INFO( 0, row ) - ZEND_ARG_INFO( 0, offset ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 1, 1 ) - ZEND_ARG_INFO( 0, stmt ) - ZEND_ARG_INFO( 0, class_name ) - ZEND_ARG_INFO( 0, ctor_params ) - ZEND_ARG_INFO( 0, row ) - ZEND_ARG_INFO( 0, offset ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_field_metadata_arginfo, 0, 1, 1 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_free_stmt_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_config_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, setting ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 1, 2 ) - ZEND_ARG_INFO( 0, stmt ) - ZEND_ARG_INFO( 0, field_index ) - ZEND_ARG_INFO( 0, get_as_type ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_has_rows_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_next_result_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_num_fields_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_num_rows_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 1, 2 ) - ZEND_ARG_INFO( 0, conn ) - ZEND_ARG_INFO( 0, tsql ) - ZEND_ARG_INFO( 0, params ) - ZEND_ARG_INFO( 0, options ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_query_arginfo, 0, 1, 2 ) - ZEND_ARG_INFO( 0, conn ) - ZEND_ARG_INFO( 0, tsql ) - ZEND_ARG_INFO( 0, params ) - ZEND_ARG_INFO( 0, options ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX( sqlsrv_rollback_arginfo, 0, 0, 1 ) - ZEND_ARG_INFO( 0, conn ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_rows_affected_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_send_stream_data_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_server_info_arginfo, 0 ) - ZEND_ARG_INFO( 0, stmt ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_size_arginfo, 0 ) - ZEND_ARG_INFO( 0, size ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_precision_scale_arginfo, 0 ) - ZEND_ARG_INFO( 0, precision ) - ZEND_ARG_INFO( 0, scale ) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO( sqlsrv_phptype_encoding_arginfo, 0 ) - ZEND_ARG_INFO( 0, encoding ) -ZEND_END_ARG_INFO() - -// function table with associated arginfo structures -zend_function_entry sqlsrv_functions[] = { - PHP_FE( sqlsrv_connect, sqlsrv_connect_arginfo ) - PHP_FE( sqlsrv_close, sqlsrv_close_arginfo ) - PHP_FE( sqlsrv_commit, sqlsrv_commit_arginfo ) - PHP_FE( sqlsrv_begin_transaction, sqlsrv_begin_transaction_arginfo ) - PHP_FE( sqlsrv_rollback, sqlsrv_rollback_arginfo ) - PHP_FE( sqlsrv_errors, sqlsrv_errors_arginfo ) - PHP_FE( sqlsrv_configure, sqlsrv_configure_arginfo ) - PHP_FE( sqlsrv_get_config, sqlsrv_get_config_arginfo ) - PHP_FE( sqlsrv_prepare, sqlsrv_prepare_arginfo ) - PHP_FE( sqlsrv_execute, sqlsrv_execute_arginfo ) - PHP_FE( sqlsrv_query, sqlsrv_query_arginfo ) - PHP_FE( sqlsrv_fetch, sqlsrv_fetch_arginfo ) - PHP_FE( sqlsrv_get_field, sqlsrv_get_field_arginfo ) - PHP_FE( sqlsrv_fetch_array, sqlsrv_fetch_array_arginfo ) - PHP_FE( sqlsrv_fetch_object, sqlsrv_fetch_object_arginfo ) - PHP_FE( sqlsrv_has_rows, sqlsrv_has_rows_arginfo ) - PHP_FE( sqlsrv_num_fields, sqlsrv_num_fields_arginfo ) - PHP_FE( sqlsrv_next_result, sqlsrv_next_result_arginfo ) - PHP_FE( sqlsrv_num_rows, sqlsrv_num_rows_arginfo ) - PHP_FE( sqlsrv_rows_affected, sqlsrv_rows_affected_arginfo ) - PHP_FE( SQLSRV_PHPTYPE_STREAM, sqlsrv_phptype_encoding_arginfo ) - PHP_FE( SQLSRV_PHPTYPE_STRING, sqlsrv_phptype_encoding_arginfo ) - PHP_FE( sqlsrv_client_info, sqlsrv_client_info_arginfo ) - PHP_FE( sqlsrv_server_info, sqlsrv_server_info_arginfo ) - PHP_FE( sqlsrv_cancel, sqlsrv_cancel_arginfo ) - PHP_FE( sqlsrv_free_stmt, sqlsrv_close_arginfo ) - PHP_FE( sqlsrv_field_metadata, sqlsrv_field_metadata_arginfo ) - PHP_FE( sqlsrv_send_stream_data, sqlsrv_send_stream_data_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_BINARY, sqlsrv_sqltype_size_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_CHAR, sqlsrv_sqltype_size_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_DECIMAL, sqlsrv_sqltype_precision_scale_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_NCHAR, sqlsrv_sqltype_size_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_NUMERIC, sqlsrv_sqltype_precision_scale_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_NVARCHAR, sqlsrv_sqltype_size_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_VARBINARY, sqlsrv_sqltype_size_arginfo ) - PHP_FE( SQLSRV_SQLTYPE_VARCHAR, sqlsrv_sqltype_size_arginfo ) - - {NULL, NULL, NULL} // end of the table -}; - -// the structure returned to Zend that exposes the extension to the Zend engine. -// this structure is defined in zend_modules.h in the PHP sources - -zend_module_entry g_sqlsrv_module_entry = -{ - STANDARD_MODULE_HEADER, - "sqlsrv", - sqlsrv_functions, // exported function table - // initialization and shutdown functions - PHP_MINIT(sqlsrv), - PHP_MSHUTDOWN(sqlsrv), - PHP_RINIT(sqlsrv), - PHP_RSHUTDOWN(sqlsrv), - PHP_MINFO(sqlsrv), - // version of the extension. Matches the version resource of the extension dll - VER_FILEVERSION_STR, - PHP_MODULE_GLOBALS(sqlsrv), - NULL, - NULL, - NULL, - STANDARD_MODULE_PROPERTIES_EX -}; - -// Module initialization -// This function is called once per execution of the Zend engine -// We use it to: -// 1) Register our constants. See MSDN or the function below for the exact constants -// we register. -// 2) Register our resource types (connection, statement, and stream types) -// 3) Allocate the environment handles for ODBC connections (1 for non pooled -// connections and 1 for pooled connections) -// 4) Register our INI entries. See MSDN or php_sqlsrv.h for our supported INI entries - -PHP_MINIT_FUNCTION(sqlsrv) -{ - SQLSRV_UNUSED( type ); - - core_sqlsrv_register_logger( ss_sqlsrv_log ); - - // our global variables are initialized in the RINIT function -#if defined(ZTS) - if( ts_allocate_id( &sqlsrv_globals_id, - sizeof( zend_sqlsrv_globals ), - (ts_allocate_ctor) NULL, - (ts_allocate_dtor) NULL ) == 0 ) - return FAILURE; - ZEND_TSRMLS_CACHE_UPDATE(); -#endif - - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long )); - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); - - REGISTER_INI_ENTRIES(); - - LOG_FUNCTION( "PHP_MINIT_FUNCTION for php_sqlsrv" ); - - REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ERRORS", SQLSRV_ERR_ERRORS, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_ERR_WARNINGS", SQLSRV_ERR_WARNINGS, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ALL", SQLSRV_ERR_ALL, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_OFF", 0, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_INIT", LOG_INIT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_CONN", LOG_CONN, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_STMT", LOG_STMT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_UTIL", LOG_UTIL, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set - - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ERROR", SEV_ERROR, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_WARNING", SEV_WARNING, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_NOTICE", SEV_NOTICE, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set - - // register connection resource - ss_sqlsrv_conn::descriptor = zend_register_list_destructors_ex( sqlsrv_conn_dtor, NULL, "SQL Server Connection", - module_number ); - - if( ss_sqlsrv_conn::descriptor == FAILURE ) { - LOG( SEV_ERROR, "%1!s!: connection resource registration failed", _FN_ ); - return FAILURE; - } - - // register statement resources - ss_sqlsrv_stmt::descriptor = zend_register_list_destructors_ex( sqlsrv_stmt_dtor, NULL, "SQL Server Statement", module_number ); - - if( ss_sqlsrv_stmt::descriptor == FAILURE ) { - LOG( SEV_ERROR, "%1!s!: statement resource regisration failed", _FN_ ); - return FAILURE; - } - - sqlsrv_sqltype constant_type; - - REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_NUMERIC", SQLSRV_FETCH_NUMERIC, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_ASSOC", SQLSRV_FETCH_ASSOC, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_BOTH", SQLSRV_FETCH_BOTH, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_NULL", SQLSRV_PHPTYPE_NULL, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_INT", SQLSRV_PHPTYPE_INT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_FLOAT", SQLSRV_PHPTYPE_FLOAT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_DATETIME", SQLSRV_PHPTYPE_DATETIME, CONST_PERSISTENT | CONST_CS ); - - REGISTER_STRING_CONSTANT( "SQLSRV_ENC_BINARY", "binary", CONST_PERSISTENT | CONST_CS ); - REGISTER_STRING_CONSTANT( "SQLSRV_ENC_CHAR", "char", CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_NO", 0, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_YES", 1, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_UNKNOWN", 2, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIGINT", SQL_BIGINT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIT", SQL_BIT, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; - constant_type.typeinfo.size = 23; - constant_type.typeinfo.scale = 3; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_FLOAT", SQL_FLOAT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_IMAGE", SQL_LONGVARBINARY, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_INT", SQL_INTEGER, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_DECIMAL; - constant_type.typeinfo.size = 19; - constant_type.typeinfo.scale = 4; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_MONEY", constant_type.value, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_NTEXT", SQL_WLONGVARCHAR, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TEXT", SQL_LONGVARCHAR, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_REAL", SQL_REAL, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; - constant_type.typeinfo.size = 16; - constant_type.typeinfo.scale = 0; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLDATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLINT", SQL_SMALLINT, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_DECIMAL; - constant_type.typeinfo.size = 10; - constant_type.typeinfo.scale = 4; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLMONEY", constant_type.value, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_BINARY; - constant_type.typeinfo.size = 8; - constant_type.typeinfo.scale = 0; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIMESTAMP", constant_type.value, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TINYINT", SQL_TINYINT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UDT", SQL_SS_UDT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UNIQUEIDENTIFIER", SQL_GUID, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_XML", SQL_SS_XML, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_TYPE_DATE; - constant_type.typeinfo.size = 10; - constant_type.typeinfo.scale = 0; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATE", constant_type.value, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_SS_TIME2; - constant_type.typeinfo.size = 16; - constant_type.typeinfo.scale = 7; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_SS_TIMESTAMPOFFSET; - constant_type.typeinfo.size = 34; - constant_type.typeinfo.scale = 7; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIMEOFFSET", constant_type.value, CONST_PERSISTENT | CONST_CS ); - constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; - constant_type.typeinfo.size = 27; - constant_type.typeinfo.scale = 7; - REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME2", constant_type.value, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_IN", SQL_PARAM_INPUT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_OUT", SQL_PARAM_OUTPUT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_INOUT", SQL_PARAM_INPUT_OUTPUT, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_UNCOMMITTED", SQL_TXN_READ_UNCOMMITTED, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_COMMITTED", SQL_TXN_READ_COMMITTED, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_TXN_REPEATABLE_READ", SQL_TXN_REPEATABLE_READ, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SERIALIZABLE", SQL_TXN_SERIALIZABLE, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SNAPSHOT", SQL_TXN_SS_SNAPSHOT, CONST_PERSISTENT | CONST_CS ); - - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_NEXT", SQL_FETCH_NEXT, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_PRIOR", SQL_FETCH_PRIOR, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_FIRST", SQL_FETCH_FIRST, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_LAST", SQL_FETCH_LAST, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_ABSOLUTE", SQL_FETCH_ABSOLUTE, CONST_PERSISTENT | CONST_CS ); - REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_RELATIVE", SQL_FETCH_RELATIVE, CONST_PERSISTENT | CONST_CS ); - - REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_FORWARD", "forward", CONST_PERSISTENT | CONST_CS ); - REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_STATIC", "static", CONST_PERSISTENT | CONST_CS ); - REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_DYNAMIC", "dynamic", CONST_PERSISTENT | CONST_CS ); - REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_KEYSET", "keyset", CONST_PERSISTENT | CONST_CS ); - REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_CLIENT_BUFFERED", "buffered", CONST_PERSISTENT | CONST_CS ); - - try { - - // initialize list of warnings to ignore - g_ss_warnings_to_ignore_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - zend_hash_init( g_ss_warnings_to_ignore_ht, 6, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); - - sqlsrv_error_const error_to_ignore; - - // changed database warning - error_to_ignore.sqlstate = (SQLCHAR*)"01000"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = 5701; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // changed language warning - error_to_ignore.sqlstate = (SQLCHAR*)"01000"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = 5703; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // option value changed - error_to_ignore.sqlstate = (SQLCHAR*)"01S02"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = -1; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // cursor operation conflict - error_to_ignore.sqlstate = (SQLCHAR*)"01001"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = -1; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // null value eliminated in set function - error_to_ignore.sqlstate = (SQLCHAR*)"01003"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = -1; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // SQL Azure warning: This session has been assigned a tracing id of .. - error_to_ignore.sqlstate = (SQLCHAR*)"01000"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = 40608; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - // Full-text search condition contained noise words warning - error_to_ignore.sqlstate = (SQLCHAR*)"01000"; - error_to_ignore.native_message = NULL; - error_to_ignore.native_code = 9927; - error_to_ignore.format = false; - if (NULL == zend_hash_next_index_insert_mem(g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { - throw ss::SSException(); - } - - } - catch( ss::SSException& ) { - - LOG( SEV_ERROR, "PHP_MINIT: warnings hash table failure" ); - return FAILURE; - } - - try { - - // supported encodings - g_ss_encodings_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - zend_hash_init( g_ss_encodings_ht, 3, NULL /*use standard hash function*/, sqlsrv_encoding_dtor /*resource destructor*/, 1 /*persistent*/ ); - - sqlsrv_encoding sql_enc_char( "char", SQLSRV_ENCODING_CHAR ); - if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_char, sizeof( sqlsrv_encoding ))) { - throw ss::SSException(); - } - - sqlsrv_encoding sql_enc_bin( "binary", SQLSRV_ENCODING_BINARY, true ); - if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_bin, sizeof( sqlsrv_encoding ))) { - throw ss::SSException(); - } - - sqlsrv_encoding sql_enc_utf8( "utf-8", CP_UTF8 ); - if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_utf8, sizeof( sqlsrv_encoding ))) { - throw ss::SSException(); - } - } - catch( ss::SSException& ) { - - LOG( SEV_ERROR, "PHP_RINIT: encodings hash table failure" ); - return FAILURE; - } - - // initialize list of sqlsrv errors - g_ss_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); - ::zend_hash_init( g_ss_errors_ht, 50, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); - - for( int i = 0; SS_ERRORS[ i ].error_code != UINT_MAX; ++i ) { - if (NULL == ::zend_hash_index_update_mem( g_ss_errors_ht, SS_ERRORS[ i ].error_code, - &( SS_ERRORS[ i ].sqlsrv_error ), sizeof( SS_ERRORS[ i ].sqlsrv_error ))) { - LOG( SEV_ERROR, "%1!s!: Failed to insert data into sqlsrv errors hashtable.", _FN_ ); - return FAILURE; - } - } - - if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper TSRMLS_CC ) == FAILURE ) { - LOG( SEV_ERROR, "%1!s!: stream registration failed", _FN_ ); - return FAILURE; - } - - try { - // retrieve the handles for the environments - core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, ss_error_handler, "PHP_MINIT_FUNCTION for sqlsrv" TSRMLS_CC ); - } - - catch( core::CoreException& ) { - return FAILURE; - } - - catch( ... ) { - - LOG( SEV_ERROR, "PHP_RINIT: Unknown exception caught." ); - return FAILURE; - } - - return SUCCESS; -} - -// called by Zend for each parameter in the g_ss_warnings_to_ignore_ht and g_ss_errors_ht hash table when it is destroyed -void sqlsrv_error_const_dtor( zval* elem ) { - sqlsrv_error_const* error_to_ignore = static_cast( Z_PTR_P(elem) ); - pefree(error_to_ignore, 1); -} - -// called by Zend for each parameter in the g_ss_encodings_ht hash table when it is destroyed -void sqlsrv_encoding_dtor( zval* elem ) { - sqlsrv_encoding* sql_enc = static_cast( Z_PTR_P(elem) ); - pefree(sql_enc, 1); -} - - -// Module shutdown function -// Free the environment handles allocated in MINIT and unregister our stream wrapper. -// Resource types and constants are automatically released since we don't flag them as -// persistent when they are registered. - -PHP_MSHUTDOWN_FUNCTION(sqlsrv) -{ - SQLSRV_UNUSED( type ); - - UNREGISTER_INI_ENTRIES(); - - // clean up the list of sqlsrv errors - zend_hash_destroy( g_ss_errors_ht ); - pefree( g_ss_errors_ht, 1 /*persistent*/ ); - - zend_hash_destroy( g_ss_warnings_to_ignore_ht ); - pefree( g_ss_warnings_to_ignore_ht, 1 ); - - zend_hash_destroy( g_ss_encodings_ht ); - pefree( g_ss_encodings_ht, 1 ); - - core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp ); - - if( php_unregister_url_stream_wrapper( SQLSRV_STREAM_WRAPPER TSRMLS_CC ) == FAILURE ) { - return FAILURE; - } - - return SUCCESS; -} - - -// Request initialization function -// This function is called once per PHP script execution -// Initialize request globals used in the request, including those that correspond to INI entries. -// Also, we allocate a list of warnings "to ignore", meaning that they are warnings that do not -// trigger errors when WarningsReturnAsErrors is true. If you have warnings that you want ignored -// (such as return values from stored procedures), add them to this collection and they won't be -// returned as errors. Or you could just set WarningsReturnAsErrors to false. - -PHP_RINIT_FUNCTION(sqlsrv) -{ - SQLSRV_UNUSED( module_number ); - SQLSRV_UNUSED( type ); - -#if defined(ZTS) - ZEND_TSRMLS_CACHE_UPDATE(); -#endif - - SQLSRV_G( warnings_return_as_errors ) = true; - ZVAL_NULL( &SQLSRV_G( errors )); - ZVAL_NULL( &SQLSRV_G( warnings )); - - LOG_FUNCTION( "PHP_RINIT for php_sqlsrv" ); - - // read INI settings - SQLSRV_G( warnings_return_as_errors ) = INI_BOOL( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS ); - SQLSRV_G( log_severity ) = INI_INT( INI_PREFIX INI_LOG_SEVERITY ); - SQLSRV_G( log_subsystems ) = INI_INT( INI_PREFIX INI_LOG_SUBSYSTEMS ); - SQLSRV_G( buffered_query_limit ) = INI_INT( INI_PREFIX INI_BUFFERED_QUERY_LIMIT ); - - LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); - LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); - LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); - LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit )); - - return SUCCESS; -} - - -// Request shutdown -// Called at the end of a script's execution -// Simply releases the variables allocated during request initialization. - -PHP_RSHUTDOWN_FUNCTION(sqlsrv) -{ - SQLSRV_UNUSED( module_number ); - SQLSRV_UNUSED( type ); - - LOG_FUNCTION( "PHP_RSHUTDOWN for php_sqlsrv" ); - reset_errors( TSRMLS_C ); - - // TODO - destruction - zval_ptr_dtor( &SQLSRV_G( errors )); - zval_ptr_dtor( &SQLSRV_G( warnings )); - - return SUCCESS; -} - -// Called for php_info(); Displays the INI settings registered and their current values -PHP_MINFO_FUNCTION(sqlsrv) -{ - php_info_print_table_start(); - php_info_print_table_header(2, "sqlsrv support", "enabled"); - php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); - php_info_print_table_end(); - DISPLAY_INI_ENTRIES(); -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: init.cpp +// Contents: initialization routines for the extension +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_sqlsrv.h" + +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE(); +#endif +ZEND_GET_MODULE(g_sqlsrv) + +extern "C" { + +ZEND_DECLARE_MODULE_GLOBALS(sqlsrv); + +} + +// module global variables (initialized in minit and freed in mshutdown) +HashTable* g_ss_errors_ht = NULL; +// special list of warnings to ignore even if warnings are treated as errors +HashTable* g_ss_warnings_to_ignore_ht = NULL; +// encodings we understand +HashTable* g_ss_encodings_ht = NULL; + +// Destructors called by Zend for each element in the hashtable +void sqlsrv_error_const_dtor( zval* element ); +void sqlsrv_encoding_dtor( zval* element ); + +// henv context for creating connections +sqlsrv_context* g_henv_cp; +sqlsrv_context* g_henv_ncp; + +namespace { + +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros +unsigned int current_log_subsystem = LOG_INIT; +} + +// argument info structures for functions, arranged alphabetically. +// see zend_API.h in the PHP sources for more information about these macros +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_begin_transaction_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, conn ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_cancel_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_close_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, conn ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_client_info_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, conn ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_commit_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, conn ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_configure_arginfo, 0, 0, 2 ) + ZEND_ARG_INFO( 0, setting ) + ZEND_ARG_INFO( 0, value ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_connect_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, server_name ) + ZEND_ARG_ARRAY_INFO( 0, connection_info, 0 ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_errors_arginfo, 0, 1, 0 ) + ZEND_ARG_INFO( 0, errors_and_or_warnings ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_execute_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 1, 1 ) + ZEND_ARG_INFO( 0, stmt ) + ZEND_ARG_INFO( 0, fetch_type ) + ZEND_ARG_INFO( 0, row ) + ZEND_ARG_INFO( 0, offset ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 1, 1 ) + ZEND_ARG_INFO( 0, stmt ) + ZEND_ARG_INFO( 0, class_name ) + ZEND_ARG_INFO( 0, ctor_params ) + ZEND_ARG_INFO( 0, row ) + ZEND_ARG_INFO( 0, offset ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_field_metadata_arginfo, 0, 1, 1 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_free_stmt_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_config_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, setting ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 1, 2 ) + ZEND_ARG_INFO( 0, stmt ) + ZEND_ARG_INFO( 0, field_index ) + ZEND_ARG_INFO( 0, get_as_type ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_has_rows_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_next_result_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_num_fields_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_num_rows_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 1, 2 ) + ZEND_ARG_INFO( 0, conn ) + ZEND_ARG_INFO( 0, tsql ) + ZEND_ARG_INFO( 0, params ) + ZEND_ARG_INFO( 0, options ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_query_arginfo, 0, 1, 2 ) + ZEND_ARG_INFO( 0, conn ) + ZEND_ARG_INFO( 0, tsql ) + ZEND_ARG_INFO( 0, params ) + ZEND_ARG_INFO( 0, options ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX( sqlsrv_rollback_arginfo, 0, 0, 1 ) + ZEND_ARG_INFO( 0, conn ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_rows_affected_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_send_stream_data_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_server_info_arginfo, 0 ) + ZEND_ARG_INFO( 0, stmt ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_size_arginfo, 0 ) + ZEND_ARG_INFO( 0, size ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_precision_scale_arginfo, 0 ) + ZEND_ARG_INFO( 0, precision ) + ZEND_ARG_INFO( 0, scale ) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO( sqlsrv_phptype_encoding_arginfo, 0 ) + ZEND_ARG_INFO( 0, encoding ) +ZEND_END_ARG_INFO() + +// function table with associated arginfo structures +zend_function_entry sqlsrv_functions[] = { + PHP_FE( sqlsrv_connect, sqlsrv_connect_arginfo ) + PHP_FE( sqlsrv_close, sqlsrv_close_arginfo ) + PHP_FE( sqlsrv_commit, sqlsrv_commit_arginfo ) + PHP_FE( sqlsrv_begin_transaction, sqlsrv_begin_transaction_arginfo ) + PHP_FE( sqlsrv_rollback, sqlsrv_rollback_arginfo ) + PHP_FE( sqlsrv_errors, sqlsrv_errors_arginfo ) + PHP_FE( sqlsrv_configure, sqlsrv_configure_arginfo ) + PHP_FE( sqlsrv_get_config, sqlsrv_get_config_arginfo ) + PHP_FE( sqlsrv_prepare, sqlsrv_prepare_arginfo ) + PHP_FE( sqlsrv_execute, sqlsrv_execute_arginfo ) + PHP_FE( sqlsrv_query, sqlsrv_query_arginfo ) + PHP_FE( sqlsrv_fetch, sqlsrv_fetch_arginfo ) + PHP_FE( sqlsrv_get_field, sqlsrv_get_field_arginfo ) + PHP_FE( sqlsrv_fetch_array, sqlsrv_fetch_array_arginfo ) + PHP_FE( sqlsrv_fetch_object, sqlsrv_fetch_object_arginfo ) + PHP_FE( sqlsrv_has_rows, sqlsrv_has_rows_arginfo ) + PHP_FE( sqlsrv_num_fields, sqlsrv_num_fields_arginfo ) + PHP_FE( sqlsrv_next_result, sqlsrv_next_result_arginfo ) + PHP_FE( sqlsrv_num_rows, sqlsrv_num_rows_arginfo ) + PHP_FE( sqlsrv_rows_affected, sqlsrv_rows_affected_arginfo ) + PHP_FE( SQLSRV_PHPTYPE_STREAM, sqlsrv_phptype_encoding_arginfo ) + PHP_FE( SQLSRV_PHPTYPE_STRING, sqlsrv_phptype_encoding_arginfo ) + PHP_FE( sqlsrv_client_info, sqlsrv_client_info_arginfo ) + PHP_FE( sqlsrv_server_info, sqlsrv_server_info_arginfo ) + PHP_FE( sqlsrv_cancel, sqlsrv_cancel_arginfo ) + PHP_FE( sqlsrv_free_stmt, sqlsrv_close_arginfo ) + PHP_FE( sqlsrv_field_metadata, sqlsrv_field_metadata_arginfo ) + PHP_FE( sqlsrv_send_stream_data, sqlsrv_send_stream_data_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_BINARY, sqlsrv_sqltype_size_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_CHAR, sqlsrv_sqltype_size_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_DECIMAL, sqlsrv_sqltype_precision_scale_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_NCHAR, sqlsrv_sqltype_size_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_NUMERIC, sqlsrv_sqltype_precision_scale_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_NVARCHAR, sqlsrv_sqltype_size_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_VARBINARY, sqlsrv_sqltype_size_arginfo ) + PHP_FE( SQLSRV_SQLTYPE_VARCHAR, sqlsrv_sqltype_size_arginfo ) + + {NULL, NULL, NULL} // end of the table +}; + +// the structure returned to Zend that exposes the extension to the Zend engine. +// this structure is defined in zend_modules.h in the PHP sources + +zend_module_entry g_sqlsrv_module_entry = +{ + STANDARD_MODULE_HEADER, + "sqlsrv", + sqlsrv_functions, // exported function table + // initialization and shutdown functions + PHP_MINIT(sqlsrv), + PHP_MSHUTDOWN(sqlsrv), + PHP_RINIT(sqlsrv), + PHP_RSHUTDOWN(sqlsrv), + PHP_MINFO(sqlsrv), + // version of the extension. Matches the version resource of the extension dll + VER_FILEVERSION_STR, + PHP_MODULE_GLOBALS(sqlsrv), + NULL, + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +// Module initialization +// This function is called once per execution of the Zend engine +// We use it to: +// 1) Register our constants. See MSDN or the function below for the exact constants +// we register. +// 2) Register our resource types (connection, statement, and stream types) +// 3) Allocate the environment handles for ODBC connections (1 for non pooled +// connections and 1 for pooled connections) +// 4) Register our INI entries. See MSDN or php_sqlsrv.h for our supported INI entries + +PHP_MINIT_FUNCTION(sqlsrv) +{ + SQLSRV_UNUSED( type ); + + core_sqlsrv_register_logger( ss_sqlsrv_log ); + + // our global variables are initialized in the RINIT function +#if defined(ZTS) + if( ts_allocate_id( &sqlsrv_globals_id, + sizeof( zend_sqlsrv_globals ), + (ts_allocate_ctor) NULL, + (ts_allocate_dtor) NULL ) == 0 ) + return FAILURE; + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long )); + SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); + + REGISTER_INI_ENTRIES(); + + LOG_FUNCTION( "PHP_MINIT_FUNCTION for php_sqlsrv" ); + + REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ERRORS", SQLSRV_ERR_ERRORS, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_ERR_WARNINGS", SQLSRV_ERR_WARNINGS, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ALL", SQLSRV_ERR_ALL, CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_OFF", 0, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_INIT", LOG_INIT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_CONN", LOG_CONN, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_STMT", LOG_STMT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_UTIL", LOG_UTIL, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set + + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ERROR", SEV_ERROR, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_WARNING", SEV_WARNING, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_NOTICE", SEV_NOTICE, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set + + // register connection resource + ss_sqlsrv_conn::descriptor = zend_register_list_destructors_ex( sqlsrv_conn_dtor, NULL, "SQL Server Connection", + module_number ); + + if( ss_sqlsrv_conn::descriptor == FAILURE ) { + LOG( SEV_ERROR, "%1!s!: connection resource registration failed", _FN_ ); + return FAILURE; + } + + // register statement resources + ss_sqlsrv_stmt::descriptor = zend_register_list_destructors_ex( sqlsrv_stmt_dtor, NULL, "SQL Server Statement", module_number ); + + if( ss_sqlsrv_stmt::descriptor == FAILURE ) { + LOG( SEV_ERROR, "%1!s!: statement resource regisration failed", _FN_ ); + return FAILURE; + } + + sqlsrv_sqltype constant_type; + + REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_NUMERIC", SQLSRV_FETCH_NUMERIC, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_ASSOC", SQLSRV_FETCH_ASSOC, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_BOTH", SQLSRV_FETCH_BOTH, CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_NULL", SQLSRV_PHPTYPE_NULL, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_INT", SQLSRV_PHPTYPE_INT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_FLOAT", SQLSRV_PHPTYPE_FLOAT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_DATETIME", SQLSRV_PHPTYPE_DATETIME, CONST_PERSISTENT | CONST_CS ); + + std::string bin = "binary"; + std::string chr = "char"; + + REGISTER_STRING_CONSTANT( "SQLSRV_ENC_BINARY", &bin[0], CONST_PERSISTENT | CONST_CS ); + REGISTER_STRING_CONSTANT( "SQLSRV_ENC_CHAR", &chr[0], CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_NO", 0, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_YES", 1, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_UNKNOWN", 2, CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIGINT", SQL_BIGINT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIT", SQL_BIT, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; + constant_type.typeinfo.size = 23; + constant_type.typeinfo.scale = 3; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_FLOAT", SQL_FLOAT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_IMAGE", SQL_LONGVARBINARY, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_INT", SQL_INTEGER, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_DECIMAL; + constant_type.typeinfo.size = 19; + constant_type.typeinfo.scale = 4; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_MONEY", constant_type.value, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_NTEXT", SQL_WLONGVARCHAR, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TEXT", SQL_LONGVARCHAR, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_REAL", SQL_REAL, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; + constant_type.typeinfo.size = 16; + constant_type.typeinfo.scale = 0; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLDATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLINT", SQL_SMALLINT, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_DECIMAL; + constant_type.typeinfo.size = 10; + constant_type.typeinfo.scale = 4; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLMONEY", constant_type.value, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_BINARY; + constant_type.typeinfo.size = 8; + constant_type.typeinfo.scale = 0; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIMESTAMP", constant_type.value, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TINYINT", SQL_TINYINT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UDT", SQL_SS_UDT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UNIQUEIDENTIFIER", SQL_GUID, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_XML", SQL_SS_XML, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_TYPE_DATE; + constant_type.typeinfo.size = 10; + constant_type.typeinfo.scale = 0; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATE", constant_type.value, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_SS_TIME2; + constant_type.typeinfo.size = 16; + constant_type.typeinfo.scale = 7; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIME", constant_type.value, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_SS_TIMESTAMPOFFSET; + constant_type.typeinfo.size = 34; + constant_type.typeinfo.scale = 7; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIMEOFFSET", constant_type.value, CONST_PERSISTENT | CONST_CS ); + constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP; + constant_type.typeinfo.size = 27; + constant_type.typeinfo.scale = 7; + REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME2", constant_type.value, CONST_PERSISTENT | CONST_CS ); + + // These constant are defined to provide type checking (type ==SQLSRV_SQLTYPE_DECIMAL). + // There are functions with the same name which accept parameters and is used in binding paramters. + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_DECIMAL", SQL_DECIMAL, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NUMERIC", SQL_NUMERIC, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_CHAR", SQL_CHAR, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NCHAR", SQL_WCHAR, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_VARCHAR", SQL_VARCHAR, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NVARCHAR", SQL_WVARCHAR, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_BINARY", SQL_BINARY, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_VARBINARY", SQL_VARBINARY, CONST_PERSISTENT | CONST_CS); + + REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_IN", SQL_PARAM_INPUT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_OUT", SQL_PARAM_OUTPUT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_INOUT", SQL_PARAM_INPUT_OUTPUT, CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_UNCOMMITTED", SQL_TXN_READ_UNCOMMITTED, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_COMMITTED", SQL_TXN_READ_COMMITTED, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_TXN_REPEATABLE_READ", SQL_TXN_REPEATABLE_READ, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SERIALIZABLE", SQL_TXN_SERIALIZABLE, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SNAPSHOT", SQL_TXN_SS_SNAPSHOT, CONST_PERSISTENT | CONST_CS ); + + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_NEXT", SQL_FETCH_NEXT, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_PRIOR", SQL_FETCH_PRIOR, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_FIRST", SQL_FETCH_FIRST, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_LAST", SQL_FETCH_LAST, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_ABSOLUTE", SQL_FETCH_ABSOLUTE, CONST_PERSISTENT | CONST_CS ); + REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_RELATIVE", SQL_FETCH_RELATIVE, CONST_PERSISTENT | CONST_CS ); + + std::string fwd = "forward"; + std::string stc = "static"; + std::string dyn = "dynamic"; + std::string key = "keyset"; + std::string buf = "buffered"; + + REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_FORWARD", &fwd[0], CONST_PERSISTENT | CONST_CS ); + REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_STATIC", &stc[0], CONST_PERSISTENT | CONST_CS ); + REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_DYNAMIC", &dyn[0], CONST_PERSISTENT | CONST_CS ); + REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_KEYSET", &key[0], CONST_PERSISTENT | CONST_CS ); + REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_CLIENT_BUFFERED", &buf[0], CONST_PERSISTENT | CONST_CS ); + + try { + + // initialize list of warnings to ignore + g_ss_warnings_to_ignore_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); + zend_hash_init( g_ss_warnings_to_ignore_ht, 6, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); + + sqlsrv_error_const error_to_ignore; + + // changed database warning + error_to_ignore.sqlstate = (SQLCHAR*)"01000"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = 5701; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // changed language warning + error_to_ignore.sqlstate = (SQLCHAR*)"01000"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = 5703; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // option value changed + error_to_ignore.sqlstate = (SQLCHAR*)"01S02"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = -1; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // cursor operation conflict + error_to_ignore.sqlstate = (SQLCHAR*)"01001"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = -1; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // null value eliminated in set function + error_to_ignore.sqlstate = (SQLCHAR*)"01003"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = -1; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // SQL Azure warning: This session has been assigned a tracing id of .. + error_to_ignore.sqlstate = (SQLCHAR*)"01000"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = 40608; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + // Full-text search condition contained noise words warning + error_to_ignore.sqlstate = (SQLCHAR*)"01000"; + error_to_ignore.native_message = NULL; + error_to_ignore.native_code = 9927; + error_to_ignore.format = false; + if (NULL == zend_hash_next_index_insert_mem(g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) { + throw ss::SSException(); + } + + } + catch( ss::SSException& ) { + + LOG( SEV_ERROR, "PHP_MINIT: warnings hash table failure" ); + return FAILURE; + } + + try { + + // supported encodings + g_ss_encodings_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); + zend_hash_init( g_ss_encodings_ht, 3, NULL /*use standard hash function*/, sqlsrv_encoding_dtor /*resource destructor*/, 1 /*persistent*/ ); + + sqlsrv_encoding sql_enc_char( "char", SQLSRV_ENCODING_CHAR ); + if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_char, sizeof( sqlsrv_encoding ))) { + throw ss::SSException(); + } + + sqlsrv_encoding sql_enc_bin( "binary", SQLSRV_ENCODING_BINARY, true ); + if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_bin, sizeof( sqlsrv_encoding ))) { + throw ss::SSException(); + } + + sqlsrv_encoding sql_enc_utf8( "utf-8", CP_UTF8 ); + if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_utf8, sizeof( sqlsrv_encoding ))) { + throw ss::SSException(); + } + } + catch( ss::SSException& ) { + + LOG( SEV_ERROR, "PHP_RINIT: encodings hash table failure" ); + return FAILURE; + } + + // initialize list of sqlsrv errors + g_ss_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); + ::zend_hash_init( g_ss_errors_ht, 50, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); + + for( int i = 0; SS_ERRORS[ i ].error_code != UINT_MAX; ++i ) { + if (NULL == ::zend_hash_index_update_mem( g_ss_errors_ht, SS_ERRORS[ i ].error_code, + &( SS_ERRORS[ i ].sqlsrv_error ), sizeof( SS_ERRORS[ i ].sqlsrv_error ))) { + LOG( SEV_ERROR, "%1!s!: Failed to insert data into sqlsrv errors hashtable.", _FN_ ); + return FAILURE; + } + } + + if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper TSRMLS_CC ) == FAILURE ) { + LOG( SEV_ERROR, "%1!s!: stream registration failed", _FN_ ); + return FAILURE; + } + + try { + // retrieve the handles for the environments + core_sqlsrv_minit( &g_henv_cp, &g_henv_ncp, ss_error_handler, "PHP_MINIT_FUNCTION for sqlsrv" TSRMLS_CC ); + } + + catch( core::CoreException& ) { + return FAILURE; + } + + catch( ... ) { + + LOG( SEV_ERROR, "PHP_RINIT: Unknown exception caught." ); + return FAILURE; + } + + return SUCCESS; +} + +// called by Zend for each parameter in the g_ss_warnings_to_ignore_ht and g_ss_errors_ht hash table when it is destroyed +void sqlsrv_error_const_dtor( zval* elem ) { + sqlsrv_error_const* error_to_ignore = static_cast( Z_PTR_P(elem) ); + pefree(error_to_ignore, 1); +} + +// called by Zend for each parameter in the g_ss_encodings_ht hash table when it is destroyed +void sqlsrv_encoding_dtor( zval* elem ) { + sqlsrv_encoding* sql_enc = static_cast( Z_PTR_P(elem) ); + pefree(sql_enc, 1); +} + + +// Module shutdown function +// Free the environment handles allocated in MINIT and unregister our stream wrapper. +// Resource types and constants are automatically released since we don't flag them as +// persistent when they are registered. + +PHP_MSHUTDOWN_FUNCTION(sqlsrv) +{ + SQLSRV_UNUSED( type ); + + UNREGISTER_INI_ENTRIES(); + + // clean up the list of sqlsrv errors + zend_hash_destroy( g_ss_errors_ht ); + pefree( g_ss_errors_ht, 1 /*persistent*/ ); + + zend_hash_destroy( g_ss_warnings_to_ignore_ht ); + pefree( g_ss_warnings_to_ignore_ht, 1 ); + + zend_hash_destroy( g_ss_encodings_ht ); + pefree( g_ss_encodings_ht, 1 ); + + core_sqlsrv_mshutdown( *g_henv_cp, *g_henv_ncp ); + + if( php_unregister_url_stream_wrapper( SQLSRV_STREAM_WRAPPER TSRMLS_CC ) == FAILURE ) { + return FAILURE; + } + + return SUCCESS; +} + + +// Request initialization function +// This function is called once per PHP script execution +// Initialize request globals used in the request, including those that correspond to INI entries. +// Also, we allocate a list of warnings "to ignore", meaning that they are warnings that do not +// trigger errors when WarningsReturnAsErrors is true. If you have warnings that you want ignored +// (such as return values from stored procedures), add them to this collection and they won't be +// returned as errors. Or you could just set WarningsReturnAsErrors to false. + +PHP_RINIT_FUNCTION(sqlsrv) +{ + SQLSRV_UNUSED( module_number ); + SQLSRV_UNUSED( type ); + +#if defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + SQLSRV_G( warnings_return_as_errors ) = true; + ZVAL_NULL( &SQLSRV_G( errors )); + ZVAL_NULL( &SQLSRV_G( warnings )); + + LOG_FUNCTION( "PHP_RINIT for php_sqlsrv" ); + + // read INI settings + SQLSRV_G( warnings_return_as_errors ) = INI_BOOL( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS ); + SQLSRV_G( log_severity ) = INI_INT( INI_PREFIX INI_LOG_SEVERITY ); + SQLSRV_G( log_subsystems ) = INI_INT( INI_PREFIX INI_LOG_SUBSYSTEMS ); + SQLSRV_G( buffered_query_limit ) = INI_INT( INI_PREFIX INI_BUFFERED_QUERY_LIMIT ); + + LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); + LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); + LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); + LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit )); + + return SUCCESS; +} + + +// Request shutdown +// Called at the end of a script's execution +// Simply releases the variables allocated during request initialization. + +PHP_RSHUTDOWN_FUNCTION(sqlsrv) +{ + SQLSRV_UNUSED( module_number ); + SQLSRV_UNUSED( type ); + + LOG_FUNCTION( "PHP_RSHUTDOWN for php_sqlsrv" ); + reset_errors( TSRMLS_C ); + + // TODO - destruction + zval_ptr_dtor( &SQLSRV_G( errors )); + zval_ptr_dtor( &SQLSRV_G( warnings )); + + return SUCCESS; +} + +// Called for php_info(); Displays the INI settings registered and their current values +PHP_MINFO_FUNCTION(sqlsrv) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "sqlsrv support", "enabled"); + php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR); + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} diff --git a/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h similarity index 95% rename from sqlsrv/php_sqlsrv.h rename to source/sqlsrv/php_sqlsrv.h index 2f5be3ccf..043a108bb 100644 --- a/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -1,618 +1,620 @@ -#ifndef PHP_SQLSRV_H -#define PHP_SQLSRV_H - -//--------------------------------------------------------------------------------------------------------------------------------- -// File: php_sqlsrv.h -// -// Contents: Declarations for the extension -// -// Comments: Also contains "internal" declarations shared across source files. -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" -#include "version.h" - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef PHP_WIN32 -#define PHP_SQLSRV_API __declspec(dllexport) -#else -#define PHP_SQLSRV_API -#endif - -// OACR is an internal Microsoft static code analysis tool -#if defined(OACR) -#include -OACR_WARNING_PUSH -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) -OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) -#endif - -extern "C" { - -#pragma warning(push) -#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) - -#ifdef ZTS -#include "TSRM.h" -#endif - -#if _MSC_VER >= 1400 -// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. -// php.h defines this constant as unsigned int which causes a compile error -// in ws2tcpip.h. Fortunately php.h allows an override by defining -// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define -// socklen_t here and override the php.h version. -typedef int socklen_t; -#define HAVE_SOCKLEN_T -#endif - -#include "php.h" -#include "php_globals.h" -#include "php_ini.h" -#include "ext/standard/php_standard.h" -#include "ext/standard/info.h" - -#pragma warning(pop) - -#if PHP_MAJOR_VERSION < 7 -#error Trying to compile "Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)" with an unsupported version of PHP -#endif - -#if ZEND_DEBUG -// debug build causes warning C4505 to pop up from the Zend header files -#pragma warning( disable: 4505 ) -#endif - -} // extern "C" - -//********************************************************************************************************************************* -// Initialization Functions -//********************************************************************************************************************************* - -// module global variables (initialized in minit and freed in mshutdown) -extern HashTable* g_ss_errors_ht; -extern HashTable* g_ss_encodings_ht; -extern HashTable* g_ss_warnings_to_ignore_ht; - -// variables set during initialization -extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP -extern HMODULE g_sqlsrv_hmodule; // used for getting the version information - -// henv context for creating connections -extern sqlsrv_context* g_henv_cp; -extern sqlsrv_context* g_henv_ncp; - -extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in - -// module initialization -PHP_MINIT_FUNCTION(sqlsrv); -// module shutdown function -PHP_MSHUTDOWN_FUNCTION(sqlsrv); -// request initialization function -PHP_RINIT_FUNCTION(sqlsrv); -// request shutdown function -PHP_RSHUTDOWN_FUNCTION(sqlsrv); -// module info function (info returned by phpinfo()) -PHP_MINFO_FUNCTION(sqlsrv); - -//********************************************************************************************************************************* -// Connection -//********************************************************************************************************************************* -PHP_FUNCTION(sqlsrv_connect); -PHP_FUNCTION(sqlsrv_begin_transaction); -PHP_FUNCTION(sqlsrv_client_info); -PHP_FUNCTION(sqlsrv_close); -PHP_FUNCTION(sqlsrv_commit); -PHP_FUNCTION(sqlsrv_query); -PHP_FUNCTION(sqlsrv_prepare); -PHP_FUNCTION(sqlsrv_rollback); -PHP_FUNCTION(sqlsrv_server_info); - -struct ss_sqlsrv_conn : sqlsrv_conn -{ - HashTable* stmts; - bool date_as_string; - bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls - - // static variables used in process_params - static char* resource_name; // char because const char forces casting all over the place. Just easier to leave it char here. - static int descriptor; - - // initialize with default values - ss_sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ) : - sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ), - stmts( NULL ), - date_as_string( false ), - in_transaction( false ) - { - } -}; - -// resource destructor -void __cdecl sqlsrv_conn_dtor( zend_resource *rsrc TSRMLS_DC ); - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -// holds the field names for reuse by sqlsrv_fetch_array/object as keys -struct sqlsrv_fetch_field_name { - char* name; - unsigned int len; -}; - -struct stmt_option_scrollable : public stmt_option_functor { - - virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); -}; - -// This object inherits and overrides the callbacks necessary -struct ss_sqlsrv_stmt : public sqlsrv_stmt { - - ss_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); - - virtual ~ss_sqlsrv_stmt( void ); - - void new_result_set( TSRMLS_D ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ); - - bool prepared; // whether the statement has been prepared yet (used for error messages) - zend_ulong conn_index; // index into the connection hash that contains this statement structure - zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute - sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys - int fetch_fields_count; - - // static variables used in process_params - static char* resource_name; // char because const char forces casting all over the place in ODBC functions - static int descriptor; - -}; - -// holds the field names for reuse by sqlsrv_fetch_array/object as keys -struct sqlsrv_fetch_field { - char* name; - unsigned int len; -}; - -// holds the stream param and the encoding that it was assigned -struct sqlsrv_stream_encoding { - zval* stream_z; - unsigned int encoding; - - sqlsrv_stream_encoding( zval* str_z, unsigned int enc ) : - stream_z( str_z ), encoding( enc ) - { - } -}; - -// *** statement functions *** -PHP_FUNCTION(sqlsrv_cancel); -PHP_FUNCTION(sqlsrv_execute); -PHP_FUNCTION(sqlsrv_fetch); -PHP_FUNCTION(sqlsrv_fetch_array); -PHP_FUNCTION(sqlsrv_fetch_object); -PHP_FUNCTION(sqlsrv_field_metadata); -PHP_FUNCTION(sqlsrv_free_stmt); -PHP_FUNCTION(sqlsrv_get_field); -PHP_FUNCTION(sqlsrv_has_rows); -PHP_FUNCTION(sqlsrv_next_result); -PHP_FUNCTION(sqlsrv_num_fields); -PHP_FUNCTION(sqlsrv_num_rows); -PHP_FUNCTION(sqlsrv_rows_affected); -PHP_FUNCTION(sqlsrv_send_stream_data); - -// resource destructor -void __cdecl sqlsrv_stmt_dtor( zend_resource *rsrc TSRMLS_DC ); - -// "internal" statement functions shared by functions in conn.cpp and stmt.cpp -void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC ); -void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC ); -bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function - TSRMLS_DC ); -void free_odbc_resources( ss_sqlsrv_stmt* stmt TSRMLS_DC ); -void free_stmt_resource( zval* stmt_z TSRMLS_DC ); - -//********************************************************************************************************************************* -// Type Functions -//********************************************************************************************************************************* - -// type functions for SQL types. -// to expose SQL Server paramterized types, we use functions that return encoded integers that contain the size/precision etc. -// for example, SQLSRV_SQLTYPE_VARCHAR(4000) matches the usage of SQLSRV_SQLTYPE_INT with the size added. -PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY); -PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR); -PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL); -PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR); -PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC); -PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR); -PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY); -PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR); - -// PHP type functions -// strings and streams may have an encoding parameterized, so we use the functions -// the valid encodings are SQLSRV_ENC_BINARY and SQLSRV_ENC_CHAR. -PHP_FUNCTION(SQLSRV_PHPTYPE_STREAM); -PHP_FUNCTION(SQLSRV_PHPTYPE_STRING); - -//********************************************************************************************************************************* -// Global variables -//********************************************************************************************************************************* - -extern "C" { - -// request level variables -ZEND_BEGIN_MODULE_GLOBALS(sqlsrv) - -// global objects for errors and warnings. These are returned by sqlsrv_errors. -zval errors; -zval warnings; - -// flags for error handling and logging (set via sqlsrv_configure or php.ini) -zend_long log_severity; -zend_long log_subsystems; -zend_long current_subsystem; -zend_bool warnings_return_as_errors; -zend_long buffered_query_limit; - -ZEND_END_MODULE_GLOBALS(sqlsrv) - -ZEND_EXTERN_MODULE_GLOBALS(sqlsrv); - -} - -// macro used to access the global variables. Use it to make global variable access agnostic to threads -#define SQLSRV_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(sqlsrv, v) - -#if defined(ZTS) -ZEND_TSRMLS_CACHE_EXTERN(); -#endif - -// INI settings and constants -// (these are defined as macros to allow concatenation as we do below) -#define INI_WARNINGS_RETURN_AS_ERRORS "WarningsReturnAsErrors" -#define INI_LOG_SEVERITY "LogSeverity" -#define INI_LOG_SUBSYSTEMS "LogSubsystems" -#define INI_BUFFERED_QUERY_LIMIT "ClientBufferMaxKBSize" -#define INI_PREFIX "sqlsrv." - -PHP_INI_BEGIN() - STD_PHP_INI_BOOLEAN( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS , "1", PHP_INI_ALL, OnUpdateBool, warnings_return_as_errors, - zend_sqlsrv_globals, sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SEVERITY, "0", PHP_INI_ALL, OnUpdateLong, log_severity, zend_sqlsrv_globals, - sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SUBSYSTEMS, "0", PHP_INI_ALL, OnUpdateLong, log_subsystems, zend_sqlsrv_globals, - sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_BUFFERED_QUERY_LIMIT, INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, buffered_query_limit, - zend_sqlsrv_globals, sqlsrv_globals ) -PHP_INI_END() - -//********************************************************************************************************************************* -// Configuration -//********************************************************************************************************************************* -// These functions set and retrieve configuration settings. Configuration settings defined are: -// WarningsReturnAsErrors - treat all ODBC warnings as errors and return false from sqlsrv APIs. -// LogSeverity - combination of severity of messages to log (see Logging) -// LogSubsystems - subsystems within sqlsrv to log messages (see Logging) - -PHP_FUNCTION(sqlsrv_configure); -PHP_FUNCTION(sqlsrv_get_config); - -//********************************************************************************************************************************* -// Errors -//********************************************************************************************************************************* - -// represents the mapping between an error_code and the corresponding error message. -struct ss_error { - - unsigned int error_code; - sqlsrv_error_const sqlsrv_error; -}; - -// List of all driver specific error codes. -enum SS_ERROR_CODES { - - SS_SQLSRV_ERROR_ALREADY_IN_TXN = SQLSRV_ERROR_DRIVER_SPECIFIC, - SS_SQLSRV_ERROR_NOT_IN_TXN, - SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, - SS_SQLSRV_ERROR_REGISTER_RESOURCE, - SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY, - SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED, - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE, - SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, - SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, - SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, - SS_SQLSRV_ERROR_ZEND_BAD_CLASS, - SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE, - SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, - SS_SQLSRV_ERROR_INVALID_OPTION, - SS_SQLSRV_ERROR_PARAM_INVALID_INDEX, - SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, - SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, - SS_SQLSRV_ERROR_VAR_REQUIRED, - SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, - SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, - SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF -}; - -extern ss_error SS_ERRORS[]; - -bool ss_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args ); - -// *** extension error functions *** -PHP_FUNCTION(sqlsrv_errors); - -// convert from the default encoding specified by the "CharacterSet" -// connection option to UTF-16. mbcs_len and utf16_len are sizes in -// bytes. The return is the number of UTF-16 characters in the string -// returned in utf16_out_string. -unsigned int convert_string_from_default_encoding( unsigned int php_encoding, char const* mbcs_in_string, - unsigned int mbcs_len, _Out_ wchar_t* utf16_out_string, - unsigned int utf16_len ); -// create a wide char string from the passed in mbcs string. NULL is returned if the string -// could not be created. No error is posted by this function. utf16_len is the number of -// wchar_t characters, not the number of bytes. -wchar_t* utf16_string_from_mbcs_string( unsigned int php_encoding, const char* mbcs_string, - unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); - -// *** internal error macros and functions *** -bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, - sqlsrv_error const* ssphp TSRMLS_DC, ... ); -void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function, - sqlsrv_error const* ssphp TSRMLS_DC, ... ); -void __cdecl sqlsrv_error_dtor( zend_resource *rsrc TSRMLS_DC ); - -// release current error lists and set to NULL -inline void reset_errors( TSRMLS_D ) -{ - if( Z_TYPE( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE( SQLSRV_G( errors )) != IS_NULL ) { - DIE( "sqlsrv_errors contains an invalid type" ); - } - if( Z_TYPE( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE( SQLSRV_G( warnings )) != IS_NULL ) { - DIE( "sqlsrv_warnings contains an invalid type" ); - } - - if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY ) { - zend_hash_destroy( Z_ARRVAL( SQLSRV_G( errors ))); - FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( errors ))); - } - if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY ) { - zend_hash_destroy( Z_ARRVAL( SQLSRV_G( warnings ))); - FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( warnings ))); - } - - ZVAL_NULL( &SQLSRV_G( errors )); - ZVAL_NULL( &SQLSRV_G( warnings )); -} - -#define THROW_SS_ERROR( ctx, error_code, ... ) \ - (void)call_error_handler( ctx, error_code TSRMLS_CC, false /*warning*/, __VA_ARGS__ ); \ - throw ss::SSException(); - - -class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr > { - -public: - - sqlsrv_context_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - sqlsrv_context_auto_ptr( const sqlsrv_context_auto_ptr& src ) : - sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >( src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( sqlsrv_context* ptr = NULL ) - { - if( _ptr ) { - _ptr->~sqlsrv_context(); - sqlsrv_free( (void*) _ptr ); - } - _ptr = ptr; - } - - sqlsrv_context* operator=( sqlsrv_context* ptr ) - { - return sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >::operator=( ptr ); - } - - void operator=( sqlsrv_context_auto_ptr& src ) - { - sqlsrv_context* p = src.get(); - src.transferred(); - this->_ptr = p; - } -}; - - - -//********************************************************************************************************************************* -// Logging -//********************************************************************************************************************************* -#define LOG_FUNCTION( function_name ) \ - const char* _FN_ = function_name; \ - SQLSRV_G( current_subsystem ) = current_log_subsystem; \ - LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); - -#define SET_FUNCTION_NAME( context ) \ -{ \ - (context).set_func( _FN_ ); \ -} - -// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro -void ss_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); - -// subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise. -enum logging_subsystems { - LOG_INIT = 0x01, - LOG_CONN = 0x02, - LOG_STMT = 0x04, - LOG_UTIL = 0x08, - LOG_ALL = -1, -}; - - -//********************************************************************************************************************************* -// Utility Functions -//********************************************************************************************************************************* - -// generic function used to validate parameters to a PHP function. -// Register an invalid parameter error and returns NULL when parameters don't match the spec given. -template -inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, char const* param_spec, const char* calling_func, size_t param_count, ... ) -{ - SQLSRV_UNUSED( return_value ); - - zval* rsrc; - H* h; - - // reset the errors from the previous API call - reset_errors( TSRMLS_C ); - - if( ZEND_NUM_ARGS() > param_count + 1 ) { - DIE( "Param count and argument count don't match." ); - return NULL; // for static analysis tools - } - - try { - - if( param_count > 6 ) { - DIE( "Param count cannot exceed 6" ); - return NULL; // for static analysis tools - } - - void* arr[6]; - va_list vaList; - va_start(vaList, param_count); //set the pointer to first argument - - for(size_t i = 0; i < param_count; ++i) { - - arr[i] = va_arg(vaList, void*); - } - - va_end(vaList); - - int result = SUCCESS; - - // dummy context to pass to the error handler - sqlsrv_context error_ctx( 0, ss_error_handler, NULL );; - error_ctx.set_func( calling_func ); - - switch( param_count ) { - - case 0: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc ); - break; - - case 1: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0] ); - break; - - case 2: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1] ); - break; - - case 3: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2] ); - break; - - case 4: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3] ); - break; - - case 5: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3], arr[4] ); - break; - - case 6: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3], arr[4], arr[5] ); - break; - - default: - { - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ); - break; - } - } - - CHECK_CUSTOM_ERROR(( result == FAILURE ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { - - throw ss::SSException(); - } - - // get the resource registered - h = static_cast( zend_fetch_resource(Z_RES_P(rsrc) TSRMLS_CC, H::resource_name, H::descriptor )); - - CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { - - throw ss::SSException(); - } - - h->set_func( calling_func ); - - return h; - } - - catch( core::CoreException& ) { - - return NULL; - } - catch ( ... ) { - - DIE( "%1!s!: Unknown exception caught in process_params.", calling_func ); - } -} -//********************************************************************************************************************************* -// Common function wrappers -//********************************************************************************************************************************* -namespace ss { - - // an error which occurred in our SQLSRV driver - struct SSException : public core::CoreException { - - SSException() - { - } - }; - - inline void zend_register_resource(_Out_ zval& rsrc_result, void* rsrc_pointer, int rsrc_type, char* rsrc_name TSRMLS_DC) - { - int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE, - rsrc_name ) { - throw ss::SSException(); - } - Z_TYPE_INFO(rsrc_result) = IS_RESOURCE_EX; - } -} // namespace ss - -#endif /* PHP_SQLSRV_H */ +#ifndef PHP_SQLSRV_H +#define PHP_SQLSRV_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: php_sqlsrv.h +// +// Contents: Declarations for the extension +// +// Comments: Also contains "internal" declarations shared across source files. +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" +#include "version.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef PHP_WIN32 +#define PHP_SQLSRV_API __declspec(dllexport) +#else +#define PHP_SQLSRV_API +#endif + +// OACR is an internal Microsoft static code analysis tool +#if defined(OACR) +#include +OACR_WARNING_PUSH +OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) +OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) +OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) +OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) +OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) +#endif + +extern "C" { + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#if _MSC_VER >= 1400 +// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. +// php.h defines this constant as unsigned int which causes a compile error +// in ws2tcpip.h. Fortunately php.h allows an override by defining +// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define +// socklen_t here and override the php.h version. +typedef int socklen_t; +#define HAVE_SOCKLEN_T +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if PHP_MAJOR_VERSION < 7 +#error Trying to compile "Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)" with an unsupported version of PHP +#endif + +#if ZEND_DEBUG +// debug build causes warning C4505 to pop up from the Zend header files +#pragma warning( disable: 4505 ) +#endif + +} // extern "C" + +//********************************************************************************************************************************* +// Initialization Functions +//********************************************************************************************************************************* + +// module global variables (initialized in minit and freed in mshutdown) +extern HashTable* g_ss_errors_ht; +extern HashTable* g_ss_encodings_ht; +extern HashTable* g_ss_warnings_to_ignore_ht; + +// variables set during initialization +extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP +extern HMODULE g_sqlsrv_hmodule; // used for getting the version information + +// henv context for creating connections +extern sqlsrv_context* g_henv_cp; +extern sqlsrv_context* g_henv_ncp; + +extern OSVERSIONINFO g_osversion; // used to determine which OS we're running in + +#define phpext_sqlsrv_ptr &g_sqlsrv_module_entry + +// module initialization +PHP_MINIT_FUNCTION(sqlsrv); +// module shutdown function +PHP_MSHUTDOWN_FUNCTION(sqlsrv); +// request initialization function +PHP_RINIT_FUNCTION(sqlsrv); +// request shutdown function +PHP_RSHUTDOWN_FUNCTION(sqlsrv); +// module info function (info returned by phpinfo()) +PHP_MINFO_FUNCTION(sqlsrv); + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* +PHP_FUNCTION(sqlsrv_connect); +PHP_FUNCTION(sqlsrv_begin_transaction); +PHP_FUNCTION(sqlsrv_client_info); +PHP_FUNCTION(sqlsrv_close); +PHP_FUNCTION(sqlsrv_commit); +PHP_FUNCTION(sqlsrv_query); +PHP_FUNCTION(sqlsrv_prepare); +PHP_FUNCTION(sqlsrv_rollback); +PHP_FUNCTION(sqlsrv_server_info); + +struct ss_sqlsrv_conn : sqlsrv_conn +{ + HashTable* stmts; + bool date_as_string; + bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls + + // static variables used in process_params + static char* resource_name; // char because const char forces casting all over the place. Just easier to leave it char here. + static int descriptor; + + // initialize with default values + ss_sqlsrv_conn( SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ) : + sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ), + stmts( NULL ), + date_as_string( false ), + in_transaction( false ) + { + } +}; + +// resource destructor +void __cdecl sqlsrv_conn_dtor( zend_resource *rsrc TSRMLS_DC ); + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +// holds the field names for reuse by sqlsrv_fetch_array/object as keys +struct sqlsrv_fetch_field_name { + char* name; + unsigned int len; +}; + +struct stmt_option_scrollable : public stmt_option_functor { + + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + +// This object inherits and overrides the callbacks necessary +struct ss_sqlsrv_stmt : public sqlsrv_stmt { + + ss_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ); + + virtual ~ss_sqlsrv_stmt( void ); + + void new_result_set( TSRMLS_D ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ); + + bool prepared; // whether the statement has been prepared yet (used for error messages) + zend_ulong conn_index; // index into the connection hash that contains this statement structure + zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute + sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys + int fetch_fields_count; + + // static variables used in process_params + static char* resource_name; // char because const char forces casting all over the place in ODBC functions + static int descriptor; + +}; + +// holds the field names for reuse by sqlsrv_fetch_array/object as keys +struct sqlsrv_fetch_field { + char* name; + unsigned int len; +}; + +// holds the stream param and the encoding that it was assigned +struct sqlsrv_stream_encoding { + zval* stream_z; + unsigned int encoding; + + sqlsrv_stream_encoding( zval* str_z, unsigned int enc ) : + stream_z( str_z ), encoding( enc ) + { + } +}; + +// *** statement functions *** +PHP_FUNCTION(sqlsrv_cancel); +PHP_FUNCTION(sqlsrv_execute); +PHP_FUNCTION(sqlsrv_fetch); +PHP_FUNCTION(sqlsrv_fetch_array); +PHP_FUNCTION(sqlsrv_fetch_object); +PHP_FUNCTION(sqlsrv_field_metadata); +PHP_FUNCTION(sqlsrv_free_stmt); +PHP_FUNCTION(sqlsrv_get_field); +PHP_FUNCTION(sqlsrv_has_rows); +PHP_FUNCTION(sqlsrv_next_result); +PHP_FUNCTION(sqlsrv_num_fields); +PHP_FUNCTION(sqlsrv_num_rows); +PHP_FUNCTION(sqlsrv_rows_affected); +PHP_FUNCTION(sqlsrv_send_stream_data); + +// resource destructor +void __cdecl sqlsrv_stmt_dtor( zend_resource *rsrc TSRMLS_DC ); + +// "internal" statement functions shared by functions in conn.cpp and stmt.cpp +void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC ); +void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC ); +bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function + TSRMLS_DC ); +void free_odbc_resources( ss_sqlsrv_stmt* stmt TSRMLS_DC ); +void free_stmt_resource( zval* stmt_z TSRMLS_DC ); + +//********************************************************************************************************************************* +// Type Functions +//********************************************************************************************************************************* + +// type functions for SQL types. +// to expose SQL Server paramterized types, we use functions that return encoded integers that contain the size/precision etc. +// for example, SQLSRV_SQLTYPE_VARCHAR(4000) matches the usage of SQLSRV_SQLTYPE_INT with the size added. +PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY); +PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR); +PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL); +PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR); +PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC); +PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR); +PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY); +PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR); + +// PHP type functions +// strings and streams may have an encoding parameterized, so we use the functions +// the valid encodings are SQLSRV_ENC_BINARY and SQLSRV_ENC_CHAR. +PHP_FUNCTION(SQLSRV_PHPTYPE_STREAM); +PHP_FUNCTION(SQLSRV_PHPTYPE_STRING); + +//********************************************************************************************************************************* +// Global variables +//********************************************************************************************************************************* + +extern "C" { + +// request level variables +ZEND_BEGIN_MODULE_GLOBALS(sqlsrv) + +// global objects for errors and warnings. These are returned by sqlsrv_errors. +zval errors; +zval warnings; + +// flags for error handling and logging (set via sqlsrv_configure or php.ini) +zend_long log_severity; +zend_long log_subsystems; +zend_long current_subsystem; +zend_bool warnings_return_as_errors; +zend_long buffered_query_limit; + +ZEND_END_MODULE_GLOBALS(sqlsrv) + +ZEND_EXTERN_MODULE_GLOBALS(sqlsrv); + +} + +// macro used to access the global variables. Use it to make global variable access agnostic to threads +#define SQLSRV_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(sqlsrv, v) + +#if defined(ZTS) +ZEND_TSRMLS_CACHE_EXTERN(); +#endif + +// INI settings and constants +// (these are defined as macros to allow concatenation as we do below) +#define INI_WARNINGS_RETURN_AS_ERRORS "WarningsReturnAsErrors" +#define INI_LOG_SEVERITY "LogSeverity" +#define INI_LOG_SUBSYSTEMS "LogSubsystems" +#define INI_BUFFERED_QUERY_LIMIT "ClientBufferMaxKBSize" +#define INI_PREFIX "sqlsrv." + +PHP_INI_BEGIN() + STD_PHP_INI_BOOLEAN( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS , "1", PHP_INI_ALL, OnUpdateBool, warnings_return_as_errors, + zend_sqlsrv_globals, sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SEVERITY, "0", PHP_INI_ALL, OnUpdateLong, log_severity, zend_sqlsrv_globals, + sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SUBSYSTEMS, "0", PHP_INI_ALL, OnUpdateLong, log_subsystems, zend_sqlsrv_globals, + sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_BUFFERED_QUERY_LIMIT, INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, buffered_query_limit, + zend_sqlsrv_globals, sqlsrv_globals ) +PHP_INI_END() + +//********************************************************************************************************************************* +// Configuration +//********************************************************************************************************************************* +// These functions set and retrieve configuration settings. Configuration settings defined are: +// WarningsReturnAsErrors - treat all ODBC warnings as errors and return false from sqlsrv APIs. +// LogSeverity - combination of severity of messages to log (see Logging) +// LogSubsystems - subsystems within sqlsrv to log messages (see Logging) + +PHP_FUNCTION(sqlsrv_configure); +PHP_FUNCTION(sqlsrv_get_config); + +//********************************************************************************************************************************* +// Errors +//********************************************************************************************************************************* + +// represents the mapping between an error_code and the corresponding error message. +struct ss_error { + + unsigned int error_code; + sqlsrv_error_const sqlsrv_error; +}; + +// List of all driver specific error codes. +enum SS_ERROR_CODES { + + SS_SQLSRV_ERROR_ALREADY_IN_TXN = SQLSRV_ERROR_DRIVER_SPECIFIC, + SS_SQLSRV_ERROR_NOT_IN_TXN, + SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, + SS_SQLSRV_ERROR_REGISTER_RESOURCE, + SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY, + SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED, + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE, + SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, + SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, + SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, + SS_SQLSRV_ERROR_ZEND_BAD_CLASS, + SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE, + SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, + SS_SQLSRV_ERROR_INVALID_OPTION, + SS_SQLSRV_ERROR_PARAM_INVALID_INDEX, + SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, + SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, + SS_SQLSRV_ERROR_VAR_REQUIRED, + SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, + SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, + SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF +}; + +extern ss_error SS_ERRORS[]; + +bool ss_error_handler( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args ); + +// *** extension error functions *** +PHP_FUNCTION(sqlsrv_errors); + +// convert from the default encoding specified by the "CharacterSet" +// connection option to UTF-16. mbcs_len and utf16_len are sizes in +// bytes. The return is the number of UTF-16 characters in the string +// returned in utf16_out_string. +unsigned int convert_string_from_default_encoding( unsigned int php_encoding, char const* mbcs_in_string, + unsigned int mbcs_len, _Out_ wchar_t* utf16_out_string, + unsigned int utf16_len ); +// create a wide char string from the passed in mbcs string. NULL is returned if the string +// could not be created. No error is posted by this function. utf16_len is the number of +// wchar_t characters, not the number of bytes. +SQLWCHAR* utf16_string_from_mbcs_string( unsigned int php_encoding, const char* mbcs_string, + unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); + +// *** internal error macros and functions *** +bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, + sqlsrv_error const* ssphp TSRMLS_DC, ... ); +void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function, + sqlsrv_error const* ssphp TSRMLS_DC, ... ); +void __cdecl sqlsrv_error_dtor( zend_resource *rsrc TSRMLS_DC ); + +// release current error lists and set to NULL +inline void reset_errors( TSRMLS_D ) +{ + if( Z_TYPE( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE( SQLSRV_G( errors )) != IS_NULL ) { + DIE( "sqlsrv_errors contains an invalid type" ); + } + if( Z_TYPE( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE( SQLSRV_G( warnings )) != IS_NULL ) { + DIE( "sqlsrv_warnings contains an invalid type" ); + } + + if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( SQLSRV_G( errors ))); + FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( errors ))); + } + if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( SQLSRV_G( warnings ))); + FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( warnings ))); + } + + ZVAL_NULL( &SQLSRV_G( errors )); + ZVAL_NULL( &SQLSRV_G( warnings )); +} + +#define THROW_SS_ERROR( ctx, error_code, ... ) \ + (void)call_error_handler( ctx, error_code TSRMLS_CC, false /*warning*/, ## __VA_ARGS__ ); \ + throw ss::SSException(); + + +class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr > { + +public: + + sqlsrv_context_auto_ptr( void ) : + sqlsrv_auto_ptr( NULL ) + { + } + + sqlsrv_context_auto_ptr( const sqlsrv_context_auto_ptr& src ) : + sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >( src ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( sqlsrv_context* ptr = NULL ) + { + if( _ptr ) { + _ptr->~sqlsrv_context(); + sqlsrv_free( (void*) _ptr ); + } + _ptr = ptr; + } + + sqlsrv_context* operator=( sqlsrv_context* ptr ) + { + return sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >::operator=( ptr ); + } + + void operator=( sqlsrv_context_auto_ptr& src ) + { + sqlsrv_context* p = src.get(); + src.transferred(); + this->_ptr = p; + } +}; + + + +//********************************************************************************************************************************* +// Logging +//********************************************************************************************************************************* +#define LOG_FUNCTION( function_name ) \ + const char* _FN_ = function_name; \ + SQLSRV_G( current_subsystem ) = current_log_subsystem; \ + LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); + +#define SET_FUNCTION_NAME( context ) \ +{ \ + (context).set_func( _FN_ ); \ +} + +// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro +void ss_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ); + +// subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise. +enum logging_subsystems { + LOG_INIT = 0x01, + LOG_CONN = 0x02, + LOG_STMT = 0x04, + LOG_UTIL = 0x08, + LOG_ALL = -1, +}; + +//********************************************************************************************************************************* +// Common function wrappers +// have to place this namespace before the utility functions +// otherwise can't compile in Linux because 'ss' not defined +//********************************************************************************************************************************* +namespace ss { + + // an error which occurred in our SQLSRV driver + struct SSException : public core::CoreException { + + SSException() + { + } + }; + + inline void zend_register_resource(_Out_ zval& rsrc_result, void* rsrc_pointer, int rsrc_type, char* rsrc_name TSRMLS_DC) + { + int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE, + rsrc_name ) { + throw ss::SSException(); + } + Z_TYPE_INFO(rsrc_result) = IS_RESOURCE_EX; + } +} // namespace ss + +//********************************************************************************************************************************* +// Utility Functions +//********************************************************************************************************************************* + +// generic function used to validate parameters to a PHP function. +// Register an invalid parameter error and returns NULL when parameters don't match the spec given. +template +inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, char const* param_spec, const char* calling_func, size_t param_count, ... ) +{ + SQLSRV_UNUSED( return_value ); + + zval* rsrc; + H* h; + + // reset the errors from the previous API call + reset_errors( TSRMLS_C ); + + if( ZEND_NUM_ARGS() > param_count + 1 ) { + DIE( "Param count and argument count don't match." ); + return NULL; // for static analysis tools + } + + try { + + if( param_count > 6 ) { + DIE( "Param count cannot exceed 6" ); + return NULL; // for static analysis tools + } + + void* arr[6]; + va_list vaList; + va_start(vaList, param_count); //set the pointer to first argument + + for(size_t i = 0; i < param_count; ++i) { + + arr[i] = va_arg(vaList, void*); + } + + va_end(vaList); + + int result = SUCCESS; + + // dummy context to pass to the error handler + sqlsrv_context error_ctx( 0, ss_error_handler, NULL ); + error_ctx.set_func( calling_func ); + + switch( param_count ) { + + case 0: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc ); + break; + + case 1: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0] ); + break; + + case 2: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1] ); + break; + + case 3: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2] ); + break; + + case 4: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3] ); + break; + + case 5: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3], arr[4] ); + break; + + case 6: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3], arr[4], arr[5] ); + break; + + default: + { + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ); + break; + } + } + + CHECK_CUSTOM_ERROR(( result == FAILURE ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { + + throw ss::SSException(); + } + + // get the resource registered + h = static_cast( zend_fetch_resource(Z_RES_P(rsrc) TSRMLS_CC, H::resource_name, H::descriptor )); + + CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { + + throw ss::SSException(); + } + + h->set_func( calling_func ); + } + + catch( core::CoreException& ) { + + return NULL; + } + catch ( ... ) { + + DIE( "%1!s!: Unknown exception caught in process_params.", calling_func ); + } + + return h; +} + +#endif /* PHP_SQLSRV_H */ diff --git a/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp similarity index 93% rename from sqlsrv/stmt.cpp rename to source/sqlsrv/stmt.cpp index 40eb589f8..7834aa831 100644 --- a/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1,2247 +1,2265 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: stmt.cpp -// -// Contents: Routines that use statement handles -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -// *** header files *** -#include "php_sqlsrv.h" -#include - -// -// *** internal variables and constants *** -// -// our resource descriptor assigned in minit -int ss_sqlsrv_stmt::descriptor = 0; -char* ss_sqlsrv_stmt::resource_name = "ss_sqlsrv_stmt"; // not const for a reason. see sqlsrv_stmt in php_sqlsrv.h - -namespace { - -// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros -unsigned int current_log_subsystem = LOG_STMT; - -// constants used as invalid types for type errors -const zend_uchar PHPTYPE_INVALID = SQLSRV_PHPTYPE_INVALID; -const int SQLTYPE_INVALID = 0; -const int SQLSRV_INVALID_PRECISION = -1; -const SQLUINTEGER SQLSRV_INVALID_SIZE = (~1U); -const int SQLSRV_INVALID_SCALE = -1; -const int SQLSRV_SIZE_MAX_TYPE = -1; - -// constants for maximums in SQL Server -const int SQL_SERVER_MAX_FIELD_SIZE = 8000; -const int SQL_SERVER_MAX_PRECISION = 38; -const int SQL_SERVER_DEFAULT_PRECISION = 18; -const int SQL_SERVER_DEFAULT_SCALE = 0; - -// default class used when no class is specified by sqlsrv_fetch_object -const char STDCLASS_NAME[] = "stdclass"; -const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1; - -// map a Zend PHP type constant to our constant type -enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = { - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_NULL, - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_INT, - SQLSRV_PHPTYPE_FLOAT, - SQLSRV_PHPTYPE_STRING, - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_DATETIME, - SQLSRV_PHPTYPE_STREAM, - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_INVALID, - SQLSRV_PHPTYPE_INVALID -}; - -// constant strings used for the field metadata results -// (char to avoid having to cast them where they are used) -namespace FieldMetaData { - -char* NAME = "Name"; -char* TYPE = "Type"; -char* SIZE = "Size"; -char* PREC = "Precision"; -char* SCALE = "Scale"; -char* NULLABLE = "Nullable"; - -} - -// warning message printed when a parameter variable is not passed by reference -const char SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF[] = "Variable parameter %d not passed by reference (prefaced with an &). " - "Variable parameters passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value. " - "For more information, see sqlsrv_prepare or sqlsrv_query in the API Reference section of the product documentation."; - -/* internal functions */ - -void convert_to_zval( sqlsrv_stmt* stmt, SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len, zval& out_zval ); - -void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, zend_long fetch_type, _Out_ zval& fields, bool allow_empty_field_names - TSRMLS_DC ); -bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, _Out_ SQLULEN* column_size, - _Out_ SQLSMALLINT* decimal_digits ); -sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ); -void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC ); -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); -bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype type ); -void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, - _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); -void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ); -void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); -void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); -bool verify_and_set_encoding( const char* encoding_string, _Out_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ); - -} - -// query options for cursor types -namespace SSCursorTypes { - - const char QUERY_OPTION_SCROLLABLE_STATIC[] = "static"; - const char QUERY_OPTION_SCROLLABLE_DYNAMIC[] = "dynamic"; - const char QUERY_OPTION_SCROLLABLE_KEYSET[] = "keyset"; - const char QUERY_OPTION_SCROLLABLE_FORWARD[] = "forward"; - const char QUERY_OPTION_SCROLLABLE_BUFFERED[] = "buffered"; -} - -ss_sqlsrv_stmt::ss_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : - sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), - prepared( false ), - conn_index( -1 ), - params_z( NULL ), - fetch_field_names( NULL ), - fetch_fields_count ( 0 ) -{ - core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC ); -} - -ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) -{ - if( fetch_field_names != NULL ) { - - for( int i=0; i < fetch_fields_count; ++i ) { - - sqlsrv_free( fetch_field_names[ i ].name ); - } - sqlsrv_free( fetch_field_names ); - } - if( params_z ) { - zval_ptr_dtor( params_z ); - sqlsrv_free(params_z); - } -} - -// to be called whenever a new result set is created, such as after an -// execute or next_result. Resets the state variables and calls the subclass. -void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) -{ - if( fetch_field_names != NULL ) { - - for( int i=0; i < fetch_fields_count; ++i ) { - - sqlsrv_free( fetch_field_names[ i ].name ); - } - sqlsrv_free( fetch_field_names ); - } - - fetch_field_names = NULL; - fetch_fields_count = 0; - sqlsrv_stmt::new_result_set( TSRMLS_C ); -} - -// Returns a php type for a given sql type. Also sets the encoding wherever applicable. -sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ) -{ - sqlsrv_phptype ss_phptype; - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; - - switch( sql_type ) { - - case SQL_BIGINT: - case SQL_CHAR: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - case SQL_WCHAR: - case SQL_SS_VARIANT: - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - ss_phptype.typeinfo.encoding = this->conn->encoding(); - break; - - case SQL_VARCHAR: - case SQL_WVARCHAR: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - if( prefer_string_to_stream || size != SQL_SS_LENGTH_UNLIMITED ) { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - ss_phptype.typeinfo.encoding = this->conn->encoding(); - } - else { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; - ss_phptype.typeinfo.encoding = this->conn->encoding(); - } - break; - - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; - break; - - case SQL_BINARY: - case SQL_LONGVARBINARY: - case SQL_VARBINARY: - case SQL_SS_UDT: - if( prefer_string_to_stream ) { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; - } - else { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; - ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; - } - break; - - case SQL_FLOAT: - case SQL_REAL: - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; - break; - - case SQL_TYPE_DATE: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_TIME2: - case SQL_TYPE_TIMESTAMP: - if( reinterpret_cast( this->conn )->date_as_string ) { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - ss_phptype.typeinfo.encoding = this->conn->encoding(); - } - else { - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; - } - break; - - default: - ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - break; - } - - return ss_phptype; -} - -// statement specific parameter proccessing. Uses the generic function specialised to return a statement -// resource. -#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \ - rsrc = process_params( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, __VA_ARGS__ );\ - if( rsrc == NULL ) { \ - RETURN_FALSE; \ - } - -// sqlsrv_execute( resource $stmt ) -// -// Executes a previously prepared statement. See sqlsrv_prepare for information -// on preparing a statement for execution. -// -// This function is ideal for executing a prepared statement multiple times with -// different parameter values. See the MSDN documentation -// -// Parameters -// $stmt: A resource specifying the statement to be executed. For more -// information about how to create a statement resource, see sqlsrv_prepare. -// -// Return Value -// A Boolean value: true if the statement was successfully executed. Otherwise, false. - -PHP_FUNCTION( sqlsrv_execute ) -{ - LOG_FUNCTION( "sqlsrv_execute" ); - - ss_sqlsrv_stmt* stmt = NULL; - - try { - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - CHECK_CUSTOM_ERROR(( !stmt->prepared ), stmt, SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED ) { - throw ss::SSException(); - } - - // prepare for the next execution by flushing anything remaining in the result set - if( stmt->executed ) { - - // to prepare to execute the next statement, we skip any remaining results (and skip parameter finalization too) - while( stmt->past_next_result_end == false ) { - - core_sqlsrv_next_result( stmt TSRMLS_CC, false, false ); - } - } - - // bind parameters before executing - bind_params( stmt TSRMLS_CC ); - - core_sqlsrv_execute( stmt TSRMLS_CC ); - - RETURN_TRUE; - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_execute: Unknown exception caught." ); - } -} - - -// sqlsrv_fetch( resource $stmt ) -// -// Makes the next row of a result set available for reading. Use -// sqlsrv_get_field to read fields of the row. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. A -// statement must be executed before results can be retrieved. For information -// on executing a statement, see sqlsrv_query and sqlsrv_execute. -// -// Return Value -// If the next row of the result set was successfully retrieved, true is -// returned. If there are no more results in the result set, null is -// returned. If an error occured, false is returned - -PHP_FUNCTION( sqlsrv_fetch ) -{ - LOG_FUNCTION( "sqlsrv_fetch" ); - - ss_sqlsrv_stmt* stmt = NULL; - // NOTE: zend_parse_parameter expect zend_long when the type spec is 'l',and core_sqlsrv_fetch expect short int - int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied - zend_long fetch_offset = 0; // default value for parameter if one isn't supplied - - // take only the statement resource - PROCESS_PARAMS( stmt, "r|ll", _FN_, 2, &fetch_style, &fetch_offset ); - - try { - - CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { - throw ss::SSException(); - } - - bool result = core_sqlsrv_fetch( stmt, fetch_style, fetch_offset TSRMLS_CC ); - if( !result ) { - RETURN_NULL(); - } - - RETURN_TRUE; - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_fetch: Unknown exception caught." ); - } -} - -// sqlsrv_fetch_array( resource $stmt [, int $fetchType] ) -// -// Retrieves the next row of data as an array. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. -// $fetchType [OPTIONAL]: A predefined constant. See SQLSRV_FETCH_TYPE in php_sqlsrv.h -// -// Return Value -// If a row of data is retrieved, an array is returned. If there are no more -// rows to retrieve, null is returned. If an error occurs, false is returned. -// Based on the value of the $fetchType parameter, the returned array can be a -// numerically indexed array, an associative array, or both. By default, an -// array with both numeric and associative keys is returned. The data type of a -// value in the returned array will be the default PHP data type. For -// information about default PHP data types, see Default PHP Data Types. - -PHP_FUNCTION( sqlsrv_fetch_array ) -{ - LOG_FUNCTION( "sqlsrv_fetch_array" ); - - ss_sqlsrv_stmt* stmt = NULL; - zend_long fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied - int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied - zend_long fetch_offset = 0; // default value for parameter if one isn't supplied - - // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), - // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset - PROCESS_PARAMS( stmt, "r|lll", _FN_, 3, &fetch_type, &fetch_style, &fetch_offset ); - - try { - - CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt, - SS_SQLSRV_ERROR_INVALID_FETCH_TYPE ) { - throw ss::SSException(); - } - - CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { - throw ss::SSException(); - } - - bool result = core_sqlsrv_fetch( stmt, fetch_style, fetch_offset TSRMLS_CC ); - if( !result ) { - RETURN_NULL(); - } - zval fields; - ZVAL_UNDEF( &fields ); - fetch_fields_common( stmt, fetch_type, fields, true /*allow_empty_field_names*/ TSRMLS_CC ); - RETURN_ARR( Z_ARRVAL( fields )); - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_fetch_array: Unknown exception caught." ); - } -} - -// sqlsrv_field_metadata( resource $stmt ) -// -// Retrieves metadata for the fields of a prepared statement. For information -// about preparing a statement, see sqlsrv_query or sqlsrv_prepare. Note that -// sqlsrv_field_metadata can be called on any prepared statement, pre- or -// post-execution. -// -// Parameters -// $stmt: A statement resource for which field metadata is sought. -// -// Return Value -// retrieve an array of metadata for the current result set on a statement. Each element of the -// array is a sub-array containing 5 elements accessed by key: -// name - name of the field. -// type - integer of the type. Can be compared against the SQLSRV_SQLTYPE constants. -// size - length of the field. null if the field uses precision and scale instead. -// precision - number of digits in a numeric field. null if the field uses size. -// scale - number of decimal digits in a numeric field. null if the field uses sizes. -// is_nullable - if the field may contain a NULL instead of a value -// false is returned if an error occurs retrieving the metadata - -PHP_FUNCTION( sqlsrv_field_metadata ) -{ - sqlsrv_stmt* stmt = NULL; - SQLSMALLINT num_cols = -1; - - LOG_FUNCTION( "sqlsrv_field_metadata" ); - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // get the number of fields in the resultset - num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - - zval result_meta_data; - ZVAL_UNDEF( &result_meta_data ); - core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC ); - - for( SQLSMALLINT f = 0; f < num_cols; ++f ) { - - sqlsrv_malloc_auto_ptr core_meta_data; - core_meta_data = core_sqlsrv_field_metadata( stmt, f TSRMLS_CC ); - - // initialize the array - zval field_array; - ZVAL_UNDEF( &field_array ); - core::sqlsrv_array_init( *stmt, &field_array TSRMLS_CC ); - - core::sqlsrv_add_assoc_string( *stmt, &field_array, FieldMetaData::NAME, - reinterpret_cast( core_meta_data->field_name.get() ), 0 TSRMLS_CC ); - - core_meta_data->field_name.transferred(); - - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC ); - - switch( core_meta_data->field_type ) { - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SCALE, core_meta_data->field_scale TSRMLS_CC ); - break; - case SQL_BIT: - case SQL_TINYINT: - case SQL_SMALLINT: - case SQL_INTEGER: - case SQL_BIGINT: - case SQL_REAL: - case SQL_FLOAT: - case SQL_DOUBLE: - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); - break; - default: - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SIZE, core_meta_data->field_size TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::PREC TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); - break; - } - - // add the nullability to the array - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable - TSRMLS_CC ); - - // add this field's meta data to the result set meta data - core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC ); - - // always good to call destructor for allocations done through placement new operator. - core_meta_data->~field_meta_data(); - } - - // return our built collection and transfer ownership - RETURN_ZVAL(&result_meta_data, 1, 1); - - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_field_metadata: Unknown exception caught." ); - } -} - - -// sqlsrv_next_result( resource $stmt ) -// -// Makes the next result (result set, row count, or output parameter) of the -// specified statement active. The first (or only) result returned by a batch -// query or stored procedure is active without a call to sqlsrv_next_result. -// Any output parameters bound are only available after sqlsrv_next_result returns -// null as per ODBC Driver 11 for SQL Server specs: http://msdn.microsoft.com/en-us/library/ms403283.aspx -// -// Parameters -// $stmt: The executed statement on which the next result is made active. -// -// Return Value -// If the next result was successfully made active, the Boolean value true is -// returned. If an error occurred in making the next result active, false is -// returned. If no more results are available, null is returned. - -PHP_FUNCTION( sqlsrv_next_result ) -{ - LOG_FUNCTION( "sqlsrv_next_result" ); - - SQLRETURN r = SQL_SUCCESS; - ss_sqlsrv_stmt* stmt = NULL; - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - core_sqlsrv_next_result( stmt TSRMLS_CC, true ); - - if( stmt->past_next_result_end ) { - - RETURN_NULL(); - } - - RETURN_TRUE; - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_next_result: Unknown exception caught." ); - } -} - -// sqlsrv_rows_affected( resource $stmt ) -// -// Returns the number of rows modified by the last statement executed. This -// function does not return the number of rows returned by a SELECT statement. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. -// -// Return Value -// An integer indicating the number of rows modified by the last executed -// statement. If no rows were modified, zero (0) is returned. If no information -// about the number of modified rows is available, negative one (-1) is -// returned. If an error occurred in retrieving the number of modified rows, -// false is returned. See SQLRowCount in the MSDN ODBC documentation. - -PHP_FUNCTION( sqlsrv_rows_affected ) -{ - LOG_FUNCTION( "sqlsrv_rows_affected" ); - SQLRETURN r = SQL_SUCCESS; - ss_sqlsrv_stmt* stmt = NULL; - SQLLEN rows = -1; - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // make sure that the statement has already been executed. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw ss::SSException(); - } - - // make sure it is not scrollable. This function should only work for inserts, updates, and deletes, - // but this is the best we can do to enforce that. - CHECK_CUSTOM_ERROR( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY, stmt, SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE ) { - throw ss::SSException(); - } - - rows = stmt->current_results->row_count( TSRMLS_C ); - RETURN_LONG( rows ); - } - - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_rows_affected: Unknown exception caught." ); - } -} - -// sqlsrv_num_rows( resource $stmt ) -// -// Retrieves the number of rows in an active result set. The statement must -// have been created with the Scrollable attribute set to 'static'. -// -// Parameters -// $stmt: The statement on which the targeted result set is active. -// -// Return Value -// An integer value that represents the number of rows in the active result -// set. If an error occurs, the boolean value false is returned. - -PHP_FUNCTION( sqlsrv_num_rows ) -{ - LOG_FUNCTION( "sqlsrv_num_rows" ); - - ss_sqlsrv_stmt* stmt = NULL; - SQLLEN rows = -1; - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // make sure that the statement has already been executed. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw ss::SSException(); - } - - // make sure that the statement is scrollable and the cursor is not dynamic. - // if the cursor is dynamic, then the number of rows returned is always -1. - CHECK_CUSTOM_ERROR( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY || stmt->cursor_type == SQL_CURSOR_DYNAMIC, stmt, - SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE ) { - throw ss::SSException(); - } - - rows = stmt->current_results->row_count( TSRMLS_C ); - RETURN_LONG( rows ); - } - - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_num_rows: Unknown exception caught." ); - } -} - -// sqlsrv_num_fields( resource $stmt ) -// -// Retrieves the number of fields in an active result set. Note that -// sqlsrv_num_fields can be called on any prepared statement, before or after -// execution. -// -// Parameters -// $stmt: The statement on which the targeted result set is active. -// -// Return Value -// An integer value that represents the number of fields in the active result -// set. If an error occurs, the boolean value false is returned. - -PHP_FUNCTION( sqlsrv_num_fields ) -{ - LOG_FUNCTION( "sqlsrv_num_fields" ); - - ss_sqlsrv_stmt* stmt = NULL; - SQLSMALLINT fields = -1; - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // retrieve the number of columns from ODBC - fields = core::SQLNumResultCols( stmt TSRMLS_CC ); - - RETURN_LONG( fields ); - } - - catch( ss::SSException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_num_fields: Unknown exception caught." ); - } -} - -// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams ]]) -// -// Retrieves the next row of data as a PHP object. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. -// -// $className [OPTIONAL]: A string specifying the name of the class to -// instantiate. If a value for the $className parameter is not specified, an -// instance of the PHP stdClass is instantiated. -// -// $ctorParams [OPTIONAL]: An array that contains values passed to the -// constructor of the class specified with the $className parameter. If the -// constructor of the specified class accepts parameter values, the $ctorParams -// parameter must be used when calling sqlsrv_fetch_object. -// -// Return Value -// A PHP object with properties that correspond to result set field -// names. Property values are populated with the corresponding result set field -// values. If the class specified with the optional $className parameter does -// not exist or if there is no active result set associated with the specified -// statement, false is returned. -// The data type of a value in the returned object will be the default PHP data -// type. For information on default PHP data types, see Default PHP Data Types. -// -// Remarks -// If a class name is specified with the optional $className parameter, an -// object of this class type is instantiated. If the class has properties whose -// names match the result set field names, the corresponding result set values -// are applied to the properties. If a result set field name does not match a -// class property, a property with the result set field name is added to the -// object and the result set value is applied to the property. For more -// information about calling sqlsrv_fetch_object with the $className parameter, -// see How to: Retrieve Data as an Object (Microsoft Drivers for PHP for SQL Server). -// -// If a field with no name is returned, sqlsrv_fetch_object will discard the -// field value and issue a warning. - -PHP_FUNCTION( sqlsrv_fetch_object ) -{ - LOG_FUNCTION( "sqlsrv_fetch_object" ); - - ss_sqlsrv_stmt* stmt = NULL; - zval* class_name_z = NULL; - zval* ctor_params_z = NULL; - int fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied - zend_long fetch_offset = 0; // default value for parameter if one isn't supplied - - // stdClass is the name of the system's default base class in PHP - char* class_name = const_cast( STDCLASS_NAME ); - std::size_t class_name_len = STDCLASS_NAME_LEN; - HashTable* properties_ht = NULL; - zval retval_z; - ZVAL_UNDEF( &retval_z ); - - // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), - // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset - // we also use z! instead of s and a so that null may be passed in as valid values for - // the class name and ctor params - PROCESS_PARAMS( stmt, "r|z!z!ll", _FN_, 4, &class_name_z, &ctor_params_z, &fetch_style, &fetch_offset ); - - try { - - CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { - throw ss::SSException(); - } - - if( class_name_z ) { - - CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - throw ss::SSException(); - } - class_name = Z_STRVAL( *class_name_z ); - class_name_len = Z_STRLEN( *class_name_z ); - zend_str_tolower( class_name, class_name_len ); - } - - if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) { - THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - // fetch the data - bool result = core_sqlsrv_fetch( stmt, fetch_style, fetch_offset TSRMLS_CC ); - if( !result ) { - RETURN_NULL(); - } - - fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ TSRMLS_CC ); - properties_ht = Z_ARRVAL( retval_z ); - - // find the zend_class_entry of the class the user requested (stdClass by default) for use below - zend_class_entry* class_entry = NULL; - zend_string* class_name_str_z = zend_string_init( class_name, class_name_len, 0 ); - int zr = ( NULL != ( class_entry = zend_lookup_class( class_name_str_z TSRMLS_CC ))) ? SUCCESS : FAILURE; - zend_string_release( class_name_str_z ); - CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_BAD_CLASS, class_name ) { - throw ss::SSException(); - } - - // create an instance of the object with its default properties - // we pass NULL for the properties so that the object will be populated by its default properties - zr = object_and_properties_init( &retval_z, class_entry, NULL /*properties*/ ); - CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { - throw ss::SSException(); - } - - // merge in the "properties" (associative array) returned from the fetch doing this vice versa - // since putting properties_ht into object_and_properties_init and merging the default properties - // causes duplicate properties when the visibilities are different and also references the - // default parameters directly in the object, meaning the default property value is changed when - // the object's property is changed. - zend_merge_properties( &retval_z, properties_ht TSRMLS_CC ); - zend_hash_destroy( properties_ht ); - FREE_HASHTABLE( properties_ht ); - - // find and call the object's constructor - - // The header files (zend.h and zend_API.h) declare - // these functions and structures, so by working with those, we were able to - // develop this as a suitable snippet for calling constructors. Some observations: - // params must be an array of zval**, not a zval** to an array as we originally - // thought. Also, a constructor doesn't show up in the function table, but - // is put into the "magic methods" section of the class entry. - // - // The default values of the fci and fcic structures were determined by - // calling zend_fcall_info_init with a test callable. - - // if there is a constructor (e.g., stdClass doesn't have one) - if( class_entry->constructor ) { - - // take the parameters given as our last argument and put them into a sequential array - sqlsrv_malloc_auto_ptr params_m; - zval ctor_retval_z; - ZVAL_UNDEF( &ctor_retval_z ); - int num_params = 0; - - if ( ctor_params_z ) { - HashTable* ctor_params_ht = Z_ARRVAL( *ctor_params_z ); - num_params = zend_hash_num_elements( ctor_params_ht ); - params_m = reinterpret_cast( sqlsrv_malloc( num_params * sizeof( zval ) )); - - int i = 0; - zval* value_z = NULL; - ZEND_HASH_FOREACH_VAL( ctor_params_ht, value_z ) { - ZVAL_COPY_VALUE( ¶ms_m[i], value_z ); - zr = ( NULL != ¶ms_m[i] ) ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { - throw ss::SSException(); - } - i++; - } ZEND_HASH_FOREACH_END(); - } //if( !Z_ISUNDEF( ctor_params_z )) - - // call the constructor function itself. - zend_fcall_info fci; - zend_fcall_info_cache fcic; - - memset( &fci, 0, sizeof( fci )); - fci.size = sizeof( fci ); - fci.function_table = &( class_entry )->function_table; - ZVAL_UNDEF( &( fci.function_name ) ); - fci.retval = &ctor_retval_z; - fci.param_count = num_params; - fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred. - - fci.object = reinterpret_cast( &retval_z ); - - memset( &fcic, 0, sizeof( fcic )); - fcic.initialized = 1; - fcic.function_handler = class_entry->constructor; - fcic.calling_scope = class_entry; - - fci.object = reinterpret_cast( &retval_z ); - - zr = zend_call_function( &fci, &fcic TSRMLS_CC ); - CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { - throw ss::SSException(); - } - - } //if( class_entry->constructor ) - RETURN_ZVAL( &retval_z, 1, 1 ); - } - - catch( core::CoreException& ) { - - if( properties_ht != NULL ) { - - zend_hash_destroy( properties_ht ); - FREE_HASHTABLE( properties_ht ); - } - else if ( Z_TYPE( retval_z ) == IS_ARRAY ) { - zend_hash_destroy( Z_ARRVAL( retval_z )); - FREE_HASHTABLE( Z_ARRVAL( retval_z )); - } - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_fetch_object: Unknown exception caught." ); - } -} - - -// sqlsrv_has_rows( resource $stmt ) -// -// Parameters -// $stmt: The statement on which the targeted result set is active. -// -// Return Value -// Returns whether or not there are rows waiting to be processed. There are two scenarios -// for using a function like this: -// 1) To know if there are any actual rows, not just a result set (empty or not). Use sqlsrv_has_rows to determine this. -// The guarantee is that if sqlsrv_has_rows returns true immediately after a query, that sqlsrv_fetch_* will return at least -// one row of data. -// 2) To know if there is any sort of result set, empty or not, that has to be bypassed to get to something else, such as -// output parameters being returned. Use sqlsrv_num_fields > 0 to check if there is any result set that must be bypassed -// until sqlsrv_fetch returns NULL. -// The last caveat is that this function can still return FALSE if there is an error, which is fine since an error -// most likely means that there is no result data anyways. -// If this functions returs true one time, then it will return true even after the result set is exhausted -// (sqlsrv_fetch returns null) - -PHP_FUNCTION( sqlsrv_has_rows ) -{ - LOG_FUNCTION( "sqlsrv_has_rows" ); - ss_sqlsrv_stmt* stmt = NULL; - - try { - - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw ss::SSException(); - } - - if( !stmt->fetch_called ) { - - determine_stmt_has_rows( stmt TSRMLS_CC ); - } - - if( stmt->has_rows ) { - - RETURN_TRUE; - } - } - catch( core::CoreException& ) { - } - catch( ... ) { - - DIE( "sqlsrv_has_rows: Unknown exception caught." ); - } - - RETURN_FALSE; -} - - -// sqlsrv_send_stream_data( resource $stmt ) -// -// Sends data from parameter streams to the server. Up to eight kilobytes (8K) -// of data is sent with each call to sqlsrv_send_stream_data. -// By default, all stream data is sent to the server when a query is -// executed. If this default behavior is not changed, you do not have to use -// sqlsrv_send_stream_data to send stream data to the server. For information -// about changing the default behavior, see the Parameters section of -// sqlsrv_query or sqlsrv_prepare. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. -// -// Return Value -// true if there is more data to be sent. null, if all the data has been sent, -// and false if an error occurred - -PHP_FUNCTION( sqlsrv_send_stream_data ) -{ - sqlsrv_stmt* stmt = NULL; - - LOG_FUNCTION( "sqlsrv_send_stream_data" ); - - // get the statement resource that we've bound streams to - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // if everything was sent at execute time, just return that there is nothing more to send. - if( stmt->send_streams_at_exec ) { - RETURN_NULL(); - } - - // send the next packet - bool more = core_sqlsrv_send_stream_packet( stmt TSRMLS_CC ); - - // if more to send, return true - if( more ) { - RETURN_TRUE; - } - // otherwise we're done, so return null - else { - RETURN_NULL(); - } - } - catch( core::CoreException& ) { - - // return false if an error occurred - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_send_stream_data: Unknown exception caught." ); - } -} - - -// sqlsrv_get_field( resource $stmt, int $fieldIndex [, int $getAsType] ) -// -// Retrieves data from the specified field of the current row. Field data must -// be accessed in order. For example, data from the first field cannot be -// accessed after data from the second field has been accessed. -// -// Parameters -// $stmt: A statement resource corresponding to an executed statement. -// $fieldIndex: The index of the field to be retrieved. Indexes begin at zero. -// $getAsType [OPTIONAL]: A SQLSRV constant (SQLSRV_PHPTYPE) that determines -// the PHP data type for the returned data. For information about supported data -// types, see SQLSRV Constants (Microsoft Drivers for PHP for SQL Server). If no return -// type is specified, a default PHP type will be returned. For information about -// default PHP types, see Default PHP Data Types. For information about -// specifying PHP data types, see How to: Specify PHP Data Types. -// -// Return Value -// The field data. You can specify the PHP data type of the returned data by -// using the $getAsType parameter. If no return data type is specified, the -// default PHP data type will be returned. For information about default PHP -// types, see Default PHP Data Types. For information about specifying PHP data -// types, see How to: Specify PHP Data Types. - -PHP_FUNCTION( sqlsrv_get_field ) -{ - LOG_FUNCTION( "sqlsrv_get_field" ); - - ss_sqlsrv_stmt* stmt = NULL; - sqlsrv_phptype sqlsrv_php_type; - sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID; - void* field_value = NULL; - int field_index = -1; - SQLLEN field_len = -1; - zval retval_z; - ZVAL_UNDEF(&retval_z); - - // get the statement, the field index and the optional type - PROCESS_PARAMS( stmt, "rl|l", _FN_, 2, &field_index, &sqlsrv_php_type ); - - try { - - // validate that the field index is within range - int num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - - if( field_index < 0 || field_index >= num_cols ) { - THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - core_sqlsrv_get_field( stmt, field_index, sqlsrv_php_type, false, field_value, &field_len, false/*cache_field*/, - &sqlsrv_php_type_out TSRMLS_CC ); - convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z ); - sqlsrv_free( field_value ); - RETURN_ZVAL( &retval_z, 1, 1 ); - } - - catch( core::CoreException& ) { - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_get_field: Unknown exception caught." ); - } -} - - -// ** type functions. ** -// When specifying PHP and SQL Server types that take parameters, such as VARCHAR(2000), we use functions -// to match that notation and return a specially encoded integer that tells us what type and size/precision -// are. For PHP types specifically we munge the type and encoding into the integer. -// As is easily seen, since they are so similar, we delegate the actual encoding to helper methods defined -// below. - -// takes an encoding of the stream -PHP_FUNCTION( SQLSRV_PHPTYPE_STREAM ) -{ - type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STREAM ); -} - -// takes an encoding of the string -PHP_FUNCTION( SQLSRV_PHPTYPE_STRING ) -{ - type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STRING ); -} - -// takes the size of the binary field -PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_BINARY ); -} - -// takes the size of the char field -PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_CHAR ); -} - -// takes the precision and scale of the decimal field -PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL) -{ - type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_DECIMAL ); -} - -// takes the size of the nchar field -PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WCHAR ); -} - -// takes the precision and scale of the numeric field -PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC) -{ - type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_NUMERIC ); -} - -// takes the size (in characters, not bytes) of the nvarchar field -PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WVARCHAR ); -} - -// takes the size of the varbinary field -PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARBINARY ); -} - -// takes the size of the varchar field -PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR) -{ - type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARCHAR ); -} - -// mark parameters passed into sqlsrv_prepare as reference parameters so that they may be updated later in the -// script and subsequent sqlsrv_execute calls will use the new values. Marking them as references "pins" them -// to their memory location so that the buffer we give to ODBC can be relied on to be there. - -void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC ) -{ - SQLSRV_ASSERT( stmt->params_z == NULL, "mark_params_by_reference: parameters list shouldn't be present" ); - - if( params_z == NULL ) { - return; - } - - HashTable* params_ht = Z_ARRVAL_P( params_z ); - - zend_ulong index; - zend_string* key = NULL; - zval* value_z = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, value_z ) { - - // make sure it's an integer index - int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { - throw ss::SSException(); - } - - // This code turns parameters into references. Since the function declaration cannot - // pass array elements as references (without requiring & in front of each variable), - // we have to set the reference in each of the zvals ourselves. In the event of a - // parameter array (or sub array if you will) being passed in, we set the zval of the - // parameter array's first element. - - // if it's a sole variable - if ( Z_TYPE_P( value_z ) != IS_ARRAY ) { - ZVAL_MAKE_REF( value_z ); - } - else { - zval* var = NULL; - int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( value_z ), 0 ))) ? SUCCESS : FAILURE; - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { - throw ss::SSException(); - } - ZVAL_MAKE_REF( var ); - } - } ZEND_HASH_FOREACH_END(); - - // save our parameters for later. - Z_TRY_ADDREF_P( params_z ); - stmt->params_z = params_z; -} - -void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC ) -{ - // if there's nothing to do, just return - if( stmt->params_z == NULL ) { - return; - } - - try { - - stmt->free_param_data( TSRMLS_C ); - - stmt->executed = false; - - zval* params_z = stmt->params_z; - - HashTable* params_ht = Z_ARRVAL_P( params_z ); - - zend_ulong index = -1; - zend_string *key = NULL; - zval* param_z = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) { - zval* value_z = NULL; - SQLSMALLINT direction = SQL_PARAM_INPUT; - SQLSRV_ENCODING encoding = stmt->encoding(); - if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) { - encoding = stmt->conn->encoding(); - } - SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE; - SQLULEN column_size = SQLSRV_UNKNOWN_SIZE; - SQLSMALLINT decimal_digits = 0; - SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; - - // make sure it's an integer index - int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { - throw ss::SSException(); - } - - // if it's a parameter array - if( Z_TYPE_P( param_z ) == IS_ARRAY ) { - - zval* var = NULL; - int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( param_z ), 0 ))) ? SUCCESS : FAILURE; - CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { - throw ss::SSException(); - } - - // parse the parameter array that the user gave - parse_param_array( stmt, param_z, index, direction, php_out_type, encoding, sql_type, column_size, - decimal_digits TSRMLS_CC ); - value_z = var; - } - else { - value_z = param_z; - } - // bind the parameter - core_sqlsrv_bind_param( stmt, index, direction, value_z, php_out_type, encoding, sql_type, column_size, - decimal_digits TSRMLS_CC ); - - } ZEND_HASH_FOREACH_END(); - } - catch( core::CoreException& ) { - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - zval_ptr_dtor( stmt->params_z ); - sqlsrv_free( stmt->params_z ); - stmt->params_z = NULL; - throw; - } -} - -// sqlsrv_cancel( resource $stmt ) -// -// Cancels a statement. This means that any pending results for the statement -// are discarded. After this function is called, the statement can be -// re-executed if it was prepared with sqlsrv_prepare. Calling this function is -// not necessary if all the results associated with the statement have been -// consumed. -// -// Parameters -// $stmt: The statement to be canceled. -// -// Return Value -// A Boolean value: true if the operation was successful. Otherwise, false. - -PHP_FUNCTION( sqlsrv_cancel ) -{ - - LOG_FUNCTION( "sqlsrv_cancel" ); - ss_sqlsrv_stmt* stmt = NULL; - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - - try { - - // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); - - SQLRETURN r = SQLCancel( stmt->handle() ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw ss::SSException(); - } - - RETURN_TRUE; - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_cancel: Unknown exception caught." ); - } -} - -void __cdecl sqlsrv_stmt_dtor(zend_resource *rsrc TSRMLS_DC) -{ - LOG_FUNCTION( "sqlsrv_stmt_dtor" ); - - // get the structure - ss_sqlsrv_stmt *stmt = static_cast( rsrc->ptr ); - if( stmt->conn ) { - int zr = zend_hash_index_del( static_cast( stmt->conn )->stmts, stmt->conn_index ); - if( zr == FAILURE ) { - LOG( SEV_ERROR, "Failed to remove statement reference from the connection" ); - } - } - - stmt->~ss_sqlsrv_stmt(); - sqlsrv_free( stmt ); - rsrc->ptr = NULL; -} - -// sqlsrv_free_stmt( resource $stmt ) -// -// Frees all resources associated with the specified statement. The statement -// cannot be used again after this function has been called. -// -// Parameters -// $stmt: The statement to be closed. -// -// Return Value -// The Boolean value true unless the function is called with an invalid -// parameter. If the function is called with an invalid parameter, false is -// returned. -// -// Null is a valid parameter for this function. This allows the function to be -// called multiple times in a script. For example, if you free a statement in an -// error condition and free it again at the end of the script, the second call -// to sqlsrv_free_stmt will return true because the first call to -// sqlsrv_free_stmt (in the error condition) sets the statement resource to -// null. - -PHP_FUNCTION( sqlsrv_free_stmt ) -{ - - LOG_FUNCTION( "sqlsrv_free_stmt" ); - - zval* stmt_r = NULL; - ss_sqlsrv_stmt* stmt = NULL; - sqlsrv_context_auto_ptr error_ctx; - - reset_errors( TSRMLS_C ); - - try { - - // dummy context to pass to the error handler - error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); - - // take only the statement resource - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &stmt_r ) == FAILURE ) { - - // Check if it was a zval - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &stmt_r ); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - if( Z_TYPE_P( stmt_r ) == IS_NULL ) { - - RETURN_TRUE; - } - else { - - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - } - - // verify the resource so we know we're deleting a statement - stmt = static_cast(zend_fetch_resource_ex(stmt_r TSRMLS_CC, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor)); - - // if sqlsrv_free_stmt was called on an already closed statment then we just return success. - // zend_list_close sets the type of the closed statment to -1. - if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) { - RETURN_TRUE; - } - - if( stmt == NULL ) { - - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - - // delete the resource from Zend's master list, which will trigger the statement's destructor - if( zend_list_close( Z_RES_P(stmt_r) ) == FAILURE ) { - LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P( stmt_r )->handle); - } - - ZVAL_NULL( stmt_r ); - - RETURN_TRUE; - - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - - catch( ... ) { - - DIE( "sqlsrv_free_stmt: Unknown exception caught." ); - } -} - -void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) -{ - CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_STRING ), stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) { - throw ss::SSException(); - } - - const char* scroll_type = Z_STRVAL_P( value_z ); - unsigned long cursor_type = -1; - - // find which cursor type they would like and set the ODBC statement attribute as such - if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) { - - cursor_type = SQL_CURSOR_STATIC; - } - - else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_DYNAMIC )) { - - cursor_type = SQL_CURSOR_DYNAMIC; - } - - else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_KEYSET )) { - - cursor_type = SQL_CURSOR_KEYSET_DRIVEN; - } - - else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_FORWARD )) { - - cursor_type = SQL_CURSOR_FORWARD_ONLY; - } - - else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_BUFFERED )) { - - cursor_type = SQLSRV_CURSOR_BUFFERED; - } - - else { - - THROW_SS_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); - } - - core_sqlsrv_set_scrollable( stmt, cursor_type TSRMLS_CC ); - -} - -namespace { - -void convert_to_zval(sqlsrv_stmt* stmt, SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len, zval& out_zval) -{ - if ( in_val == NULL ) { - ZVAL_NULL( &out_zval); - return; - } - - switch (sqlsrv_php_type) { - - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - { - if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) { - ZVAL_LONG( &out_zval, *(static_cast( in_val ))); - } - else { - ZVAL_DOUBLE( &out_zval, *(static_cast( in_val ))); - } - break; - } - - case SQLSRV_PHPTYPE_STRING: - { - ZVAL_STRINGL( &out_zval, static_cast( in_val ), field_len); - break; - } - - case SQLSRV_PHPTYPE_STREAM: - { - out_zval = *( static_cast( in_val )); - stmt->active_stream = out_zval; - //addref here because deleting out_zval later will decrement the refcount - Z_TRY_ADDREF( out_zval ); - break; - } - case SQLSRV_PHPTYPE_DATETIME: - { - out_zval = *( static_cast( in_val )); - break; - } - - case SQLSRV_PHPTYPE_NULL: - ZVAL_NULL(&out_zval); - break; - - default: - DIE("Unknown php type"); - break; - } - return; -} - - -// put in the column size and scale/decimal digits of the sql server type -// these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx -bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, _Out_ SQLULEN* column_size, - _Out_ SQLSMALLINT* decimal_digits ) -{ - *decimal_digits = 0; - - switch( sqlsrv_type.typeinfo.type ) { - case SQL_BIGINT: - *column_size = 19; - break; - case SQL_BIT: - *column_size = 1; - break; - case SQL_INTEGER: - *column_size = 10; - break; - case SQL_SMALLINT: - *column_size = 5; - break; - case SQL_TINYINT: - *column_size = 3; - break; - case SQL_GUID: - *column_size = 36; - break; - case SQL_FLOAT: - *column_size = 53; - break; - case SQL_REAL: - *column_size = 24; - break; - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - *column_size = LONG_MAX; - break; - case SQL_WLONGVARCHAR: - *column_size = LONG_MAX >> 1; - break; - case SQL_SS_XML: - *column_size = SQL_SS_LENGTH_UNLIMITED; - break; - case SQL_BINARY: - case SQL_CHAR: - case SQL_VARBINARY: - case SQL_VARCHAR: - *column_size = sqlsrv_type.typeinfo.size; - if( *column_size == SQLSRV_SIZE_MAX_TYPE ) { - *column_size = SQL_SS_LENGTH_UNLIMITED; - } - else if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) { - *column_size = SQLSRV_INVALID_SIZE; - return false; - } - break; - case SQL_WCHAR: - case SQL_WVARCHAR: - *column_size = sqlsrv_type.typeinfo.size; - if( *column_size == SQLSRV_SIZE_MAX_TYPE ) { - *column_size = SQL_SS_LENGTH_UNLIMITED; - break; - } - if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) { - *column_size = SQLSRV_INVALID_SIZE; - return false; - } - break; - case SQL_DECIMAL: - case SQL_NUMERIC: - *column_size = sqlsrv_type.typeinfo.size; - *decimal_digits = sqlsrv_type.typeinfo.scale; - // if there was something wrong with the values given on type_and_precision_calc, these are set to invalid precision - if( *column_size == SQLSRV_INVALID_PRECISION || *decimal_digits == SQLSRV_INVALID_PRECISION ) { - *column_size = SQLSRV_INVALID_SIZE; - return false; - } - break; - // this can represent one of three data types: smalldatetime, datetime, and datetime2 - // we present the largest for the version and let SQL Server downsize it - case SQL_TYPE_TIMESTAMP: - *column_size = sqlsrv_type.typeinfo.size; - *decimal_digits = sqlsrv_type.typeinfo.scale; - break; - case SQL_SS_TIMESTAMPOFFSET: - *column_size = 34; - *decimal_digits = 7; - break; - case SQL_TYPE_DATE: - *column_size = 10; - *decimal_digits = 0; - break; - case SQL_SS_TIME2: - *column_size = 16; - *decimal_digits = 7; - break; - default: - // an invalid sql type should have already been dealt with, so we assert here. - DIE( "Trying to determine column size for an invalid type. Type should have already been verified." ); - return false; - } - - return true; -} - - -// given a SQL Server type, return a sqlsrv php type -sqlsrv_phptype determine_sqlsrv_php_type( ss_sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ) -{ - sqlsrv_phptype sqlsrv_phptype; - sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; - - switch( sql_type ) { - case SQL_BIGINT: - case SQL_CHAR: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - case SQL_WCHAR: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - break; - case SQL_VARCHAR: - case SQL_WVARCHAR: - if( prefer_string || size != SQL_SS_LENGTH_UNLIMITED ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - } - break; - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; - break; - case SQL_BINARY: - case SQL_LONGVARBINARY: - case SQL_VARBINARY: - case SQL_SS_UDT: - if( prefer_string ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; - } - break; - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - if( prefer_string ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - } - break; - case SQL_FLOAT: - case SQL_REAL: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; - break; - case SQL_TYPE_DATE: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_TIME2: - case SQL_TYPE_TIMESTAMP: - { - ss_sqlsrv_conn* c = static_cast( stmt->conn ); - if( c->date_as_string ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); - } - else { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; - } - break; - } - default: - sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID; - break; - } - - // if an encoding hasn't been set for the statement, then use the connection's encoding - if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - sqlsrv_phptype.typeinfo.encoding = stmt->conn->encoding(); - } - - return sqlsrv_phptype; -} - - -// determine if a query returned any rows of data. It does this by actually fetching the first row -// (though not retrieving the data) and setting the has_rows flag in the stmt the fetch was successful. -// The return value simply states whether or not if an error occurred during the determination. -// (All errors are posted here before returning.) - -void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC ) -{ - SQLRETURN r = SQL_SUCCESS; - - if( stmt->fetch_called ) { - - return; - } - - // default condition - stmt->has_rows = false; - - // if there are no columns then there are no rows - if( core::SQLNumResultCols( stmt TSRMLS_CC ) == 0 ) { - - return; - } - - // if the statement is scrollable, our work is easier though less performant. We simply - // fetch the first row, and then roll the cursor back to be prior to the first row - if( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { - - r = stmt->current_results->fetch( SQL_FETCH_FIRST, 0 TSRMLS_CC ); - if( SQL_SUCCEEDED( r )) { - - stmt->has_rows = true; - CHECK_SQL_WARNING( r, stmt ); - // restore the cursor to its original position. - r = stmt->current_results->fetch( SQL_FETCH_ABSOLUTE, 0 TSRMLS_CC ); - SQLSRV_ASSERT(( r == SQL_NO_DATA ), "core_sqlsrv_has_rows: Should have scrolled the cursor to the beginning " - "of the result set." ); - } - } - else { - - // otherwise, we fetch the first row, but record that we did. sqlsrv_fetch checks this - // flag and simply skips the first fetch, knowing it was already done. It records its own - // flags to know if it should fetch on subsequent calls. - - r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); - if( SQL_SUCCEEDED( r )) { - - stmt->has_rows = true; - CHECK_SQL_WARNING( r, stmt ); - return; - } - } -} - -void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, zend_long fetch_type, _Out_ zval& fields, bool allow_empty_field_names - TSRMLS_DC ) -{ - void* field_value = NULL; - sqlsrv_phptype sqlsrv_php_type; - sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID; - - // make sure that the fetch type is legal - CHECK_CUSTOM_ERROR((fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, stmt->func()) { - throw ss::SSException(); - } - - // get the numer of columns in the result set - SQLSMALLINT num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); - - // if this is the first fetch in a new result set, then get the field names and - // store them off for successive fetches. - if(( fetch_type & SQLSRV_FETCH_ASSOC ) && stmt->fetch_field_names == NULL) { - - SQLSMALLINT field_name_len; - char field_name_temp[SS_MAXCOLNAMELEN + 1]; - sqlsrv_malloc_auto_ptr field_names; - field_names = static_cast( sqlsrv_malloc( num_cols * sizeof(sqlsrv_fetch_field_name))); - - for(int i = 0; i < num_cols; ++i) { - - core::SQLColAttribute(stmt, i + 1, SQL_DESC_NAME, field_name_temp, SS_MAXCOLNAMELEN + 1, &field_name_len, NULL - TSRMLS_CC); - field_names[i].name = static_cast( sqlsrv_malloc( field_name_len, sizeof(char), 1 )); - memcpy_s(( void* )field_names[i].name, ( field_name_len * sizeof( char )) ,field_name_temp, field_name_len); - field_names[i].name[field_name_len] = '\0'; // null terminate the field name since SQLColAttribute doesn't. - field_names[i].len = field_name_len + 1; - } - - stmt->fetch_field_names = field_names; - stmt->fetch_fields_count = num_cols; - field_names.transferred(); - } - - int zr = array_init( &fields ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } - - for( int i = 0; i < num_cols; ++i ) { - SQLLEN field_len = -1; - - core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/, - field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out TSRMLS_CC ); - - zval field; - ZVAL_UNDEF( &field ); - convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, field ); - sqlsrv_free( field_value ); - if( fetch_type & SQLSRV_FETCH_NUMERIC ) { - - zr = add_next_index_zval( &fields, &field ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } - } - - if( fetch_type & SQLSRV_FETCH_ASSOC ) { - - CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 1 && !allow_empty_field_names ), stmt, - SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) { - throw ss::SSException(); - } - - if( stmt->fetch_field_names[ i ].len > 1 || allow_empty_field_names ) { - - zr = add_assoc_zval( &fields, stmt->fetch_field_names[i].name, &field ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } - } - } - //only addref when the fetch_type is BOTH because this is the only case when fields(hashtable) - //has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because - //fields now only has 1 element pointing to field and we want the ref count to be only 1 - if (fetch_type == SQLSRV_FETCH_BOTH) { - Z_TRY_ADDREF(field); - } - } //for loop - -} - -void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, - _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) - -{ - zval* var_or_val = NULL; - zval* temp = NULL; - HashTable* param_ht = Z_ARRVAL_P( param_array ); - sqlsrv_sqltype sqlsrv_sql_type; - - try { - - bool php_type_param_was_null = true; - bool sql_type_param_was_null = true; - - php_out_type = SQLSRV_PHPTYPE_INVALID; - encoding = SQLSRV_ENCODING_INVALID; - - // handle the array parameters that contain the value/var, direction, php_type, sql_type - zend_hash_internal_pointer_reset( param_ht ); - if( zend_hash_has_more_elements( param_ht ) == FAILURE || - (var_or_val = zend_hash_get_current_data(param_ht)) == NULL) { - - THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ); - } - - // if the direction is included, then use what they gave, otherwise INPUT is assumed - if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && - Z_TYPE_P( temp ) != IS_NULL ) { - - CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { - - throw ss::SSException(); - } - direction = static_cast(Z_LVAL_P( temp )); - CHECK_CUSTOM_ERROR( direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT, - stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { - throw ss::SSException(); - } - - CHECK_CUSTOM_ERROR(!Z_ISREF_P(var_or_val) && (direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1) { - throw ss::SSException(); - } - - } - else { - direction = SQL_PARAM_INPUT; - } - - // extract the php type and encoding from the 3rd parameter - if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && - Z_TYPE_P( temp ) != IS_NULL ) { - - php_type_param_was_null = false; - sqlsrv_phptype sqlsrv_phptype; - - CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1 ) { - - throw ss::SSException(); - } - - sqlsrv_phptype.value = Z_LVAL_P( temp ); - - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_phptype ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, - index + 1 ) { - - throw ss::SSException(); - } - - php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); - encoding = (SQLSRV_ENCODING) sqlsrv_phptype.typeinfo.encoding; - // if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established - // by the connection - if( encoding == SQLSRV_ENCODING_DEFAULT ) { - encoding = stmt->conn->encoding(); - } - } - // set default for php type and encoding if not supplied - else { - - php_type_param_was_null = true; - - if (Z_ISREF_P(var_or_val)){ - php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(Z_REFVAL_P(var_or_val))]; - } - else{ - php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(var_or_val)]; - } - encoding = stmt->encoding(); - if( encoding == SQLSRV_ENCODING_DEFAULT ) { - encoding = stmt->conn->encoding(); - } - } - - // get the server type, column size/precision and the decimal digits if provided - if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && - Z_TYPE_P( temp ) != IS_NULL ) { - - sql_type_param_was_null = false; - - CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1 ) { - - throw ss::SSException(); - } - - sqlsrv_sql_type.value = Z_LVAL_P( temp ); - - // since the user supplied this type, make sure it's valid - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_sqltype( sqlsrv_sql_type ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, - index + 1 ) { - - throw ss::SSException(); - } - - bool size_okay = determine_column_size_or_precision(stmt, sqlsrv_sql_type, &column_size, &decimal_digits); - - CHECK_CUSTOM_ERROR( !size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1 ) { - - throw ss::SSException(); - } - - sql_type = sqlsrv_sql_type.typeinfo.type; - } - // else the sql type and size are uknown, so tell the core layer to use its defaults - else { - - sql_type_param_was_null = true; - - sql_type = SQL_UNKNOWN_TYPE; - column_size = SQLSRV_UNKNOWN_SIZE; - decimal_digits = 0; - } - - // if the user for some reason provides an output parameter with a null phptype and a specified - // sql server type, infer the php type from the sql server type. - if( direction == SQL_PARAM_OUTPUT && php_type_param_was_null && !sql_type_param_was_null ) { - - int encoding; - sqlsrv_phptype sqlsrv_phptype; - - sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, (SQLUINTEGER)column_size, true ); - - // we DIE here since everything should have been validated already and to return the user an error - // for our own logic error would be confusing/misleading. - SQLSRV_ASSERT( sqlsrv_phptype.typeinfo.type != PHPTYPE_INVALID, "An invalid php type was returned with (supposed) " - "validated sql type and column_size" ); - - php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); - encoding = sqlsrv_phptype.typeinfo.encoding; - } - - // verify that the parameter is a valid output param type - if( direction == SQL_PARAM_OUTPUT ) { - - switch( php_out_type ) { - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_DATETIME: - case SQLSRV_PHPTYPE_STREAM: - THROW_CORE_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE ); - break; - default: - break; - } - - } - - } - catch( core::CoreException& ) { - - SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); - throw; - } -} - -bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) -{ - switch( type.typeinfo.type ) { - - case SQLSRV_PHPTYPE_NULL: - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - case SQLSRV_PHPTYPE_DATETIME: - return true; - case SQLSRV_PHPTYPE_STRING: - case SQLSRV_PHPTYPE_STREAM: - { - if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR - || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - return true; - } - break; - } - } - - return false; -} - -// return if the type is a valid sql server type not including -// size, precision or scale. Use determine_precision_and_scale for that. -bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype sql_type ) -{ - switch( sql_type.typeinfo.type ) { - case SQL_BIGINT: - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - case SQL_GUID: - case SQL_FLOAT: - case SQL_REAL: - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - case SQL_BINARY: - case SQL_CHAR: - case SQL_WCHAR: - case SQL_WVARCHAR: - case SQL_VARBINARY: - case SQL_VARCHAR: - case SQL_DECIMAL: - case SQL_NUMERIC: - case SQL_TYPE_TIMESTAMP: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - break; - default: - return false; - } - - return true; -} - -// verify an encoding given to type_and_encoding by looking through the list -// of standard encodings created at module initialization time -bool verify_and_set_encoding( const char* encoding_string, _Out_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ) -{ - void* encoding_temp = NULL; - zend_ulong index = -1; - zend_string* key = NULL; - ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, encoding_temp ) { - if ( !encoding_temp ) { - DIE( "Fatal: Error retrieving encoding from encoding hash table." ); - } - sqlsrv_encoding* encoding = reinterpret_cast( encoding_temp ); - encoding_temp = NULL; - if( !stricmp( encoding_string, encoding->iana )) { - phptype_encoding.typeinfo.encoding = encoding->code_page; - return true; - } - } ZEND_HASH_FOREACH_END(); - - return false; -} - -// called when one of the SQLSRV_SQLTYPE type functions is called. Encodes the type and size -// into a sqlsrv_sqltype bit fields (see php_sqlsrv.h). -void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) -{ - char* size_p = NULL; - size_t size_len = 0; - long size = 0; - - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &size_p, &size_len ) == FAILURE ) { - - return; - } - - if( !strnicmp( "max", size_p, sizeof( "max" ) / sizeof(char)) ) { - size = SQLSRV_SIZE_MAX_TYPE; - } - else { - _set_errno( 0 ); // reset errno for atol - size = atol( size_p ); - if( errno != 0 ) { - size = SQLSRV_INVALID_SIZE; - } - } - - int max_size = SQL_SERVER_MAX_FIELD_SIZE; - // size is actually the number of characters, not the number of bytes, so if they ask for a - // 2 byte per character type, then we half the maximum size allowed. - if( type == SQL_WVARCHAR || type == SQL_WCHAR ) { - max_size >>= 1; - } - - if( size > max_size || size < SQLSRV_SIZE_MAX_TYPE || size == 0 ) { - LOG( SEV_ERROR, "invalid size. size must be > 0 and <= %1!d! characters or 'max'", max_size ); - size = SQLSRV_INVALID_SIZE; - } - - sqlsrv_sqltype sql_type; - sql_type.typeinfo.type = type; - sql_type.typeinfo.size = size; - sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE; - - ZVAL_LONG( return_value, sql_type.value ); -} - -// called when the user gives SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC sql types as the type of the -// field. encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h) -void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) -{ - zend_long prec = SQLSRV_INVALID_PRECISION; - zend_long scale = SQLSRV_INVALID_SCALE; - - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &prec, &scale ) == FAILURE ) { - - return; - } - - if( prec > SQL_SERVER_MAX_PRECISION ) { - LOG( SEV_ERROR, "Invalid precision. Precision can't be > 38" ); - prec = SQLSRV_INVALID_PRECISION; - } - - if( prec < 0 ) { - LOG( SEV_ERROR, "Invalid precision. Precision can't be negative" ); - prec = SQLSRV_INVALID_PRECISION; - } - - if( scale > prec ) { - LOG( SEV_ERROR, "Invalid scale. Scale can't be > precision" ); - scale = SQLSRV_INVALID_SCALE; - } - - sqlsrv_sqltype sql_type; - sql_type.typeinfo.type = type; - sql_type.typeinfo.size = prec; - sql_type.typeinfo.scale = scale; - - ZVAL_LONG( return_value, sql_type.value ); -} - -// common code for SQLSRV_PHPTYPE_STREAM and SQLSRV_PHPTYPE_STRING php types given as parameters. -// encodes the type and encoding into a sqlsrv_phptype structure (see php_sqlsrv.h) -void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ) -{ - - SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." ); - - char* encoding_param; - size_t encoding_param_len = 0; - - // set the default encoding values to invalid so that - // if the encoding isn't validated, it will return the invalid setting. - sqlsrv_phptype sqlsrv_php_type; - sqlsrv_php_type.typeinfo.type = type; - sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID; - - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &encoding_param, &encoding_param_len ) == FAILURE ) { - - ZVAL_LONG( return_value, sqlsrv_php_type.value ); - } - - if( !verify_and_set_encoding( encoding_param, sqlsrv_php_type TSRMLS_CC )) { - LOG( SEV_ERROR, "Invalid encoding for php type." ); - } - - ZVAL_LONG( return_value, sqlsrv_php_type.value ); -} - -} +//--------------------------------------------------------------------------------------------------------------------------------- +// File: stmt.cpp +// +// Contents: Routines that use statement handles +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +// *** header files *** +#include "php_sqlsrv.h" +#ifdef _WIN32 +#include +#endif // _WIN32 + +// +// *** internal variables and constants *** +// +// our resource descriptor assigned in minit +int ss_sqlsrv_stmt::descriptor = 0; +char* ss_sqlsrv_stmt::resource_name = static_cast("ss_sqlsrv_stmt"); // not const for a reason. see sqlsrv_stmt in php_sqlsrv.h + +namespace { + +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros +unsigned int current_log_subsystem = LOG_STMT; + +// constants used as invalid types for type errors +const zend_uchar PHPTYPE_INVALID = SQLSRV_PHPTYPE_INVALID; +const int SQLTYPE_INVALID = 0; +const int SQLSRV_INVALID_PRECISION = -1; +const SQLUINTEGER SQLSRV_INVALID_SIZE = (~1U); +const int SQLSRV_INVALID_SCALE = -1; +const int SQLSRV_SIZE_MAX_TYPE = -1; + +// constants for maximums in SQL Server +const int SQL_SERVER_MAX_FIELD_SIZE = 8000; +const int SQL_SERVER_MAX_PRECISION = 38; +const int SQL_SERVER_DEFAULT_PRECISION = 18; +const int SQL_SERVER_DEFAULT_SCALE = 0; + +// default class used when no class is specified by sqlsrv_fetch_object +const char STDCLASS_NAME[] = "stdclass"; +const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1; + +// map a Zend PHP type constant to our constant type +enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = { + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_NULL, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_INT, + SQLSRV_PHPTYPE_FLOAT, + SQLSRV_PHPTYPE_STRING, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_DATETIME, + SQLSRV_PHPTYPE_STREAM, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_INVALID, + SQLSRV_PHPTYPE_INVALID +}; + +// constant strings used for the field metadata results +// (char to avoid having to cast them where they are used) +namespace FieldMetaData { + +const char* NAME = "Name"; +const char* TYPE = "Type"; +const char* SIZE = "Size"; +const char* PREC = "Precision"; +const char* SCALE = "Scale"; +const char* NULLABLE = "Nullable"; + +} + +// warning message printed when a parameter variable is not passed by reference +const char SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF[] = "Variable parameter %d not passed by reference (prefaced with an &). " + "Variable parameters passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value. " + "For more information, see sqlsrv_prepare or sqlsrv_query in the API Reference section of the product documentation."; + +/* internal functions */ + +void convert_to_zval( sqlsrv_stmt* stmt, SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len, zval& out_zval ); + +void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, zend_long fetch_type, _Out_ zval& fields, bool allow_empty_field_names + TSRMLS_DC ); +bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, _Out_ SQLULEN* column_size, + _Out_ SQLSMALLINT* decimal_digits ); +sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ); +void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC ); +bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ); +bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype type ); +void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, + _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); +void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ); +void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); +void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ); +bool verify_and_set_encoding( const char* encoding_string, _Out_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ); + +} + +// query options for cursor types +namespace SSCursorTypes { + + const char QUERY_OPTION_SCROLLABLE_STATIC[] = "static"; + const char QUERY_OPTION_SCROLLABLE_DYNAMIC[] = "dynamic"; + const char QUERY_OPTION_SCROLLABLE_KEYSET[] = "keyset"; + const char QUERY_OPTION_SCROLLABLE_FORWARD[] = "forward"; + const char QUERY_OPTION_SCROLLABLE_BUFFERED[] = "buffered"; +} + +ss_sqlsrv_stmt::ss_sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) : + sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), + prepared( false ), + conn_index( -1 ), + params_z( NULL ), + fetch_field_names( NULL ), + fetch_fields_count ( 0 ) +{ + core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC ); +} + +ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) +{ + if( fetch_field_names != NULL ) { + + for( int i=0; i < fetch_fields_count; ++i ) { + + sqlsrv_free( fetch_field_names[ i ].name ); + } + sqlsrv_free( fetch_field_names ); + } + if( params_z ) { + zval_ptr_dtor( params_z ); + sqlsrv_free(params_z); + } +} + +// to be called whenever a new result set is created, such as after an +// execute or next_result. Resets the state variables and calls the subclass. +void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) +{ + if( fetch_field_names != NULL ) { + + for( int i=0; i < fetch_fields_count; ++i ) { + + sqlsrv_free( fetch_field_names[ i ].name ); + } + sqlsrv_free( fetch_field_names ); + } + + fetch_field_names = NULL; + fetch_fields_count = 0; + sqlsrv_stmt::new_result_set( TSRMLS_C ); +} + +// Returns a php type for a given sql type. Also sets the encoding wherever applicable. +sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) +{ + sqlsrv_phptype ss_phptype; + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + + switch( sql_type ) { + + case SQL_BIGINT: + case SQL_CHAR: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + case SQL_WCHAR: + case SQL_SS_VARIANT: + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + ss_phptype.typeinfo.encoding = this->conn->encoding(); + break; + + case SQL_VARCHAR: + case SQL_WVARCHAR: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + if( prefer_string_to_stream || size != SQL_SS_LENGTH_UNLIMITED ) { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + ss_phptype.typeinfo.encoding = this->conn->encoding(); + } + else { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + ss_phptype.typeinfo.encoding = this->conn->encoding(); + } + break; + + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + break; + + case SQL_BINARY: + case SQL_LONGVARBINARY: + case SQL_VARBINARY: + case SQL_SS_UDT: + if( prefer_string_to_stream ) { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + } + else { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + } + break; + + case SQL_FLOAT: + case SQL_REAL: + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + break; + + case SQL_TYPE_DATE: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_TIME2: + case SQL_TYPE_TIMESTAMP: + if( reinterpret_cast( this->conn )->date_as_string ) { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + ss_phptype.typeinfo.encoding = this->conn->encoding(); + } + else { + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; + } + break; + + default: + ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + break; + } + + return ss_phptype; +} + +// statement specific parameter proccessing. Uses the generic function specialised to return a statement +// resource. +#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \ + rsrc = process_params( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, ## __VA_ARGS__ );\ + if( rsrc == NULL ) { \ + RETURN_FALSE; \ + } + +// sqlsrv_execute( resource $stmt ) +// +// Executes a previously prepared statement. See sqlsrv_prepare for information +// on preparing a statement for execution. +// +// This function is ideal for executing a prepared statement multiple times with +// different parameter values. See the MSDN documentation +// +// Parameters +// $stmt: A resource specifying the statement to be executed. For more +// information about how to create a statement resource, see sqlsrv_prepare. +// +// Return Value +// A Boolean value: true if the statement was successfully executed. Otherwise, false. + +PHP_FUNCTION( sqlsrv_execute ) +{ + LOG_FUNCTION( "sqlsrv_execute" ); + + ss_sqlsrv_stmt* stmt = NULL; + + try { + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + CHECK_CUSTOM_ERROR(( !stmt->prepared ), stmt, SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED ) { + throw ss::SSException(); + } + + // prepare for the next execution by flushing anything remaining in the result set + if( stmt->executed ) { + + // to prepare to execute the next statement, we skip any remaining results (and skip parameter finalization too) + while( stmt->past_next_result_end == false ) { + + core_sqlsrv_next_result( stmt TSRMLS_CC, false, false ); + } + } + + // bind parameters before executing + bind_params( stmt TSRMLS_CC ); + + core_sqlsrv_execute( stmt TSRMLS_CC ); + + RETURN_TRUE; + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_execute: Unknown exception caught." ); + } +} + + +// sqlsrv_fetch( resource $stmt ) +// +// Makes the next row of a result set available for reading. Use +// sqlsrv_get_field to read fields of the row. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. A +// statement must be executed before results can be retrieved. For information +// on executing a statement, see sqlsrv_query and sqlsrv_execute. +// +// Return Value +// If the next row of the result set was successfully retrieved, true is +// returned. If there are no more results in the result set, null is +// returned. If an error occured, false is returned + +PHP_FUNCTION( sqlsrv_fetch ) +{ + LOG_FUNCTION( "sqlsrv_fetch" ); + + ss_sqlsrv_stmt* stmt = NULL; + // NOTE: zend_parse_parameter expect zend_long when the type spec is 'l',and core_sqlsrv_fetch expect short int + zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied + zend_long fetch_offset = 0; // default value for parameter if one isn't supplied + + // take only the statement resource + PROCESS_PARAMS( stmt, "r|ll", _FN_, 2, &fetch_style, &fetch_offset ); + + try { + + CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { + throw ss::SSException(); + } + + bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset TSRMLS_CC ); + if( !result ) { + RETURN_NULL(); + } + + RETURN_TRUE; + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_fetch: Unknown exception caught." ); + } +} + +// sqlsrv_fetch_array( resource $stmt [, int $fetchType] ) +// +// Retrieves the next row of data as an array. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. +// $fetchType [OPTIONAL]: A predefined constant. See SQLSRV_FETCH_TYPE in php_sqlsrv.h +// +// Return Value +// If a row of data is retrieved, an array is returned. If there are no more +// rows to retrieve, null is returned. If an error occurs, false is returned. +// Based on the value of the $fetchType parameter, the returned array can be a +// numerically indexed array, an associative array, or both. By default, an +// array with both numeric and associative keys is returned. The data type of a +// value in the returned array will be the default PHP data type. For +// information about default PHP data types, see Default PHP Data Types. + +PHP_FUNCTION( sqlsrv_fetch_array ) +{ + LOG_FUNCTION( "sqlsrv_fetch_array" ); + + ss_sqlsrv_stmt* stmt = NULL; + zend_long fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied + zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied + zend_long fetch_offset = 0; // default value for parameter if one isn't supplied + + // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), + // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset + PROCESS_PARAMS( stmt, "r|lll", _FN_, 3, &fetch_type, &fetch_style, &fetch_offset ); + + try { + + CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt, + SS_SQLSRV_ERROR_INVALID_FETCH_TYPE ) { + throw ss::SSException(); + } + + CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { + throw ss::SSException(); + } + + bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset TSRMLS_CC ); + if( !result ) { + RETURN_NULL(); + } + zval fields; + ZVAL_UNDEF( &fields ); + fetch_fields_common( stmt, fetch_type, fields, true /*allow_empty_field_names*/ TSRMLS_CC ); + RETURN_ARR( Z_ARRVAL( fields )); + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_fetch_array: Unknown exception caught." ); + } +} + +// sqlsrv_field_metadata( resource $stmt ) +// +// Retrieves metadata for the fields of a prepared statement. For information +// about preparing a statement, see sqlsrv_query or sqlsrv_prepare. Note that +// sqlsrv_field_metadata can be called on any prepared statement, pre- or +// post-execution. +// +// Parameters +// $stmt: A statement resource for which field metadata is sought. +// +// Return Value +// retrieve an array of metadata for the current result set on a statement. Each element of the +// array is a sub-array containing 5 elements accessed by key: +// name - name of the field. +// type - integer of the type. Can be compared against the SQLSRV_SQLTYPE constants. +// size - length of the field. null if the field uses precision and scale instead. +// precision - number of digits in a numeric field. null if the field uses size. +// scale - number of decimal digits in a numeric field. null if the field uses sizes. +// is_nullable - if the field may contain a NULL instead of a value +// false is returned if an error occurs retrieving the metadata + +PHP_FUNCTION( sqlsrv_field_metadata ) +{ + sqlsrv_stmt* stmt = NULL; + SQLSMALLINT num_cols = -1; + + LOG_FUNCTION( "sqlsrv_field_metadata" ); + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // get the number of fields in the resultset + num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + + zval result_meta_data; + ZVAL_UNDEF( &result_meta_data ); + core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC ); + + for( SQLSMALLINT f = 0; f < num_cols; ++f ) { + + sqlsrv_malloc_auto_ptr core_meta_data; + core_meta_data = core_sqlsrv_field_metadata( stmt, f TSRMLS_CC ); + + // initialize the array + zval field_array; + ZVAL_UNDEF( &field_array ); + core::sqlsrv_array_init( *stmt, &field_array TSRMLS_CC ); + + core::sqlsrv_add_assoc_string( *stmt, &field_array, FieldMetaData::NAME, + reinterpret_cast( core_meta_data->field_name.get() ), 0 TSRMLS_CC ); + + core_meta_data->field_name.transferred(); + + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC ); + + switch( core_meta_data->field_type ) { + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TYPE_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SCALE, core_meta_data->field_scale TSRMLS_CC ); + break; + case SQL_BIT: + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIGINT: + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); + break; + default: + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SIZE, core_meta_data->field_size TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::PREC TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); + break; + } + + // add the nullability to the array + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable + TSRMLS_CC ); + + // add this field's meta data to the result set meta data + core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC ); + + // always good to call destructor for allocations done through placement new operator. + core_meta_data->~field_meta_data(); + } + + // return our built collection and transfer ownership + RETURN_ZVAL(&result_meta_data, 1, 1); + + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_field_metadata: Unknown exception caught." ); + } +} + + +// sqlsrv_next_result( resource $stmt ) +// +// Makes the next result (result set, row count, or output parameter) of the +// specified statement active. The first (or only) result returned by a batch +// query or stored procedure is active without a call to sqlsrv_next_result. +// Any output parameters bound are only available after sqlsrv_next_result returns +// null as per ODBC Driver 11 for SQL Server specs: http://msdn.microsoft.com/en-us/library/ms403283.aspx +// +// Parameters +// $stmt: The executed statement on which the next result is made active. +// +// Return Value +// If the next result was successfully made active, the Boolean value true is +// returned. If an error occurred in making the next result active, false is +// returned. If no more results are available, null is returned. + +PHP_FUNCTION( sqlsrv_next_result ) +{ + LOG_FUNCTION( "sqlsrv_next_result" ); + + ss_sqlsrv_stmt* stmt = NULL; + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + core_sqlsrv_next_result( stmt TSRMLS_CC, true ); + + if( stmt->past_next_result_end ) { + + RETURN_NULL(); + } + + RETURN_TRUE; + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_next_result: Unknown exception caught." ); + } +} + +// sqlsrv_rows_affected( resource $stmt ) +// +// Returns the number of rows modified by the last statement executed. This +// function does not return the number of rows returned by a SELECT statement. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. +// +// Return Value +// An integer indicating the number of rows modified by the last executed +// statement. If no rows were modified, zero (0) is returned. If no information +// about the number of modified rows is available, negative one (-1) is +// returned. If an error occurred in retrieving the number of modified rows, +// false is returned. See SQLRowCount in the MSDN ODBC documentation. + +PHP_FUNCTION( sqlsrv_rows_affected ) +{ + LOG_FUNCTION( "sqlsrv_rows_affected" ); + ss_sqlsrv_stmt* stmt = NULL; + SQLLEN rows = -1; + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // make sure that the statement has already been executed. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw ss::SSException(); + } + + // make sure it is not scrollable. This function should only work for inserts, updates, and deletes, + // but this is the best we can do to enforce that. + CHECK_CUSTOM_ERROR( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY, stmt, SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE ) { + throw ss::SSException(); + } + + rows = stmt->current_results->row_count( TSRMLS_C ); + RETURN_LONG( rows ); + } + + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_rows_affected: Unknown exception caught." ); + } +} + +// sqlsrv_num_rows( resource $stmt ) +// +// Retrieves the number of rows in an active result set. The statement must +// have been created with the Scrollable attribute set to 'static'. +// +// Parameters +// $stmt: The statement on which the targeted result set is active. +// +// Return Value +// An integer value that represents the number of rows in the active result +// set. If an error occurs, the boolean value false is returned. + +PHP_FUNCTION( sqlsrv_num_rows ) +{ + LOG_FUNCTION( "sqlsrv_num_rows" ); + + ss_sqlsrv_stmt* stmt = NULL; + SQLLEN rows = -1; + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // make sure that the statement has already been executed. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw ss::SSException(); + } + + // make sure that the statement is scrollable and the cursor is not dynamic. + // if the cursor is dynamic, then the number of rows returned is always -1. + CHECK_CUSTOM_ERROR( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY || stmt->cursor_type == SQL_CURSOR_DYNAMIC, stmt, + SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE ) { + throw ss::SSException(); + } + + rows = stmt->current_results->row_count( TSRMLS_C ); + RETURN_LONG( rows ); + } + + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_num_rows: Unknown exception caught." ); + } +} + +// sqlsrv_num_fields( resource $stmt ) +// +// Retrieves the number of fields in an active result set. Note that +// sqlsrv_num_fields can be called on any prepared statement, before or after +// execution. +// +// Parameters +// $stmt: The statement on which the targeted result set is active. +// +// Return Value +// An integer value that represents the number of fields in the active result +// set. If an error occurs, the boolean value false is returned. + +PHP_FUNCTION( sqlsrv_num_fields ) +{ + LOG_FUNCTION( "sqlsrv_num_fields" ); + + ss_sqlsrv_stmt* stmt = NULL; + SQLSMALLINT fields = -1; + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // retrieve the number of columns from ODBC + fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + + RETURN_LONG( fields ); + } + + catch( ss::SSException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_num_fields: Unknown exception caught." ); + } +} + +// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams ]]) +// +// Retrieves the next row of data as a PHP object. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. +// +// $className [OPTIONAL]: A string specifying the name of the class to +// instantiate. If a value for the $className parameter is not specified, an +// instance of the PHP stdClass is instantiated. +// +// $ctorParams [OPTIONAL]: An array that contains values passed to the +// constructor of the class specified with the $className parameter. If the +// constructor of the specified class accepts parameter values, the $ctorParams +// parameter must be used when calling sqlsrv_fetch_object. +// +// Return Value +// A PHP object with properties that correspond to result set field +// names. Property values are populated with the corresponding result set field +// values. If the class specified with the optional $className parameter does +// not exist or if there is no active result set associated with the specified +// statement, false is returned. +// The data type of a value in the returned object will be the default PHP data +// type. For information on default PHP data types, see Default PHP Data Types. +// +// Remarks +// If a class name is specified with the optional $className parameter, an +// object of this class type is instantiated. If the class has properties whose +// names match the result set field names, the corresponding result set values +// are applied to the properties. If a result set field name does not match a +// class property, a property with the result set field name is added to the +// object and the result set value is applied to the property. For more +// information about calling sqlsrv_fetch_object with the $className parameter, +// see How to: Retrieve Data as an Object (Microsoft Drivers for PHP for SQL Server). +// +// If a field with no name is returned, sqlsrv_fetch_object will discard the +// field value and issue a warning. + +PHP_FUNCTION( sqlsrv_fetch_object ) +{ + LOG_FUNCTION( "sqlsrv_fetch_object" ); + + ss_sqlsrv_stmt* stmt = NULL; + zval* class_name_z = NULL; + zval* ctor_params_z = NULL; + zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied + zend_long fetch_offset = 0; // default value for parameter if one isn't supplied + + // stdClass is the name of the system's default base class in PHP + char* class_name = const_cast( STDCLASS_NAME ); + std::size_t class_name_len = STDCLASS_NAME_LEN; + HashTable* properties_ht = NULL; + zval retval_z; + ZVAL_UNDEF( &retval_z ); + + // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), + // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset + // we also use z! instead of s and a so that null may be passed in as valid values for + // the class name and ctor params + PROCESS_PARAMS( stmt, "r|z!z!ll", _FN_, 4, &class_name_z, &ctor_params_z, &fetch_style, &fetch_offset ); + + try { + + CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { + throw ss::SSException(); + } + + if( class_name_z ) { + + CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + throw ss::SSException(); + } + class_name = Z_STRVAL( *class_name_z ); + class_name_len = Z_STRLEN( *class_name_z ); + } + + if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) { + THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + // fetch the data + bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset TSRMLS_CC ); + if( !result ) { + RETURN_NULL(); + } + + fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ TSRMLS_CC ); + properties_ht = Z_ARRVAL( retval_z ); + + // find the zend_class_entry of the class the user requested (stdClass by default) for use below + zend_class_entry* class_entry = NULL; + zend_string* class_name_str_z = zend_string_init( class_name, class_name_len, 0 ); + int zr = ( NULL != ( class_entry = zend_lookup_class( class_name_str_z TSRMLS_CC ))) ? SUCCESS : FAILURE; + zend_string_release( class_name_str_z ); + CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_BAD_CLASS, class_name ) { + throw ss::SSException(); + } + + // create an instance of the object with its default properties + // we pass NULL for the properties so that the object will be populated by its default properties + zr = object_and_properties_init( &retval_z, class_entry, NULL /*properties*/ ); + CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { + throw ss::SSException(); + } + + // merge in the "properties" (associative array) returned from the fetch doing this vice versa + // since putting properties_ht into object_and_properties_init and merging the default properties + // causes duplicate properties when the visibilities are different and also references the + // default parameters directly in the object, meaning the default property value is changed when + // the object's property is changed. + zend_merge_properties( &retval_z, properties_ht TSRMLS_CC ); + zend_hash_destroy( properties_ht ); + FREE_HASHTABLE( properties_ht ); + + // find and call the object's constructor + + // The header files (zend.h and zend_API.h) declare + // these functions and structures, so by working with those, we were able to + // develop this as a suitable snippet for calling constructors. Some observations: + // params must be an array of zval**, not a zval** to an array as we originally + // thought. Also, a constructor doesn't show up in the function table, but + // is put into the "magic methods" section of the class entry. + // + // The default values of the fci and fcic structures were determined by + // calling zend_fcall_info_init with a test callable. + + // if there is a constructor (e.g., stdClass doesn't have one) + if( class_entry->constructor ) { + + // take the parameters given as our last argument and put them into a sequential array + sqlsrv_malloc_auto_ptr params_m; + zval ctor_retval_z; + ZVAL_UNDEF( &ctor_retval_z ); + int num_params = 0; + + if ( ctor_params_z ) { + HashTable* ctor_params_ht = Z_ARRVAL( *ctor_params_z ); + num_params = zend_hash_num_elements( ctor_params_ht ); + params_m = reinterpret_cast( sqlsrv_malloc( num_params * sizeof( zval ) )); + + int i = 0; + zval* value_z = NULL; + ZEND_HASH_FOREACH_VAL( ctor_params_ht, value_z ) { + zr = ( value_z ) ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { + throw ss::SSException(); + } + ZVAL_COPY_VALUE(¶ms_m[i], value_z); + i++; + } ZEND_HASH_FOREACH_END(); + } //if( !Z_ISUNDEF( ctor_params_z )) + + // call the constructor function itself. + zend_fcall_info fci; + zend_fcall_info_cache fcic; + + memset( &fci, 0, sizeof( fci )); + fci.size = sizeof( fci ); +#if PHP_VERSION_ID < 70100 + fci.function_table = &( class_entry )->function_table; +#endif + ZVAL_UNDEF( &( fci.function_name ) ); + fci.retval = &ctor_retval_z; + fci.param_count = num_params; + fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred. + + fci.object = Z_OBJ_P( &retval_z ); + + memset( &fcic, 0, sizeof( fcic )); + fcic.initialized = 1; + fcic.function_handler = class_entry->constructor; + fcic.calling_scope = class_entry; + + fcic.object = Z_OBJ_P( &retval_z ); + + zr = zend_call_function( &fci, &fcic TSRMLS_CC ); + CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { + throw ss::SSException(); + } + + } //if( class_entry->constructor ) + RETURN_ZVAL( &retval_z, 1, 1 ); + } + + catch( core::CoreException& ) { + + if( properties_ht != NULL ) { + + zend_hash_destroy( properties_ht ); + FREE_HASHTABLE( properties_ht ); + } + else if ( Z_TYPE( retval_z ) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( retval_z )); + FREE_HASHTABLE( Z_ARRVAL( retval_z )); + } + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_fetch_object: Unknown exception caught." ); + } +} + + +// sqlsrv_has_rows( resource $stmt ) +// +// Parameters +// $stmt: The statement on which the targeted result set is active. +// +// Return Value +// Returns whether or not there are rows waiting to be processed. There are two scenarios +// for using a function like this: +// 1) To know if there are any actual rows, not just a result set (empty or not). Use sqlsrv_has_rows to determine this. +// The guarantee is that if sqlsrv_has_rows returns true immediately after a query, that sqlsrv_fetch_* will return at least +// one row of data. +// 2) To know if there is any sort of result set, empty or not, that has to be bypassed to get to something else, such as +// output parameters being returned. Use sqlsrv_num_fields > 0 to check if there is any result set that must be bypassed +// until sqlsrv_fetch returns NULL. +// The last caveat is that this function can still return FALSE if there is an error, which is fine since an error +// most likely means that there is no result data anyways. +// If this functions returs true one time, then it will return true even after the result set is exhausted +// (sqlsrv_fetch returns null) + +PHP_FUNCTION( sqlsrv_has_rows ) +{ + LOG_FUNCTION( "sqlsrv_has_rows" ); + ss_sqlsrv_stmt* stmt = NULL; + + try { + + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw ss::SSException(); + } + + if( !stmt->has_rows && !stmt->fetch_called ) { + + determine_stmt_has_rows( stmt TSRMLS_CC ); + } + + if( stmt->has_rows ) { + + RETURN_TRUE; + } + } + catch( core::CoreException& ) { + } + catch( ... ) { + + DIE( "sqlsrv_has_rows: Unknown exception caught." ); + } + + RETURN_FALSE; +} + + +// sqlsrv_send_stream_data( resource $stmt ) +// +// Sends data from parameter streams to the server. Up to eight kilobytes (8K) +// of data is sent with each call to sqlsrv_send_stream_data. +// By default, all stream data is sent to the server when a query is +// executed. If this default behavior is not changed, you do not have to use +// sqlsrv_send_stream_data to send stream data to the server. For information +// about changing the default behavior, see the Parameters section of +// sqlsrv_query or sqlsrv_prepare. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. +// +// Return Value +// true if there is more data to be sent. null, if all the data has been sent, +// and false if an error occurred + +PHP_FUNCTION( sqlsrv_send_stream_data ) +{ + sqlsrv_stmt* stmt = NULL; + + LOG_FUNCTION( "sqlsrv_send_stream_data" ); + + // get the statement resource that we've bound streams to + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // if everything was sent at execute time, just return that there is nothing more to send. + if( stmt->send_streams_at_exec ) { + RETURN_NULL(); + } + + // send the next packet + bool more = core_sqlsrv_send_stream_packet( stmt TSRMLS_CC ); + + // if more to send, return true + if( more ) { + RETURN_TRUE; + } + // otherwise we're done, so return null + else { + RETURN_NULL(); + } + } + catch( core::CoreException& ) { + + // return false if an error occurred + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_send_stream_data: Unknown exception caught." ); + } +} + + +// sqlsrv_get_field( resource $stmt, int $fieldIndex [, int $getAsType] ) +// +// Retrieves data from the specified field of the current row. Field data must +// be accessed in order. For example, data from the first field cannot be +// accessed after data from the second field has been accessed. +// +// Parameters +// $stmt: A statement resource corresponding to an executed statement. +// $fieldIndex: The index of the field to be retrieved. Indexes begin at zero. +// $getAsType [OPTIONAL]: A SQLSRV constant (SQLSRV_PHPTYPE) that determines +// the PHP data type for the returned data. For information about supported data +// types, see SQLSRV Constants (Microsoft Drivers for PHP for SQL Server). If no return +// type is specified, a default PHP type will be returned. For information about +// default PHP types, see Default PHP Data Types. For information about +// specifying PHP data types, see How to: Specify PHP Data Types. +// +// Return Value +// The field data. You can specify the PHP data type of the returned data by +// using the $getAsType parameter. If no return data type is specified, the +// default PHP data type will be returned. For information about default PHP +// types, see Default PHP Data Types. For information about specifying PHP data +// types, see How to: Specify PHP Data Types. + +PHP_FUNCTION( sqlsrv_get_field ) +{ + LOG_FUNCTION( "sqlsrv_get_field" ); + + ss_sqlsrv_stmt* stmt = NULL; + sqlsrv_phptype sqlsrv_php_type; + sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID; + void* field_value = NULL; + zend_long field_index = -1; + SQLLEN field_len = -1; + zval retval_z; + ZVAL_UNDEF(&retval_z); + + // get the statement, the field index and the optional type + PROCESS_PARAMS( stmt, "rl|l", _FN_, 2, &field_index, &sqlsrv_php_type ); + + try { + + // validate that the field index is within range + int num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + + if( field_index < 0 || field_index >= num_cols ) { + THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + core_sqlsrv_get_field( stmt, static_cast( field_index ), sqlsrv_php_type, false, field_value, &field_len, false/*cache_field*/, + &sqlsrv_php_type_out TSRMLS_CC ); + convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z ); + sqlsrv_free( field_value ); + RETURN_ZVAL( &retval_z, 1, 1 ); + } + + catch( core::CoreException& ) { + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_get_field: Unknown exception caught." ); + } +} + + +// ** type functions. ** +// When specifying PHP and SQL Server types that take parameters, such as VARCHAR(2000), we use functions +// to match that notation and return a specially encoded integer that tells us what type and size/precision +// are. For PHP types specifically we munge the type and encoding into the integer. +// As is easily seen, since they are so similar, we delegate the actual encoding to helper methods defined +// below. + +// takes an encoding of the stream +PHP_FUNCTION( SQLSRV_PHPTYPE_STREAM ) +{ + type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STREAM ); +} + +// takes an encoding of the string +PHP_FUNCTION( SQLSRV_PHPTYPE_STRING ) +{ + type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STRING ); +} + +// takes the size of the binary field +PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_BINARY ); +} + +// takes the size of the char field +PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_CHAR ); +} + +// takes the precision and scale of the decimal field +PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL) +{ + type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_DECIMAL ); +} + +// takes the size of the nchar field +PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WCHAR ); +} + +// takes the precision and scale of the numeric field +PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC) +{ + type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_NUMERIC ); +} + +// takes the size (in characters, not bytes) of the nvarchar field +PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WVARCHAR ); +} + +// takes the size of the varbinary field +PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARBINARY ); +} + +// takes the size of the varchar field +PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR) +{ + type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARCHAR ); +} + +// mark parameters passed into sqlsrv_prepare as reference parameters so that they may be updated later in the +// script and subsequent sqlsrv_execute calls will use the new values. Marking them as references "pins" them +// to their memory location so that the buffer we give to ODBC can be relied on to be there. + +void mark_params_by_reference( ss_sqlsrv_stmt* stmt, zval* params_z TSRMLS_DC ) +{ + SQLSRV_ASSERT( stmt->params_z == NULL, "mark_params_by_reference: parameters list shouldn't be present" ); + + if( params_z == NULL ) { + return; + } + + HashTable* params_ht = Z_ARRVAL_P( params_z ); + + zend_ulong index; + zend_string* key = NULL; + zval* value_z = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, value_z ) { + + // make sure it's an integer index + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + + CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { + throw ss::SSException(); + } + + // This code turns parameters into references. Since the function declaration cannot + // pass array elements as references (without requiring & in front of each variable), + // we have to set the reference in each of the zvals ourselves. In the event of a + // parameter array (or sub array if you will) being passed in, we set the zval of the + // parameter array's first element. + + // if it's a sole variable + if ( Z_TYPE_P( value_z ) != IS_ARRAY ) { + ZVAL_MAKE_REF( value_z ); + } + else { + zval* var = NULL; + int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( value_z ), 0 ))) ? SUCCESS : FAILURE; + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { + throw ss::SSException(); + } + ZVAL_MAKE_REF( var ); + } + } ZEND_HASH_FOREACH_END(); + + // save our parameters for later. + Z_TRY_ADDREF_P( params_z ); + stmt->params_z = params_z; +} + +void bind_params( ss_sqlsrv_stmt* stmt TSRMLS_DC ) +{ + // if there's nothing to do, just return + if( stmt->params_z == NULL ) { + return; + } + + try { + + stmt->free_param_data( TSRMLS_C ); + + stmt->executed = false; + + zval* params_z = stmt->params_z; + + HashTable* params_ht = Z_ARRVAL_P( params_z ); + + zend_ulong index = -1; + zend_string *key = NULL; + zval* param_z = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) { + zval* value_z = NULL; + SQLSMALLINT direction = SQL_PARAM_INPUT; + SQLSRV_ENCODING encoding = stmt->encoding(); + if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) { + encoding = stmt->conn->encoding(); + } + SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE; + SQLULEN column_size = SQLSRV_UNKNOWN_SIZE; + SQLSMALLINT decimal_digits = 0; + SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; + + // make sure it's an integer index + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { + throw ss::SSException(); + } + + // if it's a parameter array + if( Z_TYPE_P( param_z ) == IS_ARRAY ) { + + zval* var = NULL; + int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( param_z ), 0 ))) ? SUCCESS : FAILURE; + CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) { + throw ss::SSException(); + } + + // parse the parameter array that the user gave + parse_param_array( stmt, param_z, index, direction, php_out_type, encoding, sql_type, column_size, + decimal_digits TSRMLS_CC ); + value_z = var; + } + else { + value_z = param_z; + } + // bind the parameter + core_sqlsrv_bind_param( stmt, index, direction, value_z, php_out_type, encoding, sql_type, column_size, + decimal_digits TSRMLS_CC ); + + } ZEND_HASH_FOREACH_END(); + } + catch( core::CoreException& ) { + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); + zval_ptr_dtor( stmt->params_z ); + sqlsrv_free( stmt->params_z ); + stmt->params_z = NULL; + throw; + } +} + +// sqlsrv_cancel( resource $stmt ) +// +// Cancels a statement. This means that any pending results for the statement +// are discarded. After this function is called, the statement can be +// re-executed if it was prepared with sqlsrv_prepare. Calling this function is +// not necessary if all the results associated with the statement have been +// consumed. +// +// Parameters +// $stmt: The statement to be canceled. +// +// Return Value +// A Boolean value: true if the operation was successful. Otherwise, false. + +PHP_FUNCTION( sqlsrv_cancel ) +{ + + LOG_FUNCTION( "sqlsrv_cancel" ); + ss_sqlsrv_stmt* stmt = NULL; + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + + try { + + // close the stream to release the resource + close_active_stream( stmt TSRMLS_CC ); + + SQLRETURN r = SQLCancel( stmt->handle() ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw ss::SSException(); + } + + RETURN_TRUE; + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_cancel: Unknown exception caught." ); + } +} + +void __cdecl sqlsrv_stmt_dtor(zend_resource *rsrc TSRMLS_DC) +{ + LOG_FUNCTION( "sqlsrv_stmt_dtor" ); + + // get the structure + ss_sqlsrv_stmt *stmt = static_cast( rsrc->ptr ); + if( stmt->conn ) { + int zr = zend_hash_index_del( static_cast( stmt->conn )->stmts, stmt->conn_index ); + if( zr == FAILURE ) { + LOG( SEV_ERROR, "Failed to remove statement reference from the connection" ); + } + } + + stmt->~ss_sqlsrv_stmt(); + sqlsrv_free( stmt ); + rsrc->ptr = NULL; +} + +// sqlsrv_free_stmt( resource $stmt ) +// +// Frees all resources associated with the specified statement. The statement +// cannot be used again after this function has been called. +// +// Parameters +// $stmt: The statement to be closed. +// +// Return Value +// The Boolean value true unless the function is called with an invalid +// parameter. If the function is called with an invalid parameter, false is +// returned. +// +// Null is a valid parameter for this function. This allows the function to be +// called multiple times in a script. For example, if you free a statement in an +// error condition and free it again at the end of the script, the second call +// to sqlsrv_free_stmt will return true because the first call to +// sqlsrv_free_stmt (in the error condition) sets the statement resource to +// null. + +PHP_FUNCTION( sqlsrv_free_stmt ) +{ + + LOG_FUNCTION( "sqlsrv_free_stmt" ); + + zval* stmt_r = NULL; + ss_sqlsrv_stmt* stmt = NULL; + sqlsrv_context_auto_ptr error_ctx; + + reset_errors( TSRMLS_C ); + + try { + + // dummy context to pass to the error handler + error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); + SET_FUNCTION_NAME( *error_ctx ); + + // take only the statement resource + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &stmt_r ) == FAILURE ) { + + // Check if it was a zval + int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &stmt_r ); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + if( Z_TYPE_P( stmt_r ) == IS_NULL ) { + + RETURN_TRUE; + } + else { + + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + } + + // verify the resource so we know we're deleting a statement + stmt = static_cast(zend_fetch_resource_ex(stmt_r TSRMLS_CC, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor)); + + // if sqlsrv_free_stmt was called on an already closed statment then we just return success. + // zend_list_close sets the type of the closed statment to -1. + if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) { + RETURN_TRUE; + } + + if( stmt == NULL ) { + + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + + // delete the resource from Zend's master list, which will trigger the statement's destructor + if( zend_list_close( Z_RES_P(stmt_r) ) == FAILURE ) { + LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P( stmt_r )->handle); + } + + ZVAL_NULL( stmt_r ); + + RETURN_TRUE; + + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + + catch( ... ) { + + DIE( "sqlsrv_free_stmt: Unknown exception caught." ); + } +} + +void stmt_option_scrollable:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_STRING ), stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) { + throw ss::SSException(); + } + + const char* scroll_type = Z_STRVAL_P( value_z ); + unsigned long cursor_type = -1; + + // find which cursor type they would like and set the ODBC statement attribute as such + if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) { + + cursor_type = SQL_CURSOR_STATIC; + } + + else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_DYNAMIC )) { + + cursor_type = SQL_CURSOR_DYNAMIC; + } + + else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_KEYSET )) { + + cursor_type = SQL_CURSOR_KEYSET_DRIVEN; + } + + else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_FORWARD )) { + + cursor_type = SQL_CURSOR_FORWARD_ONLY; + } + + else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_BUFFERED )) { + + cursor_type = SQLSRV_CURSOR_BUFFERED; + } + + else { + + THROW_SS_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); + } + + core_sqlsrv_set_scrollable( stmt, cursor_type TSRMLS_CC ); + +} + +namespace { + +void convert_to_zval(sqlsrv_stmt* stmt, SQLSRV_PHPTYPE sqlsrv_php_type, void* in_val, SQLLEN field_len, zval& out_zval) +{ + if ( in_val == NULL ) { + ZVAL_NULL( &out_zval); + return; + } + + switch (sqlsrv_php_type) { + + case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_FLOAT: + { + if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) { + ZVAL_LONG( &out_zval, *(static_cast( in_val ))); + } + else { + ZVAL_DOUBLE( &out_zval, *(static_cast( in_val ))); + } + break; + } + + case SQLSRV_PHPTYPE_STRING: + { + ZVAL_STRINGL( &out_zval, static_cast( in_val ), field_len); + break; + } + + case SQLSRV_PHPTYPE_STREAM: + { + out_zval = *( static_cast( in_val )); + stmt->active_stream = out_zval; + //addref here because deleting out_zval later will decrement the refcount + Z_TRY_ADDREF( out_zval ); + break; + } + case SQLSRV_PHPTYPE_DATETIME: + { + out_zval = *( static_cast( in_val )); + break; + } + + case SQLSRV_PHPTYPE_NULL: + ZVAL_NULL(&out_zval); + break; + + default: + DIE("Unknown php type"); + break; + } + return; +} + + +// put in the column size and scale/decimal digits of the sql server type +// these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx +// for SQL_VARBINARY, SQL_VARCHAR, and SQL_WLONGVARCHAR types, see https://msdn.microsoft.com/en-CA/library/ms187993.aspx +bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, sqlsrv_sqltype sqlsrv_type, _Out_ SQLULEN* column_size, + _Out_ SQLSMALLINT* decimal_digits ) +{ + *decimal_digits = 0; + + switch( sqlsrv_type.typeinfo.type ) { + case SQL_BIGINT: + *column_size = 19; + break; + case SQL_BIT: + *column_size = 1; + break; + case SQL_INTEGER: + *column_size = 10; + break; + case SQL_SMALLINT: + *column_size = 5; + break; + case SQL_TINYINT: + *column_size = 3; + break; + case SQL_GUID: + *column_size = 36; + break; + case SQL_FLOAT: + *column_size = 53; + break; + case SQL_REAL: + *column_size = 24; + break; + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + *column_size = INT_MAX; + break; + case SQL_WLONGVARCHAR: + *column_size = INT_MAX >> 1; + break; + case SQL_SS_XML: + *column_size = SQL_SS_LENGTH_UNLIMITED; + break; + case SQL_BINARY: + case SQL_CHAR: + case SQL_VARBINARY: + case SQL_VARCHAR: + *column_size = sqlsrv_type.typeinfo.size; + if( *column_size == SQLSRV_SIZE_MAX_TYPE ) { + *column_size = SQL_SS_LENGTH_UNLIMITED; + } + else if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) { + *column_size = SQLSRV_INVALID_SIZE; + return false; + } + break; + case SQL_WCHAR: + case SQL_WVARCHAR: + *column_size = sqlsrv_type.typeinfo.size; + if( *column_size == SQLSRV_SIZE_MAX_TYPE ) { + *column_size = SQL_SS_LENGTH_UNLIMITED; + break; + } + if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) { + *column_size = SQLSRV_INVALID_SIZE; + return false; + } + break; + case SQL_DECIMAL: + case SQL_NUMERIC: + *column_size = sqlsrv_type.typeinfo.size; + *decimal_digits = sqlsrv_type.typeinfo.scale; + // if there was something wrong with the values given on type_and_precision_calc, these are set to invalid precision + if( *column_size == SQLSRV_INVALID_PRECISION || *decimal_digits == SQLSRV_INVALID_PRECISION ) { + *column_size = SQLSRV_INVALID_SIZE; + return false; + } + break; + // this can represent one of three data types: smalldatetime, datetime, and datetime2 + // we present the largest for the version and let SQL Server downsize it + case SQL_TYPE_TIMESTAMP: + *column_size = sqlsrv_type.typeinfo.size; + *decimal_digits = sqlsrv_type.typeinfo.scale; + break; + case SQL_SS_TIMESTAMPOFFSET: + *column_size = 34; + *decimal_digits = 7; + break; + case SQL_TYPE_DATE: + *column_size = 10; + *decimal_digits = 0; + break; + case SQL_SS_TIME2: + *column_size = 16; + *decimal_digits = 7; + break; + default: + // an invalid sql type should have already been dealt with, so we assert here. + DIE( "Trying to determine column size for an invalid type. Type should have already been verified." ); + return false; + } + + return true; +} + + +// given a SQL Server type, return a sqlsrv php type +sqlsrv_phptype determine_sqlsrv_php_type( ss_sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ) +{ + sqlsrv_phptype sqlsrv_phptype; + sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + + switch( sql_type ) { + case SQL_BIGINT: + case SQL_CHAR: + case SQL_DECIMAL: + case SQL_GUID: + case SQL_NUMERIC: + case SQL_WCHAR: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + break; + case SQL_VARCHAR: + case SQL_WVARCHAR: + if( prefer_string || size != SQL_SS_LENGTH_UNLIMITED ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + } + break; + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + break; + case SQL_BINARY: + case SQL_LONGVARBINARY: + case SQL_VARBINARY: + case SQL_SS_UDT: + if( prefer_string ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY; + } + break; + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + if( prefer_string ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + } + break; + case SQL_FLOAT: + case SQL_REAL: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + break; + case SQL_TYPE_DATE: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_TIME2: + case SQL_TYPE_TIMESTAMP: + { + ss_sqlsrv_conn* c = static_cast( stmt->conn ); + if( c->date_as_string ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; + } + break; + } + default: + sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID; + break; + } + + // if an encoding hasn't been set for the statement, then use the connection's encoding + if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + sqlsrv_phptype.typeinfo.encoding = stmt->conn->encoding(); + } + + return sqlsrv_phptype; +} + + +// determine if a query returned any rows of data. It does this by actually fetching the first row +// (though not retrieving the data) and setting the has_rows flag in the stmt the fetch was successful. +// The return value simply states whether or not if an error occurred during the determination. +// (All errors are posted here before returning.) + +void determine_stmt_has_rows( ss_sqlsrv_stmt* stmt TSRMLS_DC ) +{ + SQLRETURN r = SQL_SUCCESS; + + if( stmt->fetch_called ) { + + return; + } + + // default condition + stmt->has_rows = false; + + // if there are no columns then there are no rows + if( core::SQLNumResultCols( stmt TSRMLS_CC ) == 0 ) { + + return; + } + + // if the statement is scrollable, our work is easier though less performant. We simply + // fetch the first row, and then roll the cursor back to be prior to the first row + if( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { + + r = stmt->current_results->fetch( SQL_FETCH_FIRST, 0 TSRMLS_CC ); + if( SQL_SUCCEEDED( r )) { + + stmt->has_rows = true; + CHECK_SQL_WARNING( r, stmt ); + // restore the cursor to its original position. + r = stmt->current_results->fetch( SQL_FETCH_ABSOLUTE, 0 TSRMLS_CC ); + SQLSRV_ASSERT(( r == SQL_NO_DATA ), "core_sqlsrv_has_rows: Should have scrolled the cursor to the beginning " + "of the result set." ); + } + } + else { + + // otherwise, we fetch the first row, but record that we did. sqlsrv_fetch checks this + // flag and simply skips the first fetch, knowing it was already done. It records its own + // flags to know if it should fetch on subsequent calls. + + r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); + if( SQL_SUCCEEDED( r )) { + + stmt->has_rows = true; + CHECK_SQL_WARNING( r, stmt ); + return; + } + } +} + +void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, zend_long fetch_type, _Out_ zval& fields, bool allow_empty_field_names + TSRMLS_DC ) +{ + void* field_value = NULL; + sqlsrv_phptype sqlsrv_php_type; + sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID; + + // make sure that the fetch type is legal + CHECK_CUSTOM_ERROR((fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, stmt->func()) { + throw ss::SSException(); + } + + // get the numer of columns in the result set + SQLSMALLINT num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); + + // if this is the first fetch in a new result set, then get the field names and + // store them off for successive fetches. + if(( fetch_type & SQLSRV_FETCH_ASSOC ) && stmt->fetch_field_names == NULL ) { + + SQLLEN field_name_len = 0; + SQLSMALLINT field_name_len_w = 0; + SQLWCHAR field_name_w[( SS_MAXCOLNAMELEN + 1 ) * 2 ] = { L'\0' }; + sqlsrv_malloc_auto_ptr field_name; + sqlsrv_malloc_auto_ptr field_names; + field_names = static_cast( sqlsrv_malloc( num_cols * sizeof( sqlsrv_fetch_field_name ))); + SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); + for( int i = 0; i < num_cols; ++i ) { + + core::SQLColAttributeW ( stmt, i + 1, SQL_DESC_NAME, field_name_w, ( SS_MAXCOLNAMELEN + 1 ) * 2, &field_name_len_w, NULL TSRMLS_CC ); + + //Conversion function expects size in characters + field_name_len_w = field_name_len_w / sizeof ( SQLWCHAR ); + bool converted = convert_string_from_utf16( encoding, field_name_w, + field_name_len_w, ( char** ) &field_name, field_name_len ); + + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { + throw core::CoreException(); + } + + field_names[i].name = static_cast( sqlsrv_malloc( field_name_len, sizeof( char ), 1 )); + memcpy_s(( void* )field_names[i].name, ( field_name_len * sizeof( char )) , ( void* ) field_name, field_name_len ); + field_names[i].name[field_name_len] = '\0'; // null terminate the field name since SQLColAttribute doesn't. + field_names[i].len = field_name_len + 1; + field_name.reset(); + } + + stmt->fetch_field_names = field_names; + stmt->fetch_fields_count = num_cols; + field_names.transferred(); + } + + int zr = array_init( &fields ); + CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { + throw ss::SSException(); + } + + for( int i = 0; i < num_cols; ++i ) { + SQLLEN field_len = -1; + + core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/, + field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out TSRMLS_CC ); + + zval field; + ZVAL_UNDEF( &field ); + convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, field ); + sqlsrv_free( field_value ); + if( fetch_type & SQLSRV_FETCH_NUMERIC ) { + + zr = add_next_index_zval( &fields, &field ); + CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { + throw ss::SSException(); + } + } + + if( fetch_type & SQLSRV_FETCH_ASSOC ) { + + CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 1 && !allow_empty_field_names ), stmt, + SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) { + throw ss::SSException(); + } + + if( stmt->fetch_field_names[ i ].len > 1 || allow_empty_field_names ) { + + zr = add_assoc_zval( &fields, stmt->fetch_field_names[i].name, &field ); + CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { + throw ss::SSException(); + } + } + } + //only addref when the fetch_type is BOTH because this is the only case when fields(hashtable) + //has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because + //fields now only has 1 element pointing to field and we want the ref count to be only 1 + if (fetch_type == SQLSRV_FETCH_BOTH) { + Z_TRY_ADDREF(field); + } + } //for loop + +} + +void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, + _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) + +{ + zval* var_or_val = NULL; + zval* temp = NULL; + HashTable* param_ht = Z_ARRVAL_P( param_array ); + sqlsrv_sqltype sqlsrv_sql_type; + + try { + + bool php_type_param_was_null = true; + bool sql_type_param_was_null = true; + + php_out_type = SQLSRV_PHPTYPE_INVALID; + encoding = SQLSRV_ENCODING_INVALID; + + // handle the array parameters that contain the value/var, direction, php_type, sql_type + zend_hash_internal_pointer_reset( param_ht ); + if( zend_hash_has_more_elements( param_ht ) == FAILURE || + (var_or_val = zend_hash_get_current_data(param_ht)) == NULL) { + + THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ); + } + + // if the direction is included, then use what they gave, otherwise INPUT is assumed + if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && + Z_TYPE_P( temp ) != IS_NULL ) { + + CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { + + throw ss::SSException(); + } + direction = static_cast(Z_LVAL_P( temp )); + CHECK_CUSTOM_ERROR( direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT, + stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) { + throw ss::SSException(); + } + + CHECK_CUSTOM_ERROR(!Z_ISREF_P(var_or_val) && (direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1) { + throw ss::SSException(); + } + + } + else { + direction = SQL_PARAM_INPUT; + } + + // extract the php type and encoding from the 3rd parameter + if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && + Z_TYPE_P( temp ) != IS_NULL ) { + + php_type_param_was_null = false; + sqlsrv_phptype sqlsrv_phptype; + + CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1 ) { + + throw ss::SSException(); + } + + sqlsrv_phptype.value = Z_LVAL_P( temp ); + + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_phptype ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + index + 1 ) { + + throw ss::SSException(); + } + + php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); + encoding = (SQLSRV_ENCODING) sqlsrv_phptype.typeinfo.encoding; + // if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established + // by the connection + if( encoding == SQLSRV_ENCODING_DEFAULT ) { + encoding = stmt->conn->encoding(); + } + } + // set default for php type and encoding if not supplied + else { + + php_type_param_was_null = true; + + if (Z_ISREF_P(var_or_val)){ + php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(Z_REFVAL_P(var_or_val))]; + } + else{ + php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(var_or_val)]; + } + encoding = stmt->encoding(); + if( encoding == SQLSRV_ENCODING_DEFAULT ) { + encoding = stmt->conn->encoding(); + } + } + + // get the server type, column size/precision and the decimal digits if provided + if (zend_hash_move_forward(param_ht) == SUCCESS && (temp = zend_hash_get_current_data(param_ht)) != NULL && + Z_TYPE_P( temp ) != IS_NULL ) { + + sql_type_param_was_null = false; + + CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1 ) { + + throw ss::SSException(); + } + + sqlsrv_sql_type.value = Z_LVAL_P( temp ); + + // since the user supplied this type, make sure it's valid + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_sqltype( sqlsrv_sql_type ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + index + 1 ) { + + throw ss::SSException(); + } + + bool size_okay = determine_column_size_or_precision(stmt, sqlsrv_sql_type, &column_size, &decimal_digits); + + CHECK_CUSTOM_ERROR( !size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1 ) { + + throw ss::SSException(); + } + + sql_type = sqlsrv_sql_type.typeinfo.type; + } + // else the sql type and size are uknown, so tell the core layer to use its defaults + else { + + sql_type_param_was_null = true; + + sql_type = SQL_UNKNOWN_TYPE; + column_size = SQLSRV_UNKNOWN_SIZE; + decimal_digits = 0; + } + + // if the user for some reason provides an output parameter with a null phptype and a specified + // sql server type, infer the php type from the sql server type. + if( direction == SQL_PARAM_OUTPUT && php_type_param_was_null && !sql_type_param_was_null ) { + + int encoding; + sqlsrv_phptype sqlsrv_phptype; + + sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, (SQLUINTEGER)column_size, true ); + + // we DIE here since everything should have been validated already and to return the user an error + // for our own logic error would be confusing/misleading. + SQLSRV_ASSERT( sqlsrv_phptype.typeinfo.type != PHPTYPE_INVALID, "An invalid php type was returned with (supposed) " + "validated sql type and column_size" ); + + php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); + encoding = sqlsrv_phptype.typeinfo.encoding; + } + + // verify that the parameter is a valid output param type + if( direction == SQL_PARAM_OUTPUT ) { + + switch( php_out_type ) { + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_DATETIME: + case SQLSRV_PHPTYPE_STREAM: + THROW_CORE_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE ); + break; + default: + break; + } + + } + + } + catch( core::CoreException& ) { + + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); + throw; + } +} + +bool is_valid_sqlsrv_phptype( sqlsrv_phptype type ) +{ + switch( type.typeinfo.type ) { + + case SQLSRV_PHPTYPE_NULL: + case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_FLOAT: + case SQLSRV_PHPTYPE_DATETIME: + return true; + case SQLSRV_PHPTYPE_STRING: + case SQLSRV_PHPTYPE_STREAM: + { + if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR + || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + return true; + } + break; + } + } + + return false; +} + +// return if the type is a valid sql server type not including +// size, precision or scale. Use determine_precision_and_scale for that. +bool is_valid_sqlsrv_sqltype( sqlsrv_sqltype sql_type ) +{ + switch( sql_type.typeinfo.type ) { + case SQL_BIGINT: + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + case SQL_GUID: + case SQL_FLOAT: + case SQL_REAL: + case SQL_LONGVARBINARY: + case SQL_LONGVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_SS_XML: + case SQL_BINARY: + case SQL_CHAR: + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_VARBINARY: + case SQL_VARCHAR: + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TYPE_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_SS_TIME2: + case SQL_SS_TIMESTAMPOFFSET: + break; + default: + return false; + } + + return true; +} + +// verify an encoding given to type_and_encoding by looking through the list +// of standard encodings created at module initialization time +bool verify_and_set_encoding( const char* encoding_string, _Out_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ) +{ + void* encoding_temp = NULL; + zend_ulong index = -1; + zend_string* key = NULL; + ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, encoding_temp ) { + if ( !encoding_temp ) { + DIE( "Fatal: Error retrieving encoding from encoding hash table." ); + } + sqlsrv_encoding* encoding = reinterpret_cast( encoding_temp ); + encoding_temp = NULL; + if( !stricmp( encoding_string, encoding->iana )) { + phptype_encoding.typeinfo.encoding = encoding->code_page; + return true; + } + } ZEND_HASH_FOREACH_END(); + + return false; +} + +// called when one of the SQLSRV_SQLTYPE type functions is called. Encodes the type and size +// into a sqlsrv_sqltype bit fields (see php_sqlsrv.h). +void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) +{ + char* size_p = NULL; + size_t size_len = 0; + int size = 0; + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &size_p, &size_len ) == FAILURE ) { + + return; + } + + if( !strnicmp( "max", size_p, sizeof( "max" ) / sizeof(char)) ) { + size = SQLSRV_SIZE_MAX_TYPE; + } + else { +#ifndef _WIN32 + errno = 0; +#else + _set_errno(0); // reset errno for atol +#endif // !_WIN32 + size = atol( size_p ); + if( errno != 0 ) { + size = SQLSRV_INVALID_SIZE; + } + } + + int max_size = SQL_SERVER_MAX_FIELD_SIZE; + // size is actually the number of characters, not the number of bytes, so if they ask for a + // 2 byte per character type, then we half the maximum size allowed. + if( type == SQL_WVARCHAR || type == SQL_WCHAR ) { + max_size >>= 1; + } + + if( size > max_size || size < SQLSRV_SIZE_MAX_TYPE || size == 0 ) { + LOG( SEV_ERROR, "invalid size. size must be > 0 and <= %1!d! characters or 'max'", max_size ); + size = SQLSRV_INVALID_SIZE; + } + + sqlsrv_sqltype sql_type; + sql_type.typeinfo.type = type; + sql_type.typeinfo.size = size; + sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE; + + ZVAL_LONG( return_value, sql_type.value ); +} + +// called when the user gives SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC sql types as the type of the +// field. encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h) +void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, int type ) +{ + zend_long prec = SQLSRV_INVALID_PRECISION; + zend_long scale = SQLSRV_INVALID_SCALE; + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &prec, &scale ) == FAILURE ) { + + return; + } + + if( prec > SQL_SERVER_MAX_PRECISION ) { + LOG( SEV_ERROR, "Invalid precision. Precision can't be > 38" ); + prec = SQLSRV_INVALID_PRECISION; + } + + if( prec < 0 ) { + LOG( SEV_ERROR, "Invalid precision. Precision can't be negative" ); + prec = SQLSRV_INVALID_PRECISION; + } + + if( scale > prec ) { + LOG( SEV_ERROR, "Invalid scale. Scale can't be > precision" ); + scale = SQLSRV_INVALID_SCALE; + } + + sqlsrv_sqltype sql_type; + sql_type.typeinfo.type = type; + sql_type.typeinfo.size = prec; + sql_type.typeinfo.scale = scale; + + ZVAL_LONG( return_value, sql_type.value ); +} + +// common code for SQLSRV_PHPTYPE_STREAM and SQLSRV_PHPTYPE_STRING php types given as parameters. +// encodes the type and encoding into a sqlsrv_phptype structure (see php_sqlsrv.h) +void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, int type ) +{ + + SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." ); + + char* encoding_param; + size_t encoding_param_len = 0; + + // set the default encoding values to invalid so that + // if the encoding isn't validated, it will return the invalid setting. + sqlsrv_phptype sqlsrv_php_type; + sqlsrv_php_type.typeinfo.type = type; + sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + + if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &encoding_param, &encoding_param_len ) == FAILURE ) { + + ZVAL_LONG( return_value, sqlsrv_php_type.value ); + } + + if( !verify_and_set_encoding( encoding_param, sqlsrv_php_type TSRMLS_CC )) { + LOG( SEV_ERROR, "Invalid encoding for php type." ); + } + + ZVAL_LONG( return_value, sqlsrv_php_type.value ); +} + +} diff --git a/sqlsrv/template.rc b/source/sqlsrv/template.rc similarity index 86% rename from sqlsrv/template.rc rename to source/sqlsrv/template.rc index c6a4f82a8..5d148c59c 100644 --- a/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -1,83 +1,83 @@ -//---------------------------------------------------------------------------------------------------------------------------------- -// File: template.rc -// -// Contents: Version resource -// -// Microsoft Drivers 4.0 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#ifdef APSTUDIO_INVOKED -# error dont edit with MSVC -#endif - -#include "winresrc.h" -#include "main/php_version.h" -#include "version.h" - -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -#ifndef THANKS_GUYS -# define THANKS_GUYS "" -#endif - -#ifdef WANT_LOGO -0 ICON win32\build\php.ico -#endif - -#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build -#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel -#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build) -#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel) - -//Version -VS_VERSION_INFO VERSIONINFO - FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION - PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_MMDD,0 - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_DLL - FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright � 2001-2016 The PHP Group. All rights reserved.\0" - VALUE "CompanyName", "Microsoft Corp.\0" - VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server (PDO Driver)\0" - VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD, SQLVERSION_REVISION) - VALUE "InternalName", FILE_NAME "\0" - VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" - VALUE "OriginalFilename", FILE_NAME "\0" - VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" - VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_MMDD) - VALUE "URL", "http://www.microsoft.com\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END - -#ifdef MC_INCLUDE -#include MC_INCLUDE -#endif - +//---------------------------------------------------------------------------------------------------------------------------------- +// File: template.rc +// +// Contents: Version resource +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#ifdef APSTUDIO_INVOKED +# error dont edit with MSVC +#endif + +#include "winresrc.h" +#include "main/php_version.h" +#include "shared/version.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifndef THANKS_GUYS +# define THANKS_GUYS "" +#endif + +#ifdef WANT_LOGO +0 ICON win32\build\php.ico +#endif + +#define XSTRVER4(maj, min, rel, build) #maj "." #min "." #rel "." #build +#define XSTRVER3(maj, min, rel) #maj "." #min "." #rel +#define STRVER4(maj, min, rel, build) XSTRVER4(maj, min, rel, build) +#define STRVER3(maj, min, rel) XSTRVER3(maj, min, rel) + +//Version +VS_VERSION_INFO VERSIONINFO + FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD + PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_RELEASE,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "This product includes PHP software that is freely available from http://www.php.net/software/. Copyright © 2001-2016 The PHP Group. All rights reserved.\0" + VALUE "CompanyName", "Microsoft Corp.\0" + VALUE "FileDescription", "Microsoft Drivers for PHP for SQL Server\0" + VALUE "FileVersion", STRVER4(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE, SQLVERSION_BUILD) + VALUE "InternalName", FILE_NAME "\0" + VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0" + VALUE "OriginalFilename", FILE_NAME "\0" + VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0" + VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_RELEASE) + VALUE "URL", "http://www.microsoft.com\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#ifdef MC_INCLUDE +#include MC_INCLUDE +#endif + diff --git a/sqlsrv/util.cpp b/source/sqlsrv/util.cpp similarity index 96% rename from sqlsrv/util.cpp rename to source/sqlsrv/util.cpp index 43cc265f8..28c060111 100644 --- a/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -1,923 +1,921 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: util.cpp -// -// Contents: Utility functions used by both connection or statement functions -// -// Comments: Mostly error handling and some type handling -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "php_sqlsrv.h" - -#include - -namespace { - -// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros -unsigned int current_log_subsystem = LOG_UTIL; - -// buffer used to hold a formatted log message prior to actually logging it. -const int LOG_MSG_SIZE = 2048; -char log_msg[ LOG_MSG_SIZE ]; - -// internal error that says that FormatMessage failed -SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; - -// *** internal functions *** -void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reported_chain, zval* ignored_chain, - bool warning TSRMLS_DC ); -bool ignore_warning( char* sql_state, int native_code TSRMLS_DC ); -bool handle_errors_and_warnings( sqlsrv_context& ctx, zval* reported_chain, zval* ignored_chain, logging_severity log_severity, - unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC ); - -int sqlsrv_merge_zend_hash_dtor( zval* dest TSRMLS_DC ); -bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ); - -} - -// List of all error messages -ss_error SS_ERRORS[] = { - - { - SS_SQLSRV_ERROR_INVALID_OPTION, - { IMSSP, (SQLCHAR*)"Invalid option %1!s! was passed to sqlsrv_connect.", -1, true } - }, - - // no equivalent to error 2 in 2.0 - // error 3 is superceded by -16 - - // these two share the same code since they are basically the same error. - { - SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, - { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be" - " escaped with another right brace (}}).", -4, false } - }, - - { - SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, - { IMSSP, (SQLCHAR*)"An unescaped right brace (}) was found in option %1!s!.", -4, true } - }, - - { - SQLSRV_ERROR_NO_DATA, - { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -5, true } - }, - - { - SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, - { IMSSP, (SQLCHAR*)"Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using " - "streams.", -6, false} - }, - - { - SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - { IMSSP, (SQLCHAR*)"An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be " - "specified as output parameters.", -7, false } - }, - - { - SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY, - { IMSSP, (SQLCHAR*)"An invalid connection option key type was received. Option key types must be strings.", -8, false } - }, - - { - SS_SQLSRV_ERROR_VAR_REQUIRED, - { IMSSP, (SQLCHAR*)"Parameter array %1!d! must have at least one value or variable.", -9, true } - }, - - { - SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, - { IMSSP, (SQLCHAR*)"An invalid fetch type was specified. SQLSRV_FETCH_NUMERIC, SQLSRV_FETCH_ARRAY and SQLSRV_FETCH_BOTH are acceptable values.", -10, false } - }, - - { - SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, - { IMSSP, (SQLCHAR*)"The statement must be executed before results can be retrieved.", -11, false } - }, - - { - SS_SQLSRV_ERROR_ALREADY_IN_TXN, - { IMSSP, (SQLCHAR*)"Cannot begin a transaction until the current transaction has been completed by calling either " - "sqlsrv_commit or sqlsrv_rollback.", -12, false } - }, - - { - SS_SQLSRV_ERROR_NOT_IN_TXN, - { IMSSP, (SQLCHAR*)"A transaction must be started by calling sqlsrv_begin_transaction before calling sqlsrv_commit or " - "sqlsrv_rollback.", -13, false } - }, - - { - SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, - { IMSSP, (SQLCHAR*)"An invalid parameter was passed to %1!s!.", -14, true } - }, - - { - SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, - { IMSSP, (SQLCHAR*)"An invalid direction for parameter %1!d! was specified. SQLSRV_PARAM_IN, SQLSRV_PARAM_OUT, and " - "SQLSRV_PARAM_INOUT are valid values.", -15, true } - }, - - { - SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, - { IMSSP, (SQLCHAR*)"An invalid PHP type for parameter %1!d! was specified.", -16, true } - }, - - { - SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, - { IMSSP, (SQLCHAR*)"An invalid SQL Server type for parameter %1!d! was specified.", -17, true } - }, - - { - SQLSRV_ERROR_FETCH_NOT_CALLED, - { IMSSP, (SQLCHAR*)"A row must be retrieved with sqlsrv_fetch before retrieving data with sqlsrv_get_field.", -18, false } - }, - - { - SQLSRV_ERROR_FIELD_INDEX_ERROR, - { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. " - "The sqlsrv_get_field function cannot retrieve field %1!d! because its index is less " - "than the index of a field that has already been retrieved (%2!d!).", -19, true } - }, - - { - SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, - { IMSSP, (SQLCHAR*)"The retrieval of the DateTime object failed.", -20, false } - }, - - // no equivalent to SQLSRV_ERROR_SERVER_INFO in 2.0 so -21 is skipped - - { - SQLSRV_ERROR_FETCH_PAST_END, - { IMSSP, (SQLCHAR*)"There are no more rows in the active result set. Since this result set is not scrollable, no more " - "data may be retrieved.", -22, false } - }, - - { - SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED, - { IMSSP, (SQLCHAR*)"A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute.", -23, false } - }, - - { - SQLSRV_ERROR_ZEND_HASH, - { IMSSP, (SQLCHAR*)"An error occurred while creating or accessing a Zend hash table.", -24, false } - }, - - { - SQLSRV_ERROR_ZEND_STREAM, - { IMSSP, (SQLCHAR*)"An error occurred while reading from a PHP stream.", -25, false } - }, - - { - SQLSRV_ERROR_NEXT_RESULT_PAST_END, - { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -26, false } - }, - - { - SQLSRV_ERROR_STREAM_CREATE, - { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -27, false } - }, - - { - SQLSRV_ERROR_NO_FIELDS, - { IMSSP, (SQLCHAR*)"The active result for the query contains no fields.", -28, false } - }, - - { - SS_SQLSRV_ERROR_ZEND_BAD_CLASS, - { IMSSP, (SQLCHAR*)"Failed to find class %1!s!.", -29, true } - }, - - { - SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, - { IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true } - }, - - { - SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, - { IMSSP, (SQLCHAR*)"An invalid size or precision for parameter %1!d! was specified.", -31, true } - }, - - { - SQLSRV_ERROR_INVALID_OPTION_KEY, - { IMSSP, (SQLCHAR*)"Option %1!s! is invalid.", -32, true } - }, - - // these three errors are returned for invalid options, so they are given the same number for compatibility with 1.1 - { - SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, - { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option SQLSRV_QUERY_TIMEOUT.", -33, true } - }, - - { - SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -33, true } - }, - - { - SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -33, true } - }, - - { - SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, - { IMSSP, (SQLCHAR*)"The type of output parameter %1!d! does not match the type specified by the SQLSRV_PHPTYPE_* constant." - " For output parameters, the type of the variable's current value must match the SQLSRV_PHPTYPE_* constant, or be NULL. " - "If the type is NULL, the PHP type of the output parameter is inferred from the SQLSRV_SQLTYPE_* constant.", -34, true } - }, - - { - SQLSRV_ERROR_INVALID_TYPE, - { IMSSP, (SQLCHAR*)"Invalid type", -35, false } - }, - - // 36-38 have no equivalent 2.0 errors - - { - SS_SQLSRV_ERROR_REGISTER_RESOURCE, - { IMSSP, (SQLCHAR*)"Registering the %1!s! resource failed.", -39, true } - }, - - { - SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*)"An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -40, true } - }, - - { - SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*)"An error occurred translating string for an output param to UTF-8: %1!s!", -41, true } - }, - - { - SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -42, true } - }, - - { - SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*)"An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -43, true } - }, - - { - SQLSRV_ERROR_MARS_OFF, - { IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. " - "To make the connection available for other queries, either fetch all results or cancel or free the statement. " - "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -44, false } - }, - - { - SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, - { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of " - "key/value pairs.", -45, false } - }, - - { - SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -46, true } - }, - - { - SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, - { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -47, true } - }, - - { - SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, - { IMSSP, (SQLCHAR*)"The encoding '%1!s!' is not a supported encoding for the CharacterSet connection option.", -48, true } - }, - - { - SQLSRV_ERROR_DRIVER_NOT_INSTALLED, - { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 or 13 for SQL Server. " - "Access the following URL to download the ODBC Driver 11 or 13 for SQL Server for %1!s!: " - "http://go.microsoft.com/fwlink/?LinkId=163712", -49, true } - }, - - { - SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, - { IMSSP, (SQLCHAR*)"This function only works with statements that have static or keyset scrollable cursors.", -50, false } - }, - - { - SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE, - { IMSSP, (SQLCHAR*)"This function only works with statements that are not scrollable.", -51, false } - }, - - // new error for 2.0, used here since 1.1 didn't have a -52 - { - SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, - { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -52, true } - }, - - { - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE, - { IMSSP, (SQLCHAR*)"The scroll type passed to sqlsrv_fetch, sqlsrv_fetch_array, or sqlsrv_fetch_object was not valid. " - "Please use one of the SQLSRV_SCROLL constants.", -53, false } - }, - - { - SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, - { IMSSP, (SQLCHAR*)"The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', " - "'keyset', 'forward', or 'buffered'.", -54, false } - }, - - { - SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, - { IMSSP, (SQLCHAR*)"Failed to retrieve the server version. Unable to continue.", -55, false } - }, - - { - SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, - { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -56, true } - }, - - { - SS_SQLSRV_ERROR_PARAM_INVALID_INDEX, - { IMSSP, (SQLCHAR*)"String keys are not allowed in parameters arrays.", -57, false } - }, - - { - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, - { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -58, true } - }, - { - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, - { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -59, true } - }, - { - SQLSRV_ERROR_INVALID_BUFFER_LIMIT, - { IMSSP, (SQLCHAR*) "Setting for " INI_BUFFERED_QUERY_LIMIT " was non-int or non-positive.", -60, false } - }, - { - SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, - { IMSSP, (SQLCHAR*)"Variable parameter %1!d! not passed by reference (prefaced with an &). " - "Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value." - , -61, true } - }, - - // internal warning definitions - { - SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, - { SSPWARN, (SQLCHAR*)"An empty field name was skipped by sqlsrv_fetch_object.", -100, false } - }, - - // terminate the list of errors/warnings - { UINT_MAX, {} } -}; - -sqlsrv_error_const* get_error_message( unsigned int sqlsrv_error_code ) { - - sqlsrv_error_const *error_message = NULL; - - int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr( g_ss_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; - if( zr == FAILURE ) { - DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); - } - - SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null"); - - return error_message; -} - -// Formats an error message and finally writes it to the php log. -void ss_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ) -{ - if(( severity & SQLSRV_G( log_severity )) && ( SQLSRV_G( current_subsystem ) & SQLSRV_G( log_subsystems ))) { - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); - - // if an error occurs for FormatMessage, we just output an internal error occurred. - if( rc == 0 ) { - SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); - std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); - } - - php_log_err( log_msg TSRMLS_CC ); - } -} - -bool ss_error_handler(sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args ) -{ - logging_severity severity = SEV_ERROR; - if( warning && !SQLSRV_G( warnings_return_as_errors )) { - severity = SEV_WARNING; - } - - return handle_errors_and_warnings( ctx, &SQLSRV_G( errors ), &SQLSRV_G( warnings ), severity, sqlsrv_error_code, warning, - print_args TSRMLS_CC ); -} - -// sqlsrv_errors( [int $errorsAndOrWarnings] ) -// -// Returns extended error and/or warning information about the last sqlsrv -// operation performed. -// -// The sqlsrv_errors function can return error and/or warning information by -// calling it with one of the following parameter values below. -// -// Parameters -// -// $errorsAndOrWarnings[OPTIONAL]: A predefined constant. This parameter can -// take one of the values listed: -// -// SQLSRV_ERR_ALL -// Errors and warnings generated on the last sqlsrv function call are returned. -// SQLSRV_ERR_ERRORS -// Errors generated on the last sqlsrv function call are returned. -// SQLSRV_ERR_WARNINGS -// Warnings generated on the last sqlsrv function call are returned. -// -// If no parameter value is supplied, SQLSRV_ERR_ALL is the default -// -// Return Value -// An array of arrays, or null. An example of an error returned: -// Array -// ( -// [0] => Array -// ( -// [0] => HYT00 -// [SQLSTATE] => HYT00 -// [1] => 0 -// [code] => 0 -// [2] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired -// [message] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired -// ) -// ) - -PHP_FUNCTION( sqlsrv_errors ) -{ - SQLSRV_UNUSED( execute_data ); - - zend_long flags = SQLSRV_ERR_ALL; - - LOG_FUNCTION( "sqlsrv_errors" ); - - if(( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) || - ( flags != SQLSRV_ERR_ALL && flags != SQLSRV_ERR_ERRORS && flags != SQLSRV_ERR_WARNINGS )) { - LOG( SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ ); - RETURN_FALSE; - } - int result; - zval err_z; - ZVAL_UNDEF( &err_z ); - result = array_init( &err_z ); - if( result == FAILURE ) { - RETURN_FALSE; - } - if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_ERRORS ) { - if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( errors ) TSRMLS_CC )) { - zval_ptr_dtor(&err_z); - RETURN_FALSE; - } - } - if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_WARNINGS ) { - if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( warnings ) TSRMLS_CC )) { - zval_ptr_dtor(&err_z); - RETURN_FALSE; - } - } - if( zend_hash_num_elements( Z_ARRVAL_P( &err_z )) == 0 ) { - zval_ptr_dtor(&err_z); - RETURN_NULL(); - } - RETURN_ZVAL( &err_z, 1, 1 ); -} - -// sqlsrv_configure( string $setting, mixed $value ) -// -// Changes the settings for error handling and logging options. -// -// Parameters -// $setting: The name of the setting to be configured. The possible implemented values are -// "WarningsReturnAsErrors", "LogSubsystems", and "LogSeverity". -// -// $value: The value to be applied to the setting specified in the $setting -// parameter. See MSDN or the MINIT function for possible values. -// -// Return Value -// If sqlsrv_configure is called with an unsupported setting or value, the -// function returns false. Otherwise, the function returns true. - -PHP_FUNCTION( sqlsrv_configure ) -{ - SQLSRV_UNUSED( execute_data ); - - LOG_FUNCTION( "sqlsrv_configure" ); - - char* option; - size_t option_len; - zval* value_z; - sqlsrv_context_auto_ptr error_ctx; - - RETVAL_FALSE; - - reset_errors( TSRMLS_C ); - - try { - - // dummy context to pass onto the error handler - error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); - - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "sz", &option, &option_len, &value_z ); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - // WarningsReturnAsErrors - if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { - - SQLSRV_G( warnings_return_as_errors ) = zend_is_true( value_z ) ? true : false; - LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); - RETURN_TRUE; - } - - // LogSeverity - else if( !stricmp( option, INI_LOG_SEVERITY )) { - - CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - zend_long severity_mask = Z_LVAL_P( value_z ); - // make sure they can't use 0 to shut off the masking in the severity - if( severity_mask < SEV_ALL || severity_mask == 0 || severity_mask > (SEV_NOTICE + SEV_ERROR + SEV_WARNING) ) { - RETURN_FALSE; - } - - SQLSRV_G( log_severity ) = static_cast( severity_mask ); - LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); - RETURN_TRUE; - } - - // LogSubsystems - else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { - - CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - zend_long subsystem_mask = Z_LVAL_P( value_z ); - - if( subsystem_mask < LOG_ALL || subsystem_mask > (LOG_INIT + LOG_CONN + LOG_STMT + LOG_UTIL) ) { - RETURN_FALSE; - } - - SQLSRV_G( log_subsystems ) = static_cast( subsystem_mask ); - LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); - RETURN_TRUE; - } - - else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) { - - CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) { - - throw ss::SSException(); - } - - zend_long buffered_query_limit = Z_LVAL_P( value_z ); - - CHECK_CUSTOM_ERROR( buffered_query_limit < 0, error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) { - - throw ss::SSException(); - } - - SQLSRV_G( buffered_query_limit ) = buffered_query_limit; - LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit )); - RETURN_TRUE; - } - - else { - - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_configure: Unknown exception caught." ); - } -} - - -// sqlsrv_get_config( string $setting ) -// -// Returns the current value of the specified configuration setting. -// -// Parameters -// $setting: The configuration setting for which the value is returned. For a -// list of configurable settings, see sqlsrv_configure. -// -// Return Value -// The value of the setting specified by the $setting parameter. If an invalid -// setting is specified, false is returned and an error is added to the error -// collection. Because false is a valid value for WarningsReturnAsErrors, to -// really determine if an error occurred, call sqlsrv_errors. - -PHP_FUNCTION( sqlsrv_get_config ) -{ - SQLSRV_UNUSED( execute_data ); - - char* option = NULL; - size_t option_len; - sqlsrv_context_auto_ptr error_ctx; - - LOG_FUNCTION( "sqlsrv_get_config" ); - - reset_errors( TSRMLS_C ); - - try { - - // dummy context to pass onto the error handler - error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); - - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &option, &option_len ); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { - - throw ss::SSException(); - } - - if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { - - ZVAL_BOOL( return_value, SQLSRV_G( warnings_return_as_errors )); - return; - } - else if( !stricmp( option, INI_LOG_SEVERITY )) { - - ZVAL_LONG( return_value, SQLSRV_G( log_severity )); - return; - } - else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { - - ZVAL_LONG( return_value, SQLSRV_G( log_subsystems )); - return; - } - else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) { - - ZVAL_LONG( return_value, SQLSRV_G( buffered_query_limit )); - return; - } - else { - - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); - } - } - catch( core::CoreException& ) { - - RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_get_config: Unknown exception caught." ); - } -} - - -namespace { - -void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reported_chain, zval* ignored_chain, - bool warning TSRMLS_DC ) -{ - - if( array_init( error_z ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - // sqlstate - zval temp; - ZVAL_UNDEF(&temp); - core::sqlsrv_zval_stringl( &temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE ); - //TODO: reference? - Z_TRY_ADDREF_P( &temp ); - if( add_next_index_zval( error_z, &temp ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - if( add_assoc_zval( error_z, "SQLSTATE", &temp ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - // native_code - if( add_next_index_long( error_z, error->native_code ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - if( add_assoc_long( error_z, "code", error->native_code ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - // native_message - ZVAL_UNDEF(&temp); - ZVAL_STRING( &temp, reinterpret_cast( error->native_message ) ); - //TODO: reference? - Z_TRY_ADDREF_P(&temp); - if( add_next_index_zval( error_z, &temp ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - if( add_assoc_zval( error_z, "message", &temp ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - - // If it is an error or if warning_return_as_errors is true than - // add the error or warning to the reported_chain. - if( !warning || SQLSRV_G( warnings_return_as_errors ) ) - { - // if the warning is part of the ignored warning list than - // add to the ignored chain if the ignored chain is not null. - if( warning && ignore_warning( reinterpret_cast(error->sqlstate), error->native_code TSRMLS_CC ) && - ignored_chain != NULL ) { - - if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - } - else { - - // It is either an error or a warning which should not be ignored. - if( add_next_index_zval( reported_chain, error_z ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - } - } - else - { - // It is a warning with warning_return_as_errors as false, so simply add it to the ignored_chain list - if( ignored_chain != NULL ) { - - if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } - } - } -} - -bool handle_errors_and_warnings( sqlsrv_context& ctx, zval* reported_chain, zval* ignored_chain, logging_severity log_severity, - unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC ) -{ - bool result = true; - bool errors_ignored = false; - size_t prev_reported_cnt = 0; - bool reported_chain_was_null = false; - bool ignored_chain_was_null = false; - int zr = SUCCESS; - zval error_z; - ZVAL_UNDEF(&error_z); - sqlsrv_error_auto_ptr error; - - // array of reported errors - if( Z_TYPE_P( reported_chain ) == IS_NULL ) { - - reported_chain_was_null = true; - zr = array_init( reported_chain ); - if( zr == FAILURE ) { - DIE( "Fatal error in handle_errors_and_warnings" ); - } - } - else { - prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_P( reported_chain )); - } - - // array of ignored errors - if( ignored_chain != NULL ) { - - if( Z_TYPE_P( ignored_chain ) == IS_NULL ) { - - ignored_chain_was_null = true; - zr = array_init( ignored_chain ); - if( zr == FAILURE ) { - DIE( "Fatal error in handle_errors_and_warnings" ); - } - } - } - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, log_severity TSRMLS_CC, print_args ); - copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC ); - } - - SQLSMALLINT record_number = 0; - do { - - result = core_sqlsrv_get_odbc_error( ctx, ++record_number, error, log_severity TSRMLS_CC ); - if( result ) { - copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC ); - } - } while( result ); - - // If it were a warning, we report that warnings where ignored except if warnings_return_as_errors - // was true and we added some warnings to the reported_chain. - if( warning ) { - - errors_ignored = true; - - if( SQLSRV_G( warnings_return_as_errors ) ) { - - if( zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) > prev_reported_cnt ) { - - // We actually added some errors - errors_ignored = false; - } - } - } - - // if the error array came in as NULL and didn't have anything added to it, return it as NULL - if( reported_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) == 0 ) { - zend_hash_destroy( Z_ARRVAL_P( reported_chain )); - FREE_HASHTABLE( Z_ARRVAL_P( reported_chain )); - ZVAL_NULL( reported_chain ); - } - if( ignored_chain != NULL && ignored_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( ignored_chain )) == 0 ) { - zend_hash_destroy( Z_ARRVAL_P( ignored_chain )); - FREE_HASHTABLE( Z_ARRVAL_P( ignored_chain )); - ZVAL_NULL( ignored_chain ); - } - - // If it was an error instead of a warning than we always return errors_ignored = false. - return errors_ignored; -} - -// return whether or not a warning should be ignored or returned as an error if WarningsReturnAsErrors is true -// see RINIT in init.cpp for information about which errors are ignored. -bool ignore_warning( char* sql_state, int native_code TSRMLS_DC ) -{ - zend_ulong index = -1; - zend_string* key = NULL; - void* error_temp = NULL; - - ZEND_HASH_FOREACH_KEY_PTR( g_ss_warnings_to_ignore_ht, index, key, error_temp ) { - sqlsrv_error* error = static_cast( error_temp ); - if (NULL == error) { - return false; - } - - if( !strncmp( reinterpret_cast( error->sqlstate ), sql_state, SQL_SQLSTATE_SIZE ) && - ( error->native_code == native_code || error->native_code == -1 )) { - return true; - } - } ZEND_HASH_FOREACH_END(); - - return false; -} - -int sqlsrv_merge_zend_hash_dtor( zval* dest TSRMLS_DC ) -{ - zval_ptr_dtor( dest ); - return ZEND_HASH_APPLY_REMOVE; -} - -// sqlsrv_merge_zend_hash -// merge a source hash into a dest hash table and return any errors. -bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ) -{ - if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" ); - if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" ); - - if( Z_TYPE_P( src_z ) == IS_NULL ) { - return true; - } - - HashTable* src_ht = Z_ARRVAL_P( src_z ); - zend_ulong index = -1; - zend_string* key = NULL; - zval* value_z = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( src_ht, index, key, value_z ) { - if ( !value_z ) { - zend_hash_apply( Z_ARRVAL_P(dest_z), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); - return false; - } - - int result = add_next_index_zval( dest_z, value_z ); - - if( result == FAILURE ) { - zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); - return false; - } - Z_TRY_ADDREF_P( value_z ); - } ZEND_HASH_FOREACH_END(); - - return true; -} - -} // namespace +//--------------------------------------------------------------------------------------------------------------------------------- +// File: util.cpp +// +// Contents: Utility functions used by both connection or statement functions +// +// Comments: Mostly error handling and some type handling +// +// Microsoft Drivers 4.1 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "php_sqlsrv.h" + +namespace { + +// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros +unsigned int current_log_subsystem = LOG_UTIL; + +// buffer used to hold a formatted log message prior to actually logging it. +const int LOG_MSG_SIZE = 2048; +char log_msg[ LOG_MSG_SIZE ]; + +// internal error that says that FormatMessage failed +SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; + +// *** internal functions *** +void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reported_chain, zval* ignored_chain, + bool warning TSRMLS_DC ); +bool ignore_warning( char* sql_state, int native_code TSRMLS_DC ); +bool handle_errors_and_warnings( sqlsrv_context& ctx, zval* reported_chain, zval* ignored_chain, logging_severity log_severity, + unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC ); + +int sqlsrv_merge_zend_hash_dtor( zval* dest TSRMLS_DC ); +bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ); + +} + +// List of all error messages +ss_error SS_ERRORS[] = { + + { + SS_SQLSRV_ERROR_INVALID_OPTION, + { IMSSP, (SQLCHAR*)"Invalid option %1!s! was passed to sqlsrv_connect.", -1, true } + }, + + // no equivalent to error 2 in 2.0 + // error 3 is superceded by -16 + + // these two share the same code since they are basically the same error. + { + SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED, + { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be" + " escaped with another right brace (}}).", -4, false } + }, + + { + SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, + { IMSSP, (SQLCHAR*)"An unescaped right brace (}) was found in option %1!s!.", -4, true } + }, + + { + SQLSRV_ERROR_NO_DATA, + { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -5, true } + }, + + { + SQLSRV_ERROR_STREAMABLE_TYPES_ONLY, + { IMSSP, (SQLCHAR*)"Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using " + "streams.", -6, false} + }, + + { + SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + { IMSSP, (SQLCHAR*)"An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be " + "specified as output parameters.", -7, false } + }, + + { + SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY, + { IMSSP, (SQLCHAR*)"An invalid connection option key type was received. Option key types must be strings.", -8, false } + }, + + { + SS_SQLSRV_ERROR_VAR_REQUIRED, + { IMSSP, (SQLCHAR*)"Parameter array %1!d! must have at least one value or variable.", -9, true } + }, + + { + SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, + { IMSSP, (SQLCHAR*)"An invalid fetch type was specified. SQLSRV_FETCH_NUMERIC, SQLSRV_FETCH_ARRAY and SQLSRV_FETCH_BOTH are acceptable values.", -10, false } + }, + + { + SQLSRV_ERROR_STATEMENT_NOT_EXECUTED, + { IMSSP, (SQLCHAR*)"The statement must be executed before results can be retrieved.", -11, false } + }, + + { + SS_SQLSRV_ERROR_ALREADY_IN_TXN, + { IMSSP, (SQLCHAR*)"Cannot begin a transaction until the current transaction has been completed by calling either " + "sqlsrv_commit or sqlsrv_rollback.", -12, false } + }, + + { + SS_SQLSRV_ERROR_NOT_IN_TXN, + { IMSSP, (SQLCHAR*)"A transaction must be started by calling sqlsrv_begin_transaction before calling sqlsrv_commit or " + "sqlsrv_rollback.", -13, false } + }, + + { + SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, + { IMSSP, (SQLCHAR*)"An invalid parameter was passed to %1!s!.", -14, true } + }, + + { + SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, + { IMSSP, (SQLCHAR*)"An invalid direction for parameter %1!d! was specified. SQLSRV_PARAM_IN, SQLSRV_PARAM_OUT, and " + "SQLSRV_PARAM_INOUT are valid values.", -15, true } + }, + + { + SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + { IMSSP, (SQLCHAR*)"An invalid PHP type for parameter %1!d! was specified.", -16, true } + }, + + { + SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + { IMSSP, (SQLCHAR*)"An invalid SQL Server type for parameter %1!d! was specified.", -17, true } + }, + + { + SQLSRV_ERROR_FETCH_NOT_CALLED, + { IMSSP, (SQLCHAR*)"A row must be retrieved with sqlsrv_fetch before retrieving data with sqlsrv_get_field.", -18, false } + }, + + { + SQLSRV_ERROR_FIELD_INDEX_ERROR, + { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. " + "The sqlsrv_get_field function cannot retrieve field %1!d! because its index is less " + "than the index of a field that has already been retrieved (%2!d!).", -19, true } + }, + + { + SQLSRV_ERROR_DATETIME_CONVERSION_FAILED, + { IMSSP, (SQLCHAR*)"The retrieval of the DateTime object failed.", -20, false } + }, + + // no equivalent to SQLSRV_ERROR_SERVER_INFO in 2.0 so -21 is skipped + + { + SQLSRV_ERROR_FETCH_PAST_END, + { IMSSP, (SQLCHAR*)"There are no more rows in the active result set. Since this result set is not scrollable, no more " + "data may be retrieved.", -22, false } + }, + + { + SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED, + { IMSSP, (SQLCHAR*)"A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute.", -23, false } + }, + + { + SQLSRV_ERROR_ZEND_HASH, + { IMSSP, (SQLCHAR*)"An error occurred while creating or accessing a Zend hash table.", -24, false } + }, + + { + SQLSRV_ERROR_ZEND_STREAM, + { IMSSP, (SQLCHAR*)"An error occurred while reading from a PHP stream.", -25, false } + }, + + { + SQLSRV_ERROR_NEXT_RESULT_PAST_END, + { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -26, false } + }, + + { + SQLSRV_ERROR_STREAM_CREATE, + { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -27, false } + }, + + { + SQLSRV_ERROR_NO_FIELDS, + { IMSSP, (SQLCHAR*)"The active result for the query contains no fields.", -28, false } + }, + + { + SS_SQLSRV_ERROR_ZEND_BAD_CLASS, + { IMSSP, (SQLCHAR*)"Failed to find class %1!s!.", -29, true } + }, + + { + SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, + { IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true } + }, + + { + SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, + { IMSSP, (SQLCHAR*)"An invalid size or precision for parameter %1!d! was specified.", -31, true } + }, + + { + SQLSRV_ERROR_INVALID_OPTION_KEY, + { IMSSP, (SQLCHAR*)"Option %1!s! is invalid.", -32, true } + }, + + // these three errors are returned for invalid options, so they are given the same number for compatibility with 1.1 + { + SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, + { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option SQLSRV_QUERY_TIMEOUT.", -33, true } + }, + + { + SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, + { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -33, true } + }, + + { + SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, + { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -33, true } + }, + + { + SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, + { IMSSP, (SQLCHAR*)"The type of output parameter %1!d! does not match the type specified by the SQLSRV_PHPTYPE_* constant." + " For output parameters, the type of the variable's current value must match the SQLSRV_PHPTYPE_* constant, or be NULL. " + "If the type is NULL, the PHP type of the output parameter is inferred from the SQLSRV_SQLTYPE_* constant.", -34, true } + }, + + { + SQLSRV_ERROR_INVALID_TYPE, + { IMSSP, (SQLCHAR*)"Invalid type", -35, false } + }, + + // 36-38 have no equivalent 2.0 errors + + { + SS_SQLSRV_ERROR_REGISTER_RESOURCE, + { IMSSP, (SQLCHAR*)"Registering the %1!s! resource failed.", -39, true } + }, + + { + SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -40, true } + }, + + { + SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating string for an output param to UTF-8: %1!s!", -41, true } + }, + + { + SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -42, true } + }, + + { + SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*)"An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -43, true } + }, + + { + SQLSRV_ERROR_MARS_OFF, + { IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. " + "To make the connection available for other queries, either fetch all results or cancel or free the statement. " + "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -44, false } + }, + + { + SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE, + { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of " + "key/value pairs.", -45, false } + }, + + { + SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -46, true } + }, + + { + SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, + { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -47, true } + }, + + { + SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, + { IMSSP, (SQLCHAR*)"The encoding '%1!s!' is not a supported encoding for the CharacterSet connection option.", -48, true } + }, + + { + SQLSRV_ERROR_DRIVER_NOT_INSTALLED, + { IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 or 13 for SQL Server. " + "Access the following URL to download the ODBC Driver 11 or 13 for SQL Server for %1!s!: " + "http://go.microsoft.com/fwlink/?LinkId=163712", -49, true } + }, + + { + SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, + { IMSSP, (SQLCHAR*)"This function only works with statements that have static or keyset scrollable cursors.", -50, false } + }, + + { + SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE, + { IMSSP, (SQLCHAR*)"This function only works with statements that are not scrollable.", -51, false } + }, + + // new error for 2.0, used here since 1.1 didn't have a -52 + { + SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, + { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -52, true } + }, + + { + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE, + { IMSSP, (SQLCHAR*)"The scroll type passed to sqlsrv_fetch, sqlsrv_fetch_array, or sqlsrv_fetch_object was not valid. " + "Please use one of the SQLSRV_SCROLL constants.", -53, false } + }, + + { + SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE, + { IMSSP, (SQLCHAR*)"The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', " + "'keyset', 'forward', or 'buffered'.", -54, false } + }, + + { + SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, + { IMSSP, (SQLCHAR*)"Failed to retrieve the server version. Unable to continue.", -55, false } + }, + + { + SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, + { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -56, true } + }, + + { + SS_SQLSRV_ERROR_PARAM_INVALID_INDEX, + { IMSSP, (SQLCHAR*)"String keys are not allowed in parameters arrays.", -57, false } + }, + + { + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, + { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -58, true } + }, + { + SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, + { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -59, true } + }, + { + SQLSRV_ERROR_INVALID_BUFFER_LIMIT, + { IMSSP, (SQLCHAR*) "Setting for " INI_BUFFERED_QUERY_LIMIT " was non-int or non-positive.", -60, false } + }, + { + SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, + { IMSSP, (SQLCHAR*)"Variable parameter %1!d! not passed by reference (prefaced with an &). " + "Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value." + , -61, true } + }, + + // internal warning definitions + { + SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, + { SSPWARN, (SQLCHAR*)"An empty field name was skipped by sqlsrv_fetch_object.", -100, false } + }, + + // terminate the list of errors/warnings + { UINT_MAX, {} } +}; + +sqlsrv_error_const* get_error_message( unsigned int sqlsrv_error_code ) { + + sqlsrv_error_const *error_message = NULL; + + int zr = (error_message = reinterpret_cast(zend_hash_index_find_ptr( g_ss_errors_ht, sqlsrv_error_code))) != NULL ? SUCCESS : FAILURE; + if( zr == FAILURE ) { + DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code ); + } + + SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null"); + + return error_message; +} + +// Formats an error message and finally writes it to the php log. +void ss_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args ) +{ + if(( severity & SQLSRV_G( log_severity )) && ( SQLSRV_G( current_subsystem ) & SQLSRV_G( log_subsystems ))) { + + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); + + // if an error occurs for FormatMessage, we just output an internal error occurred. + if( rc == 0 ) { + SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); + std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); + } + + php_log_err( log_msg TSRMLS_CC ); + } +} + +bool ss_error_handler(sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC, va_list* print_args ) +{ + logging_severity severity = SEV_ERROR; + if( warning && !SQLSRV_G( warnings_return_as_errors )) { + severity = SEV_WARNING; + } + + return handle_errors_and_warnings( ctx, &SQLSRV_G( errors ), &SQLSRV_G( warnings ), severity, sqlsrv_error_code, warning, + print_args TSRMLS_CC ); +} + +// sqlsrv_errors( [int $errorsAndOrWarnings] ) +// +// Returns extended error and/or warning information about the last sqlsrv +// operation performed. +// +// The sqlsrv_errors function can return error and/or warning information by +// calling it with one of the following parameter values below. +// +// Parameters +// +// $errorsAndOrWarnings[OPTIONAL]: A predefined constant. This parameter can +// take one of the values listed: +// +// SQLSRV_ERR_ALL +// Errors and warnings generated on the last sqlsrv function call are returned. +// SQLSRV_ERR_ERRORS +// Errors generated on the last sqlsrv function call are returned. +// SQLSRV_ERR_WARNINGS +// Warnings generated on the last sqlsrv function call are returned. +// +// If no parameter value is supplied, SQLSRV_ERR_ALL is the default +// +// Return Value +// An array of arrays, or null. An example of an error returned: +// Array +// ( +// [0] => Array +// ( +// [0] => HYT00 +// [SQLSTATE] => HYT00 +// [1] => 0 +// [code] => 0 +// [2] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired +// [message] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired +// ) +// ) + +PHP_FUNCTION( sqlsrv_errors ) +{ + SQLSRV_UNUSED( execute_data ); + + zend_long flags = SQLSRV_ERR_ALL; + + LOG_FUNCTION( "sqlsrv_errors" ); + + if(( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) || + ( flags != SQLSRV_ERR_ALL && flags != SQLSRV_ERR_ERRORS && flags != SQLSRV_ERR_WARNINGS )) { + LOG( SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ ); + RETURN_FALSE; + } + int result; + zval err_z; + ZVAL_UNDEF( &err_z ); + result = array_init( &err_z ); + if( result == FAILURE ) { + RETURN_FALSE; + } + if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_ERRORS ) { + if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( errors ) TSRMLS_CC )) { + zval_ptr_dtor(&err_z); + RETURN_FALSE; + } + } + if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_WARNINGS ) { + if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( warnings ) TSRMLS_CC )) { + zval_ptr_dtor(&err_z); + RETURN_FALSE; + } + } + if( zend_hash_num_elements( Z_ARRVAL_P( &err_z )) == 0 ) { + zval_ptr_dtor(&err_z); + RETURN_NULL(); + } + RETURN_ZVAL( &err_z, 1, 1 ); +} + +// sqlsrv_configure( string $setting, mixed $value ) +// +// Changes the settings for error handling and logging options. +// +// Parameters +// $setting: The name of the setting to be configured. The possible implemented values are +// "WarningsReturnAsErrors", "LogSubsystems", and "LogSeverity". +// +// $value: The value to be applied to the setting specified in the $setting +// parameter. See MSDN or the MINIT function for possible values. +// +// Return Value +// If sqlsrv_configure is called with an unsupported setting or value, the +// function returns false. Otherwise, the function returns true. + +PHP_FUNCTION( sqlsrv_configure ) +{ + SQLSRV_UNUSED( execute_data ); + + LOG_FUNCTION( "sqlsrv_configure" ); + + char* option; + size_t option_len; + zval* value_z; + sqlsrv_context_auto_ptr error_ctx; + + RETVAL_FALSE; + + reset_errors( TSRMLS_C ); + + try { + + // dummy context to pass onto the error handler + error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); + SET_FUNCTION_NAME( *error_ctx ); + + int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "sz", &option, &option_len, &value_z ); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + // WarningsReturnAsErrors + if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { + + SQLSRV_G( warnings_return_as_errors ) = zend_is_true( value_z ) ? true : false; + LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); + RETURN_TRUE; + } + + // LogSeverity + else if( !stricmp( option, INI_LOG_SEVERITY )) { + + CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + zend_long severity_mask = Z_LVAL_P( value_z ); + // make sure they can't use 0 to shut off the masking in the severity + if( severity_mask < SEV_ALL || severity_mask == 0 || severity_mask > (SEV_NOTICE + SEV_ERROR + SEV_WARNING) ) { + RETURN_FALSE; + } + + SQLSRV_G( log_severity ) = static_cast( severity_mask ); + LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); + RETURN_TRUE; + } + + // LogSubsystems + else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { + + CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + zend_long subsystem_mask = Z_LVAL_P( value_z ); + + if( subsystem_mask < LOG_ALL || subsystem_mask > (LOG_INIT + LOG_CONN + LOG_STMT + LOG_UTIL) ) { + RETURN_FALSE; + } + + SQLSRV_G( log_subsystems ) = static_cast( subsystem_mask ); + LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); + RETURN_TRUE; + } + + else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) { + + CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) { + + throw ss::SSException(); + } + + zend_long buffered_query_limit = Z_LVAL_P( value_z ); + + CHECK_CUSTOM_ERROR( buffered_query_limit <= 0, error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) { + + throw ss::SSException(); + } + + SQLSRV_G( buffered_query_limit ) = buffered_query_limit; + LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit )); + RETURN_TRUE; + } + + else { + + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_configure: Unknown exception caught." ); + } +} + + +// sqlsrv_get_config( string $setting ) +// +// Returns the current value of the specified configuration setting. +// +// Parameters +// $setting: The configuration setting for which the value is returned. For a +// list of configurable settings, see sqlsrv_configure. +// +// Return Value +// The value of the setting specified by the $setting parameter. If an invalid +// setting is specified, false is returned and an error is added to the error +// collection. Because false is a valid value for WarningsReturnAsErrors, to +// really determine if an error occurred, call sqlsrv_errors. + +PHP_FUNCTION( sqlsrv_get_config ) +{ + SQLSRV_UNUSED( execute_data ); + + char* option = NULL; + size_t option_len; + sqlsrv_context_auto_ptr error_ctx; + + LOG_FUNCTION( "sqlsrv_get_config" ); + + reset_errors( TSRMLS_C ); + + try { + + // dummy context to pass onto the error handler + error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); + SET_FUNCTION_NAME( *error_ctx ); + + int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &option, &option_len ); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { + + throw ss::SSException(); + } + + if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { + + ZVAL_BOOL( return_value, SQLSRV_G( warnings_return_as_errors )); + return; + } + else if( !stricmp( option, INI_LOG_SEVERITY )) { + + ZVAL_LONG( return_value, SQLSRV_G( log_severity )); + return; + } + else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { + + ZVAL_LONG( return_value, SQLSRV_G( log_subsystems )); + return; + } + else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) { + + ZVAL_LONG( return_value, SQLSRV_G( buffered_query_limit )); + return; + } + else { + + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); + } + } + catch( core::CoreException& ) { + + RETURN_FALSE; + } + catch( ... ) { + + DIE( "sqlsrv_get_config: Unknown exception caught." ); + } +} + + +namespace { + +void copy_error_to_zval( zval* error_z, sqlsrv_error_const* error, zval* reported_chain, zval* ignored_chain, + bool warning TSRMLS_DC ) +{ + + if( array_init( error_z ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + // sqlstate + zval temp; + ZVAL_UNDEF(&temp); + core::sqlsrv_zval_stringl( &temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE ); + //TODO: reference? + Z_TRY_ADDREF_P( &temp ); + if( add_next_index_zval( error_z, &temp ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + if( add_assoc_zval( error_z, "SQLSTATE", &temp ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + // native_code + if( add_next_index_long( error_z, error->native_code ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + if( add_assoc_long( error_z, "code", error->native_code ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + // native_message + ZVAL_UNDEF(&temp); + ZVAL_STRING( &temp, reinterpret_cast( error->native_message ) ); + //TODO: reference? + Z_TRY_ADDREF_P(&temp); + if( add_next_index_zval( error_z, &temp ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + if( add_assoc_zval( error_z, "message", &temp ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + + // If it is an error or if warning_return_as_errors is true than + // add the error or warning to the reported_chain. + if( !warning || SQLSRV_G( warnings_return_as_errors ) ) + { + // if the warning is part of the ignored warning list than + // add to the ignored chain if the ignored chain is not null. + if( warning && ignore_warning( reinterpret_cast(error->sqlstate), error->native_code TSRMLS_CC ) && + ignored_chain != NULL ) { + + if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + } + else { + + // It is either an error or a warning which should not be ignored. + if( add_next_index_zval( reported_chain, error_z ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + } + } + else + { + // It is a warning with warning_return_as_errors as false, so simply add it to the ignored_chain list + if( ignored_chain != NULL ) { + + if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { + DIE( "Fatal error during error processing" ); + } + } + } +} + +bool handle_errors_and_warnings( sqlsrv_context& ctx, zval* reported_chain, zval* ignored_chain, logging_severity log_severity, + unsigned int sqlsrv_error_code, bool warning, va_list* print_args TSRMLS_DC ) +{ + bool result = true; + bool errors_ignored = false; + size_t prev_reported_cnt = 0; + bool reported_chain_was_null = false; + bool ignored_chain_was_null = false; + int zr = SUCCESS; + zval error_z; + ZVAL_UNDEF(&error_z); + sqlsrv_error_auto_ptr error; + + // array of reported errors + if( Z_TYPE_P( reported_chain ) == IS_NULL ) { + + reported_chain_was_null = true; + zr = array_init( reported_chain ); + if( zr == FAILURE ) { + DIE( "Fatal error in handle_errors_and_warnings" ); + } + } + else { + prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_P( reported_chain )); + } + + // array of ignored errors + if( ignored_chain != NULL ) { + + if( Z_TYPE_P( ignored_chain ) == IS_NULL ) { + + ignored_chain_was_null = true; + zr = array_init( ignored_chain ); + if( zr == FAILURE ) { + DIE( "Fatal error in handle_errors_and_warnings" ); + } + } + } + + if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, log_severity TSRMLS_CC, print_args ); + copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC ); + } + + SQLSMALLINT record_number = 0; + do { + + result = core_sqlsrv_get_odbc_error( ctx, ++record_number, error, log_severity TSRMLS_CC ); + if( result ) { + copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC ); + } + } while( result ); + + // If it were a warning, we report that warnings where ignored except if warnings_return_as_errors + // was true and we added some warnings to the reported_chain. + if( warning ) { + + errors_ignored = true; + + if( SQLSRV_G( warnings_return_as_errors ) ) { + + if( zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) > prev_reported_cnt ) { + + // We actually added some errors + errors_ignored = false; + } + } + } + + // if the error array came in as NULL and didn't have anything added to it, return it as NULL + if( reported_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) == 0 ) { + zend_hash_destroy( Z_ARRVAL_P( reported_chain )); + FREE_HASHTABLE( Z_ARRVAL_P( reported_chain )); + ZVAL_NULL( reported_chain ); + } + if( ignored_chain != NULL && ignored_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( ignored_chain )) == 0 ) { + zend_hash_destroy( Z_ARRVAL_P( ignored_chain )); + FREE_HASHTABLE( Z_ARRVAL_P( ignored_chain )); + ZVAL_NULL( ignored_chain ); + } + + // If it was an error instead of a warning than we always return errors_ignored = false. + return errors_ignored; +} + +// return whether or not a warning should be ignored or returned as an error if WarningsReturnAsErrors is true +// see RINIT in init.cpp for information about which errors are ignored. +bool ignore_warning( char* sql_state, int native_code TSRMLS_DC ) +{ + zend_ulong index = -1; + zend_string* key = NULL; + void* error_temp = NULL; + + ZEND_HASH_FOREACH_KEY_PTR( g_ss_warnings_to_ignore_ht, index, key, error_temp ) { + sqlsrv_error* error = static_cast( error_temp ); + if (NULL == error) { + return false; + } + + if( !strncmp( reinterpret_cast( error->sqlstate ), sql_state, SQL_SQLSTATE_SIZE ) && + ( error->native_code == native_code || error->native_code == -1 )) { + return true; + } + } ZEND_HASH_FOREACH_END(); + + return false; +} + +int sqlsrv_merge_zend_hash_dtor( zval* dest TSRMLS_DC ) +{ + zval_ptr_dtor( dest ); + return ZEND_HASH_APPLY_REMOVE; +} + +// sqlsrv_merge_zend_hash +// merge a source hash into a dest hash table and return any errors. +bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ) +{ + if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" ); + if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" ); + + if( Z_TYPE_P( src_z ) == IS_NULL ) { + return true; + } + + HashTable* src_ht = Z_ARRVAL_P( src_z ); + zend_ulong index = -1; + zend_string* key = NULL; + zval* value_z = NULL; + + ZEND_HASH_FOREACH_KEY_VAL( src_ht, index, key, value_z ) { + if ( !value_z ) { + zend_hash_apply( Z_ARRVAL_P(dest_z), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); + return false; + } + + int result = add_next_index_zval( dest_z, value_z ); + + if( result == FAILURE ) { + zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); + return false; + } + Z_TRY_ADDREF_P( value_z ); + } ZEND_HASH_FOREACH_END(); + + return true; +} + +} // namespace diff --git a/sqlsrv/CREDITS b/sqlsrv/CREDITS deleted file mode 100644 index dc85ce17e..000000000 --- a/sqlsrv/CREDITS +++ /dev/null @@ -1 +0,0 @@ -Microsoft Drivers 4.1 for PHP for SQL Server (PDO driver) diff --git a/sqlsrv/core_conn.cpp b/sqlsrv/core_conn.cpp deleted file mode 100644 index 97566d3c4..000000000 --- a/sqlsrv/core_conn.cpp +++ /dev/null @@ -1,774 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_conn.cpp -// -// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include -#include -#include -#include - -#include -#include - -// *** internal variables and constants *** - -namespace { - -// *** internal constants *** -// an arbitrary figure that should be large enough for most connection strings. -const int DEFAULT_CONN_STR_LEN = 2048; - -// length of buffer used to retrieve information for client and server info buffers -const int INFO_BUFFER_LEN = 256; - -// processor architectures -const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" }; - -// ODBC driver name. -const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"}; - -// default options if only the server is specified -const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}"; - -// connection option appended when no user name or password is given -const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};"; - -// connection option appended for MARS when MARS isn't explicitly mentioned -const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};"; - -// *** internal function prototypes *** - -void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, - HashTable* options_ht, const connection_option valid_conn_opts[], - void* driver,_Inout_ std::string& connection_string TSRMLS_DC ); -void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ); -const char* get_processor_arch( void ); -void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ); -connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, SQLULEN key_len TSRMLS_DC ); -void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ); - -} - -// core_sqlsrv_connect -// opens a connection and returns a sqlsrv_conn structure. -// Parameters: -// henv_cp - connection pooled env context -// henv_ncp - non connection pooled env context -// server - name of the server we're connecting to -// uid - username -// pwd - password -// options_ht - zend_hash list of options -// err - error callback to put into the connection's context -// valid_conn_opts[] - array of valid driver supported connection options. -// driver - reference to caller -// Return -// A sqlsrv_conn structure. An exception is thrown if an error occurs - -sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory, - const char* server, const char* uid, const char* pwd, - HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[], - void* driver, const char* driver_func TSRMLS_DC ) - -{ - SQLRETURN r; - std::string conn_str; - conn_str.reserve( DEFAULT_CONN_STR_LEN ); - sqlsrv_malloc_auto_ptr conn; - sqlsrv_malloc_auto_ptr wconn_string; - unsigned int wconn_len = 0; - - try { - - sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv - - // check the connection pooling setting to determine which henv to use to allocate the connection handle - // we do this earlier because we have to allocate the connection handle prior to setting attributes on - // it in build_connection_string_and_set_conn_attr. - - if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) { - - zval* option_z = NULL; - int zr = SUCCESS; - - option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING); - if (option_z) { - - // if the option was found and it's not true, then use the non pooled environment handle - if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) { - - henv = &henv_ncp; - } - } - } - - SQLHANDLE temp_conn_h; - core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC ); - - conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); - conn->set_func( driver_func ); - - for ( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) { - conn_str = CONNECTION_STRING_DRIVER_NAME[i]; - build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, - conn_str TSRMLS_CC ); - - // We only support UTF-8 encoding for connection string. - // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW - wconn_len = static_cast( conn_str.length() + 1 ) * sizeof( wchar_t ); - - wconn_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast(conn_str.length()), &wconn_len); - CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) - { - throw core::CoreException(); - } - - SQLSMALLINT output_conn_size; - r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get()), - static_cast( wconn_len ), NULL, - 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); - // clear the connection string from memory to remove sensitive data (such as a password). - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn_str.clear(); - - if( !SQL_SUCCEEDED( r )) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; - SQLSMALLINT len; - SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); - bool missing_driver_error = ( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && - state[4] == '2' ); - // if it's a IM002, meaning that the correct ODBC driver is not installed - CHECK_CUSTOM_ERROR( missing_driver_error && ( i == DRIVER_VERSION::MAX ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { - throw core::CoreException(); - } - if ( !missing_driver_error ) { - break; - } - } else { - conn->driver_version = static_cast( i ); - break; - } - - } - CHECK_SQL_ERROR( r, conn ) { - throw core::CoreException(); - } - - CHECK_SQL_WARNING_AS_ERROR( r, conn ) { - throw core::CoreException(); - } - - // determine the version of the server we're connected to. The server version is left in the - // connection upon return. - determine_server_version( conn TSRMLS_CC ); - - } - catch( std::bad_alloc& ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn->invalidate(); - DIE( "C++ memory allocation failure building the connection string." ); - } - catch( std::out_of_range const& ex ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); - conn->invalidate(); - throw; - } - catch( std::length_error const& ex ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() ); - conn->invalidate(); - throw; - } - catch( core::CoreException& ) { - memset( const_cast( conn_str.c_str()), 0, conn_str.size() ); - memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes - conn->invalidate(); - throw; - } - - sqlsrv_conn* return_conn = conn; - conn.transferred(); - - return return_conn; -} - - - -// core_sqlsrv_begin_transaction -// Begins a transaction on a specified connection. The current transaction -// includes all statements on the specified connection that were executed after -// the call to core_sqlsrv_begin_transaction and before any calls to -// core_sqlsrv_rollback or core_sqlsrv_commit. -// The default transaction mode is auto-commit. This means that all queries -// are automatically committed upon success unless they have been designated -// as part of an explicit transaction by using core_sqlsrv_begin_transaction. -// Parameters: -// sqlsrv_conn*: The connection with which the transaction is associated. - -void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_OFF ), - SQL_IS_UINTEGER TSRMLS_CC ); - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_commit -// Commits the current transaction on the specified connection and returns the -// connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or -// core_sqlsrv_commit. -// Parameters: -// sqlsrv_conn*: The connection on which the transaction is active. - -void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." ); - - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), - SQL_IS_UINTEGER TSRMLS_CC ); - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_rollback -// Rolls back the current transaction on the specified connection and returns -// the connection to the auto-commit mode. The current transaction includes all -// statements on the specified connection that were executed after the call to -// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or -// core_sqlsrv_commit. -// Parameters: -// sqlsrv_conn*: The connection on which the transaction is active. - -void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." ); - - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); - - core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), - SQL_IS_UINTEGER TSRMLS_CC ); - - } - catch ( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_close -// Called when a connection resource is destroyed by the Zend engine. -// Parameters: -// conn - The current active connection. -void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC ) -{ - // if the connection wasn't successful, just return. - if( conn == NULL ) - return; - - try { - - // rollback any transaction in progress (we don't care about the return result) - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); - } - catch( core::CoreException& ) { - LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." ); - } - - // disconnect from the server - SQLRETURN r = SQLDisconnect( conn->handle() ); - if( !SQL_SUCCEEDED( r )) { - LOG( SEV_ERROR, "Disconnect failed when closing the connection." ); - } - - // free the connection handle - conn->invalidate(); - - sqlsrv_free( conn ); -} - -// core_sqlsrv_prepare -// Create a statement object and prepare the SQL query passed in for execution at a later time. -// Parameters: -// stmt - statement to be prepared -// sql - T-SQL command to prepare -// sql_len - length of the T-SQL string - -void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC ) -{ - try { - - // convert the string from its encoding to UTf-16 - // if the string is empty, we initialize the fields and skip since an empty string is a - // failure case for utf16_string_from_mbcs_string - sqlsrv_malloc_auto_ptr wsql_string; - unsigned int wsql_len = 0; - if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) { - wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t ))); - wsql_string[0] = L'\0'; - wsql_len = 0; - } - else { - - if (sql_len > INT_MAX) - { - LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); - throw core::CoreException(); - } - - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : - stmt->encoding() ); - wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ), - static_cast( sql_len ), &wsql_len ); - CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, - get_last_error_message() ) { - throw core::CoreException(); - } - } - - // prepare our wide char query string - core::SQLPrepareW( stmt, reinterpret_cast( wsql_string.get() ), wsql_len TSRMLS_CC ); - } - catch( core::CoreException& ) { - - throw; - } -} - -// core_sqlsrv_get_server_version -// Determines the vesrion of the SQL Server we are connected to. Calls a helper function -// get_server_version to get the version of SQL Server. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// *server_version - zval for returning results. - -void core_sqlsrv_get_server_version( sqlsrv_conn* conn, _Out_ zval *server_version TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); - if (NULL != buffer) { - sqlsrv_free( buffer ); - } - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_get_server_info -// Returns the Database name, the name of the SQL Server we are connected to -// and the version of the SQL Server. -// Parameters: -// conn - The connection resource by which the client and server are connected. -// *server_info - zval for returning results. - -void core_sqlsrv_get_server_info( sqlsrv_conn* conn, _Out_ zval *server_info TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - // initialize the array - core::sqlsrv_array_init( *conn, server_info TSRMLS_CC ); - - // Get the database name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the server version - get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the server name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - -// core_sqlsrv_get_client_info -// Returns the ODBC driver's dll name, version and the ODBC version. -// Parameters -// conn - The connection resource by which the client and server are connected. -// *client_info - zval for returning the results. - -void core_sqlsrv_get_client_info( sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - // initialize the array - core::sqlsrv_array_init( *conn, client_info TSRMLS_CC ); - - // Get the ODBC driver's dll name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the ODBC driver's ODBC version - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - // Get the OBDC driver's version - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); - buffer.transferred(); - - } - - catch( core::CoreException& ) { - throw; - } -} - - -// core_is_conn_opt_value_escaped -// determine if connection string value is properly escaped. -// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that -// the value will be surrounded by { and } by the caller after it has been validated - -bool core_is_conn_opt_value_escaped( const char* value, size_t value_len ) -{ - // if the value is already quoted, then only analyse the part inside the quotes and return it as - // unquoted since we quote it when adding it to the connection string. - if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) { - ++value; - value_len -= 2; - } - // check to make sure that all right braces are escaped - size_t i = 0; - while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) { - // skip both braces - if( value[i] == '}' ) - ++i; - ++i; - } - if( i < value_len && value[i] == '}' ) { - return false; - } - - return true; -} - - -// *** internal connection functions and classes *** - -namespace { - -connection_option const* get_connection_option( sqlsrv_conn* conn, SQLULEN key, - const connection_option conn_opts[] TSRMLS_DC ) -{ - for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) { - - if( key == conn_opts[ opt_idx ].conn_option_key ) { - - return &conn_opts[ opt_idx ]; - } - } - - SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." ); - return NULL; // avoid a compiler warning -} - -// says what it does, and does what it says -// rather than have attributes and connection strings as ODBC does, we unify them into a hash table -// passed to the connection, and then break them out ourselves and either set attributes or put the -// option in the connection string. - -void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd, - HashTable* options, const connection_option valid_conn_opts[], - void* driver,_Inout_ std::string& connection_string TSRMLS_DC ) -{ - bool credentials_mentioned = false; - bool mars_mentioned = false; - connection_option const* conn_opt; - int zr = SUCCESS; - - try { - - // Add the server name - common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC ); - - // if uid is not present then we use trusted connection. - if(uid == NULL || strlen( uid ) == 0 ) { - - connection_string += "Trusted_Connection={Yes};"; - } - else { - - bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { - throw core::CoreException(); - } - - common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( uid ), connection_string TSRMLS_CC ); - - // if no password was given, then don't add a password to the connection string. Perhaps the UID - // given doesn't have a password? - if( pwd != NULL ) { - - escaped = core_is_conn_opt_value_escaped( pwd, strlen( pwd )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { - - throw core::CoreException(); - } - - common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC ); - } - } - - // if no options were given, then we set MARS the defaults and return immediately. - if( options == NULL || zend_hash_num_elements( options ) == 0 ) { - connection_string += CONNECTION_STRING_DEFAULT_OPTIONS; - return; - } - - // workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file - // if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn" - // flag is set to false. - if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) { - - zval* trace_value = NULL; - trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON); - - if (trace_value == NULL || !zend_is_true(trace_value)) { - - zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE ); - } - } - - zend_string *key = NULL; - zend_ulong index = -1; - zval* data = NULL; - - ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) { - int type = HASH_KEY_NON_EXISTENT; - type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); - - conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); - - if( index == SQLSRV_CONN_OPTION_MARS ) { - mars_mentioned = true; - } - - conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); - - // MARS on if not explicitly turned off - if( !mars_mentioned ) { - connection_string += CONNECTION_OPTION_MARS_ON; - } - - } - catch( core::CoreException& ) { - throw; - } -} - - -// get_server_version -// Helper function which returns the version of the SQL Server we are connected to. - -void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - *server_version = buffer; - len = buffer_len; - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - - -// get_processor_arch -// Calls GetSystemInfo to verify the what architecture of the processor is supported -// and return the string of the processor name. -const char* get_processor_arch( void ) -{ - SYSTEM_INFO sys_info; - GetSystemInfo( &sys_info); - switch( sys_info.wProcessorArchitecture ) { - - case PROCESSOR_ARCHITECTURE_INTEL: - return PROCESSOR_ARCH[0]; - - case PROCESSOR_ARCHITECTURE_AMD64: - return PROCESSOR_ARCH[1]; - - case PROCESSOR_ARCHITECTURE_IA64: - return PROCESSOR_ARCH[2]; - - default: - DIE( "Unknown Windows processor architecture." ); - return NULL; - } -} - - -// some features require a server of a certain version or later -// this function determines the version of the server we're connected to -// and stores it in the connection. Any errors are logged before return. -// Exception is thrown when the server version is either undetermined -// or is invalid (< 2000). - -void determine_server_version( sqlsrv_conn* conn TSRMLS_DC ) -{ - SQLSMALLINT info_len; - char p[ INFO_BUFFER_LEN ]; - core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); - - errno = 0; - char version_major_str[ 3 ]; - SERVER_VERSION version_major; - memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); - version_major_str[ 2 ] = '\0'; - version_major = static_cast( atoi( version_major_str )); - - CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION ) - { - throw core::CoreException(); - } - - // SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least - // that high - conn->server_version = version_major; -} - -void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) -{ - // wrap a connection option in a quote. It is presumed that any character that need to be escaped will - // be escaped, such as a closing }. - TSRMLS_C; - - if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) { - ++val; - val_len -= 2; - } - conn_str += odbc_name; - conn_str += "={"; - conn_str.append( val, val_len ); - conn_str += "};"; -} - -} // namespace - -// simply add the parsed value to the connection string -void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str - TSRMLS_DC ) -{ - const char* val_str = Z_STRVAL_P( value ); - size_t val_len = Z_STRLEN_P( value ); - common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC ); -} - -// do nothing for connection pooling since we handled it earlier when -// deciding which environment handle to use. -void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ - TSRMLS_DC ) -{ - TSRMLS_C; -} - -// helper function to evaluate whether a string value is true or false. -// Values = ("true" or "1") are treated as true values. Everything else is treated as false. -// Returns 1 for true and 0 for false. - -size_t core_str_zval_is_true( zval* value_z ) -{ - SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." ); - - char* value_in = Z_STRVAL_P( value_z ); - size_t val_len = Z_STRLEN_P( value_z ); - - // strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8) - size_t last_char = val_len - 1; - while( isspace( value_in[ last_char ] )) { - value_in[ last_char ] = '\0'; - val_len = last_char; - --last_char; - } - - // save adjustments to the value made by stripping whitespace at the end - Z_STRLEN_P( value_z ) = val_len; - - const char VALID_TRUE_VALUE_1[] = "true"; - const char VALID_TRUE_VALUE_2[] = "1"; - - if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) || - ( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len )) - ) { - - return 1; // true - } - - return 0; // false -} diff --git a/sqlsrv/core_init.cpp b/sqlsrv/core_init.cpp deleted file mode 100644 index 1b1e8e57b..000000000 --- a/sqlsrv/core_init.cpp +++ /dev/null @@ -1,175 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_init.cpp -// -// Contents: common initialization routines shared by PDO and sqlsrv -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - - -// module global variables (initialized in minit and freed in mshutdown) -HMODULE g_sqlsrv_hmodule = NULL; -OSVERSIONINFO g_osversion; - - -// core_sqlsrv_minit -// Module initialization -// This function is called once per execution by the driver layer's MINIT function. -// The primary responsibility of this function is to allocate the two environment -// handles used by core_sqlsrv_connect to allocate either a pooled or non pooled ODBC -// connection handle. -// Parameters: -// henv_cp - Environment handle for pooled connection. -// henv_ncp - Environment handle for non-pooled connection. -// err - Driver specific error handler which handles any errors during initialization. -void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC ) -{ - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); - SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); - - *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL - - try { - - // get the version of the OS we're running on. For now this governs certain flags used by - // WideCharToMultiByte. It might be relevant to other things in the future. - g_osversion.dwOSVersionInfoSize = sizeof( g_osversion ); - BOOL ver_return = GetVersionEx( &g_osversion ); - if( !ver_return ) { - LOG( SEV_ERROR, "Failed to retrieve Windows version information." ); - throw core::CoreException(); - } - - SQLHANDLE henv = SQL_NULL_HANDLE; - SQLRETURN r; - - // allocate the non pooled environment handle - // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so - // we use the direct ODBC function. - r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); - if( !SQL_SUCCEEDED( r )) { - throw core::CoreException(); - } - - *henv_ncp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); - (*henv_ncp)->set_func( driver_func ); - - // set to ODBC 3 - core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER - TSRMLS_CC ); - - // disable connection pooling - core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_OFF ), - SQL_IS_UINTEGER TSRMLS_CC ); - - // allocate the pooled envrionment handle - // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so - // we use the direct ODBC function. - r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); - if( !SQL_SUCCEEDED( r )) { - throw core::CoreException(); - } - - *henv_cp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL ); - (*henv_cp)->set_func( driver_func ); - - // set to ODBC 3 - core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC); - - // enable connection pooling - core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_ONE_PER_HENV ), - SQL_IS_UINTEGER TSRMLS_CC ); - - } - catch( core::CoreException& e ) { - - LOG( SEV_ERROR, "core_sqlsrv_minit: Failed to allocate environment handles." ); - - if( *henv_ncp != NULL ) { - // free the ODBC env handle allocated just above - SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); - delete *henv_ncp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) - *henv_ncp = NULL; - } - if( *henv_cp != NULL ) { - // free the ODBC env handle allocated just above - SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); - delete *henv_cp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap) - *henv_cp = NULL; - } - - throw e; // rethrow for the driver to catch - } - catch( std::bad_alloc& e ) { - - LOG( SEV_ERROR, "core_sqlsrv_minit: Failed memory allocation for environment handles." ); - - if( *henv_ncp != NULL ) { - SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp ); - delete *henv_ncp; - *henv_ncp = NULL; - } - if( *henv_cp ) { - SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp ); - delete *henv_cp; - *henv_cp = NULL; - } - - throw e; // rethrow for the driver to catch - } -} - -// core_sqlsrv_mshutdown -// Module shutdown function -// Free the environment handles allocated in MINIT and unregister our stream wrapper. -// Resource types and constants are automatically released since we don't flag them as -// persistent when they are registered. -// Parameters: -// henv_cp - Pooled environment handle. -// henv_ncp - Non-pooled environment handle. -void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp ) -{ - if( henv_ncp != SQL_NULL_HANDLE ) { - - henv_ncp.invalidate(); - } - delete &henv_ncp; - - if( henv_cp != SQL_NULL_HANDLE ) { - - henv_cp.invalidate(); - } - delete &henv_cp; - - return; -} - - -// DllMain for the extension. - -BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID ) -{ - switch( fdwReason ) { - case DLL_PROCESS_ATTACH: - // store the module handle for use by client_info and server_info - g_sqlsrv_hmodule = hinstDLL; - break; - default: - break; - } - - return TRUE; -} diff --git a/sqlsrv/core_results.cpp b/sqlsrv/core_results.cpp deleted file mode 100644 index 8b2657ed3..000000000 --- a/sqlsrv/core_results.cpp +++ /dev/null @@ -1,1361 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_results.cpp -// -// Contents: Result sets -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" - -#include -#include -#include - -using namespace core; - -// conversion matrix -// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported -// this is initialized the first time the buffered result set is created. -sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix; - -namespace { - -// *** internal types *** - -#pragma warning(disable:4200) - -// *** internal constants *** - -const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field - -// *** internal functions *** - -// return an integral type rounded up to a certain number -template -T align_to( T number ) -{ - DEBUG_SQLSRV_ASSERT( (number + align) > number, "Number to align overflowed" ); - return ((number % align) == 0) ? number : (number + align - (number % align)); -} - -// return a pointer address aligned to a certain address boundary -template -T* align_to( T* ptr ) -{ - size_t p_value = (size_t) ptr; - return align_to( p_value ); -} - -// set the nth bit of the bitstream starting at ptr -void set_bit( void* ptr, unsigned int bit ) -{ - unsigned char* null_bits = reinterpret_cast( ptr ); - null_bits += bit >> 3; - *null_bits |= 1 << ( 7 - ( bit & 0x7 )); -} - -// retrieve the nth bit from the bitstream starting at ptr -bool get_bit( void* ptr, unsigned int bit ) -{ - unsigned char* null_bits = reinterpret_cast( ptr ); - null_bits += bit >> 3; - return ((*null_bits & (1 << ( 7 - ( bit & 0x07 )))) != 0); -} - -// read in LOB field during buffered result creation -SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - zend_long mem_used TSRMLS_DC ); - -// dtor for each row in the cache -void cache_row_dtor(zval* data); - -// convert a number to a string using locales -// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of -// subclassing a new streambuf just to avoid the copy, it's easier to do the copy -template -SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - sqlsrv_error_auto_ptr& last_error ) -{ - // get to display size by removing the null terminator from buffer length - size_t display_size = ( buffer_length - sizeof( Char )) / sizeof( Char ); - - std::basic_ostringstream os; - // use the display size to determine the sql type. And if it is a double, set the precision accordingly - // the display sizes are set by the ODBC driver based on the precision of the sql type - // otherwise we can just use the default precision as long will not be truncated - size_t real_display_size = 14; - size_t float_display_size = 24; - size_t real_precision = 7; - size_t float_precision = 15; - // this is the case of sql type float(24) or real - if ( display_size == real_display_size ) { - os.precision( real_precision ); - } - // this is the case of sql type float(53) - else if ( display_size == float_display_size ) { - os.precision( float_precision ); - } - std::locale loc; - os.imbue( loc ); - std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf() ), os, ' ', *number_data ); - std::basic_string& str_num = os.str(); - - if( os.fail() ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "IMSSP", (SQLCHAR*) "Failed to convert number to string", -1 ); - return SQL_ERROR; - } - - if( str_num.size() * sizeof(Char) > (size_t) buffer_length ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); - return SQL_ERROR; - } - - *out_buffer_length = str_num.size() * sizeof( Char ); // str_num.size() already include the NULL terminator - memcpy_s( buffer, buffer_length, str_num.c_str(), *out_buffer_length ); - - return SQL_SUCCESS; -} - -template -SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) -{ - Number* number_data = reinterpret_cast( buffer ); - std::locale loc; // default locale should match system - std::basic_istringstream is; - is.str( string_data ); - is.imbue( loc ); - std::ios_base::iostate st = 0; - - std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream::_Iter( is.rdbuf( ) ), - std::basic_istream::_Iter(0), is, st, *number_data ); - - if( st & std::ios_base::failbit ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( - (SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103 ); - return SQL_ERROR; - } - - *out_buffer_length = sizeof( Number ); - - return SQL_SUCCESS; -} - -// "closure" for the hash table destructor -struct row_dtor_closure { - - sqlsrv_buffered_result_set* results; - BYTE* row_data; - - row_dtor_closure( sqlsrv_buffered_result_set* st, BYTE* row ) : - results( st ), row_data( row ) - { - } -}; - -sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number ) -{ - SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ]; - SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ]; - SQLINTEGER native_code; - SQLSMALLINT wnative_message_len = 0; - - SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message, - SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len ); - if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { - return NULL; - } - - // convert the error into the encoding of the context - SQLSRV_ENCODING enc = odbc->encoding(); - if( enc == SQLSRV_ENCODING_DEFAULT ) { - enc = odbc->conn->encoding(); - } - - // convert the error into the encoding of the context - sqlsrv_malloc_auto_ptr sql_state; - SQLLEN sql_state_len = 0; - if (!convert_string_from_utf16( enc, wsql_state, sizeof(wsql_state), (char**)&sql_state, sql_state_len )) { - return NULL; - } - - sqlsrv_malloc_auto_ptr native_message; - SQLLEN native_message_len = 0; - if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) { - return NULL; - } - - return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) sql_state, (SQLCHAR*) native_message, - native_code ); -} - -} // namespace - -// base class result set - -sqlsrv_result_set::sqlsrv_result_set( sqlsrv_stmt* stmt ) : - odbc( stmt ) -{ -} - - -// ODBC result set -// This object simply wraps ODBC function calls - -sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( sqlsrv_stmt* stmt ) : - sqlsrv_result_set( stmt ) -{ -} - -sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void ) -{ -} - -SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC ); -} - -SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC ); -} - -SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length, - out_buffer_length TSRMLS_CC ); -} - -sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( SQLSMALLINT record_number ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return odbc_get_diag_rec( odbc, record_number ); -} - -SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D ) -{ - SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLRowCount( odbc TSRMLS_CC ); -} - - -// Buffered result set -// This class holds a result set in memory - -sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS_DC ) : - sqlsrv_result_set( stmt ), - cache(NULL), - col_count(0), - meta(NULL), - current(0), - last_field_index(-1), - read_so_far(0) -{ - // 10 is an arbitrary number for now for the initial size of the cache - ALLOC_HASHTABLE( cache ); - core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); - col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); - // there is no result set to buffer - if( col_count == 0 ) { - return; - } - - SQLULEN null_bytes = ( col_count / 8 ) + 1; // number of bits to reserve at the beginning of each row for NULL flags - meta = static_cast( sqlsrv_malloc( col_count * - sizeof( sqlsrv_buffered_result_set::meta_data ))); - - // set up the conversion matrix if this is the first time we're called - if( conv_matrix.size() == 0 ) { - - conv_matrix[ SQL_C_CHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::system_to_wide_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::string_to_double; - conv_matrix[ SQL_C_CHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::string_to_long; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::wide_to_system_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::wstring_to_double; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::wstring_to_long; - conv_matrix[ SQL_C_BINARY ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_BINARY ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::binary_to_system_string; - conv_matrix[ SQL_C_BINARY ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::binary_to_wide_string; - conv_matrix[ SQL_C_LONG ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::long_to_double; - conv_matrix[ SQL_C_LONG ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[ SQL_C_LONG ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[ SQL_C_LONG ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::long_to_system_string; - conv_matrix[ SQL_C_LONG ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::long_to_wide_string; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::double_to_system_string; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::double_to_long; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::double_to_wide_string; - } - - // get the meta data and calculate the size of a row buffer - SQLULEN offset = null_bytes; - for( SQLSMALLINT i = 0; i < col_count; ++i ) { - - core::SQLDescribeCol( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC ); - - offset = align_to<4>( offset ); - meta[i].offset = offset; - - switch( meta[i].type ) { - - // these types are the display size - case SQL_BIGINT: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, - reinterpret_cast( &meta[i].length ) TSRMLS_CC ); - meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space - offset += meta[i].length; - break; - - // these types are the column size - case SQL_BINARY: - case SQL_CHAR: - case SQL_SS_UDT: - case SQL_VARBINARY: - case SQL_VARCHAR: - // var* field types are length prefixed - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - offset += sizeof( void* ); - } - else { - meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space - offset += meta[i].length; - } - break; - - case SQL_WCHAR: - case SQL_WVARCHAR: - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - offset += sizeof( void* ); - } - else { - meta[i].length *= sizeof( WCHAR ); - meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space - offset += meta[i].length; - } - break; - - // these types are LOBs - case SQL_LONGVARBINARY: - case SQL_LONGVARCHAR: - case SQL_WLONGVARCHAR: - case SQL_SS_XML: - meta[i].length = sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN; - offset += sizeof( void* ); - break; - - // these types are the ISO date size - case SQL_DATETIME: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_TYPE_TIMESTAMP: - core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, - reinterpret_cast( &meta[i].length ) TSRMLS_CC ); - meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space - offset += meta[i].length; - break; - - // these types are the native size - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - meta[i].length = sizeof( long ); - offset += meta[i].length; - break; - - case SQL_REAL: - case SQL_FLOAT: - meta[i].length = sizeof( double ); - offset += meta[i].length; - break; - - default: - SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); - break; - } - - switch( meta[i].type ) { - - case SQL_BIGINT: - case SQL_CHAR: - case SQL_DATETIME: - case SQL_DECIMAL: - case SQL_GUID: - case SQL_NUMERIC: - case SQL_LONGVARCHAR: - case SQL_TYPE_DATE: - case SQL_SS_TIME2: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_XML: - case SQL_TYPE_TIMESTAMP: - case SQL_VARCHAR: - meta[i].c_type = SQL_C_CHAR; - break; - - case SQL_SS_UDT: - case SQL_LONGVARBINARY: - case SQL_BINARY: - case SQL_VARBINARY: - meta[i].c_type = SQL_C_BINARY; - break; - - case SQL_WLONGVARCHAR: - case SQL_WCHAR: - case SQL_WVARCHAR: - meta[i].c_type = SQL_C_WCHAR; - break; - - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: - meta[i].c_type = SQL_C_LONG; - break; - - case SQL_REAL: - case SQL_FLOAT: - meta[i].c_type = SQL_C_DOUBLE; - break; - - default: - SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" ); - break; - } - - } - - // read the data into the cache - // (offset from the above loop has the size of the row buffer necessary) - zend_long mem_used = 0; - unsigned long row_count = 0; - - while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { - - // allocate the row buffer - unsigned char* row = static_cast( sqlsrv_malloc( offset )); - memset( row, 0, offset ); - - // read the fields into the row buffer - for( SQLSMALLINT i = 0; i < col_count; ++i ) { - - SQLLEN out_buffer_temp = SQL_NULL_DATA; - SQLPOINTER buffer; - SQLLEN* out_buffer_length = &out_buffer_temp; - - switch( meta[i].c_type ) { - - case SQL_C_CHAR: - case SQL_C_WCHAR: - case SQL_C_BINARY: - if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - out_buffer_length = &out_buffer_temp; - SQLPOINTER* lob_addr = reinterpret_cast( &row[ meta[i].offset ] ); - *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); - // a NULL pointer means NULL field - if( *lob_addr == NULL ) { - *out_buffer_length = SQL_NULL_DATA; - } - else { - *out_buffer_length = **reinterpret_cast( lob_addr ); - mem_used += *out_buffer_length; - } - } - else { - - mem_used += meta[i].length; - CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - - buffer = row + meta[i].offset + sizeof( SQLULEN ); - out_buffer_length = reinterpret_cast( row + meta[i].offset ); - core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); - } - break; - - case SQL_C_LONG: - case SQL_C_DOUBLE: - { - mem_used += meta[i].length; - CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - buffer = row + meta[i].offset; - out_buffer_length = &out_buffer_temp; - core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); - } - break; - - default: - SQLSRV_ASSERT( false, "Unknown C type" ); - break; - } - - if( *out_buffer_length == SQL_NULL_DATA ) { - unsigned char* null_bits = reinterpret_cast( row ); - set_bit( row, i ); - } - } - - SQLSRV_ASSERT( row_count < LONG_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); - - // add it to the cache - row_dtor_closure cl( this, row ); - sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); - } - -} - -sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void ) -{ - // free the rows - if( cache ) { - zend_hash_destroy( cache ); - FREE_HASHTABLE( cache ); - cache = NULL; - } - - // free the meta data - if( meta ) { - efree( meta ); - meta = NULL; - } -} - -SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC ) -{ - last_error = NULL; - last_field_index = -1; - read_so_far = 0; - - switch( orientation ) { - - case SQL_FETCH_NEXT: - offset = 1; - orientation = SQL_FETCH_RELATIVE; - break; - case SQL_FETCH_PRIOR: - offset = -1; - orientation = SQL_FETCH_RELATIVE; - break; - } - - switch( orientation ) { - - case SQL_FETCH_FIRST: - current = 1; - break; - case SQL_FETCH_LAST: - current = row_count( TSRMLS_C ); - break; - case SQL_FETCH_ABSOLUTE: - current = offset; - break; - case SQL_FETCH_RELATIVE: - current += offset; - break; - default: - SQLSRV_ASSERT( false, "Invalid fetch orientation. Should have been caught before here." ); - break; - } - - // check validity of current row - // the cursor can never get further away than just before the first row - if( current <= 0 && ( offset < 0 || orientation != SQL_FETCH_RELATIVE )) { - current = 0; - return SQL_NO_DATA; - } - - // the cursor can never get further away than just after the last row - if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) { - current = row_count( TSRMLS_C ) + 1; - return SQL_NO_DATA; - } - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type, - _Out_ SQLPOINTER buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) -{ - last_error = NULL; - field_index--; // convert from 1 based to 0 based - SQLSRV_ASSERT( field_index < column_count(), "Invalid field index requested" ); - - if( field_index != last_field_index ) { - last_field_index = field_index; - read_so_far = 0; - } - - unsigned char* row = get_row(); - - // if the field is null, then return SQL_NULL_DATA - if( get_bit( row, field_index )) { - *out_buffer_length = SQL_NULL_DATA; - return SQL_SUCCESS; - } - - // check to make sure the conversion type is valid - if( conv_matrix.find( meta[ field_index ].c_type ) == conv_matrix.end() || - conv_matrix.find( meta[ field_index ].c_type )->second.find( target_type ) == - conv_matrix.find( meta[ field_index ].c_type )->second.end() ) { - - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "07006", - (SQLCHAR*) "Restricted data type attribute violation", 0 ); - return SQL_ERROR; - } - - return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length, - out_buffer_length ); -} - -SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier, - _Out_ SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length, - _Out_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) -{ - SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); - SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE, - "Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); - SQLSRV_ASSERT( buffer_length >= SQL_SQLSTATE_BUFSIZE, - "Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field" ); - - if( last_error == NULL ) { - return SQL_NO_DATA; - } - - SQLSRV_ASSERT( last_error->sqlstate != NULL, - "Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" ); - - memcpy_s( diag_info_buffer, buffer_length, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE )); - - return SQL_SUCCESS; -} - -unsigned char* sqlsrv_buffered_result_set::get_row( void ) -{ - row_dtor_closure* cl_ptr; - cl_ptr = reinterpret_cast(zend_hash_index_find_ptr(cache, static_cast(current - 1))); - SQLSRV_ASSERT(cl_ptr != NULL, "Failed to find row %1!d! in the cache", current); - return cl_ptr->row_data; -} - -sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( SQLSMALLINT record_number ) -{ - // we only hold a single error if there is one, otherwise return the ODBC error(s) - if( last_error == NULL ) { - return odbc_get_diag_rec( odbc, record_number ); - } - if( record_number > 1 ) { - return NULL; - } - - return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code ); -} - -SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D ) -{ - last_error = NULL; - - return zend_hash_num_elements( cache ); -} - -// private functions -template -SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, _Out_ void* buffer, - SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, - sqlsrv_error_auto_ptr& out_error ) -{ - // hex characters for the conversion loop below - static char hex_chars[] = "0123456789ABCDEF"; - - SQLSRV_ASSERT( out_error == NULL, "Pending error for sqlsrv_buffered_results_set::binary_to_string" ); - - SQLRETURN r = SQL_ERROR; - - // Set the amount of space necessary for null characters at the end of the data. - SQLSMALLINT extra = sizeof(Char); - - SQLSRV_ASSERT( ((buffer_length - extra) % (extra * 2)) == 0, "Must be multiple of 2 for binary to system string or " - "multiple of 4 for binary to wide string" ); - - // all fields will be treated as ODBC returns varchar(max) fields: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - // *2 is for each byte to hex conversion and * extra is for either system or wide string allocation - *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra; - - // copy as much as we can into the buffer - SQLLEN to_copy; - if( buffer_length < *out_buffer_length + extra ) { - to_copy = (buffer_length - extra); - out_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - r = SQL_SUCCESS; - to_copy = *out_buffer_length; - } - - // if there are bytes to copy as hex - if( to_copy > 0 ) { - // quick hex conversion routine - Char* h = reinterpret_cast( buffer ); - BYTE* b = reinterpret_cast( field_data ); - // to_copy contains the number of bytes to copy, so we divide the number in half (or quarter) - // to get the number of hex digits we can copy - SQLLEN to_copy_hex = to_copy / (2 * extra); - for( int i = 0; i < to_copy_hex; ++i ) { - *h = hex_chars[ (*b & 0xf0) >> 4 ]; - h++; - *h = hex_chars[ (*b++ & 0x0f) ]; - h++; - } - read_so_far += to_copy_hex; - *h = static_cast( 0 ); - } - else { - reinterpret_cast( buffer )[0] = '\0'; - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLCHAR* row = get_row(); - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); - } - - return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLCHAR* row = get_row(); - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); - } - - return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); -} - - -SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof(SQLLEN), "Buffer length must be able to find a long in " - "sqlsrv_buffered_result_set::double_to_long" ); - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - LONG* long_data = reinterpret_cast( buffer ); - - if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) { - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "22003", - (SQLCHAR*) "Numeric value out of range", 0 ); - return SQL_ERROR; - } - - if( *double_data != floor( *double_data )) { - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "01S07", - (SQLCHAR*) "Fractional truncation", 0 ); - return SQL_SUCCESS_WITH_INFO; - } - - *long_data = static_cast( *double_data ); - *out_buffer_length = sizeof( LONG ); - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_system_string" ); - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" ); - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof(double), "Buffer length must be able to find a long in sqlsrv_buffered_result_set::double_to_long" ); - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( buffer ); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - *double_data = static_cast( *long_data ); - *out_buffer_length = sizeof( double ); - - return SQL_SUCCESS; -} -SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to system string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_system_string" ); - - unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to wide string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" ); - - unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to double" ); - SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); - - unsigned char* row = get_row(); - char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" ); - SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); - - unsigned char* row = get_row(); - SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); - - unsigned char* row = get_row(); - char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); - - unsigned char* row = get_row(); - SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); -} - -SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" ); - SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - SQLCHAR* field_data = NULL; - SQLULEN field_len = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; - } - else { - - field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; - } - - // all fields will be treated as ODBC returns varchar(max) fields: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR); - - // to_copy is the number of characters to copy, not including the null terminator - // supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair. - SQLLEN to_copy; - - if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) { - - to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR); // to_copy is the number of characters - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - - r = SQL_SUCCESS; - to_copy = field_len - read_so_far; - } - - if( to_copy > 0 ) { - - bool tried_again = false; - do { - if (to_copy > INT_MAX ) { - LOG(SEV_ERROR, "MultiByteToWideChar: Buffer length exceeded."); - throw core::CoreException(); - } - - int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast(to_copy), - static_cast(buffer), static_cast(to_copy)); - if( ch_space == 0 ) { - - switch( GetLastError() ) { - - case ERROR_NO_UNICODE_TRANSLATION: - // the theory here is the conversion failed because the end of the buffer we provided contained only - // half a character at the end - if( !tried_again ) { - to_copy--; - tried_again = true; - continue; - } - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); - break; - default: - SQLSRV_ASSERT( false, "Severe error translating Unicode" ); - break; - } - - return SQL_ERROR; - } - - ((WCHAR*)buffer)[ to_copy ] = L'\0'; - read_so_far += to_copy; - break; - - } while( true ); - } - else { - reinterpret_cast( buffer )[0] = L'\0'; - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::to_same_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - // Set the amount of space necessary for null characters at the end of the data. - SQLSMALLINT extra = 0; - - switch( meta[ field_index ].c_type ) { - case SQL_C_WCHAR: - extra = sizeof( SQLWCHAR ); - break; - case SQL_C_BINARY: - extra = 0; - break; - case SQL_C_CHAR: - extra = sizeof( SQLCHAR ); - break; - default: - SQLSRV_ASSERT( false, "Invalid type in get_string_data" ); - break; - } - - SQLCHAR* field_data = NULL; - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); - } - else { - - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); - } - - // all fields will be treated as ODBC returns varchar(max) fields: - // the entire length of the string is returned the first - // call in out_buffer_len. Successive calls return how much is - // left minus how much has already been read by previous reads - *out_buffer_length = *reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far; - - // copy as much as we can into the buffer - SQLLEN to_copy; - if( buffer_length < *out_buffer_length + extra ) { - to_copy = buffer_length - extra; - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - r = SQL_SUCCESS; - to_copy = *out_buffer_length; - } - - SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" ); - - if( to_copy > 0 ) { - memcpy_s( buffer, buffer_length, field_data + read_so_far, to_copy ); - read_so_far += to_copy; - } - if( extra ) { - OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" ); - memcpy_s( reinterpret_cast( buffer ) + to_copy, buffer_length, L"\0", extra ); - } - - return r; -} - -SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" ); - - SQLRETURN r = SQL_ERROR; - unsigned char* row = get_row(); - - SQLCHAR* field_data = NULL; - SQLLEN field_len = NULL; - - // if this is the first time called for this field, just convert the entire string to system first then - // use that to read from instead of converting chunk by chunk. This is because it's impossible to know - // the total length of the string for output_buffer_length without doing the conversion and returning - // SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and - // to_same_string). - - if( read_so_far == 0 ) { - - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; - } - else { - - field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; - } - - BOOL default_char_used = FALSE; - char default_char = '?'; - - // allocate enough to handle WC -> DBCS conversion if it happens - temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof(char), sizeof(char))); - temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, static_cast(field_len / sizeof(WCHAR)), - (LPSTR) temp_string.get(), static_cast(field_len), &default_char, &default_char_used ); - - if( temp_length == 0 ) { - - switch( GetLastError() ) { - - case ERROR_NO_UNICODE_TRANSLATION: - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 ); - break; - default: - SQLSRV_ASSERT( false, "Severe error translating Unicode" ); - break; - } - - return SQL_ERROR; - } - } - - *out_buffer_length = (temp_length - read_so_far); - - SQLLEN to_copy = 0; - - if( (size_t) buffer_length < (temp_length - read_so_far + sizeof(char))) { - - to_copy = buffer_length - sizeof(char); - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 ); - r = SQL_SUCCESS_WITH_INFO; - } - else { - - to_copy = (temp_length - read_so_far); - r = SQL_SUCCESS; - } - - if( to_copy > 0 ) { - - memcpy_s( buffer, buffer_length, temp_string.get() + read_so_far, to_copy ); - } - SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); - OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); - ((SQLCHAR*) buffer)[ to_copy ] = '\0'; - read_so_far += to_copy; - - return r; -} - - -SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - return to_same_string( field_index, buffer, buffer_length, out_buffer_length ); -} - -SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invlid conversion to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this - - unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - memcpy_s( buffer, buffer_length, long_data, sizeof( LONG )); - *out_buffer_length = sizeof( LONG ); - - return SQL_SUCCESS; -} - -SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, _Out_ void* buffer, SQLLEN buffer_length, - _Out_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invlid conversion to double" ); - SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); - - memcpy_s( buffer, buffer_length, double_data, sizeof( double )); - *out_buffer_length = sizeof( double ); - - return SQL_SUCCESS; -} - -namespace { - -// called for each row in the cache when the cache is destroyed in the destructor -void cache_row_dtor( zval* data ) -{ - row_dtor_closure* cl = reinterpret_cast( Z_PTR_P( data ) ); - BYTE* row = cl->row_data; - // don't release this here, since this is called from the destructor of the result_set - sqlsrv_buffered_result_set* result_set = cl->results; - - for( SQLSMALLINT i = 0; i < result_set->column_count(); ++i ) { - - if( result_set->col_meta_data(i).length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - - void* out_of_row_data = *reinterpret_cast( &row[ result_set->col_meta_data(i).offset ] ); - sqlsrv_free( out_of_row_data ); - } - } - - sqlsrv_free( row ); - sqlsrv_free( cl ); -} - -SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta, - zend_long mem_used TSRMLS_DC ) -{ - SQLSMALLINT extra = 0; - SQLULEN* output_buffer_len = NULL; - - // Set the amount of space necessary for null characters at the end of the data. - switch( meta.c_type ) { - case SQL_C_WCHAR: - extra = sizeof( SQLWCHAR ); - break; - case SQL_C_BINARY: - extra = 0; - break; - case SQL_C_CHAR: - extra = sizeof( SQLCHAR ); - break; - default: - SQLSRV_ASSERT( false, "Invalid type in read_lob_field" ); - break; - } - - SQLLEN already_read = 0; - SQLLEN to_read = INITIAL_FIELD_STRING_LEN; - sqlsrv_malloc_auto_ptr buffer; - buffer = static_cast( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN ))); - SQLRETURN r = SQL_SUCCESS; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLLEN last_field_len = 0; - bool full_length_returned = false; - - do { - - - output_buffer_len = reinterpret_cast( buffer.get() ); - r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ), - to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC ); - - // if the field is NULL, then return a NULL pointer - if( last_field_len == SQL_NULL_DATA ) { - return NULL; - } - - // if the last read was successful, we're done - if( r == SQL_SUCCESS ) { - // check to make sure we haven't overflown our memory limit - CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - break; - } - // else if it wasn't the truncated warning (01004) then we're done - else if( r == SQL_SUCCESS_WITH_INFO ) { - SQLSMALLINT len; - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - - if( !is_truncated_warning( state )) { - break; - } - } - - SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" ); - - // if the type of the field returns the total to be read, we use that and preallocate the buffer - if( last_field_len != SQL_NO_TOTAL ) { - - CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - - already_read += to_read - already_read; - to_read = last_field_len; - buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); - // record the size of the field since we have it available - *output_buffer_len = last_field_len; - full_length_returned = true; - } - // otherwise allocate another chunk of memory to read in - else { - already_read += to_read - already_read; - to_read *= 2; - CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt, - SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { - - throw core::CoreException(); - } - buffer.resize( to_read + extra + sizeof( SQLULEN )); - output_buffer_len = reinterpret_cast( buffer.get() ); - } - - } while( true ); - - SQLSRV_ASSERT( output_buffer_len != NULL, "Output buffer not allocated properly" ); - - // most LOB field types return the total length in the last_field_len, but some field types such as XML - // only return the amount read on the last read - if( !full_length_returned ) { - *output_buffer_len = already_read + last_field_len; - } - - char* return_buffer = buffer; - buffer.transferred(); - return return_buffer; -} - -} diff --git a/sqlsrv/core_stream.cpp b/sqlsrv/core_stream.cpp deleted file mode 100644 index b85c63ce7..000000000 --- a/sqlsrv/core_stream.cpp +++ /dev/null @@ -1,261 +0,0 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: core_stream.cpp -// -// Contents: Implementation of PHP streams for reading SQL Server data -// -// Microsoft Drivers 4.1 for PHP for SQL Server -// Copyright(c) Microsoft Corporation -// All rights reserved. -// MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//--------------------------------------------------------------------------------------------------------------------------------- - -#include "core_sqlsrv.h" -#include - -namespace { - -// close a stream and free the PHP resources used by it - -int sqlsrv_stream_close( php_stream* stream, int /*close_handle*/ TSRMLS_DC ) -{ - sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); - - // free the stream resources in the Zend engine - php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM ); - - // UNDEF the stream zval and delete our reference count to it. - ZVAL_UNDEF( &( ss->stmt->active_stream ) ); - - sqlsrv_free( ss ); - stream->abstract = NULL; - - return 0; -} - - -// read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are -// set when sqlsrv_get_field is called by the user specifying which field type they want. - -size_t sqlsrv_stream_read( php_stream* stream, _Out_writes_bytes_(count) char* buf, size_t count TSRMLS_DC ) -{ - SQLLEN read = 0; - SQLSMALLINT c_type = SQL_C_CHAR; - char* get_data_buffer = buf; - sqlsrv_malloc_auto_ptr temp_buf; - - sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); - - try { - - if( stream->eof ) { - return 0; - }; - - switch( ss->encoding ) { - case SQLSRV_ENCODING_CHAR: - c_type = SQL_C_CHAR; - break; - - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - break; - - case CP_UTF8: - { - c_type = SQL_C_WCHAR; - count /= 2; // divide the number of bytes we read by 2 since converting to UTF-8 can cause an increase in bytes - if( count > PHP_STREAM_BUFFER_SIZE ) { - count = PHP_STREAM_BUFFER_SIZE; - } - - // use a temporary buffer to retrieve from SQLGetData since we need to translate it to UTF-8 from UTF-16 - temp_buf = static_cast( sqlsrv_malloc( PHP_STREAM_BUFFER_SIZE )); - get_data_buffer = temp_buf; - break; - } - - default: - DIE( "Unknown encoding type when reading from a stream" ); - break; - } - - SQLRETURN r = SQLGetData( ss->stmt->handle(), ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read ); - - CHECK_SQL_ERROR( r, ss->stmt ) { - stream->eof = 1; - throw core::CoreException(); - } - - // if the stream returns either no data, NULL data, or returns data < than the count requested then - // we are at the "end of the stream" so we mark it - if( r == SQL_NO_DATA || read == SQL_NULL_DATA || ( static_cast( read ) <= count && read != SQL_NO_TOTAL )) { - stream->eof = 1; - } - - // if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator - // if it's not a binary encoded field - if( r == SQL_SUCCESS_WITH_INFO ) { - - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; - SQLSMALLINT len; - - ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); - - if( read == SQL_NO_TOTAL ) { - SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it " - "did not occur." ); - } - - if( is_truncated_warning( state ) ) { - switch( c_type ) { - - // As per SQLGetData documentation, if the length of character data exceeds the BufferLength, - // SQLGetData truncates the data to BufferLength less the length of null-termination character. - case SQL_C_BINARY: - read = count; - break; - case SQL_C_WCHAR: - read = ( count % 2 == 0 ? count - 2 : count - 3 ); - break; - case SQL_C_CHAR: - read = count - 1; - break; - default: - DIE( "sqlsrv_stream_read: should have never reached in this switch case."); - break; - } - } - else { - CHECK_SQL_WARNING( r, ss->stmt ); - } - } - - // if the encoding is UTF-8 - if (c_type == SQL_C_WCHAR) { - - count *= 2; // undo the shift to use the full buffer - - // flags set to 0 by default, which means that any invalid characters are dropped rather than causing - // an error. This happens only on XP. - DWORD flags = 0; - - // convert to UTF-8 - if (g_osversion.dwMajorVersion >= SQLSRV_OS_VISTA_OR_LATER) { - // Vista (and later) will detect invalid UTF-16 characters and raise an error. - flags = WC_ERR_INVALID_CHARS; - } - - if ( count > INT_MAX || (read >> 1) > INT_MAX ) - { - LOG(SEV_ERROR, "UTF-16 (wide character) string mapping: buffer length exceeded."); - throw core::CoreException(); - } - - int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast( temp_buf.get() ), - static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); - - if( enc_len == 0 ) { - - stream->eof = 1; - THROW_CORE_ERROR( ss->stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ); - } - - read = enc_len; - } - - return read; - } - - catch( core::CoreException& ) { - - return 0; - } - catch( ... ) { - - LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." ); - return 0; - } -} - -// function table for stream operations. We only support reading and closing the stream -php_stream_ops sqlsrv_stream_ops = { - NULL, - sqlsrv_stream_read, - sqlsrv_stream_close, - NULL, - SQLSRV_STREAM, - NULL, - NULL, - NULL, - NULL -}; - -// open a stream and return the sqlsrv_stream_ops function table as part of the -// return value. There is only one valid way to open a stream, using sqlsrv_get_field on -// certain field types. A sqlsrv stream may only be opened in read mode. -static php_stream* sqlsrv_stream_opener( php_stream_wrapper* wrapper, _In_ const char*, _In_ const char* mode, - int options, _In_ zend_string **, php_stream_context* STREAMS_DC TSRMLS_DC ) -{ - -#if ZEND_DEBUG - SQLSRV_UNUSED( __zend_orig_lineno ); - SQLSRV_UNUSED( __zend_orig_filename ); - SQLSRV_UNUSED( __zend_lineno ); - SQLSRV_UNUSED( __zend_filename ); - SQLSRV_UNUSED( __php_stream_call_depth ); -#endif - - sqlsrv_malloc_auto_ptr ss; - - ss = static_cast( sqlsrv_malloc( sizeof( sqlsrv_stream ))); - memset( ss, 0, sizeof( sqlsrv_stream )); - - // check for valid options - if( options != REPORT_ERRORS ) { - php_stream_wrapper_log_error( wrapper, options TSRMLS_CC, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" ); - return NULL; - } - - // allocate the stream from PHP - php_stream* php_str = php_stream_alloc( &sqlsrv_stream_ops, ss, 0, mode ); - if( php_str != NULL ) { - ss.transferred(); - } - - return php_str; -} - -// information structure that contains PHP stream wrapper info. We supply the minimal -// possible, including the open function and the name only. - -php_stream_wrapper_ops sqlsrv_stream_wrapper_ops = { - sqlsrv_stream_opener, - NULL, - NULL, - NULL, - NULL, - SQLSRV_STREAM_WRAPPER, - NULL, - NULL, - NULL, - NULL -}; - -} - -// structure used by PHP to get the function table for opening, closing, etc. of the stream -php_stream_wrapper g_sqlsrv_stream_wrapper = { - &sqlsrv_stream_wrapper_ops, - NULL, - 0 -}; diff --git a/sqlsrv/msodbcsql.h b/sqlsrv/msodbcsql.h deleted file mode 100644 index 8bcf83d0a..000000000 --- a/sqlsrv/msodbcsql.h +++ /dev/null @@ -1,2343 +0,0 @@ -//----------------------------------------------------------------------------- -// File: msodbcsql.h -// -// Copyright: Copyright (c) Microsoft Corporation -// -// Contents: ODBC driver for SQL Server specific definitions. -// -//----------------------------------------------------------------------------- -#ifndef __msodbcsql_h__ -#define __msodbcsql_h__ - -#if !defined(SQLODBC_VER) -#define SQLODBC_VER 1100 -#endif - -#if SQLODBC_VER >= 1100 - -#define SQLODBC_PRODUCT_NAME_FULL_VER_ANSI "Microsoft ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_FULL_ANSI "Microsoft ODBC Driver for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI "ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_ANSI "ODBC Driver for SQL Server" - -#define SQLODBC_FILE_NAME_ANSI "msodbcsql" -#define SQLODBC_FILE_NAME_VER_ANSI "msodbcsql11" -#define SQLODBC_FILE_NAME_FULL_ANSI "msodbcsql11.dll" - -#define SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_FULL_UNICODE L"Microsoft ODBC Driver for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE L"ODBC Driver 11 for SQL Server" -#define SQLODBC_PRODUCT_NAME_SHORT_UNICODE L"ODBC Driver for SQL Server" - -#define SQLODBC_FILE_NAME_UNICODE L"msodbcsql" -#define SQLODBC_FILE_NAME_VER_UNICODE L"msodbcsql11" -#define SQLODBC_FILE_NAME_FULL_UNICODE L"msodbcsql11.dll" - -// define the character type agnostic constants -#if defined(_UNICODE) || defined(UNICODE) - -#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_UNICODE -#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_UNICODE -#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_UNICODE -#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_UNICODE - -#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_UNICODE -#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_UNICODE -#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_UNICODE - -#else // _UNICODE || UNICODE - -#define SQLODBC_PRODUCT_NAME_FULL_VER SQLODBC_PRODUCT_NAME_FULL_VER_ANSI -#define SQLODBC_PRODUCT_NAME_FULL SQLODBC_PRODUCT_NAME_FULL_ANSI -#define SQLODBC_PRODUCT_NAME_SHORT_VER SQLODBC_PRODUCT_NAME_SHORT_VER_ANSI -#define SQLODBC_PRODUCT_NAME_SHORT SQLODBC_PRODUCT_NAME_SHORT_ANSI - -#define SQLODBC_FILE_NAME SQLODBC_FILE_NAME_ANSI -#define SQLODBC_FILE_NAME_VER SQLODBC_FILE_NAME_VER_ANSI -#define SQLODBC_FILE_NAME_FULL SQLODBC_FILE_NAME_FULL_ANSI - -#endif // _UNICODE || UNICODE - -#define SQLODBC_DRIVER_NAME SQLODBC_PRODUCT_NAME_SHORT_VER - -#endif // SQLODBC_VER - -#ifndef __sqlncli_h__ - -#if !defined(SQLNCLI_VER) -#define SQLNCLI_VER 1100 -#endif - -#if SQLNCLI_VER >= 1100 - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli11" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli11.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 11.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli11" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli11.dll" - -#elif SQLNCLI_VER >= 1000 - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli10" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli10.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Server Native Client 10.0" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Server Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli10" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli10.dll" - -#else - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_FULL_ANSI "Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI "SQL Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_ANSI "SQL Native Client" - -#define SQLNCLI_FILE_NAME_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_VER_ANSI "sqlncli" -#define SQLNCLI_FILE_NAME_FULL_ANSI "sqlncli.dll" - -#define SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_FULL_UNICODE L"Microsoft SQL Server Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE L"SQL Native Client" -#define SQLNCLI_PRODUCT_NAME_SHORT_UNICODE L"SQL Native Client" - -#define SQLNCLI_FILE_NAME_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_VER_UNICODE L"sqlncli" -#define SQLNCLI_FILE_NAME_FULL_UNICODE L"sqlncli.dll" - -#endif // SQLNCLI_VER >= 1100 - -// define the character type agnostic constants -#if defined(_UNICODE) || defined(UNICODE) - -#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_UNICODE -#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_UNICODE -#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_UNICODE -#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_UNICODE - -#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_UNICODE -#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_UNICODE -#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_UNICODE - - -#else // _UNICODE || UNICODE - -#define SQLNCLI_PRODUCT_NAME_FULL_VER SQLNCLI_PRODUCT_NAME_FULL_VER_ANSI -#define SQLNCLI_PRODUCT_NAME_FULL SQLNCLI_PRODUCT_NAME_FULL_ANSI -#define SQLNCLI_PRODUCT_NAME_SHORT_VER SQLNCLI_PRODUCT_NAME_SHORT_VER_ANSI -#define SQLNCLI_PRODUCT_NAME_SHORT SQLNCLI_PRODUCT_NAME_SHORT_ANSI - -#define SQLNCLI_FILE_NAME SQLNCLI_FILE_NAME_ANSI -#define SQLNCLI_FILE_NAME_VER SQLNCLI_FILE_NAME_VER_ANSI -#define SQLNCLI_FILE_NAME_FULL SQLNCLI_FILE_NAME_FULL_ANSI - -#endif // _UNICODE || UNICODE - -#define SQLNCLI_DRIVER_NAME SQLNCLI_PRODUCT_NAME_SHORT_VER - - -#ifdef ODBCVER - -#ifdef __cplusplus -extern "C" { -#endif - -// max SQL Server identifier length -#define SQL_MAX_SQLSERVERNAME 128 - -// SQLSetConnectAttr driver specific defines. -// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage. -// Connection attributes -#define SQL_COPT_SS_BASE 1200 -#define SQL_COPT_SS_REMOTE_PWD (SQL_COPT_SS_BASE+1) // dbrpwset SQLSetConnectOption only -#define SQL_COPT_SS_USE_PROC_FOR_PREP (SQL_COPT_SS_BASE+2) // Use create proc for SQLPrepare -#define SQL_COPT_SS_INTEGRATED_SECURITY (SQL_COPT_SS_BASE+3) // Force integrated security on login -#define SQL_COPT_SS_PRESERVE_CURSORS (SQL_COPT_SS_BASE+4) // Preserve server cursors after SQLTransact -#define SQL_COPT_SS_USER_DATA (SQL_COPT_SS_BASE+5) // dbgetuserdata/dbsetuserdata -#define SQL_COPT_SS_ENLIST_IN_DTC SQL_ATTR_ENLIST_IN_DTC // Enlist in a DTC transaction -#define SQL_COPT_SS_ENLIST_IN_XA SQL_ATTR_ENLIST_IN_XA // Enlist in a XA transaction -#define SQL_COPT_SS_FALLBACK_CONNECT (SQL_COPT_SS_BASE+10) // Enables FallBack connections -#define SQL_COPT_SS_PERF_DATA (SQL_COPT_SS_BASE+11) // Used to access SQL Server ODBC driver performance data -#define SQL_COPT_SS_PERF_DATA_LOG (SQL_COPT_SS_BASE+12) // Used to set the logfile name for the Performance data -#define SQL_COPT_SS_PERF_QUERY_INTERVAL (SQL_COPT_SS_BASE+13) // Used to set the query logging threshold in milliseconds. -#define SQL_COPT_SS_PERF_QUERY_LOG (SQL_COPT_SS_BASE+14) // Used to set the logfile name for saving queryies. -#define SQL_COPT_SS_PERF_QUERY (SQL_COPT_SS_BASE+15) // Used to start and stop query logging. -#define SQL_COPT_SS_PERF_DATA_LOG_NOW (SQL_COPT_SS_BASE+16) // Used to make a statistics log entry to disk. -#define SQL_COPT_SS_QUOTED_IDENT (SQL_COPT_SS_BASE+17) // Enable/Disable Quoted Identifiers -#define SQL_COPT_SS_ANSI_NPW (SQL_COPT_SS_BASE+18) // Enable/Disable ANSI NULL, Padding and Warnings -#define SQL_COPT_SS_BCP (SQL_COPT_SS_BASE+19) // Allow BCP usage on connection -#define SQL_COPT_SS_TRANSLATE (SQL_COPT_SS_BASE+20) // Perform code page translation -#define SQL_COPT_SS_ATTACHDBFILENAME (SQL_COPT_SS_BASE+21) // File name to be attached as a database -#define SQL_COPT_SS_CONCAT_NULL (SQL_COPT_SS_BASE+22) // Enable/Disable CONCAT_NULL_YIELDS_NULL -#define SQL_COPT_SS_ENCRYPT (SQL_COPT_SS_BASE+23) // Allow strong encryption for data -#define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE+24) // Multiple active result set per connection -#define SQL_COPT_SS_FAILOVER_PARTNER (SQL_COPT_SS_BASE+25) // Failover partner server -#define SQL_COPT_SS_OLDPWD (SQL_COPT_SS_BASE+26) // Old Password, used when changing password during login -#define SQL_COPT_SS_TXN_ISOLATION (SQL_COPT_SS_BASE+27) // Used to set/get any driver-specific or ODBC-defined TXN iso level -#define SQL_COPT_SS_TRUST_SERVER_CERTIFICATE (SQL_COPT_SS_BASE+28) // Trust server certificate -#define SQL_COPT_SS_SERVER_SPN (SQL_COPT_SS_BASE+29) // Server SPN -#define SQL_COPT_SS_FAILOVER_PARTNER_SPN (SQL_COPT_SS_BASE+30) // Failover partner server SPN -#define SQL_COPT_SS_INTEGRATED_AUTHENTICATION_METHOD (SQL_COPT_SS_BASE+31) // The integrated authentication method used for the connection -#define SQL_COPT_SS_MUTUALLY_AUTHENTICATED (SQL_COPT_SS_BASE+32) // Used to decide if the connection is mutually authenticated -#define SQL_COPT_SS_CLIENT_CONNECTION_ID (SQL_COPT_SS_BASE+33) // Post connection attribute used to get the ConnectionID -// Define old names -#define SQL_REMOTE_PWD SQL_COPT_SS_REMOTE_PWD -#define SQL_USE_PROCEDURE_FOR_PREPARE SQL_COPT_SS_USE_PROC_FOR_PREP -#define SQL_INTEGRATED_SECURITY SQL_COPT_SS_INTEGRATED_SECURITY -#define SQL_PRESERVE_CURSORS SQL_COPT_SS_PRESERVE_CURSORS - -// SQLSetStmtAttr SQL Server Native Client driver specific defines. -// Statement attributes -#define SQL_SOPT_SS_BASE 1225 -#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) // Text pointer logging -#define SQL_SOPT_SS_CURRENT_COMMAND (SQL_SOPT_SS_BASE+1) // dbcurcmd SQLGetStmtOption only -#define SQL_SOPT_SS_HIDDEN_COLUMNS (SQL_SOPT_SS_BASE+2) // Expose FOR BROWSE hidden columns -#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) // Set NOBROWSETABLE option -#define SQL_SOPT_SS_REGIONALIZE (SQL_SOPT_SS_BASE+4) // Regionalize output character conversions -#define SQL_SOPT_SS_CURSOR_OPTIONS (SQL_SOPT_SS_BASE+5) // Server cursor options -#define SQL_SOPT_SS_NOCOUNT_STATUS (SQL_SOPT_SS_BASE+6) // Real vs. Not Real row count indicator -#define SQL_SOPT_SS_DEFER_PREPARE (SQL_SOPT_SS_BASE+7) // Defer prepare until necessary -#define SQL_SOPT_SS_QUERYNOTIFICATION_TIMEOUT (SQL_SOPT_SS_BASE+8) // Notification timeout -#define SQL_SOPT_SS_QUERYNOTIFICATION_MSGTEXT (SQL_SOPT_SS_BASE+9) // Notification message text -#define SQL_SOPT_SS_QUERYNOTIFICATION_OPTIONS (SQL_SOPT_SS_BASE+10)// SQL service broker name -#define SQL_SOPT_SS_PARAM_FOCUS (SQL_SOPT_SS_BASE+11)// Direct subsequent calls to parameter related methods to set properties on constituent columns/parameters of container types -#define SQL_SOPT_SS_NAME_SCOPE (SQL_SOPT_SS_BASE+12)// Sets name scope for subsequent catalog function calls -#define SQL_SOPT_SS_MAX_USED SQL_SOPT_SS_NAME_SCOPE -// Define old names -#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING -#define SQL_COPT_SS_BASE_EX 1240 -#define SQL_COPT_SS_BROWSE_CONNECT (SQL_COPT_SS_BASE_EX+1) // Browse connect mode of operation -#define SQL_COPT_SS_BROWSE_SERVER (SQL_COPT_SS_BASE_EX+2) // Single Server browse request. -#define SQL_COPT_SS_WARN_ON_CP_ERROR (SQL_COPT_SS_BASE_EX+3) // Issues warning when data from the server had a loss during code page conversion. -#define SQL_COPT_SS_CONNECTION_DEAD (SQL_COPT_SS_BASE_EX+4) // dbdead SQLGetConnectOption only. It will try to ping the server. Expensive connection check -#define SQL_COPT_SS_BROWSE_CACHE_DATA (SQL_COPT_SS_BASE_EX+5) // Determines if we should cache browse info. Used when returned buffer is greater then ODBC limit (32K) -#define SQL_COPT_SS_RESET_CONNECTION (SQL_COPT_SS_BASE_EX+6) // When this option is set, we will perform connection reset on next packet -#define SQL_COPT_SS_APPLICATION_INTENT (SQL_COPT_SS_BASE_EX+7) // Application Intent -#define SQL_COPT_SS_MULTISUBNET_FAILOVER (SQL_COPT_SS_BASE_EX+8) // Multi-subnet Failover -#define SQL_COPT_SS_EX_MAX_USED SQL_COPT_SS_MULTISUBNET_FAILOVER - -// SQLColAttributes driver specific defines. -// SQLSetDescField/SQLGetDescField driver specific defines. -// Microsoft has 1200 thru 1249 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_CA_SS_BASE 1200 -#define SQL_CA_SS_COLUMN_SSTYPE (SQL_CA_SS_BASE+0) // dbcoltype/dbalttype -#define SQL_CA_SS_COLUMN_UTYPE (SQL_CA_SS_BASE+1) // dbcolutype/dbaltutype -#define SQL_CA_SS_NUM_ORDERS (SQL_CA_SS_BASE+2) // dbnumorders -#define SQL_CA_SS_COLUMN_ORDER (SQL_CA_SS_BASE+3) // dbordercol -#define SQL_CA_SS_COLUMN_VARYLEN (SQL_CA_SS_BASE+4) // dbvarylen -#define SQL_CA_SS_NUM_COMPUTES (SQL_CA_SS_BASE+5) // dbnumcompute -#define SQL_CA_SS_COMPUTE_ID (SQL_CA_SS_BASE+6) // dbnextrow status return -#define SQL_CA_SS_COMPUTE_BYLIST (SQL_CA_SS_BASE+7) // dbbylist -#define SQL_CA_SS_COLUMN_ID (SQL_CA_SS_BASE+8) // dbaltcolid -#define SQL_CA_SS_COLUMN_OP (SQL_CA_SS_BASE+9) // dbaltop -#define SQL_CA_SS_COLUMN_SIZE (SQL_CA_SS_BASE+10) // dbcollen -#define SQL_CA_SS_COLUMN_HIDDEN (SQL_CA_SS_BASE+11) // Column is hidden (FOR BROWSE) -#define SQL_CA_SS_COLUMN_KEY (SQL_CA_SS_BASE+12) // Column is key column (FOR BROWSE) -//#define SQL_DESC_BASE_COLUMN_NAME_OLD (SQL_CA_SS_BASE+13) // This is defined at another location. -#define SQL_CA_SS_COLUMN_COLLATION (SQL_CA_SS_BASE+14) // Column collation (only for chars) -#define SQL_CA_SS_VARIANT_TYPE (SQL_CA_SS_BASE+15) -#define SQL_CA_SS_VARIANT_SQL_TYPE (SQL_CA_SS_BASE+16) -#define SQL_CA_SS_VARIANT_SERVER_TYPE (SQL_CA_SS_BASE+17) - -// XML, CLR UDT, and table valued parameter related metadata -#define SQL_CA_SS_UDT_CATALOG_NAME (SQL_CA_SS_BASE+18) // UDT catalog name -#define SQL_CA_SS_UDT_SCHEMA_NAME (SQL_CA_SS_BASE+19) // UDT schema name -#define SQL_CA_SS_UDT_TYPE_NAME (SQL_CA_SS_BASE+20) // UDT type name -#define SQL_CA_SS_UDT_ASSEMBLY_TYPE_NAME (SQL_CA_SS_BASE+21) // Qualified name of the assembly containing the UDT class -#define SQL_CA_SS_XML_SCHEMACOLLECTION_CATALOG_NAME (SQL_CA_SS_BASE+22) // Name of the catalog that contains XML Schema collection -#define SQL_CA_SS_XML_SCHEMACOLLECTION_SCHEMA_NAME (SQL_CA_SS_BASE+23) // Name of the schema that contains XML Schema collection -#define SQL_CA_SS_XML_SCHEMACOLLECTION_NAME (SQL_CA_SS_BASE+24) // Name of the XML Schema collection -#define SQL_CA_SS_CATALOG_NAME (SQL_CA_SS_BASE+25) // Catalog name -#define SQL_CA_SS_SCHEMA_NAME (SQL_CA_SS_BASE+26) // Schema name -#define SQL_CA_SS_TYPE_NAME (SQL_CA_SS_BASE+27) // Type name - -// table valued parameter related metadata -#define SQL_CA_SS_COLUMN_COMPUTED (SQL_CA_SS_BASE+29) // column is computed -#define SQL_CA_SS_COLUMN_IN_UNIQUE_KEY (SQL_CA_SS_BASE+30) // column is part of a unique key -#define SQL_CA_SS_COLUMN_SORT_ORDER (SQL_CA_SS_BASE+31) // column sort order -#define SQL_CA_SS_COLUMN_SORT_ORDINAL (SQL_CA_SS_BASE+32) // column sort ordinal -#define SQL_CA_SS_COLUMN_HAS_DEFAULT_VALUE (SQL_CA_SS_BASE+33) // column has default value for all rows of the table valued parameter - -// sparse column related metadata -#define SQL_CA_SS_IS_COLUMN_SET (SQL_CA_SS_BASE+34) // column is a column-set column for sparse columns - -// Legacy datetime related metadata -#define SQL_CA_SS_SERVER_TYPE (SQL_CA_SS_BASE+35) // column type to send on the wire for datetime types - -#define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+36) - -// Defines returned by SQL_ATTR_CURSOR_TYPE/SQL_CURSOR_TYPE -#define SQL_CURSOR_FAST_FORWARD_ONLY 8 // Only returned by SQLGetStmtAttr/Option -// Defines for use with SQL_COPT_SS_USE_PROC_FOR_PREP -#define SQL_UP_OFF 0L // Procedures won't be used for prepare -#define SQL_UP_ON 1L // Procedures will be used for prepare -#define SQL_UP_ON_DROP 2L // Temp procedures will be explicitly dropped -#define SQL_UP_DEFAULT SQL_UP_ON -// Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only -#define SQL_IS_OFF 0L // Integrated security isn't used -#define SQL_IS_ON 1L // Integrated security is used -#define SQL_IS_DEFAULT SQL_IS_OFF -// Defines for use with SQL_COPT_SS_PRESERVE_CURSORS -#define SQL_PC_OFF 0L // Cursors are closed on SQLTransact -#define SQL_PC_ON 1L // Cursors remain open on SQLTransact -#define SQL_PC_DEFAULT SQL_PC_OFF -// Defines for use with SQL_COPT_SS_USER_DATA -#define SQL_UD_NOTSET NULL // No user data pointer set -// Defines for use with SQL_COPT_SS_TRANSLATE -#define SQL_XL_OFF 0L // Code page translation is not performed -#define SQL_XL_ON 1L // Code page translation is performed -#define SQL_XL_DEFAULT SQL_XL_ON -// Defines for use with SQL_COPT_SS_FALLBACK_CONNECT - Pre-Connect Option only -#define SQL_FB_OFF 0L // FallBack connections are disabled -#define SQL_FB_ON 1L // FallBack connections are enabled -#define SQL_FB_DEFAULT SQL_FB_OFF -// Defines for use with SQL_COPT_SS_BCP - Pre-Connect Option only -#define SQL_BCP_OFF 0L // BCP is not allowed on connection -#define SQL_BCP_ON 1L // BCP is allowed on connection -#define SQL_BCP_DEFAULT SQL_BCP_OFF -// Defines for use with SQL_COPT_SS_QUOTED_IDENT -#define SQL_QI_OFF 0L // Quoted identifiers are enable -#define SQL_QI_ON 1L // Quoted identifiers are disabled -#define SQL_QI_DEFAULT SQL_QI_ON -// Defines for use with SQL_COPT_SS_ANSI_NPW - Pre-Connect Option only -#define SQL_AD_OFF 0L // ANSI NULLs, Padding and Warnings are enabled -#define SQL_AD_ON 1L // ANSI NULLs, Padding and Warnings are disabled -#define SQL_AD_DEFAULT SQL_AD_ON -// Defines for use with SQL_COPT_SS_CONCAT_NULL - Pre-Connect Option only -#define SQL_CN_OFF 0L // CONCAT_NULL_YIELDS_NULL is off -#define SQL_CN_ON 1L // CONCAT_NULL_YIELDS_NULL is on -#define SQL_CN_DEFAULT SQL_CN_ON -// Defines for use with SQL_SOPT_SS_TEXTPTR_LOGGING -#define SQL_TL_OFF 0L // No logging on text pointer ops -#define SQL_TL_ON 1L // Logging occurs on text pointer ops -#define SQL_TL_DEFAULT SQL_TL_ON -// Defines for use with SQL_SOPT_SS_HIDDEN_COLUMNS -#define SQL_HC_OFF 0L // FOR BROWSE columns are hidden -#define SQL_HC_ON 1L // FOR BROWSE columns are exposed -#define SQL_HC_DEFAULT SQL_HC_OFF -// Defines for use with SQL_SOPT_SS_NOBROWSETABLE -#define SQL_NB_OFF 0L // NO_BROWSETABLE is off -#define SQL_NB_ON 1L // NO_BROWSETABLE is on -#define SQL_NB_DEFAULT SQL_NB_OFF -// Defines for use with SQL_SOPT_SS_REGIONALIZE -#define SQL_RE_OFF 0L // No regionalization occurs on output character conversions -#define SQL_RE_ON 1L // Regionalization occurs on output character conversions -#define SQL_RE_DEFAULT SQL_RE_OFF -// Defines for use with SQL_SOPT_SS_CURSOR_OPTIONS -#define SQL_CO_OFF 0L // Clear all cursor options -#define SQL_CO_FFO 1L // Fast-forward cursor will be used -#define SQL_CO_AF 2L // Autofetch on cursor open -#define SQL_CO_FFO_AF (SQL_CO_FFO|SQL_CO_AF) // Fast-forward cursor with autofetch -#define SQL_CO_FIREHOSE_AF 4L // Auto fetch on fire-hose cursors -#define SQL_CO_DEFAULT SQL_CO_OFF -//SQL_SOPT_SS_NOCOUNT_STATUS -#define SQL_NC_OFF 0L -#define SQL_NC_ON 1L -//SQL_SOPT_SS_DEFER_PREPARE -#define SQL_DP_OFF 0L -#define SQL_DP_ON 1L -//SQL_SOPT_SS_NAME_SCOPE -#define SQL_SS_NAME_SCOPE_TABLE 0L -#define SQL_SS_NAME_SCOPE_TABLE_TYPE 1L -#define SQL_SS_NAME_SCOPE_EXTENDED 2L -#define SQL_SS_NAME_SCOPE_SPARSE_COLUMN_SET 3L -#define SQL_SS_NAME_SCOPE_DEFAULT SQL_SS_NAME_SCOPE_TABLE -//SQL_COPT_SS_ENCRYPT -#define SQL_EN_OFF 0L -#define SQL_EN_ON 1L -//SQL_COPT_SS_TRUST_SERVER_CERTIFICATE -#define SQL_TRUST_SERVER_CERTIFICATE_NO 0L -#define SQL_TRUST_SERVER_CERTIFICATE_YES 1L -//SQL_COPT_SS_BROWSE_CONNECT -#define SQL_MORE_INFO_NO 0L -#define SQL_MORE_INFO_YES 1L -//SQL_COPT_SS_BROWSE_CACHE_DATA -#define SQL_CACHE_DATA_NO 0L -#define SQL_CACHE_DATA_YES 1L -//SQL_COPT_SS_RESET_CONNECTION -#define SQL_RESET_YES 1L -//SQL_COPT_SS_WARN_ON_CP_ERROR -#define SQL_WARN_NO 0L -#define SQL_WARN_YES 1L -//SQL_COPT_SS_MARS_ENABLED -#define SQL_MARS_ENABLED_NO 0L -#define SQL_MARS_ENABLED_YES 1L -/* SQL_TXN_ISOLATION_OPTION bitmasks */ -#define SQL_TXN_SS_SNAPSHOT 0x00000020L - -// The following are defines for SQL_CA_SS_COLUMN_SORT_ORDER -#define SQL_SS_ORDER_UNSPECIFIED 0L -#define SQL_SS_DESCENDING_ORDER 1L -#define SQL_SS_ASCENDING_ORDER 2L -#define SQL_SS_ORDER_DEFAULT SQL_SS_ORDER_UNSPECIFIED - -// Driver specific SQL data type defines. -// Microsoft has -150 thru -199 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_SS_VARIANT (-150) -#define SQL_SS_UDT (-151) -#define SQL_SS_XML (-152) -#define SQL_SS_TABLE (-153) -#define SQL_SS_TIME2 (-154) -#define SQL_SS_TIMESTAMPOFFSET (-155) - -// Local types to be used with SQL_CA_SS_SERVER_TYPE -#define SQL_SS_TYPE_DEFAULT 0L -#define SQL_SS_TYPE_SMALLDATETIME 1L -#define SQL_SS_TYPE_DATETIME 2L - -// Extended C Types range 4000 and above. Range of -100 thru 200 is reserved by Driver Manager. -#define SQL_C_TYPES_EXTENDED 0x04000L -#define SQL_C_SS_TIME2 (SQL_C_TYPES_EXTENDED+0) -#define SQL_C_SS_TIMESTAMPOFFSET (SQL_C_TYPES_EXTENDED+1) - -#ifndef SQLNCLI_NO_BCP -// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application -// and you want to exclude the BCP-related definitions in this header file. - -// SQL Server Data Type defines. -// New types for SQL 6.0 and later servers -#define SQLTEXT 0x23 -#define SQLVARBINARY 0x25 -#define SQLINTN 0x26 -#define SQLVARCHAR 0x27 -#define SQLBINARY 0x2d -#define SQLIMAGE 0x22 -#define SQLCHARACTER 0x2f -#define SQLINT1 0x30 -#define SQLBIT 0x32 -#define SQLINT2 0x34 -#define SQLINT4 0x38 -#define SQLMONEY 0x3c -#define SQLDATETIME 0x3d -#define SQLFLT8 0x3e -#define SQLFLTN 0x6d -#define SQLMONEYN 0x6e -#define SQLDATETIMN 0x6f -#define SQLFLT4 0x3b -#define SQLMONEY4 0x7a -#define SQLDATETIM4 0x3a -// New types for SQL 6.0 and later servers -#define SQLDECIMAL 0x6a -#define SQLNUMERIC 0x6c -// New types for SQL 7.0 and later servers -#define SQLUNIQUEID 0x24 -#define SQLBIGCHAR 0xaf -#define SQLBIGVARCHAR 0xa7 -#define SQLBIGBINARY 0xad -#define SQLBIGVARBINARY 0xa5 -#define SQLBITN 0x68 -#define SQLNCHAR 0xef -#define SQLNVARCHAR 0xe7 -#define SQLNTEXT 0x63 -// New types for SQL 2000 and later servers -#define SQLINT8 0x7f -#define SQLVARIANT 0x62 -// New types for SQL 2005 and later servers -#define SQLUDT 0xf0 -#define SQLXML 0xf1 -// New types for SQL 2008 and later servers -#define SQLTABLE 0xf3 -#define SQLDATEN 0x28 -#define SQLTIMEN 0x29 -#define SQLDATETIME2N 0x2a -#define SQLDATETIMEOFFSETN 0x2b -// Define old names -#define SQLDECIMALN 0x6a -#define SQLNUMERICN 0x6c -#endif // SQLNCLI_NO_BCP - -// SQL_SS_LENGTH_UNLIMITED is used to describe the max length of -// VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns -#define SQL_SS_LENGTH_UNLIMITED 0 - -// User Data Type definitions. -// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_UTYPE. -#define SQLudtBINARY 3 -#define SQLudtBIT 16 -#define SQLudtBITN 0 -#define SQLudtCHAR 1 -#define SQLudtDATETIM4 22 -#define SQLudtDATETIME 12 -#define SQLudtDATETIMN 15 -#define SQLudtDECML 24 -#define SQLudtDECMLN 26 -#define SQLudtFLT4 23 -#define SQLudtFLT8 8 -#define SQLudtFLTN 14 -#define SQLudtIMAGE 20 -#define SQLudtINT1 5 -#define SQLudtINT2 6 -#define SQLudtINT4 7 -#define SQLudtINTN 13 -#define SQLudtMONEY 11 -#define SQLudtMONEY4 21 -#define SQLudtMONEYN 17 -#define SQLudtNUM 10 -#define SQLudtNUMN 25 -#define SQLudtSYSNAME 18 -#define SQLudtTEXT 19 -#define SQLudtTIMESTAMP 80 -#define SQLudtUNIQUEIDENTIFIER 0 -#define SQLudtVARBINARY 4 -#define SQLudtVARCHAR 2 -#define MIN_USER_DATATYPE 256 -// Aggregate operator types. -// Returned by SQLColAttributes/SQL_CA_SS_COLUMN_OP. -#define SQLAOPSTDEV 0x30 // Standard deviation -#define SQLAOPSTDEVP 0x31 // Standard deviation population -#define SQLAOPVAR 0x32 // Variance -#define SQLAOPVARP 0x33 // Variance population -#define SQLAOPCNT 0x4b // Count -#define SQLAOPSUM 0x4d // Sum -#define SQLAOPAVG 0x4f // Average -#define SQLAOPMIN 0x51 // Min -#define SQLAOPMAX 0x52 // Max -#define SQLAOPANY 0x53 // Any -#define SQLAOPNOOP 0x56 // None -// SQLGetInfo driver specific defines. -// Microsoft has 1151 thru 1200 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_INFO_SS_FIRST 1199 -#define SQL_INFO_SS_NETLIB_NAMEW (SQL_INFO_SS_FIRST+0) // dbprocinfo -#define SQL_INFO_SS_NETLIB_NAMEA (SQL_INFO_SS_FIRST+1) // dbprocinfo -#define SQL_INFO_SS_MAX_USED SQL_INFO_SS_NETLIB_NAMEA -#ifdef UNICODE -#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEW -#else -#define SQL_INFO_SS_NETLIB_NAME SQL_INFO_SS_NETLIB_NAMEA -#endif - -// SQLGetDiagField driver specific defines. -// Microsoft has -1150 thru -1199 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_DIAG_SS_BASE (-1150) -#define SQL_DIAG_SS_MSGSTATE (SQL_DIAG_SS_BASE) -#define SQL_DIAG_SS_SEVERITY (SQL_DIAG_SS_BASE-1) -#define SQL_DIAG_SS_SRVNAME (SQL_DIAG_SS_BASE-2) -#define SQL_DIAG_SS_PROCNAME (SQL_DIAG_SS_BASE-3) -#define SQL_DIAG_SS_LINE (SQL_DIAG_SS_BASE-4) -// SQLGetDiagField/SQL_DIAG_DYNAMIC_FUNCTION_CODE driver specific defines. -// Microsoft has -200 thru -299 reserved for Microsoft SQL Server Native Client driver usage. -#define SQL_DIAG_DFC_SS_BASE (-200) -#define SQL_DIAG_DFC_SS_ALTER_DATABASE (SQL_DIAG_DFC_SS_BASE-0) -#define SQL_DIAG_DFC_SS_CHECKPOINT (SQL_DIAG_DFC_SS_BASE-1) -#define SQL_DIAG_DFC_SS_CONDITION (SQL_DIAG_DFC_SS_BASE-2) -#define SQL_DIAG_DFC_SS_CREATE_DATABASE (SQL_DIAG_DFC_SS_BASE-3) -#define SQL_DIAG_DFC_SS_CREATE_DEFAULT (SQL_DIAG_DFC_SS_BASE-4) -#define SQL_DIAG_DFC_SS_CREATE_PROCEDURE (SQL_DIAG_DFC_SS_BASE-5) -#define SQL_DIAG_DFC_SS_CREATE_RULE (SQL_DIAG_DFC_SS_BASE-6) -#define SQL_DIAG_DFC_SS_CREATE_TRIGGER (SQL_DIAG_DFC_SS_BASE-7) -#define SQL_DIAG_DFC_SS_CURSOR_DECLARE (SQL_DIAG_DFC_SS_BASE-8) -#define SQL_DIAG_DFC_SS_CURSOR_OPEN (SQL_DIAG_DFC_SS_BASE-9) -#define SQL_DIAG_DFC_SS_CURSOR_FETCH (SQL_DIAG_DFC_SS_BASE-10) -#define SQL_DIAG_DFC_SS_CURSOR_CLOSE (SQL_DIAG_DFC_SS_BASE-11) -#define SQL_DIAG_DFC_SS_DEALLOCATE_CURSOR (SQL_DIAG_DFC_SS_BASE-12) -#define SQL_DIAG_DFC_SS_DBCC (SQL_DIAG_DFC_SS_BASE-13) -#define SQL_DIAG_DFC_SS_DISK (SQL_DIAG_DFC_SS_BASE-14) -#define SQL_DIAG_DFC_SS_DROP_DATABASE (SQL_DIAG_DFC_SS_BASE-15) -#define SQL_DIAG_DFC_SS_DROP_DEFAULT (SQL_DIAG_DFC_SS_BASE-16) -#define SQL_DIAG_DFC_SS_DROP_PROCEDURE (SQL_DIAG_DFC_SS_BASE-17) -#define SQL_DIAG_DFC_SS_DROP_RULE (SQL_DIAG_DFC_SS_BASE-18) -#define SQL_DIAG_DFC_SS_DROP_TRIGGER (SQL_DIAG_DFC_SS_BASE-19) -#define SQL_DIAG_DFC_SS_DUMP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) -#define SQL_DIAG_DFC_SS_BACKUP_DATABASE (SQL_DIAG_DFC_SS_BASE-20) -#define SQL_DIAG_DFC_SS_DUMP_TABLE (SQL_DIAG_DFC_SS_BASE-21) -#define SQL_DIAG_DFC_SS_DUMP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) -#define SQL_DIAG_DFC_SS_BACKUP_TRANSACTION (SQL_DIAG_DFC_SS_BASE-22) -#define SQL_DIAG_DFC_SS_GOTO (SQL_DIAG_DFC_SS_BASE-23) -#define SQL_DIAG_DFC_SS_INSERT_BULK (SQL_DIAG_DFC_SS_BASE-24) -#define SQL_DIAG_DFC_SS_KILL (SQL_DIAG_DFC_SS_BASE-25) -#define SQL_DIAG_DFC_SS_LOAD_DATABASE (SQL_DIAG_DFC_SS_BASE-26) -#define SQL_DIAG_DFC_SS_RESTORE_DATABASE (SQL_DIAG_DFC_SS_BASE-26) -#define SQL_DIAG_DFC_SS_LOAD_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) -#define SQL_DIAG_DFC_SS_RESTORE_HEADERONLY (SQL_DIAG_DFC_SS_BASE-27) -#define SQL_DIAG_DFC_SS_LOAD_TABLE (SQL_DIAG_DFC_SS_BASE-28) -#define SQL_DIAG_DFC_SS_LOAD_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) -#define SQL_DIAG_DFC_SS_RESTORE_TRANSACTION (SQL_DIAG_DFC_SS_BASE-29) -#define SQL_DIAG_DFC_SS_PRINT (SQL_DIAG_DFC_SS_BASE-30) -#define SQL_DIAG_DFC_SS_RAISERROR (SQL_DIAG_DFC_SS_BASE-31) -#define SQL_DIAG_DFC_SS_READTEXT (SQL_DIAG_DFC_SS_BASE-32) -#define SQL_DIAG_DFC_SS_RECONFIGURE (SQL_DIAG_DFC_SS_BASE-33) -#define SQL_DIAG_DFC_SS_RETURN (SQL_DIAG_DFC_SS_BASE-34) -#define SQL_DIAG_DFC_SS_SELECT_INTO (SQL_DIAG_DFC_SS_BASE-35) -#define SQL_DIAG_DFC_SS_SET (SQL_DIAG_DFC_SS_BASE-36) -#define SQL_DIAG_DFC_SS_SET_IDENTITY_INSERT (SQL_DIAG_DFC_SS_BASE-37) -#define SQL_DIAG_DFC_SS_SET_ROW_COUNT (SQL_DIAG_DFC_SS_BASE-38) -#define SQL_DIAG_DFC_SS_SET_STATISTICS (SQL_DIAG_DFC_SS_BASE-39) -#define SQL_DIAG_DFC_SS_SET_TEXTSIZE (SQL_DIAG_DFC_SS_BASE-40) -#define SQL_DIAG_DFC_SS_SETUSER (SQL_DIAG_DFC_SS_BASE-41) -#define SQL_DIAG_DFC_SS_SHUTDOWN (SQL_DIAG_DFC_SS_BASE-42) -#define SQL_DIAG_DFC_SS_TRANS_BEGIN (SQL_DIAG_DFC_SS_BASE-43) -#define SQL_DIAG_DFC_SS_TRANS_COMMIT (SQL_DIAG_DFC_SS_BASE-44) -#define SQL_DIAG_DFC_SS_TRANS_PREPARE (SQL_DIAG_DFC_SS_BASE-45) -#define SQL_DIAG_DFC_SS_TRANS_ROLLBACK (SQL_DIAG_DFC_SS_BASE-46) -#define SQL_DIAG_DFC_SS_TRANS_SAVE (SQL_DIAG_DFC_SS_BASE-47) -#define SQL_DIAG_DFC_SS_TRUNCATE_TABLE (SQL_DIAG_DFC_SS_BASE-48) -#define SQL_DIAG_DFC_SS_UPDATE_STATISTICS (SQL_DIAG_DFC_SS_BASE-49) -#define SQL_DIAG_DFC_SS_UPDATETEXT (SQL_DIAG_DFC_SS_BASE-50) -#define SQL_DIAG_DFC_SS_USE (SQL_DIAG_DFC_SS_BASE-51) -#define SQL_DIAG_DFC_SS_WAITFOR (SQL_DIAG_DFC_SS_BASE-52) -#define SQL_DIAG_DFC_SS_WRITETEXT (SQL_DIAG_DFC_SS_BASE-53) -#define SQL_DIAG_DFC_SS_DENY (SQL_DIAG_DFC_SS_BASE-54) -#define SQL_DIAG_DFC_SS_SET_XCTLVL (SQL_DIAG_DFC_SS_BASE-55) -#define SQL_DIAG_DFC_SS_MERGE (SQL_DIAG_DFC_SS_BASE-56) - -// Severity codes for SQL_DIAG_SS_SEVERITY -#define EX_ANY 0 -#define EX_INFO 10 -#define EX_MAXISEVERITY EX_INFO -#define EX_MISSING 11 -#define EX_TYPE 12 -#define EX_DEADLOCK 13 -#define EX_PERMIT 14 -#define EX_SYNTAX 15 -#define EX_USER 16 -#define EX_RESOURCE 17 -#define EX_INTOK 18 -#define MAXUSEVERITY EX_INTOK -#define EX_LIMIT 19 -#define EX_CMDFATAL 20 -#define MINFATALERR EX_CMDFATAL -#define EX_DBFATAL 21 -#define EX_TABCORRUPT 22 -#define EX_DBCORRUPT 23 -#define EX_HARDWARE 24 -#define EX_CONTROL 25 -// Internal server datatypes - used when binding to SQL_C_BINARY -#ifndef MAXNUMERICLEN // Resolve ODS/DBLib conflicts -// DB-Library datatypes -#define DBMAXCHAR (8000+1) // Max length of DBVARBINARY and DBVARCHAR, etc. +1 for zero byte -#define MAXNAME (SQL_MAX_SQLSERVERNAME+1) // Max server identifier length including zero byte -#ifdef UNICODE -typedef wchar_t DBCHAR; -#else -typedef char DBCHAR; - -#endif -typedef short SQLSMALLINT; - -typedef unsigned short SQLUSMALLINT; - -typedef unsigned char DBBINARY; - -typedef unsigned char DBTINYINT; - -typedef short DBSMALLINT; - -typedef unsigned short DBUSMALLINT; - -typedef double DBFLT8; - -typedef unsigned char DBBIT; - -typedef unsigned char DBBOOL; - -typedef float DBFLT4; - -typedef DBFLT4 DBREAL; - -typedef UINT DBUBOOL; - -typedef struct dbmoney - { - LONG mnyhigh; - ULONG mnylow; - } DBMONEY; - -typedef struct dbdatetime - { - LONG dtdays; - ULONG dttime; - } DBDATETIME; - -typedef struct dbdatetime4 - { - USHORT numdays; - USHORT nummins; - } DBDATETIM4; - -typedef LONG DBMONEY4; - -#include // 8-byte structure packing - -// New Date Time Structures -// New Structure for TIME2 -typedef struct tagSS_TIME2_STRUCT -{ - SQLUSMALLINT hour; - SQLUSMALLINT minute; - SQLUSMALLINT second; - SQLUINTEGER fraction; -} SQL_SS_TIME2_STRUCT; -// New Structure for TIMESTAMPOFFSET -typedef struct tagSS_TIMESTAMPOFFSET_STRUCT -{ - SQLSMALLINT year; - SQLUSMALLINT month; - SQLUSMALLINT day; - SQLUSMALLINT hour; - SQLUSMALLINT minute; - SQLUSMALLINT second; - SQLUINTEGER fraction; - SQLSMALLINT timezone_hour; - SQLSMALLINT timezone_minute; -} SQL_SS_TIMESTAMPOFFSET_STRUCT; - -typedef struct tagDBTIME2 -{ - USHORT hour; - USHORT minute; - USHORT second; - ULONG fraction; -} DBTIME2; - -typedef struct tagDBTIMESTAMPOFFSET -{ - SHORT year; - USHORT month; - USHORT day; - USHORT hour; - USHORT minute; - USHORT second; - ULONG fraction; - SHORT timezone_hour; - SHORT timezone_minute; -} DBTIMESTAMPOFFSET; - -#include // restore original structure packing - -// Money value *10,000 -#define DBNUM_PREC_TYPE BYTE -#define DBNUM_SCALE_TYPE BYTE -#define DBNUM_VAL_TYPE BYTE - -#if (ODBCVER < 0x0300) -#define MAXNUMERICLEN 16 -typedef struct dbnumeric // Internal representation of NUMERIC data type -{ - DBNUM_PREC_TYPE precision; // Precision - DBNUM_SCALE_TYPE scale; // Scale - BYTE sign; // Sign (1 if positive, 0 if negative) - DBNUM_VAL_TYPE val[MAXNUMERICLEN];// Value -} DBNUMERIC; -typedef DBNUMERIC DBDECIMAL;// Internal representation of DECIMAL data type -#else // Use ODBC 3.0 definitions since same as DBLib -#define MAXNUMERICLEN SQL_MAX_NUMERIC_LEN -typedef SQL_NUMERIC_STRUCT DBNUMERIC; -typedef SQL_NUMERIC_STRUCT DBDECIMAL; -#endif // ODCBVER -#endif // MAXNUMERICLEN - -#ifndef INT -typedef int INT; -typedef LONG DBINT; -typedef DBINT * LPDBINT; -#ifndef _LPCBYTE_DEFINED -#define _LPCBYTE_DEFINED -typedef BYTE const* LPCBYTE; -#endif //_LPCBYTE_DEFINED -#endif // INT -/************************************************************************** -This struct is a global used for gathering statistical data on the driver. -Access to this structure is controlled via the pStatCrit; -***************************************************************************/ -typedef struct sqlperf -{ - // Application Profile Statistics - DWORD TimerResolution; - DWORD SQLidu; - DWORD SQLiduRows; - DWORD SQLSelects; - DWORD SQLSelectRows; - DWORD Transactions; - DWORD SQLPrepares; - DWORD ExecDirects; - DWORD SQLExecutes; - DWORD CursorOpens; - DWORD CursorSize; - DWORD CursorUsed; - LDOUBLE PercentCursorUsed; - LDOUBLE AvgFetchTime; - LDOUBLE AvgCursorSize; - LDOUBLE AvgCursorUsed; - DWORD SQLFetchTime; - DWORD SQLFetchCount; - DWORD CurrentStmtCount; - DWORD MaxOpenStmt; - DWORD SumOpenStmt; - // Connection Statistics - DWORD CurrentConnectionCount; - DWORD MaxConnectionsOpened; - DWORD SumConnectionsOpened; - DWORD SumConnectiontime; - LDOUBLE AvgTimeOpened; - // Network Statistics - DWORD ServerRndTrips; - DWORD BuffersSent; - DWORD BuffersRec; - DWORD BytesSent; - DWORD BytesRec; - // Time Statistics; - DWORD msExecutionTime; - DWORD msNetWorkServerTime; -} SQLPERF; -// The following are options for SQL_COPT_SS_PERF_DATA and SQL_COPT_SS_PERF_QUERY -#define SQL_PERF_START 1 // Starts the driver sampling performance data. -#define SQL_PERF_STOP 2 // Stops the counters from sampling performance data. -// The following are defines for SQL_COPT_SS_PERF_DATA_LOG -#define SQL_SS_DL_DEFAULT TEXT("STATS.LOG") -// The following are defines for SQL_COPT_SS_PERF_QUERY_LOG -#define SQL_SS_QL_DEFAULT TEXT("QUERY.LOG") -// The following are defines for SQL_COPT_SS_PERF_QUERY_INTERVAL -#define SQL_SS_QI_DEFAULT 30000 // 30,000 milliseconds - -#ifndef SQLNCLI_NO_BCP -// Define the symbol SQLNCLI_NO_BCP if you are not using BCP in your application -// and you want to exclude the BCP-related definitions in this header file. - -// ODBC BCP prototypes and defines -// Return codes -#define SUCCEED 1 -#define FAIL 0 -#define SUCCEED_ABORT 2 -#define SUCCEED_ASYNC 3 -// Transfer directions -#define DB_IN 1 // Transfer from client to server -#define DB_OUT 2 // Transfer from server to client -// bcp_control option -#define BCPMAXERRS 1 // Sets max errors allowed -#define BCPFIRST 2 // Sets first row to be copied out -#define BCPLAST 3 // Sets number of rows to be copied out -#define BCPBATCH 4 // Sets input batch size -#define BCPKEEPNULLS 5 // Sets to insert NULLs for empty input values -#define BCPABORT 6 // Sets to have bcpexec return SUCCEED_ABORT -#define BCPODBC 7 // Sets ODBC canonical character output -#define BCPKEEPIDENTITY 8 // Sets IDENTITY_INSERT on -#if SQLNCLI_VER < 1000 -#define BCP6xFILEFMT 9 // DEPRECATED: Sets 6x file format on -#endif -#define BCPHINTSA 10 // Sets server BCP hints (ANSI string) -#define BCPHINTSW 11 // Sets server BCP hints (UNICODE string) -#define BCPFILECP 12 // Sets clients code page for the file -#define BCPUNICODEFILE 13 // Sets that the file contains unicode header -#define BCPTEXTFILE 14 // Sets BCP mode to expect a text file and to detect Unicode or ANSI automatically -#define BCPFILEFMT 15 // Sets file format version -#define BCPFMTXML 16 // Sets the format file type to xml -#define BCPFIRSTEX 17 // Starting Row for BCP operation (64 bit) -#define BCPLASTEX 18 // Ending Row for BCP operation (64 bit) -#define BCPROWCOUNT 19 // Total Number of Rows Copied (64 bit) -#define BCPDELAYREADFMT 20 // Delay reading format file unil bcp_exec -// BCPFILECP values -// Any valid code page that is installed on the client can be passed plus: -#define BCPFILECP_ACP 0 // Data in file is in Windows code page -#define BCPFILECP_OEMCP 1 // Data in file is in OEM code page (default) -#define BCPFILECP_RAW (-1)// Data in file is in Server code page (no conversion) -// bcp_collen definition -#define SQL_VARLEN_DATA (-10) // Use default length for column -// BCP column format properties -#define BCP_FMT_TYPE 0x01 -#define BCP_FMT_INDICATOR_LEN 0x02 -#define BCP_FMT_DATA_LEN 0x03 -#define BCP_FMT_TERMINATOR 0x04 -#define BCP_FMT_SERVER_COL 0x05 -#define BCP_FMT_COLLATION 0x06 -#define BCP_FMT_COLLATION_ID 0x07 -// bcp_setbulkmode properties -#define BCP_OUT_CHARACTER_MODE 0x01 -#define BCP_OUT_WIDE_CHARACTER_MODE 0x02 -#define BCP_OUT_NATIVE_TEXT_MODE 0x03 -#define BCP_OUT_NATIVE_MODE 0x04 - - - -// BCP functions -DBINT SQL_API bcp_batch (HDBC); -RETCODE SQL_API bcp_bind (HDBC, LPCBYTE, INT, DBINT, LPCBYTE, INT, INT, INT); -RETCODE SQL_API bcp_colfmt (HDBC, INT, BYTE, INT, DBINT, LPCBYTE, INT, INT); -RETCODE SQL_API bcp_collen (HDBC, DBINT, INT); -RETCODE SQL_API bcp_colptr (HDBC, LPCBYTE, INT); -RETCODE SQL_API bcp_columns (HDBC, INT); -RETCODE SQL_API bcp_control (HDBC, INT, void *); -DBINT SQL_API bcp_done (HDBC); -RETCODE SQL_API bcp_exec (HDBC, LPDBINT); -RETCODE SQL_API bcp_getcolfmt (HDBC, INT, INT, void *, INT, INT *); -RETCODE SQL_API bcp_initA (HDBC, LPCSTR, LPCSTR, LPCSTR, INT); -RETCODE SQL_API bcp_initW (HDBC, LPCWSTR, LPCWSTR, LPCWSTR, INT); -RETCODE SQL_API bcp_moretext (HDBC, DBINT, LPCBYTE); -RETCODE SQL_API bcp_readfmtA (HDBC, LPCSTR); -RETCODE SQL_API bcp_readfmtW (HDBC, LPCWSTR); -RETCODE SQL_API bcp_sendrow (HDBC); -RETCODE SQL_API bcp_setbulkmode (HDBC, INT, _In_reads_bytes_(cbField) void*, INT cbField, _In_reads_bytes_(cbRow) void *, INT cbRow); -RETCODE SQL_API bcp_setcolfmt (HDBC, INT, INT, void *, INT); -RETCODE SQL_API bcp_writefmtA (HDBC, LPCSTR); -RETCODE SQL_API bcp_writefmtW (HDBC, LPCWSTR); -CHAR* SQL_API dbprtypeA (INT); -WCHAR* SQL_API dbprtypeW (INT); -CHAR* SQL_API bcp_gettypenameA (INT, DBBOOL); -WCHAR* SQL_API bcp_gettypenameW (INT, DBBOOL); - -#ifdef UNICODE -#define bcp_init bcp_initW -#define bcp_readfmt bcp_readfmtW -#define bcp_writefmt bcp_writefmtW -#define dbprtype dbprtypeW -#define bcp_gettypename bcp_gettypenameW -#define BCPHINTS BCPHINTSW -#else -#define bcp_init bcp_initA -#define bcp_readfmt bcp_readfmtA -#define bcp_writefmt bcp_writefmtA -#define dbprtype dbprtypeA -#define bcp_gettypename bcp_gettypenameA -#define BCPHINTS BCPHINTSA -#endif // UNICODE - -#endif // SQLNCLI_NO_BCP - -// The following options have been deprecated -#define SQL_FAST_CONNECT (SQL_COPT_SS_BASE+0) -// Defines for use with SQL_FAST_CONNECT - only useable before connecting -#define SQL_FC_OFF 0L // Fast connect is off -#define SQL_FC_ON 1L // Fast connect is on -#define SQL_FC_DEFAULT SQL_FC_OFF -#define SQL_COPT_SS_ANSI_OEM (SQL_COPT_SS_BASE+6) -#define SQL_AO_OFF 0L -#define SQL_AO_ON 1L -#define SQL_AO_DEFAULT SQL_AO_OFF -#define SQL_CA_SS_BASE_COLUMN_NAME SQL_DESC_BASE_COLUMN_NAME - - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // ODBCVER - - - -#ifdef __cplusplus -extern "C" { -#endif -#include - -//The following facilitates opening a handle to a SQL filestream -typedef enum _SQL_FILESTREAM_DESIRED_ACCESS { - SQL_FILESTREAM_READ = 0, - SQL_FILESTREAM_WRITE = 1, - SQL_FILESTREAM_READWRITE = 2 -} SQL_FILESTREAM_DESIRED_ACCESS; -#define SQL_FILESTREAM_OPEN_FLAG_ASYNC 0x00000001L -#define SQL_FILESTREAM_OPEN_FLAG_NO_BUFFERING 0x00000002L -#define SQL_FILESTREAM_OPEN_FLAG_NO_WRITE_THROUGH 0x00000004L -#define SQL_FILESTREAM_OPEN_FLAG_SEQUENTIAL_SCAN 0x00000008L -#define SQL_FILESTREAM_OPEN_FLAG_RANDOM_ACCESS 0x00000010L - - -HANDLE __stdcall OpenSqlFilestream ( - LPCWSTR FilestreamPath, - SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess, - ULONG OpenOptions, - _In_reads_bytes_(FilestreamTransactionContextLength) - LPBYTE FilestreamTransactionContext, - SSIZE_T FilestreamTransactionContextLength, - PLARGE_INTEGER AllocationSize); -#define FSCTL_SQL_FILESTREAM_FETCH_OLD_CONTENT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 2392, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#ifdef __cplusplus -} // extern "C" -#endif - - -#endif //__sqlncli_h__ - -#define SQL_COPT_SS_CONNECT_RETRY_COUNT (SQL_COPT_SS_BASE+34) // Post connection attribute used to get ConnectRetryCount -#define SQL_COPT_SS_CONNECT_RETRY_INTERVAL (SQL_COPT_SS_BASE+35) // Post connection attribute used to get ConnectRetryInterval -#ifdef SQL_COPT_SS_MAX_USED -#undef SQL_COPT_SS_MAX_USED -#endif // SQL_COPT_SS_MAX_USED -#define SQL_COPT_SS_MAX_USED SQL_COPT_SS_CONNECT_RETRY_INTERVAL - - -#ifndef _SQLUSERINSTANCE_H_ -#define _SQLUSERINSTANCE_H_ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Recommended buffer size to store a LocalDB connection string -#define LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE 260 - -// type definition for LocalDBCreateInstance function -typedef HRESULT __cdecl FnLocalDBCreateInstance ( - // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - _In_z_ PCWSTR wszVersion, - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -); - -// type definition for pointer to LocalDBCreateInstance function -typedef FnLocalDBCreateInstance* PFnLocalDBCreateInstance; - -// type definition for LocalDBStartInstance function -typedef HRESULT __cdecl FnLocalDBStartInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags, - // O the buffer to store the connection string to the LocalDB instance - _Out_writes_opt_z_(*lpcchSqlConnection) LPWSTR wszSqlConnection, - // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. - _Inout_opt_ LPDWORD lpcchSqlConnection -); - -// type definition for pointer to LocalDBStartInstance function -typedef FnLocalDBStartInstance* PFnLocalDBStartInstance; - -// Flags for the LocalDBFormatMessage function -#define LOCALDB_TRUNCATE_ERR_MESSAGE 0x0001L - -// type definition for LocalDBFormatMessage function -typedef HRESULT __cdecl FnLocalDBFormatMessage( - // I the LocalDB error code - _In_ HRESULT hrLocalDB, - // I Available flags: - // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, - // the error message will be truncated to fit into the buffer - _In_ DWORD dwFlags, - // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - _In_ DWORD dwLanguageId, - // O the buffer to store the LocalDB error message - _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, - // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. If the function succeeds - // contains the number of characters in the message, excluding the trailing null - _Inout_ LPDWORD lpcchMessage -); - -// type definition for function pointer to LocalDBFormatMessage function -typedef FnLocalDBFormatMessage* PFnLocalDBFormatMessage; - - -// MessageId: LOCALDB_ERROR_NOT_INSTALLED -// -// MessageText: -// -// LocalDB is not installed. -// -#define LOCALDB_ERROR_NOT_INSTALLED ((HRESULT)0x89C50116L) - -//--------------------------------------------------------------------- -// Function: LocalDBCreateInstance -// -// Description: This function will create the new LocalDB instance. -// -// Available Flags: -// No flags available. Reserved for future use. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_VERSION, if the version parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INVALID_OPERATION, if the user tries to create a default instance -// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH -// LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED, if the specified service level is not installed -// LOCALDB_ERROR_INSTANCE_FOLDER_ALREADY_EXISTS, if the instance folder already exists and is not empty -// LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION, if the specified instance already exists but with lower version -// LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER, if a folder cannot be created under %userprofile% -// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified -// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created -// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed. -// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted -// -FnLocalDBCreateInstance LocalDBCreateInstance; - -//--------------------------------------------------------------------- -// Function: LocalDBStartInstance -// -// Description: This function will start the given LocalDB instance. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_CONNECTION, if the wszSqlConnection parameter is NULL -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the buffer wszSqlConnection is too small -// LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG, if the path where instance should be stored is longer than MAX_PATH - -// LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER, if a user profile folder cannot be retrieved -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER, if a instance folder cannot be accessed -// LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY, if a instance registry cannot be accessed -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY, if an instance registry cannot be modified -// LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS, if a process for Sql Server cannot be created -// LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED, if a Sql Server process is started but Sql Server startup failed. -// LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT, if a instance configuration is corrupted -// -FnLocalDBStartInstance LocalDBStartInstance; - -// type definition for LocalDBStopInstance function -typedef HRESULT __cdecl FnLocalDBStopInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I Available flags: - // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately - // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - _In_ DWORD dwFlags, - // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately - // without waiting for LocalDB instance to stop - _In_ ULONG ulTimeout -); - -// type definition for pointer to LocalDBStopInstance function -typedef FnLocalDBStopInstance* PFnLocalDBStopInstance; - -// Flags for the StopLocalDBInstance function -#define LOCALDB_SHUTDOWN_KILL_PROCESS 0x0001L -#define LOCALDB_SHUTDOWN_WITH_NOWAIT 0x0002L - -//--------------------------------------------------------------------- -// Function: LocalDBStopInstance -// -// Description: This function will shutdown the given LocalDB instance. -// If the flag LOCALDB_SHUTDOWN_KILL_PROCESS is set, the LocalDB instance will be killed immediately. -// IF the flag LOCALDB_SHUTDOWN_WITH_NOWAIT is set, the LocalDB instance will shutdown with NOWAIT option. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_WAIT_TIMEOUT - if this function has not finished in given time -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBStopInstance LocalDBStopInstance; - -// type definition for LocalDBDeleteInstance function -typedef HRESULT __cdecl FnLocalDBDeleteInstance ( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -); - -// type definition for pointer to LocalDBDeleteInstance function -typedef FnLocalDBDeleteInstance* PFnLocalDBDeleteInstance; - -//--------------------------------------------------------------------- -// Function: LocalDBDeleteInstance -// -// Description: This function will remove the given LocalDB instance. If the given instance is running this function will -// fail with the error code LOCALDB_ERROR_INSTANCE_BUSY. -// -// Return Values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INVALID_PARAM_INSTANCE_NAME, if the instance name parameter is invalid -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_UNKNOWN_INSTANCE, if the specified instance doesn't exist -// LOCALDB_ERROR_INSTANCE_BUSY, if the given instance is running -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBDeleteInstance LocalDBDeleteInstance; - -// Function: LocalDBFormatMessage -// -// Description: This function will return the localized textual description for the given LocalDB error -// -// Available Flags: -// LOCALDB_TRUNCATE_ERR_MESSAGE - the error message should be truncated to fit into the provided buffer -// -// Return Value: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_UNKNOWN_HRESULT, if the given HRESULT is unknown -// LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID, if the given language id is unknown (0 is recommended for the // default language) -// LOCALDB_ERROR_UNKNOWN_ERROR_CODE, if the LocalDB error code is unknown -// LOCALDB_ERROR_INVALID_PARAM_FLAGS, if the flags are invalid -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, if the input buffer is too short and LOCALDB_TRUNCATE_ERR_MESSAGE flag -// is not set -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBFormatMessage LocalDBFormatMessage; - -#define MAX_LOCALDB_INSTANCE_NAME_LENGTH 128 -#define MAX_LOCALDB_PARENT_INSTANCE_LENGTH MAX_INSTANCE_NAME - -typedef WCHAR TLocalDBInstanceName[MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1]; -typedef TLocalDBInstanceName* PTLocalDBInstanceName; - -// type definition for LocalDBGetInstances function -typedef HRESULT __cdecl FnLocalDBGetInstances( - // O buffer for a LocalDB instance names - _Out_ PTLocalDBInstanceName pInstanceNames, - // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, - // has the number of existing LocalDB instances - _Inout_ LPDWORD lpdwNumberOfInstances -); - -// type definition for pointer to LocalDBGetInstances function -typedef FnLocalDBGetInstances* PFnLocalDBGetInstances; - -// Function: LocalDBGetInstances -// -// Description: This function returns names for all existing Local DB instances -// -// Usage Example: -// DWORD dwN = 0; -// LocalDBGetInstances(NULL, &dwN); - -// PTLocalDBInstanceName insts = (PTLocalDBInstanceName) malloc(dwN * sizeof(TLocalDBInstanceName)); -// LocalDBGetInstances(insts, &dwN); - -// for (int i = 0; i < dwN; i++) -// wprintf(L"%s\n", insts[i]); -// -// Return values: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBGetInstances LocalDBGetInstances; - -// SID string format: S - Revision(1B) - Authority ID (6B) {- Sub authority ID (4B)} * max 15 sub-authorities = 1 + 1 + 3 + 1 + 15 + (1 + 10) * 15 -#define MAX_STRING_SID_LENGTH 186 - -#pragma pack(push) -#pragma pack(8) - -// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetInstanceInfo in sqluserinstance.cpp file. -// -typedef struct _LocalDBInstanceInfo -{ - DWORD cbLocalDBInstanceInfoSize; - TLocalDBInstanceName wszInstanceName; - BOOL bExists; - BOOL bConfigurationCorrupted; - BOOL bIsRunning; - DWORD dwMajor; - DWORD dwMinor; - DWORD dwBuild; - DWORD dwRevision; - FILETIME ftLastStartDateUTC; - WCHAR wszConnection[LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE]; - BOOL bIsShared; - TLocalDBInstanceName wszSharedInstanceName; - WCHAR wszOwnerSID[MAX_STRING_SID_LENGTH + 1]; - BOOL bIsAutomatic; -} LocalDBInstanceInfo; - -#pragma pack(pop) - -typedef LocalDBInstanceInfo* PLocalDBInstanceInfo; - -// type definition for LocalDBGetInstanceInfo function -typedef HRESULT __cdecl FnLocalDBGetInstanceInfo( - // I the LocalDB instance name - _In_z_ PCWSTR wszInstanceName, - // O instance information - _Out_ PLocalDBInstanceInfo pInfo, - // I Size of LocalDBInstanceInfo structure in bytes - _In_ DWORD cbInfo); - -// type definition for pointer to LocalDBGetInstances function -typedef FnLocalDBGetInstanceInfo* PFnLocalDBGetInstanceInfo; - -// Function: LocalDBGetInstanceInfo -// -// Description: This function returns information about the given instance. -// -// Return values: -// S_OK, if the function succeeds -// -// ERROR_INVALID_PARAMETER, if some of the parameters is invalid -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurred. See event log for details -// -FnLocalDBGetInstanceInfo LocalDBGetInstanceInfo; - -// Version has format: Major.Minor[.Build[.Revision]]. Each of components is 32bit integer which is at most 40 digits and 3 dots -// -#define MAX_LOCALDB_VERSION_LENGTH 43 - -typedef WCHAR TLocalDBVersion[MAX_LOCALDB_VERSION_LENGTH + 1]; -typedef TLocalDBVersion* PTLocalDBVersion; - -// type definition for LocalDBGetVersions function -typedef HRESULT __cdecl FnLocalDBGetVersions( - // O buffer for installed LocalDB versions - _Out_ PTLocalDBVersion pVersions, - // I/O on input has the number slots for versions in the pVersions buffer. On output, - // has the number of existing LocalDB versions - _Inout_ LPDWORD lpdwNumberOfVersions -); - -// type definition for pointer to LocalDBGetVersions function -typedef FnLocalDBGetVersions* PFnLocalDBGetVersions; - -// Function: LocalDBGetVersions -// -// Description: This function returns all installed LocalDB versions. Returned versions will be in format Major.Minor -// -// Usage Example: -// DWORD dwN = 0; -// LocalDBGetVersions(NULL, &dwN); - -// PTLocalDBVersion versions = (PTLocalDBVersion) malloc(dwN * sizeof(TLocalDBVersion)); -// LocalDBGetVersions(insts, &dwN); - -// for (int i = 0; i < dwN; i++) -// wprintf(L"%s\n", insts[i]); -// -// Return values: -// S_OK, if the function succeeds -// -// LOCALDB_ERROR_INSUFFICIENT_BUFFER, the given buffer is to small -// LOCALDB_ERROR_INTERNAL_ERROR, if an unexpected error occurs. -// -FnLocalDBGetVersions LocalDBGetVersions; - -#pragma pack(push) -#pragma pack(8) - -// DEVNOTE: If you want to modify this structure please read DEVNOTEs on top of function LocalDBGetVersionInfo in sqluserinstance.cpp file. -// -typedef struct _LocalDBVersionInfo -{ - DWORD cbLocalDBVersionInfoSize; - TLocalDBVersion wszVersion; - BOOL bExists; - DWORD dwMajor; - DWORD dwMinor; - DWORD dwBuild; - DWORD dwRevision; -} LocalDBVersionInfo; - -#pragma pack(pop) - -typedef LocalDBVersionInfo* PLocalDBVersionInfo; - -// type definition for LocalDBGetVersionInfo function -typedef HRESULT __cdecl FnLocalDBGetVersionInfo( - // I LocalDB version string - _In_z_ PCWSTR wszVersion, - // O version information - _Out_ PLocalDBVersionInfo pVersionInfo, - // I Size of LocalDBVersionInfo structure in bytes - _In_ DWORD cbVersionInfo -); - -// type definition for pointer to LocalDBGetVersionInfo function -typedef FnLocalDBGetVersionInfo* PFnLocalDBGetVersionInfo; - -// Function: LocalDBGetVersionInfo -// -// Description: This function returns information about the given LocalDB version -// -// Return values: -// S_OK, if the function succeeds -// LOCALDB_ERROR_INTERNAL_ERROR, if some internal error occurred -// LOCALDB_ERROR_INVALID_PARAMETER, if a input parameter is invalid -// -FnLocalDBGetVersionInfo LocalDBGetVersionInfo; - -typedef HRESULT __cdecl FnLocalDBStartTracing(); -typedef FnLocalDBStartTracing* PFnLocalDBStartTracing; - -// Function: LocalDBStartTracing -// -// Description: This function will write in registry that Tracing sessions should be started for the current user. -// -// Return values: -// S_OK - on success -// Propper HRESULT in case of failure -// -FnLocalDBStartTracing LocalDBStartTracing; - -typedef HRESULT __cdecl FnLocalDBStopTracing(); -typedef FnLocalDBStopTracing* PFnFnLocalDBStopTracing; - -// Function: LocalDBStopTracing -// -// Description: This function will write in registry that Tracing sessions should be stopped for the current user. -// -// Return values: -// S_OK - on success -// Propper HRESULT in case of failure -// -FnLocalDBStopTracing LocalDBStopTracing; - -// type definition for LocalDBShareInstance function -typedef HRESULT __cdecl FnLocalDBShareInstance( - // I the SID of the LocalDB instance owner - _In_opt_ PSID pOwnerSID, - // I the private name of LocalDB instance which should be shared - _In_z_ PCWSTR wszPrivateLocalDBInstanceName, - // I the public shared name - _In_z_ PCWSTR wszSharedName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags); - -// type definition for pointer to LocalDBShareInstance function -typedef FnLocalDBShareInstance* PFnLocalDBShareInstance; - -// Function: LocalDBShareInstance -// -// Description: This function will share the given private instance of the given user with the given shared name. -// This function has to be executed elevated. -// -// Return values: -// HRESULT -// -FnLocalDBShareInstance LocalDBShareInstance; - -// type definition for LocalDBUnshareInstance function -typedef HRESULT __cdecl FnLocalDBUnshareInstance( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags); - -// type definition for pointer to LocalDBUnshareInstance function -typedef FnLocalDBUnshareInstance* PFnLocalDBUnshareInstance; - -// Function: LocalDBUnshareInstance -// -// Description: This function unshares the given LocalDB instance. -// If a shared name is given then that shared instance will be unshared. -// If a private name is given then we will check if the caller -// shares a private instance with the given private name and unshare it. -// -// Return values: -// HRESULT -// -FnLocalDBUnshareInstance LocalDBUnshareInstance; - -#ifdef __cplusplus -} // extern "C" -#endif - -#if defined(LOCALDB_DEFINE_PROXY_FUNCTIONS) -//--------------------------------------------------------------------- -// The following section is enabled only if the constant LOCALDB_DEFINE_PROXY_FUNCTIONS -// is defined. It provides an implementation of proxies for each of the LocalDB APIs. -// The proxy implementations use a common function to bind to entry points in the -// latest installed SqlUserInstance DLL, and then forward the requests. -// -// The current implementation loads the SqlUserInstance DLL on the first call into -// a proxy function. There is no provision for unloading the DLL. Note that if the -// process includes multiple binaries (EXE and one or more DLLs), each of them could -// load a separate instance of the SqlUserInstance DLL. -// -// For future consideration: allow the SqlUserInstance DLL to be unloaded dynamically. -// -// WARNING: these functions must not be called in DLL initialization, since a deadlock -// could result loading dependent DLLs. -//--------------------------------------------------------------------- - -// This macro provides the body for each proxy function. -// -#define LOCALDB_PROXY(LocalDBFn) static Fn##LocalDBFn* pfn##LocalDBFn = NULL; if (!pfn##LocalDBFn) {HRESULT hr = LocalDBGetPFn(#LocalDBFn, (FARPROC *)&pfn##LocalDBFn); if (FAILED(hr)) return hr;} return (*pfn##LocalDBFn) - -// Structure and function to parse the "Installed Versions" registry subkeys -// -typedef struct { - DWORD dwComponent[2]; - WCHAR wszKeyName[256]; -} Version; - -// The following algorithm is intended to match, in part, the .NET Version class. -// A maximum of two components are allowed, which must be separated with a period. -// Valid: "11", "11.0" -// Invalid: "", ".0", "11.", "11.0." -// -static BOOL ParseVersion(Version * pVersion) -{ - pVersion->dwComponent[0] = 0; - pVersion->dwComponent[1] = 0; - WCHAR * pwch = pVersion->wszKeyName; - - for (int i = 0; i<2; i++) - { - LONGLONG llVal = 0; - BOOL fHaveDigit = FALSE; - - while (*pwch >= L'0' && *pwch <= L'9') - { - llVal = llVal * 10 + (*pwch++ - L'0'); - fHaveDigit = TRUE; - - if (llVal > 0x7fffffff) - { - return FALSE; - } - } - - if (!fHaveDigit) - return FALSE; - - pVersion->dwComponent[i] = (DWORD) llVal; - - if (*pwch == L'\0') - return TRUE; - - if (*pwch != L'.') - return FALSE; - - pwch++; - } - // If we get here, the version string was terminated with L'.', which is not valid - // - return FALSE; -} - -#include - -// This function loads the correct LocalDB API DLL (if required) and returns a pointer to a procedure. -// Note that the first-loaded API DLL for the process will be used until process termination: installation of -// a new version of the API will not be recognized after first load. -// -static HRESULT LocalDBGetPFn(LPCSTR szLocalDBFn, FARPROC *pfnLocalDBFn) -{ - static volatile HMODULE hLocalDBDll = NULL; - - if (!hLocalDBDll) - { - LONG ec; - HKEY hkeyVersions = NULL; - HKEY hkeyVersion = NULL; - Version verHigh = {0}; - Version verCurrent; - DWORD cchKeyName; - DWORD dwValueType; - WCHAR wszLocalDBDll[MAX_PATH+1]; - DWORD cbLocalDBDll = sizeof(wszLocalDBDll) - sizeof(WCHAR); // to deal with RegQueryValueEx null-termination quirk - HMODULE hLocalDBDllTemp = NULL; - - if (ERROR_SUCCESS != (ec = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions", 0, KEY_READ, &hkeyVersions))) - { - goto Cleanup; - } - - for (int i = 0; ; i++) - { - cchKeyName = 256; - if (ERROR_SUCCESS != (ec = RegEnumKeyExW(hkeyVersions, i, verCurrent.wszKeyName, &cchKeyName, 0, NULL, NULL, NULL))) - { - if (ERROR_NO_MORE_ITEMS == ec) - { - break; - } - goto Cleanup; - } - - if (!ParseVersion(&verCurrent)) - { - continue; // invalid version syntax - } - - if (verCurrent.dwComponent[0] > verHigh.dwComponent[0] || - (verCurrent.dwComponent[0] == verHigh.dwComponent[0] && verCurrent.dwComponent[1] > verHigh.dwComponent[1])) - { - verHigh = verCurrent; - } - } - if (!verHigh.wszKeyName[0]) - { - // ec must be ERROR_NO_MORE_ITEMS here - // - assert(ec == ERROR_NO_MORE_ITEMS); - - // We will change the error code to ERROR_FILE_NOT_FOUND in order to indicate that - // LocalDB instalation is not found. Registry key "SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions" exists - // but it is empty. - // - ec = ERROR_FILE_NOT_FOUND; - goto Cleanup; - } - - if (ERROR_SUCCESS != (ec = RegOpenKeyExW(hkeyVersions, verHigh.wszKeyName, 0, KEY_READ, &hkeyVersion))) - { - goto Cleanup; - } - if (ERROR_SUCCESS != (ec = RegQueryValueExW(hkeyVersion, L"InstanceAPIPath", NULL, &dwValueType, (PBYTE) wszLocalDBDll, &cbLocalDBDll))) - { - goto Cleanup; - } - if (dwValueType != REG_SZ) - { - ec = ERROR_INVALID_DATA; - goto Cleanup; - } - // Ensure string value null-terminated - // Note that we left a spare character in the output buffer for RegQueryValueEx for this purpose - // - wszLocalDBDll[cbLocalDBDll/sizeof(WCHAR)] = L'\0'; - - hLocalDBDllTemp = LoadLibraryW(wszLocalDBDll); - if (NULL == hLocalDBDllTemp) - { - ec = GetLastError(); - goto Cleanup; - } - if (NULL == InterlockedCompareExchangePointer((volatile PVOID *)&hLocalDBDll, hLocalDBDllTemp, NULL)) - { - // We were the winner: we gave away our DLL handle - // - hLocalDBDllTemp = NULL; - } - ec = ERROR_SUCCESS; -Cleanup: - if (hLocalDBDllTemp) - FreeLibrary(hLocalDBDllTemp); - if (hkeyVersion) - RegCloseKey(hkeyVersion); - if (hkeyVersions) - RegCloseKey(hkeyVersions); - - // Error code ERROR_FILE_NOT_FOUND can occure if registry hive with installed LocalDB versions is missing. - // In that case we should return the LocalDB specific error code - // - if (ec == ERROR_FILE_NOT_FOUND) - return LOCALDB_ERROR_NOT_INSTALLED; - - if (ec != ERROR_SUCCESS) - return HRESULT_FROM_WIN32(ec); - } - - FARPROC pfn = GetProcAddress(hLocalDBDll, szLocalDBFn); - - if (!pfn) - { - return HRESULT_FROM_WIN32(GetLastError()); - } - *pfnLocalDBFn = pfn; - return S_OK; -} - -// The following proxy functions forward calls to the latest LocalDB API DLL. -// - -HRESULT __cdecl -LocalDBCreateInstance ( - // I the LocalDB version (e.g. 11.0 or 11.0.1094.2) - _In_z_ PCWSTR wszVersion, - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -) -{ - LOCALDB_PROXY(LocalDBCreateInstance)(wszVersion, pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBStartInstance( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags, - // O the buffer to store the connection string to the LocalDB instance - _Out_writes_z__opt(*lpcchSqlConnection) LPWSTR wszSqlConnection, - // I/O on input has the size of the wszSqlConnection buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. - _Inout_opt_ LPDWORD lpcchSqlConnection -) -{ - LOCALDB_PROXY(LocalDBStartInstance)(pInstanceName, dwFlags, wszSqlConnection, lpcchSqlConnection); -} - -HRESULT __cdecl -LocalDBStopInstance ( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // I Available flags: - // LOCALDB_SHUTDOWN_KILL_PROCESS - force the instance to stop immediately - // LOCALDB_SHUTDOWN_WITH_NOWAIT - shutdown the instance with NOWAIT option - _In_ DWORD dwFlags, - // I the time in seconds to wait this operation to complete. If this value is 0, this function will return immediately - // without waiting for LocalDB instance to stop - _In_ ULONG ulTimeout -) -{ - LOCALDB_PROXY(LocalDBStopInstance)(pInstanceName, dwFlags, ulTimeout); -} - -HRESULT __cdecl -LocalDBDeleteInstance ( - // I the instance name - _In_z_ PCWSTR pInstanceName, - // reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags -) -{ - LOCALDB_PROXY(LocalDBDeleteInstance)(pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBFormatMessage( - // I the LocalDB error code - _In_ HRESULT hrLocalDB, - // I Available flags: - // LOCALDB_TRUNCATE_ERR_MESSAGE - if the input buffer is too short, - // the error message will be truncated to fit into the buffer - _In_ DWORD dwFlags, - // I Language desired (LCID) or 0 (in which case Win32 FormatMessage order is used) - _In_ DWORD dwLanguageId, - // O the buffer to store the LocalDB error message - _Out_writes_z_(*lpcchMessage) LPWSTR wszMessage, - // I/O on input has the size of the wszMessage buffer in characters. On output, if the given buffer size is - // too small, has the buffer size required, in characters, including trailing null. If the function succeeds - // contains the number of characters in the message, excluding the trailing null - _Inout_ LPDWORD lpcchMessage -) -{ - LOCALDB_PROXY(LocalDBFormatMessage)(hrLocalDB, dwFlags, dwLanguageId, wszMessage, lpcchMessage); -} - -HRESULT __cdecl -LocalDBGetInstances( - // O buffer with instance names - _Out_ PTLocalDBInstanceName pInstanceNames, - // I/O on input has the number slots for instance names in the pInstanceNames buffer. On output, - // has the number of existing LocalDB instances - _Inout_ LPDWORD lpdwNumberOfInstances -) -{ - LOCALDB_PROXY(LocalDBGetInstances)(pInstanceNames, lpdwNumberOfInstances); -} - -HRESULT __cdecl -LocalDBGetInstanceInfo( - // I the instance name - _In_z_ PCWSTR wszInstanceName, - // O instance information - _Out_ PLocalDBInstanceInfo pInfo, - // I Size of LocalDBInstanceInfo structure in bytes - _In_ DWORD cbInfo -) -{ - LOCALDB_PROXY(LocalDBGetInstanceInfo)(wszInstanceName, pInfo, cbInfo); -} - -HRESULT __cdecl -LocalDBStartTracing() -{ - LOCALDB_PROXY(LocalDBStartTracing)(); -} - -HRESULT __cdecl -LocalDBStopTracing() -{ - LOCALDB_PROXY(LocalDBStopTracing)(); -} - -HRESULT __cdecl -LocalDBShareInstance( - // I the SID of the LocalDB instance owner - _In_opt_ PSID pOwnerSID, - // I the private name of LocalDB instance which should be shared - _In_z_ PCWSTR wszLocalDBInstancePrivateName, - // I the public shared name - _In_z_ PCWSTR wszSharedName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags) -{ - LOCALDB_PROXY(LocalDBShareInstance)(pOwnerSID, wszLocalDBInstancePrivateName, wszSharedName, dwFlags); -} - -HRESULT __cdecl -LocalDBGetVersions( - // O buffer for installed LocalDB versions - _Out_ PTLocalDBVersion pVersions, - // I/O on input has the number slots for versions in the pVersions buffer. On output, - // has the number of existing LocalDB versions - _Inout_ LPDWORD lpdwNumberOfVersions -) -{ - LOCALDB_PROXY(LocalDBGetVersions)(pVersions, lpdwNumberOfVersions); -} - -HRESULT __cdecl -LocalDBUnshareInstance( - // I the LocalDB instance name - _In_z_ PCWSTR pInstanceName, - // I reserved for the future use. Currently should be set to 0. - _In_ DWORD dwFlags) -{ - LOCALDB_PROXY(LocalDBUnshareInstance)(pInstanceName, dwFlags); -} - -HRESULT __cdecl -LocalDBGetVersionInfo( - // I LocalDB version string - _In_z_ PCWSTR wszVersion, - // O version information - _Out_ PLocalDBVersionInfo pVersionInfo, - // I Size of LocalDBVersionInfo structure in bytes - _In_ DWORD cbVersionInfo) -{ - LOCALDB_PROXY(LocalDBGetVersionInfo)(wszVersion, pVersionInfo, cbVersionInfo); -} - -#endif - -#endif // _SQLUSERINSTANCE_H_ - -//----------------------------------------------------------------------------- -// File: sqluserinstancemsgs.mc -// -// Copyright: Copyright (c) Microsoft Corporation -//----------------------------------------------------------------------------- -#ifndef _LOCALDB_MESSAGES_H_ -#define _LOCALDB_MESSAGES_H_ -// Header section -// -// Section with the LocalDB messages -// -// -// Values are 32 bit values laid out as follows: -// -// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 -// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 -// +-+-+-+-+-+---------------------+-------------------------------+ -// |S|R|C|N|r| Facility | Code | -// +-+-+-+-+-+---------------------+-------------------------------+ -// -// where -// -// S - Severity - indicates success/fail -// -// 0 - Success -// 1 - Fail (COERROR) -// -// R - reserved portion of the facility code, corresponds to NT's -// second severity bit. -// -// C - reserved portion of the facility code, corresponds to NT's -// C field. -// -// N - reserved portion of the facility code. Used to indicate a -// mapped NT status value. -// -// r - reserved portion of the facility code. Reserved for internal -// use. Used to indicate HRESULT values that are not status -// values, but are instead message ids for display strings. -// -// Facility - is the facility code -// -// Code - is the facility's status code -// -// -// Define the facility codes -// -#define FACILITY_LOCALDB 0x9C5 - - -// -// Define the severity codes -// -#define LOCALDB_SEVERITY_SUCCESS 0x0 -#define LOCALDB_SEVERITY_ERROR 0x2 - - -// -// MessageId: LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot create folder for the LocalDB instance at: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER ((HRESULT)0x89C50100L) - -// -// MessageId: LOCALDB_ERROR_INVALID_PARAMETER -// -// MessageText: -// -// The parameter for the LocalDB Instance API method is incorrect. Consult the API documentation. -// -#define LOCALDB_ERROR_INVALID_PARAMETER ((HRESULT)0x89C50101L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION -// -// MessageText: -// -// Unable to create the LocalDB instance with specified version. An instance with the same name already exists, but it has lower version than the specified version. -// -#define LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION ((HRESULT)0x89C50102L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER -// -// MessageText: -// -// Cannot access the user profile folder for local application data (%%LOCALAPPDATA%%). -// -#define LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER ((HRESULT)0x89C50103L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG -// -// MessageText: -// -// The full path length of the LocalDB instance folder is longer than MAX_PATH. The instance must be stored in folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG ((HRESULT)0x89C50104L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot access LocalDB instance folder: %%LOCALAPPDATA%%\Microsoft\Microsoft SQL Server Local DB\Instances\. -// -#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50105L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY -// -// MessageText: -// -// Unexpected error occurred while trying to access the LocalDB instance registry configuration. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY ((HRESULT)0x89C50106L) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_INSTANCE -// -// MessageText: -// -// The specified LocalDB instance does not exist. -// -#define LOCALDB_ERROR_UNKNOWN_INSTANCE ((HRESULT)0x89C50107L) - -// -// MessageId: LOCALDB_ERROR_INTERNAL_ERROR -// -// MessageText: -// -// Unexpected error occurred inside a LocalDB instance API method call. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_INTERNAL_ERROR ((HRESULT)0x89C50108L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY -// -// MessageText: -// -// Unexpected error occurred while trying to modify the registry configuration for the LocalDB instance. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY ((HRESULT)0x89C50109L) - -// -// MessageId: LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED -// -// MessageText: -// -// Error occurred during LocalDB instance startup: SQL Server process failed to start. -// -#define LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED ((HRESULT)0x89C5010AL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT -// -// MessageText: -// -// LocalDB instance is corrupted. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT ((HRESULT)0x89C5010BL) - -// -// MessageId: LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS -// -// MessageText: -// -// Error occurred during LocalDB instance startup: unable to create the SQL Server process. -// -#define LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS ((HRESULT)0x89C5010CL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_VERSION -// -// MessageText: -// -// The specified LocalDB version is not available on this computer. -// -#define LOCALDB_ERROR_UNKNOWN_VERSION ((HRESULT)0x89C5010DL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID -// -// MessageText: -// -// Error getting the localized error message. The language specified by 'Language ID' parameter is unknown. -// -#define LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID ((HRESULT)0x89C5010EL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_STOP_FAILED -// -// MessageText: -// -// Stop operation for LocalDB instance failed to complete within the specified time. -// -#define LOCALDB_ERROR_INSTANCE_STOP_FAILED ((HRESULT)0x89C5010FL) - -// -// MessageId: LOCALDB_ERROR_UNKNOWN_ERROR_CODE -// -// MessageText: -// -// Error getting the localized error message. The specified error code is unknown. -// -#define LOCALDB_ERROR_UNKNOWN_ERROR_CODE ((HRESULT)0x89C50110L) - -// -// MessageId: LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED -// -// MessageText: -// -// The LocalDB version available on this workstation is lower than the requested LocalDB version. -// -#define LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED ((HRESULT)0x89C50111L) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_BUSY -// -// MessageText: -// -// Requested operation on LocalDB instance cannot be performed because specified instance is currently in use. Stop the instance and try again. -// -#define LOCALDB_ERROR_INSTANCE_BUSY ((HRESULT)0x89C50112L) - -// -// MessageId: LOCALDB_ERROR_INVALID_OPERATION -// -// MessageText: -// -// Default LocalDB instances cannot be created, stopped or deleted manually. -// -#define LOCALDB_ERROR_INVALID_OPERATION ((HRESULT)0x89C50113L) - -// -// MessageId: LOCALDB_ERROR_INSUFFICIENT_BUFFER -// -// MessageText: -// -// The buffer passed to the LocalDB instance API method has insufficient size. -// -#define LOCALDB_ERROR_INSUFFICIENT_BUFFER ((HRESULT)0x89C50114L) - -// -// MessageId: LOCALDB_ERROR_WAIT_TIMEOUT -// -// MessageText: -// -// Timeout occurred inside the LocalDB instance API method. -// -#define LOCALDB_ERROR_WAIT_TIMEOUT ((HRESULT)0x89C50115L) - -// MessageId=0x0116 message id is reserved. This message ID will be used for error LOCALDB_ERROR_NOT_INSTALLED. -// This message is specific since it has to be present in SqlUserIntsnace.h because it can be returned by discovery API. -// -// -// MessageId: LOCALDB_ERROR_XEVENT_FAILED -// -// MessageText: -// -// Failed to start XEvent engine within the LocalDB Instance API. -// -#define LOCALDB_ERROR_XEVENT_FAILED ((HRESULT)0x89C50117L) - -// -// MessageId: LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED -// -// MessageText: -// -// Cannot create an automatic instance. See the Windows Application event log for error details. -// -#define LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED ((HRESULT)0x89C50118L) - -// -// MessageId: LOCALDB_ERROR_SHARED_NAME_TAKEN -// -// MessageText: -// -// Cannot create a shared instance. The specified shared instance name is already in use. -// -#define LOCALDB_ERROR_SHARED_NAME_TAKEN ((HRESULT)0x89C50119L) - -// -// MessageId: LOCALDB_ERROR_CALLER_IS_NOT_OWNER -// -// MessageText: -// -// API caller is not LocalDB instance owner. -// -#define LOCALDB_ERROR_CALLER_IS_NOT_OWNER ((HRESULT)0x89C5011AL) - -// -// MessageId: LOCALDB_ERROR_INVALID_INSTANCE_NAME -// -// MessageText: -// -// Specified LocalDB instance name is invalid. -// -#define LOCALDB_ERROR_INVALID_INSTANCE_NAME ((HRESULT)0x89C5011BL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_ALREADY_SHARED -// -// MessageText: -// -// The specified LocalDB instance is already shared with different shared name. -// -#define LOCALDB_ERROR_INSTANCE_ALREADY_SHARED ((HRESULT)0x89C5011CL) - -// -// MessageId: LOCALDB_ERROR_INSTANCE_NOT_SHARED -// -// MessageText: -// -// The specified LocalDB instance is not shared. -// -#define LOCALDB_ERROR_INSTANCE_NOT_SHARED ((HRESULT)0x89C5011DL) - -// -// MessageId: LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED -// -// MessageText: -// -// Administrator privileges are required in order to execute this operation. -// -#define LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED ((HRESULT)0x89C5011EL) - -// -// MessageId: LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES -// -// MessageText: -// -// There are too many shared instance and we cannot generate unique User Instance Name. Unshare some of the existing shared instances. -// -#define LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES ((HRESULT)0x89C5011FL) - -// -// MessageId: LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH -// -// MessageText: -// -// Cannot get a local application data path. Most probably a user profile is not loaded. If LocalDB is executed under IIS, make sure that profile loading is enabled for the current user. -// -#define LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH ((HRESULT)0x89C50120L) - -// -// MessageId: LOCALDB_ERROR_CANNOT_LOAD_RESOURCES -// -// MessageText: -// -// Cannot load resources for this DLL. Resources for this DLL should be stored in a subfolder Resources, with the same file name as this DLL and the extension ".RLL". -// -#define LOCALDB_ERROR_CANNOT_LOAD_RESOURCES ((HRESULT)0x89C50121L) - - // Detailed error descriptions -// -// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING -// -// MessageText: -// -// The "DataDirectory" registry value is missing in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING ((HRESULT)0x89C50200L) - -// -// MessageId: LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER -// -// MessageText: -// -// Cannot access LocalDB instance folder: %1 -// -#define LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER ((HRESULT)0x89C50201L) - -// -// MessageId: LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG -// -// MessageText: -// -// The "DataDirectory" registry value is too long in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG ((HRESULT)0x89C50202L) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING -// -// MessageText: -// -// The "Parent Instance" registry value is missing in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING ((HRESULT)0x89C50203L) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG -// -// MessageText: -// -// The "Parent Instance" registry value is too long in the LocalDB instance registry key: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG ((HRESULT)0x89C50204L) - -// -// MessageId: LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID -// -// MessageText: -// -// Data directory for LocalDB instance is invalid: %1 -// -#define LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID ((HRESULT)0x89C50205L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_ASSERT -// -// MessageText: -// -// LocalDB instance API: XEvent engine assert: %1 in %2:%3 (%4) -// -#define LOCALDB_EDETAIL_XEVENT_ASSERT ((HRESULT)0x89C50206L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_ERROR -// -// MessageText: -// -// LocalDB instance API: XEvent error: %1 -// -#define LOCALDB_EDETAIL_XEVENT_ERROR ((HRESULT)0x89C50207L) - -// -// MessageId: LOCALDB_EDETAIL_INSTALLATION_CORRUPTED -// -// MessageText: -// -// LocalDB installation is corrupted. Reinstall the LocalDB. -// -#define LOCALDB_EDETAIL_INSTALLATION_CORRUPTED ((HRESULT)0x89C50208L) - -// -// MessageId: LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION -// -// MessageText: -// -// LocalDB XEvent error: cannot determine %ProgramFiles% folder location. -// -#define LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION ((HRESULT)0x89C50209L) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE -// -// MessageText: -// -// LocalDB XEvent error: Cannot initialize XEvent engine. -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE ((HRESULT)0x89C5020AL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE -// -// MessageText: -// -// LocalDB XEvent error: Cannot find XEvents configuration file: %1 -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE ((HRESULT)0x89C5020BL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE -// -// MessageText: -// -// LocalDB XEvent error: Cannot configure XEvents engine with the configuration file: %1 -// HRESULT returned: %2 -// -#define LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE ((HRESULT)0x89C5020CL) - -// -// MessageId: LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG -// -// MessageText: -// -// LocalDB XEvent error: XEvents engine configuration file too long -// -#define LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG ((HRESULT)0x89C5020DL) - -// -// MessageId: LOCALDB_EDETAIL_COINITIALIZEEX_FAILED -// -// MessageText: -// -// CoInitializeEx API failed. HRESULT returned: %1 -// -#define LOCALDB_EDETAIL_COINITIALIZEEX_FAILED ((HRESULT)0x89C5020EL) - -// -// MessageId: LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID -// -// MessageText: -// -// LocalDB parent instance version is invalid: %1 -// -#define LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID ((HRESULT)0x89C5020FL) - -// -// MessageId: LOCALDB_EDETAIL_WINAPI_ERROR -// -// MessageText: -// -// Windows API call %1 returned error code: %2. Windows system error message is: %3Reported at line: %4. %5 -// -#define LOCALDB_EDETAIL_WINAPI_ERROR ((HRESULT)0xC9C50210L) - -// -// MessageId: LOCALDB_EDETAIL_UNEXPECTED_RESULT -// -// MessageText: -// -// Function %1 returned %2 at line %3. -// -#define LOCALDB_EDETAIL_UNEXPECTED_RESULT ((HRESULT)0x89C50211L) - -// -#endif // _LOCALDB_MESSAGES_H_ - -#endif //__msodbcsql_h__ diff --git a/test/output.py b/test/output.py new file mode 100644 index 000000000..bb6fa0d7f --- /dev/null +++ b/test/output.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +######################################################################################### +# +# Description: +# Requirement of python 3.4 to execute this script and required result log file are in the same location +# Run with command line without options required. Example: py output.py +# This script parse output of PHP Native Test +# +# +############################################################################################# + +import os +import stat +import re + +# This module returns either the number of test or the number of failed test +# depending on the argument you requested. +# Input: var - a single variable containing either "FAIL" or "TOTAL" +# Output: Returns a number of test/s or failed test/s +def returnCount(var): + with open(os.path.dirname(os.path.realpath(__file__)) + os.sep + logfile) as f: + num = 0 + failnum = 0 + for line in f: + if "FAIL" in line or "PASS" in line: + if ".phpt" in line: + if "FAIL" in line: + failnum += 1 + num += 1 + if var == 'total': + return str(num) + else: + return str(failnum) + +# This module prints the line that matches the expression. +# Input: inputStr - String that matches +# file - file name +# path - path of the file. +# Output: null +def readAndPrint(inputStr, file, path): + filn = open(path + os.sep + file).readlines() + for lines in filn: + if inputStr in lines: + print(lines) + +# This module returns the test file name. +# Input: line - current line of the log file +# Output: Returns the filename. +def TestFilename(line): + terminateChar = os.sep + currentPos = 0 + while True: + currentPos = currentPos - 1 + line[currentPos] + if line[currentPos] == terminateChar: + break + file = line[currentPos+1:-1] + return file + +def genXML(logfile,number): + # Generating the nativeresult.xml file. + file = open('nativeresult' + str(number) + '.xml','w') + file.write('' + os.linesep) + file.write('' + os.linesep) + file.close() + + # Extract individual test results from the log file and + # enter it in the nativeresult.xml file. + + with open(os.path.dirname(os.path.realpath(__file__)) + os.sep + logfile) as f: + num = 1 + failnum = 0 + for line in f: + file = open('nativeresult' + str(number) + '.xml','a') + if "FAIL" in line or "PASS" in line: + if ".phpt" in line: + + file.write('\t' + os.linesep) + stop_pos = result.group(1).find('[') + file.write('\t\t' + os.linesep) + file.write('\t' + os.linesep) + else: + result = re.search('PASS(.*).', line) + file.write(TestFilename(str(result.group(1))) + '-' + str(num) + '"/>' + os.linesep) + num += 1 + file.close() + + file = open('nativeresult' + str(number) + '.xml','a') + file.write('' + os.linesep) + file.close() + +def run(): + num = 1 + for f in os.listdir(os.path.dirname(os.path.realpath(__file__))): + if f.endswith("log"): + print('================================================') + print(os.path.splitext(f)[0]) + readAndPrint('Number of tests :', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests skipped ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests warned ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests failed ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Expected fail ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests passed ', f, os.path.dirname(os.path.realpath(__file__))) + print('================================================') + logfile = f + genXML(logfile,num) + num = num + 1 + + +# ------------------------------------------------------- Main Function --------------------------------------------------- + +# Display results on screen from result log file. +if __name__ == '__main__': + num = 1 + for f in os.listdir(os.path.dirname(os.path.realpath(__file__))): + if f.endswith("log"): + print('================================================') + print("\n" + os.path.splitext(f)[0] + "\n") + readAndPrint('Number of tests :', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests skipped ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests warned ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests failed ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Expected fail ', f, os.path.dirname(os.path.realpath(__file__))) + readAndPrint('Tests passed ', f, os.path.dirname(os.path.realpath(__file__))) + print('================================================') + logfile = f + genXML(logfile,num) + num = num + 1 + diff --git a/test/pdo_sqlsrv/autonomous_setup.php b/test/pdo_sqlsrv/autonomous_setup.php new file mode 100644 index 000000000..2e80f6a2b --- /dev/null +++ b/test/pdo_sqlsrv/autonomous_setup.php @@ -0,0 +1,16 @@ +"; + + + // Generate unique DB name, example: php_20160817_1471475608267 + $dbName = "php_" . date("Ymd") . "_" . round(microtime(true)*1000); + + // Generic table name example: php_20160817_1471475608267.dbo.php_firefly + $tableName = $dbName.".dbo.php_firefly"; + + // Connection options + $connectionInfo = array("UID"=>"$username", "PWD"=>"$password"); +?> diff --git a/test/pdo_sqlsrv/pdo_002_connect_app.phpt b/test/pdo_sqlsrv/pdo_002_connect_app.phpt new file mode 100644 index 000000000..fa94dcaef --- /dev/null +++ b/test/pdo_sqlsrv/pdo_002_connect_app.phpt @@ -0,0 +1,34 @@ +--TEST-- +Connection option APP name unicode +--SKIPIF-- +--FILE-- +query($query); +while ( $row = $stmt->fetch(PDO::FETCH_NUM) ){ + echo $row[0]."\n"; +} + +$stmt = $conn->query($query); +while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ){ + echo $row['']."\n"; +} + +// Free the connection +$conn=null; +echo "Done" +?> + +--EXPECTREGEX-- +APP_PoP_银河 +APP_PoP_银河 +Done diff --git a/test/pdo_sqlsrv/pdo_011_quote.phpt b/test/pdo_sqlsrv/pdo_011_quote.phpt new file mode 100644 index 000000000..46cd9b28d --- /dev/null +++ b/test/pdo_sqlsrv/pdo_011_quote.phpt @@ -0,0 +1,55 @@ +--TEST-- +Insert with quoted parameters +--SKIPIF-- + +--FILE-- +quote( $param ); + +// CREATE database +$conn->query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$query = "CREATE TABLE ".$tableName." (col1 VARCHAR(10), col2 VARCHAR(20))"; +$stmt = $conn->query($query); +if( $stmt === false ) { die(); } + +// Inserd data +$query = "INSERT INTO $tableName VALUES( ?, '1' )"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param)); + +// Inserd data +$query = "INSERT INTO $tableName VALUES( ?, ? )"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param, $param2)); + +// Query +$query = "SELECT * FROM $tableName"; +$stmt = $conn->query($query); +while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) ){ + print_r( $row['col1'] ." was inserted\n" ); +} + +// Revert the inserts +$query = "delete from $tableName where col1 = ?"; +$stmt = $conn->prepare( $query ); +$stmt->execute(array($param)); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +//free the statement and connection +$stmt=null; +$conn=null; +?> +--EXPECT-- +a ' g was inserted +a ' g was inserted + diff --git a/test/pdo_sqlsrv/pdo_012_bind_param.phpt b/test/pdo_sqlsrv/pdo_012_bind_param.phpt new file mode 100644 index 000000000..bdd786df6 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_012_bind_param.phpt @@ -0,0 +1,30 @@ +--TEST-- +uses an input/output parameter +--SKIPIF-- + +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +$dbh->query("IF OBJECT_ID('dbo.sp_ReverseString', 'P') IS NOT NULL DROP PROCEDURE dbo.sp_ReverseString"); +$dbh->query("CREATE PROCEDURE dbo.sp_ReverseString @String as VARCHAR(2048) OUTPUT as SELECT @String = REVERSE(@String)"); +$stmt = $dbh->prepare("EXEC dbo.sp_ReverseString ?"); +$string = "123456789"; +$stmt->bindParam(1, $string, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 2048); +$stmt->execute(); +print "Result: ".$string."\n"; // Expect 987654321 + +// DROP database +$dbh->query("DROP DATABASE ". $dbName) ?: die(); + +//free the statement and connection +$stmt = null; +$dbh = null; +?> +--EXPECT-- +Result: 987654321 diff --git a/test/pdo_sqlsrv/pdo_013_row_count.phpt b/test/pdo_sqlsrv/pdo_013_row_count.phpt new file mode 100644 index 000000000..fb27996c7 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_013_row_count.phpt @@ -0,0 +1,60 @@ +--TEST-- +Number of rows in a result set +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$stmt = $conn->query("CREATE TABLE ".$tableName." (c1 VARCHAR(32))"); +$stmt=null; + +// Insert data +$query = "INSERT INTO ".$tableName." VALUES ('Salmon'),('Butterfish'),('Cod'),('NULL'),('Crab')"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='Cod'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='NULL'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='Salmon' WHERE c1='NO_NAME'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +// Update data +$query = "UPDATE ".$tableName." SET c1='N/A'"; +$stmt = $conn->query($query); +$res[] = $stmt->rowCount(); + +print_r($res); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +$stmt=null; +$conn=null; +print "Done" +?> +--EXPECT-- +Array +( + [0] => 5 + [1] => 1 + [2] => 1 + [3] => 0 + [4] => 5 +) +Done diff --git a/test/pdo_sqlsrv/pdo_014_integer_custom_formats.phpt b/test/pdo_sqlsrv/pdo_014_integer_custom_formats.phpt new file mode 100644 index 000000000..9503f397e --- /dev/null +++ b/test/pdo_sqlsrv/pdo_014_integer_custom_formats.phpt @@ -0,0 +1,56 @@ +--TEST-- +Number MAX_INT to string with custom formats +--SKIPIF-- +--FILE-- +query("CREATE DATABASE $dbName") ?: die(); + +// Create table +$query = "CREATE TABLE $tableName (col1 INT)"; +$stmt = $conn->query($query); + +// Query number with custom format +$query ="SELECT CAST($sample as varchar) + '.00'"; +$stmt = $conn->query($query); +$data = $stmt->fetchColumn(); +var_dump ($data); + +// Insert data using bind parameters +$query = "INSERT INTO $tableName VALUES(:p0)"; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +// Fetching. Prepare with client buffered cursor +$query = "SELECT TOP 1 cast(col1 as varchar) + '.00 EUR' FROM $tableName"; +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, + PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +//Free the statement and connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +int(2147483647) +string(13) "2147483647.00" +string(17) "2147483647.00 EUR" +Done diff --git a/test/pdo_sqlsrv/pdo_015_integer_custom_formats_pooling.phpt b/test/pdo_sqlsrv/pdo_015_integer_custom_formats_pooling.phpt new file mode 100644 index 000000000..2a2172e73 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_015_integer_custom_formats_pooling.phpt @@ -0,0 +1,61 @@ +--TEST-- +Number MAX_INT to string with custom formats, see pdo_014. Pooling enabled. +--SKIPIF-- +--FILE-- +query("select 1"); +$conn0 = null; + +/* Connect */ +$conn = new PDO("sqlsrv:server=$serverName;ConnectionPooling=$pooling", $username, $password); + +// Create database +$conn->query("CREATE DATABASE $dbName") ?: die(); + +// Create table +$query = "CREATE TABLE $tableName (col1 INT)"; +$stmt = $conn->query($query); + +// Query number with custom format +$query ="SELECT CAST($sample as varchar) + '.00'"; +$stmt = $conn->query($query); +$data = $stmt->fetchColumn(); +var_dump ($data); + +// Insert data using bind parameters +$query = "INSERT INTO $tableName VALUES(:p0)"; +$stmt = $conn->prepare($query); +$stmt->bindValue(':p0', $sample, PDO::PARAM_INT); +$stmt->execute(); + +// Fetching. Prepare with client buffered cursor +$query = "SELECT TOP 1 cast(col1 as varchar) + '.00 EUR' FROM $tableName"; +$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, + PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); +$stmt->execute(); +$value = $stmt->fetchColumn(); +var_dump ($value); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +//Free the statement and connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +string(13) "2147483647.00" +string(17) "2147483647.00 EUR" +Done diff --git a/test/pdo_sqlsrv/pdo_016.phpt b/test/pdo_sqlsrv/pdo_016.phpt new file mode 100644 index 000000000..33441cdbd --- /dev/null +++ b/test/pdo_sqlsrv/pdo_016.phpt @@ -0,0 +1,52 @@ +--TEST-- +Bind integer parameters; allow fetch numeric types. +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (c1 INT, c2 INT)"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName VALUES (:num1, :num2)"; +$stmt = $conn->prepare($sql); +$stmt->bindParam(':num1', $sample[0], PDO::PARAM_INT); +$stmt->bindParam(':num2', $sample[1], PDO::PARAM_INT); +$stmt->execute(); + +// Fetch, get data +$sql = "SELECT * FROM $tableName"; +$stmt = $conn->query($sql); +$row = $stmt->fetch(PDO::FETCH_NUM); +var_dump ($row); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +array(2) { + [0]=> + int(-2147483648) + [1]=> + int(2147483647) +} +Done diff --git a/test/pdo_sqlsrv/pdo_017.phpt b/test/pdo_sqlsrv/pdo_017.phpt new file mode 100644 index 000000000..082f31d88 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_017.phpt @@ -0,0 +1,60 @@ +--TEST-- +Fetch string with new line and tab characters +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE ".$tableName. + " (c1 VARCHAR(32), c2 CHAR(32), c3 NVARCHAR(32), c4 NCHAR(32))"; +$stmt = $conn->query($sql); + +// Bind parameters and insert data +$sql = "INSERT INTO $tableName VALUES (:val1, :val2, :val3, :val4)"; +$value = "I USE\nMSPHPSQL\tDRIVERS WITH PHP7"; +$stmt = $conn->prepare($sql); +$stmt->bindParam(':val1', $value); +$stmt->bindParam(':val2', $value); +$stmt->bindParam(':val3', $value); +$stmt->bindParam(':val4', $value); +$stmt->execute(); + +// Get data +$sql = "SELECT UPPER(c1) AS VARCHAR, UPPER(c2) AS CHAR, + UPPER(c3) AS NVARCHAR, UPPER(c4) AS NCHAR FROM $tableName"; +$stmt = $conn->query($sql); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt=null; +$conn=null; +print "Done" +?> + +--EXPECT-- +array(4) { + ["VARCHAR"]=> + string(32) "I USE +MSPHPSQL DRIVERS WITH PHP7" + ["CHAR"]=> + string(32) "I USE +MSPHPSQL DRIVERS WITH PHP7" + ["NVARCHAR"]=> + string(32) "I USE +MSPHPSQL DRIVERS WITH PHP7" + ["NCHAR"]=> + string(32) "I USE +MSPHPSQL DRIVERS WITH PHP7" +} +Done diff --git a/test/pdo_sqlsrv/pdo_018_next_result_set.phpt b/test/pdo_sqlsrv/pdo_018_next_result_set.phpt new file mode 100644 index 000000000..647681c39 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_018_next_result_set.phpt @@ -0,0 +1,60 @@ +--TEST-- +Moves the cursor to the next result set +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (c1 INT, c2 VARCHAR(40))"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName VALUES (?,?)"; +for($t=200; $t<220; $t++) { + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $t); + $ts = sha1($t); + $stmt->bindParam(2, $ts); + $stmt->execute(); +} + +// Fetch, get data and move the cursor to the next result set +$sql = "SELECT * from $tableName WHERE c1 = '204' OR c1 = '210'; + SELECT Top 3 * FROM $tableName ORDER BY c1 DESC"; +$stmt = $conn->query($sql); +$data1 = $stmt->fetchAll(PDO::FETCH_ASSOC); +$stmt->nextRowset(); +$data2 = $stmt->fetchAll(PDO::FETCH_NUM); + +// Array: FETCH_ASSOC +foreach ($data1 as $a) +echo $a['c1'] . "|" . $a['c2'] . "\n"; + +// Array: FETCH_NUM +foreach ($data2 as $a) +echo $a[0] . "|" . $a[1] . "\n"; + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +204|1cc641954099c249e0e4ef0402da3fd0364d95f0 +210|135debd4837026bf06c7bfc5d1e0c6a31611af1d +219|c0ba17c23a26ff8c314478bc69f30963a6e4a754 +218|3d5bdf107de596ce77e8ce48a61b585f52bbb61d +217|49e3d046636e06b2d82ee046db8e6eb9a2e11e16 +Done diff --git a/test/pdo_sqlsrv/pdo_019_next_result_set_pooling.phpt b/test/pdo_sqlsrv/pdo_019_next_result_set_pooling.phpt new file mode 100644 index 000000000..9e5701596 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_019_next_result_set_pooling.phpt @@ -0,0 +1,65 @@ +--TEST-- +Moves the cursor to the next result set with pooling enabled +--SKIPIF-- +--FILE-- +query("SELECT 1"); +$conn0 = null; + +// Connect +$conn = new PDO("sqlsrv:server=$serverName;ConnectionPooling=1", $username, $password); + +// CREATE database +$conn->query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (c1 INT, c2 XML)"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName VALUES (?,?)"; +for($t=200; $t<220; $t++) { + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $t); + $ts = substr(sha1($t),0,5); + $stmt->bindParam(2, $ts); + $stmt->execute(); +} + +// Fetch, get data and move the cursor to the next result set +$sql = "SELECT * from $tableName WHERE c1 = '204' OR c1 = '210'; + SELECT Top 3 * FROM $tableName ORDER BY c1 DESC"; +$stmt = $conn->query($sql); +$data1 = $stmt->fetchAll(PDO::FETCH_ASSOC); +$stmt->nextRowset(); +$data2 = $stmt->fetchAll(PDO::FETCH_NUM); + +// Array: FETCH_ASSOC +foreach ($data1 as $a) +echo $a['c1']."|".$a['c2']."\n"; + +// Array: FETCH_NUM +foreach ($data2 as $a) +echo $a[0] . "|".$a[1]."\n"; + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +204|1cc64 +210|135de +219|c0ba1 +218|3d5bd +217|49e3d +Done diff --git a/test/pdo_sqlsrv/pdo_020_bind_params_array.phpt b/test/pdo_sqlsrv/pdo_020_bind_params_array.phpt new file mode 100644 index 000000000..05ea05a0e --- /dev/null +++ b/test/pdo_sqlsrv/pdo_020_bind_params_array.phpt @@ -0,0 +1,58 @@ +--TEST-- +Bind parameters using an array +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (ID TINYINT, SID CHAR(5))"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName VALUES (?,?)"; +for($t=100; $t<103; $t++) { + $stmt = $conn->prepare($sql); + $ts = substr(sha1($t),0,5); + $params = array($t,$ts); + $stmt->execute($params); +} + +// Query, but do not fetch +$sql = "SELECT * from $tableName"; +$stmt = $conn->query($sql); + +// Insert duplicate row, ID = 100 +$t = 100; +$sql = "INSERT INTO $tableName VALUES (?,?)"; +$stmt1 = $conn->prepare($sql); +$ts = substr(sha1($t),0,5); +$params = array($t,$ts); +$stmt1->execute($params); + +// Fetch. The result set should not contain duplicates +$data = $stmt->fetchAll(); +foreach ($data as $a) +echo $a['ID'] . "|" . $a['SID'] . "\n"; + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +100|310b8 +101|dbc0f +102|c8306 +Done diff --git a/test/pdo_sqlsrv/pdo_021_extended_ascii.phpt b/test/pdo_sqlsrv/pdo_021_extended_ascii.phpt new file mode 100644 index 000000000..6158c1b6a --- /dev/null +++ b/test/pdo_sqlsrv/pdo_021_extended_ascii.phpt @@ -0,0 +1,52 @@ +--TEST-- +Bind parameters VARCHAR(n) extended ASCII +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (code CHAR(2), city VARCHAR(32))"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +$sql = "INSERT INTO $tableName VALUES (?,?)"; + +// First row +$stmt = $conn->prepare($sql); +$params = array("FI","Järvenpää"); +$stmt->execute($params); + +// Second row +$params = array("DE","München"); +$stmt->execute($params); + +// Query, fetch +$sql = "SELECT * from $tableName"; +$stmt = $conn->query($sql); +$data = $stmt->fetchAll(); + +// Print out +foreach ($data as $a) +echo $a[0] . "|" . $a[1] . "\n"; + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +FI|Järvenpää +DE|München +Done diff --git a/test/pdo_sqlsrv/pdo_022_xml_bind_value.phpt b/test/pdo_sqlsrv/pdo_022_xml_bind_value.phpt new file mode 100644 index 000000000..6b1164a61 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_022_xml_bind_value.phpt @@ -0,0 +1,79 @@ +--TEST-- +Unicode XML message using bindValue() +--SKIPIF-- +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +// Create database +$conn->query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (ID INT PRIMARY KEY NOT NULL IDENTITY, XMLMessage XML)"; +$stmt = $conn->query($sql); + +// XML samples +$xml1 = ' + + APP_PoP_银河 + Το Παρίσι (γαλλικά: Paris, ΔΦΑ [paˈʁi]), γνωστό και ως η Πόλη του φωτός (Ville lumière), από τότε που εφοδιάστηκαν οι κύριες λεωφόροι του με φανούς γκαζιού το 1828, είναι η πρωτεύουσα της Γαλλίας και της περιφέρειας Ιλ ντε Φρανς (Île-de-France) και μία από τις ιστορικότερες πόλεις της Ευρώπης. +'; + +$xml2 = ' + + NULL + +'; + +// Insert data +try +{ + $stmt = $conn->prepare("INSERT INTO $tableName (XMLMessage) VALUES (:msg)"); + $stmt->bindValue(':msg', $xml1); + $stmt->execute(); + + $stmt = $conn->prepare("INSERT INTO $tableName (XMLMessage) VALUES (?)"); + $stmt->bindValue(1, $xml2); + $stmt->execute(); +} +catch (PDOException $ex) { + echo "Error: " . $ex->getMessage(); +} + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetchAll(PDO::FETCH_ASSOC); +var_dump($row); + +// Drop database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt=null; +$conn=null; + +print "Done" +?> + +--EXPECT-- +array(2) { + [0]=> + array(2) { + ["ID"]=> + string(1) "1" + ["XMLMessage"]=> + string(553) "APP_PoP_银河Το Παρίσι (γαλλικά: Paris, ΔΦΑ [paˈʁi]), γνωστό και ως η Πόλη του φωτός (Ville lumière), από τότε που εφοδιάστηκαν οι κύριες λεωφόροι του με φανούς γκαζιού το 1828, είναι η πρωτεύουσα της Γαλλίας και της περιφέρειας Ιλ ντε Φρανς (Île-de-France) και μία από τις ιστορικότερες πόλεις της Ευρώπης." + } + [1]=> + array(2) { + ["ID"]=> + string(1) "2" + ["XMLMessage"]=> + string(43) "NULL" + } +} +Done diff --git a/test/pdo_sqlsrv/pdo_023.phpt b/test/pdo_sqlsrv/pdo_023.phpt new file mode 100644 index 000000000..c9d634dbc --- /dev/null +++ b/test/pdo_sqlsrv/pdo_023.phpt @@ -0,0 +1,90 @@ +--TEST-- +Bind values with PDO::PARAM_BOOL, enable/disable fetch numeric type attribute +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Run test +Test(); + +// Set PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE = false (default) +$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, FALSE); +Test(); + +// Set PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE = true +$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, TRUE); +Test(); + +// Drop database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; + +// Generic test starts here +function Test() +{ + global $conn, $tableName, $sample; + + // Drop table if exists + $sql = "IF OBJECT_ID('$tableName') IS NOT NULL DROP TABLE $tableName"; + $stmt = $conn->query($sql); + + // Create table + $sql = "CREATE TABLE $tableName (c1 INT, c2 BIT)"; + $stmt = $conn->query($sql) ?: die(); + + // Insert data using bind values + $sql = "INSERT INTO $tableName VALUES (:v1, :v2)"; + foreach ($sample as $s) { + $stmt = $conn->prepare($sql); + $stmt->bindValue(':v1', $s[0], PDO::PARAM_BOOL); + $stmt->bindValue(':v2', $s[1], PDO::PARAM_BOOL); + $stmt->execute(); + } + + // Get data + $sql = "SELECT * FROM $tableName"; + $stmt = $conn->query($sql); + $row = $stmt->fetchAll(PDO::FETCH_NUM); + + // Print out + for($i=0; $i<$stmt->rowCount(); $i++) + { var_dump($row[$i][0]); var_dump($row[$i][1]); } +} +?> + +--EXPECT-- +string(1) "1" +string(1) "0" +string(1) "1" +string(1) "1" +string(1) "0" +NULL +string(1) "1" +string(1) "0" +string(1) "1" +string(1) "1" +string(1) "0" +NULL +int(1) +int(0) +int(1) +int(1) +int(0) +NULL +Done diff --git a/test/pdo_sqlsrv/pdo_040_error_information.phpt b/test/pdo_sqlsrv/pdo_040_error_information.phpt new file mode 100644 index 000000000..a9c834105 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_040_error_information.phpt @@ -0,0 +1,45 @@ +--TEST-- +Retrieve error information; supplied values does not match table definition +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (code INT)"; +$stmt = $conn->query($sql); + +// Insert data using bind parameters +// Number of supplied values does not match table definition +$sql = "INSERT INTO $tableName VALUES (?,?)"; +$stmt = $conn->prepare($sql); +$params = array(2010,"London"); + +// SQL statement has an error, which is then reported +$stmt->execute($params); +print_r($stmt->errorInfo()); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt = null; +$conn = null; + +print "Done"; +?> + +--EXPECT-- +Array +( + [0] => 21S01 + [1] => 213 + [2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Column name or number of supplied values does not match table definition. +) +Done diff --git a/test/pdo_sqlsrv/pdo_060_prepare_execute_fetch_pooling_default.phpt b/test/pdo_sqlsrv/pdo_060_prepare_execute_fetch_pooling_default.phpt new file mode 100644 index 000000000..ee2e66cb6 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_060_prepare_execute_fetch_pooling_default.phpt @@ -0,0 +1,73 @@ +--TEST-- +Prepare, execute statement and fetch with pooling unset (default) +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (Столица NVARCHAR(32), year INT)"; +$stmt = $conn->query($sql); + +// Insert data +$sql = "INSERT INTO ".$tableName." VALUES (?,?)"; +$stmt = $conn->prepare($sql); +$stmt->execute(array("Лондон",2012)); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); +$conn = null; + +// Create a new pool +$conn0 = new PDO( "sqlsrv:server=$serverName;", + $username, $password); +$conn0 = null; + +// Connection can use an existing pool +$conn = new PDO( "sqlsrv:server=$serverName;", + $username, $password); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt=null; +$conn=null; +print "Done" +?> +--EXPECT-- +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + int(2012) +} +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + string(4) "2012" +} +Done diff --git a/test/pdo_sqlsrv/pdo_061_prepare_execute_fetch_pooling_enabled.phpt b/test/pdo_sqlsrv/pdo_061_prepare_execute_fetch_pooling_enabled.phpt new file mode 100644 index 000000000..0c46ff877 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_061_prepare_execute_fetch_pooling_enabled.phpt @@ -0,0 +1,73 @@ +--TEST-- +Prepare, execute statement and fetch with pooling enabled +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (Столица NVARCHAR(32), year INT)"; +$stmt = $conn->query($sql); + +// Insert data +$sql = "INSERT INTO ".$tableName." VALUES (?,?)"; +$stmt = $conn->prepare($sql); +$stmt->execute(array("Лондон",2012)); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); +$conn = null; + +// Create a new pool +$conn0 = new PDO( "sqlsrv:server=$serverName;ConnectionPooling=1;", + $username, $password); +$conn0 = null; + +// Connection can use an existing pool +$conn = new PDO( "sqlsrv:server=$serverName;ConnectionPooling=1;", + $username, $password); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt=null; +$conn=null; +print "Done" +?> +--EXPECT-- +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + int(2012) +} +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + string(4) "2012" +} +Done diff --git a/test/pdo_sqlsrv/pdo_062_prepare_execute_fetch_pooling_disabled.phpt b/test/pdo_sqlsrv/pdo_062_prepare_execute_fetch_pooling_disabled.phpt new file mode 100644 index 000000000..dc30a7723 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_062_prepare_execute_fetch_pooling_disabled.phpt @@ -0,0 +1,73 @@ +--TEST-- +Prepare, execute statement and fetch with pooling disabled +--SKIPIF-- +--FILE-- +query("CREATE DATABASE ". $dbName) ?: die(); + +// Create table +$sql = "CREATE TABLE $tableName (Столица NVARCHAR(32), year INT)"; +$stmt = $conn->query($sql); + +// Insert data +$sql = "INSERT INTO ".$tableName." VALUES (?,?)"; +$stmt = $conn->prepare($sql); +$stmt->execute(array("Лондон",2012)); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); +$conn = null; + +// Create a new pool +$conn0 = new PDO( "sqlsrv:server=$serverName;ConnectionPooling=0;", + $username, $password); +$conn0 = null; + +// Connection can use an existing pool +$conn = new PDO( "sqlsrv:server=$serverName;ConnectionPooling=0;", + $username, $password); + +// Get data +$stmt = $conn->query("select * from $tableName"); +$row = $stmt->fetch(PDO::FETCH_ASSOC); +var_dump($row); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); + +// Close connection +$stmt=null; +$conn=null; +print "Done" +?> +--EXPECT-- +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + int(2012) +} +array(2) { + ["Столица"]=> + string(12) "Лондон" + ["year"]=> + string(4) "2012" +} +Done diff --git a/test/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt b/test/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt new file mode 100644 index 000000000..4d240b966 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt @@ -0,0 +1,58 @@ +--TEST-- +sqlsrv_has_rows() using a forward and scrollable cursor +--SKIPIF-- +--FILE-- +setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + +// Create database +$conn->query("CREATE DATABASE ". $dbName); +// Create table +$stmt = $conn->query("CREATE TABLE ".$dbName.".[dbo].[php_test_table_1] ([c1_int] int, [c2_varchar] varchar(1000))"); +$query = "INSERT INTO ".$dbName.".[dbo].[php_test_table_1] (c1_int, c2_varchar) VALUES ((990021574), ('>vh~Ö.bÐ*äß/ÄAabýZâOüzr£ðAß+|~|OU¢a|U<ßrv.uCB.ÐÜh_î+ãå@üðöã,U+ßvuU:/ý_Öãî/ð|bB|_Zbua©r++BA¢z£.üî¢öåäözÜ¢ßb:aöCrÄ~ýZ¢uªÐö.hhßð*zÜÜß*ãüåýãÄ+åýüüaߢÃÐBî@~AZöÃOßC@äoÃuCÜ,ÐÄa:îäÄÖý:h*ouªuåvUz_ArßAªãaãvÐåAUüAB:¢Äz|öub<üZvößüå:ãÄ@r/ZAÄðÄÄvzîv~C/£|ýýbüÖ~£|Öå<Üa~/v@åAz©¢£U_ßhbaÃß,zz<ã¢|<ä©>öAuövÖ>abu,zå,+ß/ü/ª_bbB:ÃC~£ü/O©O©ªAª_,|a¢~ýý/b>ßC@/böîöh>~£ð+Bßr©ÄÐÖßã:bA@:>B:UAbããîÜ~uÜ£îCöÖ£©_ÜßzÐ+ÖýZb,A:<z.ãîÄzC@©*ä|ã._ßZOäb¢Cßovå+uv.£B~~b£ª|ÖÄîßö>©Ãbb|©©ðA£åO~âãüîuvÄÜýUzîOÖ/oOßO*>ªßzêÖÐböÄåbîðîÐa~©ßîÄßУ<î>åBã_ý*ah¢rOĪ,ßo¢¢a|BÖäzU£.B£@Ü,ßAÃ>,ðßß+ßÜ©|Ðr©bCðТüãz>AßðåÃ>bÄåÄ|Z~äÃ/Cb*£bð_/Ða@~AÜãO+ý*CîîÃzÄöÃa©+@vuz>î>©.Cv>hÃý>©Bä,ö~@~@r,AðCu@Ü,@U*ÐvöÃêuã.Öa*uZªoZ/ðÖ©ßv_<ÖvåÜÐÜOÐoðßðÃUýZÐB:+ÄÃã£'))"; +$stmt = $conn->query($query); + +$stmt = $conn->query("CREATE TABLE ".$dbName.".[dbo].[php_test_table_2] ([c1_int] int, [c2_varchar] varchar(max))"); +$query = "INSERT INTO ".$dbName.".[dbo].[php_test_table_2] (c1_int, c2_varchar) VALUES ((990021574), ('>vh~Ö.bÐ*äß/ÄAabýZâOüzr£ðAß+|~|OU¢a|U<ßrv.uCB.ÐÜh_î+ãå@üðöã,U+ßvuU:/ý_Öãî/ð|bB|_Zbua©r++BA¢z£.üî¢öåäözÜ¢ßb:aöCrÄ~ýZ¢uªÐö.hhßð*zÜÜß*ãüåýãÄ+åýüüaߢÃÐBî@~AZöÃOßC@äoÃuCÜ,ÐÄa:îäÄÖý:h*ouªuåvUz_ArßAªãaãvÐåAUüAB:¢Äz|öub<üZvößüå:ãÄ@r/ZAÄðÄÄvzîv~C/£|ýýbüÖ~£|Öå<Üa~/v@åAz©¢£U_ßhbaÃß,zz<ã¢|<ä©>öAuövÖ>abu,zå,+ß/ü/ª_bbB:ÃC~£ü/O©O©ªAª_,|a¢~ýý/b>ßC@/böîöh>~£ð+Bßr©ÄÐÖßã:bA@:>B:UAbããîÜ~uÜ£îCöÖ£©_ÜßzÐ+ÖýZb,A:<z.ãîÄzC@©*ä|ã._ßZOäb¢Cßovå+uv.£B~~b£ª|ÖÄîßö>©Ãbb|©©ðA£åO~âãüîuvÄÜýUzîOÖ/oOßO*>ªßzêÖÐböÄåbîðîÐa~©ßîÄßУ<î>åBã_ý*ah¢rOĪ,ßo¢¢a|BÖäzU£.B£@Ü,ßAÃ>,ðßß+ßÜ©|Ðr©bCðТüãz>AßðåÃ>bÄåÄ|Z~äÃ/Cb*£bð_/Ða@~AÜãO+ý*CîîÃzÄöÃa©+@vuz>î>©.Cv>hÃý>©Bä,ö~@~@r,AðCu@Ü,@U*ÐvöÃêuã.Öa*uZªoZ/ðÖ©ßv_<ÖvåÜÐÜOÐoðßðÃUýZÐB:+ÄÃã£'))"; +$stmt = $conn->query($query); + +$size = 2; +$stmt = $conn->prepare("SELECT * FROM ".$dbName.".[dbo].[php_test_table_1]", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); +$attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); +echo("Client Buffer Size in KB: $attr\n"); +$stmt->execute(); +$numRows = 0; +while ($result = $stmt->fetch()) + $numRows++; + +echo ("Number of rows: $numRows\n"); + +$size = 3; +$stmt = $conn->prepare("SELECT * FROM ".$dbName.".[dbo].[php_test_table_2]", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); +$attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); +echo("Client Buffer Size in KB: $attr\n"); +$stmt->execute(); +$numRows = 0; +while ($result = $stmt->fetch()) + $numRows++; + +echo ("Number of rows: $numRows\n"); + +// DROP database +$conn->query("DROP DATABASE ". $dbName) ?: die(); +$stmt=null; +$conn=null; +print "Done" +?> + +--EXPECT-- +Client Buffer Size in KB: 2 +Number of rows: 1 +Client Buffer Size in KB: 3 +Number of rows: 1 +Done diff --git a/test/sqlsrv/autonomous_setup.php b/test/sqlsrv/autonomous_setup.php new file mode 100644 index 000000000..2e80f6a2b --- /dev/null +++ b/test/sqlsrv/autonomous_setup.php @@ -0,0 +1,16 @@ +"; + + + // Generate unique DB name, example: php_20160817_1471475608267 + $dbName = "php_" . date("Ymd") . "_" . round(microtime(true)*1000); + + // Generic table name example: php_20160817_1471475608267.dbo.php_firefly + $tableName = $dbName.".dbo.php_firefly"; + + // Connection options + $connectionInfo = array("UID"=>"$username", "PWD"=>"$password"); +?> diff --git a/test/sqlsrv/srv_001.phpt b/test/sqlsrv/srv_001.phpt new file mode 100644 index 000000000..91c17f660 --- /dev/null +++ b/test/sqlsrv/srv_001.phpt @@ -0,0 +1,21 @@ +--TEST-- +Connect to the default database with credentials +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password" ); +$conn = sqlsrv_connect( $serverName, $connectionInfo ); + +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +Done diff --git a/test/sqlsrv/srv_002.phpt b/test/sqlsrv/srv_002.phpt new file mode 100644 index 000000000..7b4226cae --- /dev/null +++ b/test/sqlsrv/srv_002.phpt @@ -0,0 +1,21 @@ +--TEST-- +Connect with options +--SKIPIF-- +--FILE-- +"master", "UID"=>"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +Done diff --git a/test/sqlsrv/srv_011_temporary_table.phpt b/test/sqlsrv/srv_011_temporary_table.phpt new file mode 100644 index 000000000..fb89e7bf2 --- /dev/null +++ b/test/sqlsrv/srv_011_temporary_table.phpt @@ -0,0 +1,32 @@ +--TEST-- +Temporary table +--SKIPIF-- +--FILE-- + + +--EXPECT-- +string(11) "PHP7 SQLSRV" +Done diff --git a/test/sqlsrv/srv_012_sqlsrv_fetch_array.phpt b/test/sqlsrv/srv_012_sqlsrv_fetch_array.phpt new file mode 100644 index 000000000..82786d5a0 --- /dev/null +++ b/test/sqlsrv/srv_012_sqlsrv_fetch_array.phpt @@ -0,0 +1,52 @@ +--TEST-- +sqlsrv_fetch_array() using a scrollable cursor +--SKIPIF-- +--FILE-- +"buffered")) + ?: die( print_r(sqlsrv_errors(), true)); + +// Fetch first row +$row = sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC,SQLSRV_SCROLL_NEXT); +echo $row['ID']."\n"; + +// Fetch third row +$row = sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC,SQLSRV_SCROLL_ABSOLUTE,2); +echo $row['ID']."\n"; + +// Fetch last row +$row = sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC,SQLSRV_SCROLL_LAST); +echo $row['ID']."\n"; + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +1998.1 +2016 +4.2EUR +Done diff --git a/test/sqlsrv/srv_013_sqlsrv_get_field.phpt b/test/sqlsrv/srv_013_sqlsrv_get_field.phpt new file mode 100644 index 000000000..935add7f1 --- /dev/null +++ b/test/sqlsrv/srv_013_sqlsrv_get_field.phpt @@ -0,0 +1,48 @@ +--TEST-- +sqlsrv_get_field() using SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR) +--SKIPIF-- +--FILE-- + + +--EXPECT-- +resource(9) of type (stream) +1998.1 +resource(10) of type (stream) +-2004.2436 +resource(11) of type (stream) +4.2 EUR +Done diff --git a/test/sqlsrv/srv_014_sqlsrv_get_field.phpt b/test/sqlsrv/srv_014_sqlsrv_get_field.phpt new file mode 100644 index 000000000..270edc1bb --- /dev/null +++ b/test/sqlsrv/srv_014_sqlsrv_get_field.phpt @@ -0,0 +1,53 @@ +--TEST-- +sqlsrv_get_field() using SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY) +--SKIPIF-- +--FILE-- + + +--EXPECT-- +resource(10) of type (stream) +1998.1 +resource(11) of type (stream) +-2004.2436 +resource(12) of type (stream) +4.2EUR +Done diff --git a/test/sqlsrv/srv_028_data_conversion_nvarchar.phpt b/test/sqlsrv/srv_028_data_conversion_nvarchar.phpt new file mode 100644 index 000000000..f12a8e541 --- /dev/null +++ b/test/sqlsrv/srv_028_data_conversion_nvarchar.phpt @@ -0,0 +1,55 @@ +--TEST-- +Data type precedence: conversion NVARCHAR(n) +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"UTF-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo) ?: die(); + +// Create database +sqlsrv_query($conn,"CREATE DATABASE ". $dbName) ?: die(); + +// Create table. Column names: passport +$sql = "CREATE TABLE $tableName (c1 NVARCHAR(8))"; +$stmt = sqlsrv_query($conn, $sql); + +// Insert data. The data type with the lower precedence +// is converted to the data type with the higher precedence +$sql = "INSERT INTO $tableName VALUES (3.1415),(-32),(null)"; +$stmt = sqlsrv_query($conn, $sql); + +// Insert more data +$sql = "INSERT INTO $tableName VALUES (''),('Galaxy'),('-- GO'),(N'银河系')"; +$stmt = sqlsrv_query($conn, $sql); + +// Read data from the table +$sql = "SELECT * FROM $tableName"; +$stmt = sqlsrv_query($conn, $sql); + +while($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC)) { + var_dump($row[0]); +} + +// DROP database +sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +// Free statement and connection resources +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +print "Done" +?> + +--EXPECT-- +string(6) "3.1415" +string(8) "-32.0000" +NULL +string(0) "" +string(6) "Galaxy" +string(5) "-- GO" +string(9) "银河系" +Done diff --git a/test/sqlsrv/srv_029_data_conversion_varchar_datetime.phpt b/test/sqlsrv/srv_029_data_conversion_varchar_datetime.phpt new file mode 100644 index 000000000..a519f3f93 --- /dev/null +++ b/test/sqlsrv/srv_029_data_conversion_varchar_datetime.phpt @@ -0,0 +1,47 @@ +--TEST-- +Data type precedence: conversion VARCHAR(n) +--SKIPIF-- +--FILE-- + + +--EXPECT-- +string(19) "Jan 1 1900 12:00AM" +string(19) "Dec 18 1898 2:24PM" +string(19) "Sep 24 2017 9:36AM" +Done diff --git a/test/sqlsrv/srv_031_sqlsrv_field_metadata.phpt b/test/sqlsrv/srv_031_sqlsrv_field_metadata.phpt new file mode 100644 index 000000000..7bf25190a --- /dev/null +++ b/test/sqlsrv/srv_031_sqlsrv_field_metadata.phpt @@ -0,0 +1,97 @@ +--TEST-- +sqlsrv_field_metadata() VARCHAR(n), NVARCHAR(n), INT +--SKIPIF-- +--FILE-- + + +--EXPECT-- +array(3) { + [0]=> + array(6) { + ["Name"]=> + string(9) "FirstName" + ["Type"]=> + int(12) + ["Size"]=> + int(10) + ["Precision"]=> + NULL + ["Scale"]=> + NULL + ["Nullable"]=> + int(1) + } + [1]=> + array(6) { + ["Name"]=> + string(8) "LastName" + ["Type"]=> + int(-9) + ["Size"]=> + int(20) + ["Precision"]=> + NULL + ["Scale"]=> + NULL + ["Nullable"]=> + int(1) + } + [2]=> + array(6) { + ["Name"]=> + string(3) "Age" + ["Type"]=> + int(4) + ["Size"]=> + NULL + ["Precision"]=> + int(10) + ["Scale"]=> + NULL + ["Nullable"]=> + int(1) + } +} +Done diff --git a/test/sqlsrv/srv_034_field_metadata_unicode.phpt b/test/sqlsrv/srv_034_field_metadata_unicode.phpt new file mode 100644 index 000000000..800c7f850 --- /dev/null +++ b/test/sqlsrv/srv_034_field_metadata_unicode.phpt @@ -0,0 +1,68 @@ +--TEST-- +Field metadata unicode +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password", "CharacterSet"=>"UTF-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo) ?: die(); + +// Create database +sqlsrv_query($conn,"CREATE DATABASE ". $dbName) ?: die(); + +// Create table. Column names: passport +$sql = "CREATE TABLE $tableName (पासपोर्ट CHAR(2), پاسپورٹ VARCHAR(2), Διαβατήριο VARCHAR(MAX))"; +$stmt = sqlsrv_query($conn, $sql); + +// Prepare the statement +$sql = "SELECT * FROM $tableName"; +$stmt = sqlsrv_prepare($conn, $sql); + +// Get and display field metadata +foreach(sqlsrv_field_metadata($stmt) as $meta) +{ + print_r($meta); +} + +// DROP database +sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +// Free statement and connection resources +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +print "Done" +?> + +--EXPECT-- +Array +( + [Name] => पासपोर्ट + [Type] => 1 + [Size] => 2 + [Precision] => + [Scale] => + [Nullable] => 1 +) +Array +( + [Name] => پاسپورٹ + [Type] => 12 + [Size] => 2 + [Precision] => + [Scale] => + [Nullable] => 1 +) +Array +( + [Name] => Διαβατήριο + [Type] => 12 + [Size] => 0 + [Precision] => + [Scale] => + [Nullable] => 1 +) +Done diff --git a/test/sqlsrv/srv_036_transaction_commit.phpt b/test/sqlsrv/srv_036_transaction_commit.phpt new file mode 100644 index 000000000..55b6de222 --- /dev/null +++ b/test/sqlsrv/srv_036_transaction_commit.phpt @@ -0,0 +1,83 @@ +--TEST-- +Transaction operations: commit successful transactions +--SKIPIF-- +--FILE-- += 0))"; +$stmt = sqlsrv_query($conn, $sql); + + +// Set initial data +$sql = "INSERT INTO $tableName VALUES ('ID1','12','5'),('ID102','20','1')"; +$stmt = sqlsrv_query($conn, $sql) ?: die(print_r(sqlsrv_errors(), true)); + +//Initiate transaction +sqlsrv_begin_transaction($conn) ?: die(print_r( sqlsrv_errors(), true)); + +// Update parameters +$count = 4; +$groupId = "ID1"; +$params = array($count, $groupId); + +// Update Accepted column +$sql = "UPDATE $tableName SET Accepted = (Accepted + ?) WHERE GroupId = ?"; +$stmt1 = sqlsrv_query( $conn, $sql, $params) ?: die(print_r(sqlsrv_errors(), true)); + +// Update Tentative column +$sql = "UPDATE $tableName SET Tentative = (Tentative - ?) WHERE GroupId = ?"; +$stmt2 = sqlsrv_query($conn, $sql, $params); + +// Commit the transactions +if ($stmt1 && $stmt2) +{ + sqlsrv_commit($conn); +} +else +{ + echo "\nERROR: $stmt1 and $stmt2 should be valid\n"; + sqlsrv_rollback($conn); + echo "\nTransactions were rolled back.\n"; +} + +PrintContent($conn); + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +Array +( + [GroupId] => ID1 + [Accepted] => 16 + [Tentative] => 1 +) +Done + diff --git a/test/sqlsrv/srv_037_sqlsrv_has_rows.phpt b/test/sqlsrv/srv_037_sqlsrv_has_rows.phpt new file mode 100644 index 000000000..c93bef778 --- /dev/null +++ b/test/sqlsrv/srv_037_sqlsrv_has_rows.phpt @@ -0,0 +1,107 @@ +--TEST-- +sqlsrv_has_rows() using a forward and scrollable cursor +--DESCRIPTION-- +This test calls sqlsrv_has_rows multiple times. Previously, multiple calls +with a forward cursor would advance the cursor. Subsequent fetch calls +would then fail. +--SKIPIF-- +--FILE-- +"buffered")) + ?: die( print_r(sqlsrv_errors(), true)); + +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; + +if (sqlsrv_has_rows($stmt)) { + while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC)) + { + echo $row[0]."\n"; + } +} + +$query = "SELECT ID FROM $tableName where ID='nomatch'"; +$stmt = sqlsrv_query($conn, $query) + ?: die( print_r(sqlsrv_errors(), true)); + +// repeated calls should return false if there are no rows. +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; + +// Fetch data using a scrollable cursor +$stmt = sqlsrv_query($conn, $query, [], array("Scrollable"=>"buffered")) + ?: die( print_r(sqlsrv_errors(), true)); + +// repeated calls should return false if there are no rows. +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; +echo "Has Rows?" . (sqlsrv_has_rows($stmt) ? " Yes!" : " NO!") . "\n"; + + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +Has Rows? Yes! +Has Rows? Yes! +Has Rows? Yes! +Has Rows? Yes! +1998.1 +-2004 +2016 +4.2EUR +Has Rows? Yes! +Has Rows? Yes! +Has Rows? Yes! +Has Rows? Yes! +1998.1 +-2004 +2016 +4.2EUR +Has Rows? NO! +Has Rows? NO! +Has Rows? NO! +Has Rows? NO! +Done diff --git a/test/sqlsrv/srv_037_transaction_rollback.phpt b/test/sqlsrv/srv_037_transaction_rollback.phpt new file mode 100644 index 000000000..b7065a666 --- /dev/null +++ b/test/sqlsrv/srv_037_transaction_rollback.phpt @@ -0,0 +1,82 @@ +--TEST-- +Transaction operations: rolled-back transactions +--SKIPIF-- +--FILE-- += 0))"; +$stmt = sqlsrv_query($conn, $sql); + + +// Set initial data +$sql = "INSERT INTO $tableName VALUES ('ID1','12','5'),('ID102','20','1')"; +$stmt = sqlsrv_query($conn, $sql) ?: die(print_r(sqlsrv_errors(), true)); + +//Initiate transaction +sqlsrv_begin_transaction($conn) ?: die(print_r( sqlsrv_errors(), true)); + +// Update parameters +$count = 8; +$groupId = "ID1"; +$params = array($count, $groupId); + +// Update Accepted column +$sql = "UPDATE $tableName SET Accepted = (Accepted + ?) WHERE GroupId = ?"; +$stmt1 = sqlsrv_query( $conn, $sql, $params) ?: die(print_r(sqlsrv_errors(), true)); + +// Update Tentative column +// This statement returns FALSE because Tentative column should be non-negative +$sql = "UPDATE $tableName SET Tentative = (Tentative - ?) WHERE GroupId = ?"; +$stmt2 = sqlsrv_query($conn, $sql, $params); + +// Commit the transactions +if ($stmt1 && $stmt2) +{ + echo "\nERROR: $stmt2 should be bool(false)\n"; +} +else +{ + sqlsrv_rollback($conn); +} + +PrintContent($conn); + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +Array +( + [GroupId] => ID1 + [Accepted] => 12 + [Tentative] => 5 +) +Done + diff --git a/test/sqlsrv/srv_047_stream_nvarchar.phpt b/test/sqlsrv/srv_047_stream_nvarchar.phpt new file mode 100644 index 000000000..13d14f04d --- /dev/null +++ b/test/sqlsrv/srv_047_stream_nvarchar.phpt @@ -0,0 +1,55 @@ +--TEST-- +Streaming nvarchar(max) unicode (Russian) with CharacterSet=utf-8 +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"utf-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo); +if( !$conn ) { die( print_r( sqlsrv_errors(), true)); } + +// Create table +$sql = "CREATE TABLE #Table (c1 NVARCHAR(max))"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Insert data, 4538 characters +$data = "Первые публикации об объектно-ориентированных базах данных появились в середине 80-х годов. Поддержка сложных объектов. В системе должна быть предусмотрена возможность создания составных объектов за счет применения конструкторов составных объектов. Необходимо, чтобы конструкторы объектов были ортогональны, то есть любой конструктор можно было применять к любому объекту. Поддержка индивидуальности объектов. Все объекты должны иметь уникальный идентификатор, который не зависит от значений их атрибутов. Поддержка инкапсуляции. Корректная инкапсуляция достигается за счет того, что программисты обладают правом доступа только к спецификации интерфейса методов, а данные и реализация методов скрыты внутри объектов. Поддержка типов и классов. Требуется, чтобы в ООБД поддерживалась хотя бы одна концепция различия между типами и классами. (Термин «тип» более соответствует понятию абстрактного типа данных. В языках программирования переменная объявляется с указанием её типа. Компилятор может использовать эту информацию для проверки выполняемых с переменной операций на совместимость с её типом, что позволяет гарантировать корректность программного обеспечения. С другой стороны класс является неким шаблоном для создания объектов и предоставляет методы, которые могут применяться к этим объектам. Таким образом, понятие «класс» в большей степени относится ко времени исполнения, чем ко времени компиляции.) Поддержка наследования типов и классов от их предков. Подтип, или подкласс, должен наследовать атрибуты и методы от его супертипа, или суперкласса, соответственно. Перегрузка в сочетании с полным связыванием. Методы должны применяться к объектам разных типов. Реализация метода должна зависеть от типа объектов, к которым данный метод применяется. Для обеспечения этой функциональности связывание имен методов в системе не должно выполняться до времени выполнения программы. Вычислительная полнота. Язык манипулирования данными должен быть языком программирования общего назначения. Набор типов данных должен быть расширяемым. Пользователь должен иметь средства создания новых типов данных на основе набора предопределенных системных типов. Более того, между способами использования системных и пользовательских типов данных не должно быть никаких различий.Первые публикации об объектно-ориентированных базах данных появились в середине 80-х годов. Поддержка сложных объектов. В системе должна быть предусмотрена возможность создания составных объектов за счет применения конструкторов составных объектов. Необходимо, чтобы конструкторы объектов были ортогональны, то есть любой конструктор можно было применять к любому объекту. Поддержка индивидуальности объектов. Все объекты должны иметь уникальный идентификатор, который не зависит от значений их атрибутов. Поддержка инкапсуляции. Корректная инкапсуляция достигается за счет того, что программисты обладают правом доступа только к спецификации интерфейса методов, а данные и реализация методов скрыты внутри объектов. Поддержка типов и классов. Требуется, чтобы в ООБД поддерживалась хотя бы одна концепция различия между типами и классами. (Термин «тип» более соответствует понятию абстрактного типа данных. В языках программирования переменная объявляется с указанием её типа. Компилятор может использовать эту информацию для проверки выполняемых с переменной операций на совместимость с её типом, что позволяет гарантировать корректность программного обеспечения. С другой стороны класс является неким шаблоном для создания объектов и предоставляет методы, которые могут применяться к этим объектам. Таким образом, понятие «класс» в большей степени относится ко времени исполнения, чем ко времени компиляции.) Поддержка наследования типов и классов от их предков. Подтип, или подкласс, должен наследовать атрибуты и методы от его супертипа, или суперкласса, соответственно. Перегрузка в сочетании с полным связыванием. Методы должны применяться к объектам разных типов. Реализация метода должна зависеть от типа объектов, к которым данный метод применяется. Для обеспечения этой функциональности связывание имен методов в системе не должно выполняться до времени выполнения программы. Вычислительная полнота. Язык манипулирования данными должен быть языком программирования общего назначения. Набор типов данных должен быть расширяемым. Пользователь должен иметь средства создания новых типов данных на основе набора предопределенных системных типов. Более того, между способами использования системных и пользовательских типов данных не должно быть никаких различий."; +$sql = "INSERT INTO #Table VALUES (N'$data')"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Query and fetch +$sql = "SELECT * FROM #Table"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } +sqlsrv_fetch($stmt); + +// Get the data +$field = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STREAM('utf-8')); +$out = ""; +while(!feof($field)) +{ + $out .= fread($field, 512); +} + +// Output string length +var_dump(strlen($out)); + +// Compare output +echo ($out === $data) ? "True\n" : "False\n"; + +// Close connection +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +print "Done"; +?> + +--EXPECT-- +int(8408) +True +Done diff --git a/test/sqlsrv/srv_048_stream_nvarchar.phpt b/test/sqlsrv/srv_048_stream_nvarchar.phpt new file mode 100644 index 000000000..93e8972f0 --- /dev/null +++ b/test/sqlsrv/srv_048_stream_nvarchar.phpt @@ -0,0 +1,42 @@ +--TEST-- +Streaming nvarchar(max) with sqlsrv_fetch_array() +--SKIPIF-- +--FILE-- + + +--EXPECT-- +True +Done diff --git a/test/sqlsrv/srv_049_stream_nvarchar_utf8.phpt b/test/sqlsrv/srv_049_stream_nvarchar_utf8.phpt new file mode 100644 index 000000000..d245873a2 --- /dev/null +++ b/test/sqlsrv/srv_049_stream_nvarchar_utf8.phpt @@ -0,0 +1,43 @@ +--TEST-- +Streaming nvarchar(max) with sqlsrv_fetch_array() and CharacterSet=utf-8 +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"utf-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo); +if( !$conn ) { die( print_r( sqlsrv_errors(), true)); } + +// Create table +$sql = "CREATE TABLE #Table (c1 NVARCHAR(max))"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Insert data +$data = "Первые публикации об объектно-ориентированных базах данных появились в середине 80-х годов. Поддержка сложных объектов. В системе должна быть предусмотрена возможность создания составных объектов за счет применения конструкторов составных объектов. Необходимо, чтобы конструкторы объектов были ортогональны, то есть любой конструктор можно было применять к любому объекту. Поддержка индивидуальности объектов. Все объекты должны иметь уникальный идентификатор, который не зависит от значений их атрибутов. Поддержка инкапсуляции. Корректная инкапсуляция достигается за счет того, что программисты обладают правом доступа только к спецификации интерфейса методов, а данные и реализация методов скрыты внутри объектов. Поддержка типов и классов. Требуется, чтобы в ООБД поддерживалась хотя бы одна концепция различия между типами и классами. (Термин «тип» более соответствует понятию абстрактного типа данных. В языках программирования переменная объявляется с указанием её типа. Компилятор может использовать эту информацию для проверки выполняемых с переменной операций на совместимость с её типом, что позволяет гарантировать корректность программного обеспечения. С другой стороны класс является неким шаблоном для создания объектов и предоставляет методы, которые могут применяться к этим объектам. Таким образом, понятие «класс» в большей степени относится ко времени исполнения, чем ко времени компиляции.) Поддержка наследования типов и классов от их предков. Подтип, или подкласс, должен наследовать атрибуты и методы от его супертипа, или суперкласса, соответственно. Перегрузка в сочетании с полным связыванием. Методы должны применяться к объектам разных типов. Реализация метода должна зависеть от типа объектов, к которым данный метод применяется. Для обеспечения этой функциональности связывание имен методов в системе не должно выполняться до времени выполнения программы. Вычислительная полнота. Язык манипулирования данными должен быть языком программирования общего назначения. Набор типов данных должен быть расширяемым. Пользователь должен иметь средства создания новых типов данных на основе набора предопределенных системных типов. Более того, между способами использования системных и пользовательских типов данных не должно быть никаких различий.Первые публикации об объектно-ориентированных базах данных появились в середине 80-х годов. Поддержка сложных объектов. В системе должна быть предусмотрена возможность создания составных объектов за счет применения конструкторов составных объектов. Необходимо, чтобы конструкторы объектов были ортогональны, то есть любой конструктор можно было применять к любому объекту. Поддержка индивидуальности объектов. Все объекты должны иметь уникальный идентификатор, который не зависит от значений их атрибутов. Поддержка инкапсуляции. Корректная инкапсуляция достигается за счет того, что программисты обладают правом доступа только к спецификации интерфейса методов, а данные и реализация методов скрыты внутри объектов. Поддержка типов и классов. Требуется, чтобы в ООБД поддерживалась хотя бы одна концепция различия между типами и классами. (Термин «тип» более соответствует понятию абстрактного типа данных. В языках программирования переменная объявляется с указанием её типа. Компилятор может использовать эту информацию для проверки выполняемых с переменной операций на совместимость с её типом, что позволяет гарантировать корректность программного обеспечения. С другой стороны класс является неким шаблоном для создания объектов и предоставляет методы, которые могут применяться к этим объектам. Таким образом, понятие «класс» в большей степени относится ко времени исполнения, чем ко времени компиляции.) Поддержка наследования типов и классов от их предков. Подтип, или подкласс, должен наследовать атрибуты и методы от его супертипа, или суперкласса, соответственно. Перегрузка в сочетании с полным связыванием. Методы должны применяться к объектам разных типов. Реализация метода должна зависеть от типа объектов, к которым данный метод применяется. Для обеспечения этой функциональности связывание имен методов в системе не должно выполняться до времени выполнения программы. Вычислительная полнота. Язык манипулирования данными должен быть языком программирования общего назначения. Набор типов данных должен быть расширяемым. Пользователь должен иметь средства создания новых типов данных на основе набора предопределенных системных типов. Более того, между способами использования системных и пользовательских типов данных не должно быть никаких различий."; +$sql = "INSERT INTO #Table VALUES (N'$data')"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Fetch +$sql = "SELECT * FROM #Table"; +$stmt = sqlsrv_query($conn, $sql); +if( !$stmt ) { die( print_r( sqlsrv_errors(), true)); } + +// Compare output +$row = sqlsrv_fetch_array($stmt); +echo ($row['c1'] == $data) ? "True\n" : "False\n"; + +// Close connection +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +print "Done"; +?> + +--EXPECT-- +True +Done diff --git a/test/sqlsrv/srv_050_error_conversion_varchar_int.phpt b/test/sqlsrv/srv_050_error_conversion_varchar_int.phpt new file mode 100644 index 000000000..6ffd189e9 --- /dev/null +++ b/test/sqlsrv/srv_050_error_conversion_varchar_int.phpt @@ -0,0 +1,46 @@ +--TEST-- +Error checking for failed conversion: VARCHAR value 'null' to data type INT +--SKIPIF-- +--FILE-- + + +--EXPECT-- +Array +( + [0] => 22018 + [SQLSTATE] => 22018 + [1] => 245 + [code] => 245 + [2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the varchar value 'null' to data type int. + [message] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the varchar value 'null' to data type int. +) +Done \ No newline at end of file diff --git a/test/sqlsrv/srv_051_error_conversion_nchar.phpt b/test/sqlsrv/srv_051_error_conversion_nchar.phpt new file mode 100644 index 000000000..46870645e --- /dev/null +++ b/test/sqlsrv/srv_051_error_conversion_nchar.phpt @@ -0,0 +1,47 @@ +--TEST-- +Error checking for failed explicit data type conversions NCHAR(2) +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, "CharacterSet"=>"UTF-8"); +$conn = sqlsrv_connect($serverName, $connectionInfo) ?: die(); + +// Create database +sqlsrv_query($conn,"CREATE DATABASE ". $dbName) ?: die(); + +// Create table. Column names: passport +$sql = "CREATE TABLE $tableName (c1 NCHAR(2))"; +$stmt = sqlsrv_query($conn, $sql); + +// Insert data. Invalid statement +$sql = "INSERT INTO $tableName VALUES (10),(N'银河')"; +$stmt = sqlsrv_query($conn, $sql); + +// Get extended error +$err = sqlsrv_errors(); +print_r($err[0]); + +// DROP database +sqlsrv_query($conn,"DROP DATABASE ". $dbName); + +// Free statement and connection resources +sqlsrv_close($conn); + +print "Done" +?> + +--EXPECT-- +Array +( + [0] => 22018 + [SQLSTATE] => 22018 + [1] => 245 + [code] => 245 + [2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the nvarchar value '银河' to data type int. + [message] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the nvarchar value '银河' to data type int. +) +Done diff --git a/test/sqlsrv/srv_052_mars.phpt b/test/sqlsrv/srv_052_mars.phpt new file mode 100644 index 000000000..ee61f5c45 --- /dev/null +++ b/test/sqlsrv/srv_052_mars.phpt @@ -0,0 +1,40 @@ +--TEST-- +Enable multiple active result sets (MARS) +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, 'MultipleActiveResultSets' => true); +$conn = sqlsrv_connect($serverName, $connectionInfo) ?: die(); + +// Query +$stmt1 = sqlsrv_query( $conn, "SELECT 'ONE'" ) ?: die(print_r( sqlsrv_errors(), true )); +sqlsrv_fetch( $stmt1 ); + +// Query. Returns if multiple result sets are disabled +$stmt2 = sqlsrv_query( $conn, "SELECT 'TWO'" ) ?: die(print_r( sqlsrv_errors(), true )); +sqlsrv_fetch( $stmt2 ); + +// Print the data +$res = [ sqlsrv_get_field($stmt1, 0), sqlsrv_get_field($stmt2, 0) ]; +var_dump($res); + +// Free statement and connection resources +sqlsrv_free_stmt($stmt1); +sqlsrv_free_stmt($stmt2); +sqlsrv_close($conn); + +print "Done" +?> + +--EXPECT-- +array(2) { + [0]=> + string(3) "ONE" + [1]=> + string(3) "TWO" +} +Done diff --git a/test/sqlsrv/srv_053_mars_disabled_error_checks.phpt b/test/sqlsrv/srv_053_mars_disabled_error_checks.phpt new file mode 100644 index 000000000..0f03db7cd --- /dev/null +++ b/test/sqlsrv/srv_053_mars_disabled_error_checks.phpt @@ -0,0 +1,56 @@ +--TEST-- +Error checking for multiple active result sets (MARS) disabled +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password, 'MultipleActiveResultSets' => false); +$conn = sqlsrv_connect($serverName, $connectionInfo) ?: die(); + +// Query +$stmt1 = sqlsrv_query( $conn, "SELECT 'ONE'" ) ?: die(print_r( sqlsrv_errors(), true )); +sqlsrv_fetch( $stmt1 ); + +// Query. Returns if multiple result sets are disabled +$stmt2 = sqlsrv_query( $conn, "SELECT 'TWO'" ) ?: die(print_r( sqlsrv_errors(), true )); +sqlsrv_fetch( $stmt2 ); + +// Print the data +$res = [ sqlsrv_get_field($stmt1, 0), sqlsrv_get_field($stmt2, 0) ]; +var_dump($res); + +// Free statement and connection resources +sqlsrv_free_stmt($stmt1); +sqlsrv_free_stmt($stmt2); +sqlsrv_close($conn); + +print "Done" +?> + +--EXPECT-- +Array +( + [0] => Array + ( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -44 + [code] => -44 + [2] => The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. + [message] => The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. + ) + + [1] => Array + ( + [0] => HY000 + [SQLSTATE] => HY000 + [1] => 0 + [code] => 0 + [2] => [Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command + [message] => [Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command + ) + +) diff --git a/test/sqlsrv/srv_073_database.phpt b/test/sqlsrv/srv_073_database.phpt new file mode 100644 index 000000000..639941c8e --- /dev/null +++ b/test/sqlsrv/srv_073_database.phpt @@ -0,0 +1,41 @@ +--TEST-- +Create/drop database +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { die( print_r( sqlsrv_errors(), true)); } + +// Set database name +$dbUniqueName = "php_uniqueDB01"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +if($stmt === false){ die( print_r( sqlsrv_errors(), true )); } +sqlsrv_free_stmt($stmt); + +// CREATE database +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); +if($stmt === false){ die( print_r( sqlsrv_errors(), true )); } +echo "DATABASE CREATED\n"; + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbUniqueName); +if($stmt === false){ die( print_r( sqlsrv_errors(), true )); } +echo "DATABASE DROPPED\n"; + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +DATABASE CREATED +DATABASE DROPPED +Done diff --git a/test/sqlsrv/srv_074_create_existing_database.phpt b/test/sqlsrv/srv_074_create_existing_database.phpt new file mode 100644 index 000000000..598a8c72d --- /dev/null +++ b/test/sqlsrv/srv_074_create_existing_database.phpt @@ -0,0 +1,62 @@ +--TEST-- +Create database that already exists +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// Set database name +$dbUniqueName = "uniqueDB01"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +sqlsrv_free_stmt($stmt); + + +// CREATE database +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); + +if( $stmt === false) +{ + printf("%-20s%10s\n","CREATE DATABASE","FAILED"); + die( print_r( sqlsrv_errors(), true )); +} + + +// CREATE database that already exists +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); +var_dump($stmt); +if( $stmt === false) +{ + $res = array_values(sqlsrv_errors()); + var_dump($res[0]['SQLSTATE']); + var_dump($res[0][1]); + // var_dump($res[0][2]); +} +else { + printf("%-20s\n","ERROR: CREATE database MUST return bool(false)"); +} + +// DROP database +sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); + +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +bool(false) +string(5) "42000" +int(1801) +Done diff --git a/test/sqlsrv/srv_074_database.phpt b/test/sqlsrv/srv_074_database.phpt new file mode 100644 index 000000000..598a8c72d --- /dev/null +++ b/test/sqlsrv/srv_074_database.phpt @@ -0,0 +1,62 @@ +--TEST-- +Create database that already exists +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// Set database name +$dbUniqueName = "uniqueDB01"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +sqlsrv_free_stmt($stmt); + + +// CREATE database +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); + +if( $stmt === false) +{ + printf("%-20s%10s\n","CREATE DATABASE","FAILED"); + die( print_r( sqlsrv_errors(), true )); +} + + +// CREATE database that already exists +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); +var_dump($stmt); +if( $stmt === false) +{ + $res = array_values(sqlsrv_errors()); + var_dump($res[0]['SQLSTATE']); + var_dump($res[0][1]); + // var_dump($res[0][2]); +} +else { + printf("%-20s\n","ERROR: CREATE database MUST return bool(false)"); +} + +// DROP database +sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); + +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +bool(false) +string(5) "42000" +int(1801) +Done diff --git a/test/sqlsrv/srv_074_database_wide_string.phpt b/test/sqlsrv/srv_074_database_wide_string.phpt new file mode 100644 index 000000000..0f70a6484 --- /dev/null +++ b/test/sqlsrv/srv_074_database_wide_string.phpt @@ -0,0 +1,61 @@ +--TEST-- +Create database that already exists +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// Set database name +$dbUniqueName = "uniqueDB01"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +sqlsrv_free_stmt($stmt); + + +// CREATE database +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); + +if( $stmt === false) +{ + printf("%-20s%10s\n","CREATE DATABASE","FAILED"); + die( print_r( sqlsrv_errors(), true )); +} + + +// CREATE database that already exists +$stmt = sqlsrv_query($conn,"CREATE DATABASE ". $dbUniqueName); +var_dump($stmt); +if( $stmt === false) +{ + $res = array_values(sqlsrv_errors()); + var_dump($res[0]['SQLSTATE']); + var_dump($res[0][1]); +} +else { + printf("%-20s\n","ERROR: CREATE database MUST return bool(false)"); +} + +// DROP database +sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); + +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +bool(false) +string(5) "42000" +int(1801) +Done diff --git a/test/sqlsrv/srv_075_database.phpt b/test/sqlsrv/srv_075_database.phpt new file mode 100644 index 000000000..6f0fa74e2 --- /dev/null +++ b/test/sqlsrv/srv_075_database.phpt @@ -0,0 +1,44 @@ +--TEST-- +Drop missing database +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password"); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// Set database name +$dbUniqueName = "uniqueDB01"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +sqlsrv_free_stmt($stmt); + +// DROP missing database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbUniqueName); +var_dump($stmt); +if( $stmt === false ) +{ + $res = array_values(sqlsrv_errors()); + var_dump($res[0]['code']); +} +else { + printf("%-20s\n","ERROR: DROP missing database MUST return bool(false)"); +} + +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +bool(false) +int(3701) +Done diff --git a/test/sqlsrv/srv_075_database_wide_string.phpt b/test/sqlsrv/srv_075_database_wide_string.phpt new file mode 100644 index 000000000..8a448bd39 --- /dev/null +++ b/test/sqlsrv/srv_075_database_wide_string.phpt @@ -0,0 +1,49 @@ +--TEST-- +Drop missing database unicode +--SKIPIF-- +--FILE-- +"$username", "PWD"=>"$password", "CharacterSet" => 'UTF-8'); +$conn = sqlsrv_connect( $serverName, $connectionInfo); + +// Check if connected +if( !$conn ) { + echo "Connection could not be established.\n"; + die( print_r( sqlsrv_errors(), true)); +} + +// Set database name +$dbUniqueName = "uniqueDB01_银河系"; + +// DROP database if exists +$stmt = sqlsrv_query($conn,"IF EXISTS(SELECT name FROM sys.databases WHERE name = '" + .$dbUniqueName."') DROP DATABASE ".$dbUniqueName); +sqlsrv_free_stmt($stmt); + +// DROP missing database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbUniqueName); +var_dump($stmt); +if( $stmt === false ) +{ + $res = array_values(sqlsrv_errors()); + var_dump($res[0]['SQLSTATE']); + var_dump($res[0][1]); + var_dump($res[0][2]); + +} +else { + printf("%-20s\n","ERROR: DROP missing database MUST return bool(false)"); +} + +sqlsrv_close($conn); +print "Done"; +?> + +--EXPECT-- +bool(false) +string(5) "42S02" +int(3701) +string(159) "[Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Cannot drop the database 'uniqueDB01_银河系', because it does not exist or you do not have permission." +Done diff --git a/test/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt b/test/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt new file mode 100644 index 000000000..d047ed033 --- /dev/null +++ b/test/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt @@ -0,0 +1,81 @@ +--TEST-- +sqlsrv_has_rows() using a forward and scrollable cursor +--SKIPIF-- +--FILE-- +"buffered")); + $attr = sqlsrv_get_config('ClientBufferMaxKBSize'); + echo("ClientBufferMaxKBSize is $attr\n"); + + sqlsrv_execute($stmt); + $rows = sqlsrv_has_rows($stmt); + var_dump($rows); + + sqlsrv_execute($stmt); + $numRowsFetched = 0; + while ($row = sqlsrv_fetch_array($stmt)) + { + $numRowsFetched++; + } + echo("Number of rows fetched: $numRowsFetched\n"); +} + +// Connect +$conn = sqlsrv_connect( $serverName, $connectionInfo); +if( !$conn ) { die( print_r( sqlsrv_errors(), true)); } + +// Create database +sqlsrv_query($conn,"CREATE DATABASE ". $dbName) ?: die(); +// Create table +$stmt = sqlsrv_query($conn, "CREATE TABLE ".$dbName.".[dbo].[php_test_table_1] ([c1_int] int, [c2_varchar_max] varchar(max))"); +$stmt = sqlsrv_query($conn, "CREATE TABLE ".$dbName.".[dbo].[php_test_table_2] ([c1_int] int, [c2_varchar_1036] varchar(1036))"); +// insert > 1KB into c2_varchar_max & c2_varchar_1036 (1036 characters). +$stmt = sqlsrv_query($conn, "INSERT INTO ".$dbName.".[dbo].[php_test_table_1] (c1_int, c2_varchar_max) VALUES (1, 'This is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a test')"); +$stmt = sqlsrv_query($conn, "INSERT INTO ".$dbName.".[dbo].[php_test_table_2] (c1_int, c2_varchar_1036) VALUES (1, 'This is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a test')"); + +// set client buffer size to 0KB returns false +$ret = sqlsrv_configure('ClientBufferMaxKBSize', 0); +var_dump($ret); +// set client buffer size to 1KB +$size = 1; +fetchData($conn, $dbName.".[dbo].[php_test_table_1]", $size); // this should return 0 rows. +fetchData($conn, $dbName.".[dbo].[php_test_table_2]", $size); // this should return 0 rows. +// set client buffer size to 2KB +$size = 2; +fetchData($conn, $dbName.".[dbo].[php_test_table_1]", $size); // this should return 1 row. +fetchData($conn, $dbName.".[dbo].[php_test_table_2]", $size); // this should return 1 row. + +// DROP database +$stmt = sqlsrv_query($conn,"DROP DATABASE ". $dbName); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +bool(false) +bool(true) +ClientBufferMaxKBSize is 1 +bool(false) +Number of rows fetched: 0 +bool(true) +ClientBufferMaxKBSize is 1 +bool(false) +Number of rows fetched: 0 +bool(true) +ClientBufferMaxKBSize is 2 +bool(true) +Number of rows fetched: 1 +bool(true) +ClientBufferMaxKBSize is 2 +bool(true) +Number of rows fetched: 1 +Done