Compare commits

...

No commits in common. "debian/latest" and "pristine-tar" have entirely different histories.

51 changed files with 1 additions and 22410 deletions

5
debian/changelog vendored
View file

@ -1,5 +0,0 @@
php-pdo-sqlsrv (5.10.1-1) bullseye; urgency=medium
* Initial packaging
-- David Prévot <taffit@debian.org> Wed, 06 Dec 2023 14:41:15 +0100

79
debian/control vendored
View file

@ -1,79 +0,0 @@
Source: php-pdo-sqlsrv
Section: php
Priority: optional
Maintainer: Debian PHP PECL Maintainers <team+php-pecl@tracker.debian.org>
Uploaders: David Prévot <taffit@debian.org>
Build-Depends: debhelper-compat (= 13),
dh-php (>= 4~),
php-all-dev,
unixodbc-dev
Standards-Version: 4.5.1
Rules-Requires-Root: no
Vcs-Git: https://salsa.debian.org/php-team/pecl/php-pdo-sqlsrv.git
Vcs-Browser: https://salsa.debian.org/php-team/pecl/php-pdo-sqlsrv
Homepage: https://pecl.php.net/package/pdo_sqlsrv
Package: php-pdo-sqlsrv
Architecture: any
Pre-Depends: php-common (>= 2:69~)
Depends: ${misc:Depends}, ${pecl:Depends}, ${php:Depends}, ${shlibs:Depends}
Suggests: ${pecl:Suggests}
Breaks: ${pecl:Breaks}
Replaces: ${pecl:Replaces}
Provides: ${pecl:Provides}, ${php:Provides}
Recommends: ${misc:Recommends}
Description: Microsoft Drivers for PHP for SQL Server (PDO_SQLSRV)
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 2012 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 package contains only the PDO_SQLSRV driver.
.
This is empty package that depends on default PHP version.
Package: php-pdo-sqlsrv-all-dev
Architecture: all
Pre-Depends: php-common (>= 2:69~)
Depends: ${misc:Depends}, ${pecl:Depends}, ${php:Depends}, ${shlibs:Depends}
Suggests: ${pecl:Suggests}
Breaks: ${pecl:Breaks}
Replaces: ${pecl:Replaces}
Provides: ${pecl:Provides}, ${php:Provides}
Recommends: ${misc:Recommends}
Description: Microsoft Drivers for PHP for SQL Server (PDO_SQLSRV)
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 2012 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 package contains only the PDO_SQLSRV driver.
.
This is empty package that depends on all PHP versions.
Package: php7.4-pdo-sqlsrv
Architecture: any
Pre-Depends: php-common (>= 2:69~)
Depends: ${misc:Depends}, ${pecl:Depends}, ${php:Depends}, ${shlibs:Depends}
Suggests: ${pecl:Suggests}
Breaks: ${pecl:Breaks}
Replaces: ${pecl:Replaces}
Provides: ${pecl:Provides}, ${php:Provides}
Recommends: ${misc:Recommends}
Description: Microsoft Drivers for PHP for SQL Server (PDO_SQLSRV)
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 2012 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 package contains only the PDO_SQLSRV driver.

34
debian/control.in vendored
View file

@ -1,34 +0,0 @@
Source: php-pdo-sqlsrv
Section: php
Priority: optional
Maintainer: Debian PHP PECL Maintainers <team+php-pecl@tracker.debian.org>
Uploaders: David Prévot <taffit@debian.org>
Build-Depends: debhelper-compat (= 13),
dh-php (>= 4~),
php-all-dev,
unixodbc-dev
Standards-Version: 4.5.1
Rules-Requires-Root: no
Vcs-Git: https://salsa.debian.org/php-team/pecl/php-pdo-sqlsrv.git
Vcs-Browser: https://salsa.debian.org/php-team/pecl/php-pdo-sqlsrv
Homepage: https://pecl.php.net/package/pdo_sqlsrv
Package: php-pdo-sqlsrv
Architecture: any
Pre-Depends: php-common (>= 2:69~)
Depends: ${misc:Depends}, ${pecl:Depends}, ${php:Depends}, ${shlibs:Depends}
Suggests: ${pecl:Suggests}
Breaks: ${pecl:Breaks}
Replaces: ${pecl:Replaces}
Provides: ${pecl:Provides}, ${php:Provides}
Recommends: ${misc:Recommends}
Description: Microsoft Drivers for PHP for SQL Server (PDO_SQLSRV)
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 2012 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 package contains only the PDO_SQLSRV driver.

26
debian/copyright vendored
View file

@ -1,26 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: PDO_SQLSRV
Upstream-Contact: Marie Barwin
Source: https://github.com/Microsoft/msphpsql
Files: *
Copyright: 2022 Microsoft Corporation
License: Expat
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.

13
debian/gbp.conf vendored
View file

@ -1,13 +0,0 @@
[DEFAULT]
debian-branch = debian/latest
debian-tag = debian/%(version)s
upstream-branch = upstream/latest
upstream-tag = upstream/%(version)s
pristine-tar = True
upstream-vcs-tag = v%(version%~%-)s
[dch]
meta = 1
[import-orig]
filter = ['.gitignore']

View file

@ -1,2 +0,0 @@
; priority=30
extension=pdo_sqlsrv.so

View file

@ -1 +0,0 @@
mod debian/pdo_sqlsrv.ini

3
debian/rules vendored
View file

@ -1,3 +0,0 @@
#!/usr/bin/make -f
export REPORT_EXIT_STATUS:=0
include /usr/share/dh-php/pkg-pecl.mk

View file

@ -1 +0,0 @@
3.0 (quilt)

View file

@ -1,5 +0,0 @@
---
Bug-Database: https://github.com/microsoft/msphpsql/issues
Bug-Submit: https://github.com/microsoft/msphpsql/issues/new
Repository: https://github.com/microsoft/msphpsql.git
Repository-Browse: https://github.com/Microsoft/msphpsql

3
debian/watch vendored
View file

@ -1,3 +0,0 @@
version=4
https://pecl.php.net/package/pdo_sqlsrv \
/get/pdo_sqlsrv-(5\.10\.[\d\.p?]*).tgz debian uupdate

View file

@ -1,122 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.9.0" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
<name>pdo_sqlsrv</name>
<channel>pecl.php.net</channel>
<summary>Microsoft Drivers for PHP for SQL Server (PDO_SQLSRV)</summary>
<description>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 2012 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 package contains only the PDO_SQLSRV driver.*
</description>
<lead>
<name>Meet Bhagdev</name>
<user>meetbhagdev</user>
<email>meetb@microsoft.com</email>
<active>no</active>
</lead>
<lead>
<name>Jay Kint</name>
<user>jkint</user>
<email>jaykint@microsoft.com</email>
<active>no</active>
</lead>
<lead>
<name>Marie Barwin</name>
<user>mbar</user>
<email>v-mabarw@microsoft.com</email>
<active>yes</active>
</lead>
<lead>
<name>Jenny Tam</name>
<user>yitam</user>
<email>v-yitam@microsoft.com</email>
<active>no</active>
</lead>
<date>2022-05-13</date>
<time>21:44:44</time>
<version>
<release>5.10.1</release>
<api>5.10.1</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="https://opensource.org/licenses/mit">The MIT License (MIT)</license>
<notes>
[Added]
- Pull request [#1382](https://github.com/microsoft/msphpsql/pull/1382) - Support for ActiveDirectoryIntegrated authentication
[Fixed]
- Pull request [#1374](https://github.com/microsoft/msphpsql/pull/1374) - Fixed ActiveDirectoryMsi Authentication behavior when specified UID by laclefyoshi
[Limitations]
- No support for inout / output params when using sql_variant type
- No support for inout / output params when formatting decimal values
- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work
- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)
- Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported
- Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported
- Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns
- [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted)
[Known Issues]
- This preview release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver.
- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) &lt; 2.3.7
- When pooling is enabled in Linux or macOS
- unixODBC &lt;= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages
- due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling)
</notes>
<contents>
<dir name="/">
<file md5sum="12c51672bd54784a06c52f3efc7cf85f" name="CREDITS" role="doc" />
<file md5sum="5367e8bd4b5c4f8034578525fbaff449" name="LICENSE" role="doc" />
<file md5sum="9c0ebc090525b9a42fd8d497415a9893" name="config.m4" role="src" />
<file md5sum="a2410683e00f94a4a56554dd596dc69e" name="config.w32" role="src" />
<file md5sum="a3218c2fed8368a03ba5cda56733ff6d" name="pdo_dbh.cpp" role="src" />
<file md5sum="7e592616e1b465577a1c710ee33efbf1" name="pdo_init.cpp" role="src" />
<file md5sum="d3b524dd16b30016bc09f847540a2a36" name="pdo_parser.cpp" role="src" />
<file md5sum="22d64f72caa89b5b358ae112bfba3e38" name="pdo_stmt.cpp" role="src" />
<file md5sum="91713fff2e691893aa6735dc06de1067" name="pdo_util.cpp" role="src" />
<file md5sum="5aeee2085203a055a107faf2705c2b4f" name="php_pdo_sqlsrv.h" role="src" />
<file md5sum="33f4dd39a0b1f1d0992538d48bdae3fa" name="php_pdo_sqlsrv_int.h" role="src" />
<file md5sum="e88a54492774fd7454c958bad5be9d26" name="shared/FormattedPrint.cpp" role="src" />
<file md5sum="958afdbccf2140006c5d0f31cf4a1f76" name="shared/FormattedPrint.h" role="src" />
<file md5sum="6d437757bcd8cf8ba3ed42f54a12854b" name="shared/StringFunctions.cpp" role="src" />
<file md5sum="96329050a701facc6653564a1d497316" name="shared/StringFunctions.h" role="src" />
<file md5sum="43b0ab59acdb12feed86124f5e0fb07e" name="shared/core_conn.cpp" role="src" />
<file md5sum="8a4283011a38594a4564293f6bca9484" name="shared/core_init.cpp" role="src" />
<file md5sum="e456bcea9ac3ad1bb6c7b5f33718bc63" name="shared/core_results.cpp" role="src" />
<file md5sum="6fc6834030518ef5f6f6dfb0847655ae" name="shared/core_sqlsrv.h" role="src" />
<file md5sum="2e47d9ce2f0c1203da38935497412cf5" name="shared/core_stmt.cpp" role="src" />
<file md5sum="bbb7c8c8243838e2448f1b0f71ba34a7" name="shared/core_stream.cpp" role="src" />
<file md5sum="b27d168f625fa3921f8dd4d0d42a3da5" name="shared/core_util.cpp" role="src" />
<file md5sum="4d95c2179b17ceef3ae795dfe1c8fbde" name="shared/globalization.h" role="src" />
<file md5sum="a49783c30f55ee89d50f041cb7101b82" name="shared/interlockedatomic.h" role="src" />
<file md5sum="d8d435fa231f022ac3d4ee07737cc7da" name="shared/interlockedatomic_gcc.h" role="src" />
<file md5sum="077c93cbc32fb2b4d75da0a60d860a00" name="shared/interlockedslist.h" role="src" />
<file md5sum="f93f1b6fe442bbb48f3934df73e2be46" name="shared/localization.hpp" role="src" />
<file md5sum="50243ab88f61fe6e9ca04154af4fc42a" name="shared/localizationimpl.cpp" role="src" />
<file md5sum="3d8a3276eda8880a7edd42baf972eb10" name="shared/msodbcsql.h" role="src" />
<file md5sum="7ebc8a074bceac47649f6e3bcd1759fc" name="shared/sal_def.h" role="src" />
<file md5sum="cf350aec37652ce03c7e44729d422b10" name="shared/typedefs_for_linux.h" role="src" />
<file md5sum="6439f1bed7d9d654809a3c5a6d2a6a5c" name="shared/version.h" role="src" />
<file md5sum="29980e0674d44758c8521ed27cb39ce7" name="shared/xplat.h" role="src" />
<file md5sum="d99a98a25d4f9b2ae1c3bd129b4fe64a" name="shared/xplat_intsafe.h" role="src" />
<file md5sum="9456033e0e237f49d7fed6a851cd7d7b" name="shared/xplat_winerror.h" role="src" />
<file md5sum="8073aaa724a423504b4e65ad5effbd2f" name="shared/xplat_winnls.h" role="src" />
<file md5sum="e4bb2a59e60d0f60c48860bf37c083e2" name="template.rc" role="src" />
</dir>
</contents>
<dependencies>
<required>
<php>
<min>7.3.0</min>
</php>
<pearinstaller>
<min>1.4.0b1</min>
</pearinstaller>
<arch>
<pattern>linux-*-i?86-*</pattern>
<conflicts/>
</arch>
</required>
</dependencies>
<providesextension>pdo_sqlsrv</providesextension>
<extsrcrelease />
</package>

View file

@ -1 +0,0 @@
Microsoft Drivers for PHP for SQL Server (PDO driver)

View file

@ -1,14 +0,0 @@
Copyright(c) 2022 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.

View file

@ -1,101 +0,0 @@
dnl ----------------------------------------------------------------------------------------------------------------------------------
dnl File: config.m4
dnl
dnl Contents: the code that will go into the configure script, indicating options,
dnl external libraries and includes, and what source files are to be compiled.
dnl
dnl Microsoft Drivers 5.10 for PHP for SQL Server
dnl Copyright(c) Microsoft Corporation
dnl All rights reserved.
dnl MIT License
dnl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
dnl to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
dnl 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 :
dnl The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
dnl THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
dnl FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
dnl 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
dnl IN THE SOFTWARE.
dnl ---------------------------------------------------------------------------------------------------------------------------------
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"
CXXFLAGS="$CXXFLAGS -D_FORTIFY_SOURCE=2 -O2"
CXXFLAGS="$CXXFLAGS -fstack-protector"
HOST_OS_ARCH=`uname`
if test "${HOST_OS_ARCH}" = "Darwin"; then
PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-bind_at_load"
else
PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-z,now"
IS_ALPINE_1=`uname -a | cut -f 4 -d ' ' | cut -f 2 -d '-'`
IS_ALPINE_2=`cat /etc/os-release | grep ID | grep alpine | cut -f 2 -d '='`
if test "${IS_ALPINE_1}" = "Alpine" || test "${IS_ALPINE_2}" = "alpine"; then
AC_DEFINE(__MUSL__, 1, [ ])
fi
fi
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

View file

@ -1,54 +0,0 @@
//----------------------------------------------------------------------------------------------------------------------------------
// File: config.w32
//
// Contents: JScript build configuration used by buildconf.bat
//
// Microsoft Drivers 5.10 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 /guard:cf" );
ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/EHsc" );
ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/GS" );
ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/Zi" );
if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/guard:cf /O2" );
ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" );
if (VCVERS >= 1913) {
ADD_FLAG("LDFLAGS_PDO_SQLSRV", "/d2:-guardspecload");
ADD_FLAG("CFLAGS_PDO_SQLSRV", "/Qspectre");
} else if (VCVERS == 1900) {
var subver1900 = probe_binary(PHP_CL).substr(6);
if (subver1900 >= 24241) {
ADD_FLAG("LDFLAGS_PDO_SQLSRV", "/d2:-guardspecload");
ADD_FLAG('CFLAGS_PDO_SQLSRV', "/Qspectre");
}
}
ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo');
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");
}
}

View file

@ -1,2106 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// file: pdo_dbh.cpp
//
// Contents: Implements the PDO object for PDO_SQLSRV
//
// Microsoft Drivers 5.10 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.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_pdo_sqlsrv.h"
}
#include "php_pdo_sqlsrv_int.h"
#include <string>
#include <sstream>
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 = 50; // size of the buffer to hold the string value of the last inserted id, which may be an int, bigint, decimal(p,0) or numeric(p,0)
const char SEQUENCE_CURRENT_VALUE_QUERY[] = "SELECT current_value FROM sys.sequences WHERE name=N'%s'";
const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( SEQUENCE_CURRENT_VALUE_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes
// List of PDO supported connection options.
namespace PDOConnOptionNames {
const char Server[] = "Server";
const char APP[] = "APP";
const char AccessToken[] = "AccessToken";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char Authentication[] = "Authentication";
const char ColumnEncryption[] = "ColumnEncryption";
const char ConnectionPooling[] = "ConnectionPooling";
const char Language[] = "Language";
const char ConnectRetryCount[] = "ConnectRetryCount";
const char ConnectRetryInterval[] = "ConnectRetryInterval";
const char Database[] = "Database";
const char Driver[] = "Driver";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char KeyStoreAuthentication[] = "KeyStoreAuthentication";
const char KeyStorePrincipalId[] = "KeyStorePrincipalId";
const char KeyStoreSecret[] = "KeyStoreSecret";
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 TransparentNetworkIPResolution[] = "TransparentNetworkIPResolution";
const char WSID[] = "WSID";
const char ComputePool[] = "ComputePool";
const char HostNameInCertificate[] = "HostNameInCertificate";
}
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,
PDO_STMT_OPTION_FETCHES_DATETIME_TYPE,
PDO_STMT_OPTION_FORMAT_DECIMALS,
PDO_STMT_OPTION_DECIMAL_PLACES,
PDO_STMT_OPTION_DATA_CLASSIFICATION
};
// 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<stmt_option_query_timeout>( new stmt_option_query_timeout ) },
{ NULL, 0, SQLSRV_STMT_OPTION_SCROLLABLE, std::unique_ptr<stmt_option_pdo_scrollable>( new stmt_option_pdo_scrollable ) },
{ NULL, 0, PDO_STMT_OPTION_ENCODING, std::unique_ptr<stmt_option_encoding>( new stmt_option_encoding ) },
{ NULL, 0, PDO_STMT_OPTION_DIRECT_QUERY, std::unique_ptr<stmt_option_direct_query>( new stmt_option_direct_query ) },
{ NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, std::unique_ptr<stmt_option_cursor_scroll_type>( new stmt_option_cursor_scroll_type ) },
{ NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, std::unique_ptr<stmt_option_buffered_query_limit>( new stmt_option_buffered_query_limit ) },
{ NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr<stmt_option_emulate_prepares>( new stmt_option_emulate_prepares ) },
{ NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr<stmt_option_fetch_numeric>( new stmt_option_fetch_numeric ) },
{ NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr<stmt_option_fetch_datetime>( new stmt_option_fetch_datetime ) },
{ NULL, 0, PDO_STMT_OPTION_FORMAT_DECIMALS, std::unique_ptr<stmt_option_format_decimals>( new stmt_option_format_decimals ) },
{ NULL, 0, PDO_STMT_OPTION_DECIMAL_PLACES, std::unique_ptr<stmt_option_decimal_places>( new stmt_option_decimal_places ) },
{ NULL, 0, PDO_STMT_OPTION_DATA_CLASSIFICATION, std::unique_ptr<stmt_option_data_classification>( new stmt_option_data_classification ) },
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
};
// boolean connection string
struct pdo_bool_conn_str_func
{
static void func( _In_ connection_option const* option, _Inout_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str );
};
struct pdo_txn_isolation_conn_attr_func
{
static void func( connection_option const* /*option*/, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ );
};
struct pdo_int_conn_str_func {
static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str )
{
SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "Wrong zval type for this keyword");
std::string val_str = Z_STRVAL_P( value );
conn_str += option->odbc_name;
conn_str += "={";
conn_str += val_str;
conn_str += "};";
}
};
struct pdo_encrypt_set_func
{
static void func(_In_ connection_option const* option, _Inout_ zval* value_z, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str)
{
SQLSRV_ASSERT(Z_TYPE_P(value_z) == IS_STRING, "Wrong zval type for this keyword");
std::string val_str = Z_STRVAL_P(value_z);
std::string whitespaces(" \t\f\v\n\r");
// Trim white spaces
std::size_t found = val_str.find_last_not_of(whitespaces);
if (found != std::string::npos)
val_str.erase(found + 1);
const char TRUE_VALUE_1[] = "true";
const char TRUE_VALUE_2[] = "1";
const char FALSE_VALUE_1[] = "false";
const char FALSE_VALUE_2[] = "0";
// For backward compatibility, convert true/1 to yes and false/0 to no
std::string attr;
if (!val_str.compare(TRUE_VALUE_1) || !val_str.compare(TRUE_VALUE_2)) {
attr = "yes";
} else if (!val_str.compare(FALSE_VALUE_1) || !val_str.compare(FALSE_VALUE_2)) {
attr = "no";
} else {
// simply pass the attribute value to ODBC driver
attr = val_str;
}
conn_str += option->odbc_name;
conn_str += "={";
conn_str += attr;
conn_str += "};";
}
};
template <unsigned int Attr>
struct pdo_int_conn_attr_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
try {
SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "pdo_int_conn_attr_func: Unexpected zval type." );
size_t val = static_cast<size_t>( atoi( Z_STRVAL_P( value )) );
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( val ), SQL_IS_UINTEGER );
}
catch( core::CoreException& ) {
throw;
}
}
};
template <unsigned int Attr>
struct pdo_bool_conn_attr_func {
static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( core_str_zval_is_true( value )),
SQL_IS_UINTEGER );
}
catch( core::CoreException& ) {
throw;
}
}
};
// statement options related functions
void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ HashTable* options_ht,
_Inout_ zval** data );
void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht );
} // 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::AccessToken,
sizeof( PDOConnOptionNames::AccessToken ),
SQLSRV_CONN_OPTION_ACCESS_TOKEN,
ODBCConnOptions::AccessToken,
sizeof( ODBCConnOptions::AccessToken),
CONN_ATTR_STRING,
access_token_set_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::Authentication,
sizeof( PDOConnOptionNames::Authentication ),
SQLSRV_CONN_OPTION_AUTHENTICATION,
ODBCConnOptions::Authentication,
sizeof( ODBCConnOptions::Authentication ),
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::Language,
sizeof( PDOConnOptionNames::Language ),
SQLSRV_CONN_OPTION_LANGUAGE,
ODBCConnOptions::Language,
sizeof( ODBCConnOptions::Language ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::Driver,
sizeof(PDOConnOptionNames::Driver),
SQLSRV_CONN_OPTION_DRIVER,
ODBCConnOptions::Driver,
sizeof(ODBCConnOptions::Driver),
CONN_ATTR_STRING,
driver_set_func::func
},
{
PDOConnOptionNames::ColumnEncryption,
sizeof(PDOConnOptionNames::ColumnEncryption),
SQLSRV_CONN_OPTION_COLUMNENCRYPTION,
ODBCConnOptions::ColumnEncryption,
sizeof(ODBCConnOptions::ColumnEncryption),
CONN_ATTR_STRING,
column_encryption_set_func::func
},
{
PDOConnOptionNames::ConnectRetryCount,
sizeof( PDOConnOptionNames::ConnectRetryCount ),
SQLSRV_CONN_OPTION_CONN_RETRY_COUNT,
ODBCConnOptions::ConnectRetryCount,
sizeof( ODBCConnOptions::ConnectRetryCount ),
CONN_ATTR_INT,
pdo_int_conn_str_func::func
},
{
PDOConnOptionNames::ConnectRetryInterval,
sizeof( PDOConnOptionNames::ConnectRetryInterval ),
SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL,
ODBCConnOptions::ConnectRetryInterval,
sizeof( ODBCConnOptions::ConnectRetryInterval ),
CONN_ATTR_INT,
pdo_int_conn_str_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_MIXED,
pdo_encrypt_set_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::KeyStoreAuthentication,
sizeof( PDOConnOptionNames::KeyStoreAuthentication ),
SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION,
ODBCConnOptions::KeyStoreAuthentication,
sizeof( ODBCConnOptions::KeyStoreAuthentication ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
PDOConnOptionNames::KeyStorePrincipalId,
sizeof( PDOConnOptionNames::KeyStorePrincipalId ),
SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID,
ODBCConnOptions::KeyStorePrincipalId,
sizeof( ODBCConnOptions::KeyStorePrincipalId ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
PDOConnOptionNames::KeyStoreSecret,
sizeof( PDOConnOptionNames::KeyStoreSecret ),
SQLSRV_CONN_OPTION_KEYSTORE_SECRET,
ODBCConnOptions::KeyStoreSecret,
sizeof( ODBCConnOptions::KeyStoreSecret ),
CONN_ATTR_STRING,
ce_akv_str_set_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<SQL_ATTR_LOGIN_TIMEOUT>::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<SQL_ATTR_TRACEFILE>::func
},
{
PDOConnOptionNames::TraceOn,
sizeof( PDOConnOptionNames::TraceOn ),
SQLSRV_CONN_OPTION_TRACE_ON,
ODBCConnOptions::TraceOn,
sizeof( ODBCConnOptions::TraceOn ),
CONN_ATTR_BOOL,
pdo_bool_conn_attr_func<SQL_ATTR_TRACE>::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::TransparentNetworkIPResolution,
sizeof(PDOConnOptionNames::TransparentNetworkIPResolution),
SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION,
ODBCConnOptions::TransparentNetworkIPResolution,
sizeof(ODBCConnOptions::TransparentNetworkIPResolution),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::WSID,
sizeof( PDOConnOptionNames::WSID ),
SQLSRV_CONN_OPTION_WSID,
ODBCConnOptions::WSID,
sizeof( ODBCConnOptions::WSID ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::ComputePool,
sizeof(PDOConnOptionNames::ComputePool),
SQLSRV_CONN_OPTION_COMPUTE_POOL,
ODBCConnOptions::ComputePool,
sizeof(ODBCConnOptions::ComputePool),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::HostNameInCertificate,
sizeof(PDOConnOptionNames::HostNameInCertificate),
SQLSRV_CONN_OPTION_HOSTNAME_IN_CERT,
ODBCConnOptions::HostNameInCertificate,
sizeof(ODBCConnOptions::HostNameInCertificate),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{ NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table
};
#if PHP_VERSION_ID < 80100
// close the connection
int pdo_sqlsrv_dbh_close(_Inout_ pdo_dbh_t *dbh);
// execute queries
int pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql,
_Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options);
zend_long pdo_sqlsrv_dbh_do(_Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len);
// quote a string, meaning put quotes around it and escape any quotes within it
int pdo_sqlsrv_dbh_quote(_Inout_ pdo_dbh_t* dbh, _In_reads_(unquotedlen) const char* unquoted, _In_ size_t unquotedlen, _Outptr_result_buffer_(*quotedlen) char **quoted, _Out_ size_t* quotedlen,
enum pdo_param_type paramtype);
// transaction support functions
int pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh);
int pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh);
int pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh);
// attribute functions
int pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val);
int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value);
// return the last id generated by an executed SQL statement
char * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len);
// return more information
int pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info);
#else
// close the connection
void pdo_sqlsrv_dbh_close(_Inout_ pdo_dbh_t *dbh);
// execute queries
bool pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_ zend_string *sql,
_Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options);
zend_long pdo_sqlsrv_dbh_do(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *sql);
// quote a string, meaning put quotes around it and escape any quotes within it
zend_string* pdo_sqlsrv_dbh_quote(_Inout_ pdo_dbh_t* dbh, _In_ const zend_string *unquoted, _In_ enum pdo_param_type paramtype);
// transaction support functions
bool pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh);
bool pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh);
bool pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh);
// attribute functions
bool pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val);
int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value);
// return the last id generated by an executed SQL statement
zend_string * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *name);
// return more information
void pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info);
#endif
// additional methods are supported in this function
pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( _Inout_ pdo_dbh_t *dbh, int kind );
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
#if PHP_VERSION_ID < 80100
NULL // in transaction not implemented
};
#else
NULL, // in transaction not implemented
NULL // get_gc not implemented
};
#endif
// log a function entry point
#define PDO_LOG_DBH_ENTRY \
{ \
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data ); \
if (driver_dbh != NULL) driver_dbh->set_func(__FUNCTION__); \
core_sqlsrv_register_severity_checker(pdo_severity_check); \
LOG(SEV_NOTICE, "%1!s!: entering", __FUNCTION__); \
}
// constructor for the internal object for connections
pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver ) :
sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 ),
stmts( NULL ),
direct_query( false ),
query_timeout( QUERY_TIMEOUT_INVALID ),
client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )),
fetch_numeric( false ),
fetch_datetime( false ),
format_decimals( false ),
decimal_places( NO_CHANGE_DECIMAL_PLACES ),
use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED),
emulate_prepare(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( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options)
{
PDO_LOG_DBH_ENTRY;
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<conn_string_parser> 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_pdo_henv_cp->set_driver( dbh );
g_pdo_henv_ncp->set_driver( dbh );
CHECK_CUSTOM_ERROR( driver_options && Z_TYPE_P( driver_options ) != IS_ARRAY, *g_pdo_henv_cp, SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE ) {
throw core::CoreException();
}
// throws PDOException if the ATTR_PERSISTENT is in connection options
CHECK_CUSTOM_ERROR( dbh->is_persistent, *g_pdo_henv_cp, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR ) {
dbh->refcount--;
throw pdo::PDOException();
}
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( pdo_conn_options_ht );
core::sqlsrv_zend_hash_init( *g_pdo_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */,
ZVAL_PTR_DTOR, 0 /*persistent*/ );
// Either of g_pdo_henv_cp or g_pdo_henv_ncp can be used to propogate the error.
dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_pdo_henv_cp, dbh->data_source,
static_cast<int>( dbh->data_source_len ), pdo_conn_options_ht );
dsn_parser->parse_conn_string();
// 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_pdo_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_pdo_henv_cp, *g_pdo_henv_ncp, core::allocate_conn<pdo_sqlsrv_dbh>, 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" );
// 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_pdo_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. (for PHP_VERSION_ID < 80100)
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh )
#else
void pdo_sqlsrv_dbh_close(_Inout_ pdo_dbh_t *dbh)
#endif
{
LOG( SEV_NOTICE, "pdo_sqlsrv_dbh_close: entering" );
// if the connection didn't complete properly, driver_data isn't initialized.
if( dbh->driver_data == NULL ) {
#if PHP_VERSION_ID < 80100
return 1;
#else
return;
#endif
}
PDO_RESET_DBH_ERROR;
// call the core layer close
core_sqlsrv_close( reinterpret_cast<sqlsrv_conn*>( dbh->driver_data ) );
dbh->driver_data = NULL;
#if PHP_VERSION_ID < 80100
// always return success that the connection is closed
return 1;
#endif
}
// pdo_sqlsrv_dbh_prepare
// 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.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql,
_Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options)
#else
bool pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_ zend_string *sql_zstr, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
hash_auto_ptr pdo_stmt_options_ht;
sqlsrv_malloc_auto_ptr<char> sql_rewrite;
size_t sql_rewrite_len = 0;
sqlsrv_malloc_auto_ptr<pdo_sqlsrv_stmt> driver_stmt;
hash_auto_ptr placeholders;
sqlsrv_malloc_auto_ptr<sql_string_parser> sql_parser;
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( 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;
// if not emulate_prepare, we support parameterized queries with ?, not names
stmt->supports_placeholders = (driver_dbh->emulate_prepare) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; // the statement options may override this later
// 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*/ );
// Either of g_pdo_henv_cp or g_pdo_henv_ncp can be used to propogate the error.
validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht );
driver_stmt = static_cast<pdo_sqlsrv_stmt*>( core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt<pdo_sqlsrv_stmt>,
pdo_stmt_options_ht, PDO_STMT_OPTS,
pdo_sqlsrv_handle_stmt_error, stmt ));
// 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 PHP_VERSION_ID >= 80100
zend_string* sql_rewrite_zstr = NULL;
const char* sql = ZSTR_VAL(sql_zstr);
size_t sql_len = ZSTR_LEN(sql_zstr);
#endif
// rewrite named parameters in the query to positional parameters if we aren't letting PDO do the
// parameter substitution for us
if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) {
// rewrite the query to map named parameters to positional parameters. We do this rather than use the ODBC named
// parameters for consistency with the PDO MySQL and PDO ODBC drivers.
#if PHP_VERSION_ID < 80100
int zr = pdo_parse_params( stmt, const_cast<char*>( sql ), sql_len, &sql_rewrite, &sql_rewrite_len );
CHECK_ZEND_ERROR(zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE) {
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;
}
#else
int zr = pdo_parse_params(stmt, sql_zstr, &sql_rewrite_zstr);
CHECK_ZEND_ERROR(zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE) {
throw core::CoreException();
}
// if parameter substitution happened, use that query instead of the original
if (sql_rewrite_zstr != NULL) {
sql = ZSTR_VAL(sql_rewrite_zstr);
sql_len = ZSTR_LEN(sql_rewrite_zstr);
}
#endif
}
if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) {
core_sqlsrv_prepare(driver_stmt, sql, sql_len);
}
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<void*>( const_cast<char*>( driver_stmt->direct_query_subst_string )));
}
driver_stmt->direct_query_subst_string = estrdup( sql );
driver_stmt->direct_query_subst_string_len = sql_len;
}
#if PHP_VERSION_ID >= 80100
if (sql_rewrite_zstr != NULL) {
zend_string_release(sql_rewrite_zstr);
}
#endif
// else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be
// set to the substituted query
if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) {
// parse placeholders in the sql query into the placeholders ht
ALLOC_HASHTABLE( placeholders );
core::sqlsrv_zend_hash_init(*driver_dbh, placeholders, 5, ZVAL_PTR_DTOR /* dtor */, 0 /* persistent */);
#if PHP_VERSION_ID < 80100
sql_parser = new (sqlsrv_malloc(sizeof(sql_string_parser))) sql_string_parser(*driver_dbh, stmt->query_string,
static_cast<int>(stmt->query_stringlen), placeholders);
#else
sql_parser = new (sqlsrv_malloc(sizeof(sql_string_parser))) sql_string_parser(*driver_dbh, ZSTR_VAL(stmt->query_string),
ZSTR_LEN(stmt->query_string), placeholders);
#endif
sql_parser->parse_sql_string();
driver_stmt->placeholders = placeholders;
placeholders.transferred();
}
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<const char*>( driver_dbh->last_error()->sqlstate ));
}
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// catch any errant exception and die
catch(...) {
DIE( "pdo_sqlsrv_dbh_prepare: Unknown exception caught." );
}
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
// 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.
#if PHP_VERSION_ID < 80100
zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len )
#else
zend_long pdo_sqlsrv_dbh_do(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *sql)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> 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." );
SQLSRV_ASSERT( driver_dbh != NULL, "pdo_sqlsrv_dbh_do: driver_data object was NULL.");
// 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<pdo_sqlsrv_stmt>, NULL /*options_ht*/,
NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt );
driver_stmt->set_func( __FUNCTION__ );
#if PHP_VERSION_ID < 80100
SQLRETURN execReturn = core_sqlsrv_execute(driver_stmt, sql, static_cast<int>(sql_len));
#else
SQLRETURN execReturn = core_sqlsrv_execute(driver_stmt, ZSTR_VAL(sql), ZSTR_LEN(sql));
#endif
// since the user can give us a compound statement, we return the row count for the last set, and since the row count
// isn't guaranteed to be valid until all the results have been fetched, we fetch them all first.
if ( execReturn != SQL_NO_DATA && core_sqlsrv_has_any_result( driver_stmt )) {
SQLRETURN r = SQL_SUCCESS;
do {
rows = core::SQLRowCount( driver_stmt );
r = core::SQLMoreResults( driver_stmt );
} 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<const char*>( 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. (if PHP_VERSION_ID < 80100)
// Return true if currently inside a transaction, false otherwise
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh)
#else
bool pdo_sqlsrv_dbh_begin(_Inout_ pdo_dbh_t *dbh)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
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<sqlsrv_conn*>( 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 );
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_begin: Uncaught exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// 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. (if PHP_VERSION_ID < 80100)
// Return true for success and false otherwise
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh)
#else
bool pdo_sqlsrv_dbh_commit(_Inout_ pdo_dbh_t *dbh)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
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<sqlsrv_conn*>( 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 );
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_commit: Uncaught exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// pdo_sqlsrv_dbh_rollback
// 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. (if PHP_VERSION_ID < 80100)
// Return true for success and false otherwise
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh)
#else
bool pdo_sqlsrv_dbh_rollback(_Inout_ pdo_dbh_t *dbh)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
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<sqlsrv_conn*>( 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 );
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
catch( ... ) {
DIE ("pdo_sqlsrv_dbh_rollback: Uncaught exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
// pdo_sqlsrv_dbh_set_attr
// 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. (if PHP_VERSION_ID < 80100)
// Return true on success and false in case of failure
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val)
#else
bool pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ASSERT( driver_dbh != NULL, "pdo_sqlsrv_dbh_set_attr: driver_data object was NULL.");
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<SQLSRV_ENCODING>( 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);
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<long>( 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);
break;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
driver_dbh->fetch_datetime = zend_is_true(val);
break;
case SQLSRV_ATTR_FORMAT_DECIMALS:
driver_dbh->format_decimals = zend_is_true(val);
break;
case SQLSRV_ATTR_DECIMAL_PLACES:
{
// first check if the input is an integer
if (Z_TYPE_P(val) != IS_LONG) {
THROW_PDO_ERROR(driver_dbh, SQLSRV_ERROR_INVALID_DECIMAL_PLACES);
}
zend_long decimal_places = Z_LVAL_P(val);
if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) {
// ignore decimal_places as this is out of range
decimal_places = NO_CHANGE_DECIMAL_PLACES;
}
driver_dbh->decimal_places = static_cast<short>(decimal_places);
}
break;
#if PHP_VERSION_ID >= 70200
case PDO_ATTR_DEFAULT_STR_PARAM:
{
if (Z_TYPE_P(val) != IS_LONG) {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID);
}
zend_long value = Z_LVAL_P(val);
if (value == PDO_PARAM_STR_NATL) {
driver_dbh->use_national_characters = 1;
}
else if (value == PDO_PARAM_STR_CHAR) {
driver_dbh->use_national_characters = 0;
}
else {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID);
}
}
break;
#endif
// 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 );
}
case PDO_ATTR_EMULATE_PREPARES:
{
driver_dbh->emulate_prepare = zend_is_true(val);
if (driver_dbh->emulate_prepare && driver_dbh->ce_option.enabled) {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED);
}
}
break;
// Statement level only
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
case SQLSRV_ATTR_DATA_CLASSIFICATION:
{
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& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return false;
#endif
}
#if PHP_VERSION_ID < 80100
return 1;
#else
return true;
#endif
}
// 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. (if PHP_VERSION_ID < 80100)
// There are 3 return states:
// -1 for errors while retrieving a valid attribute
// 0 for attempting to retrieve an attribute which is not supported by the driver
// any other value for success, *return_value must be set to the attribute value
int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value)
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ASSERT( driver_dbh != NULL, "pdo_sqlsrv_dbh_get_attr: driver_data object was NULL.");
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:
{
#if PHP_VERSION_ID < 80100
// PDO does not throw "not supported" error message for these attributes.
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR);
#else
return 0;
#endif
}
case PDO_ATTR_EMULATE_PREPARES:
{
ZVAL_BOOL(return_value, driver_dbh->emulate_prepare);
break;
}
// Statement level only
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
case SQLSRV_ATTR_DATA_CLASSIFICATION:
{
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 );
break;
}
case PDO_ATTR_SERVER_VERSION:
{
core_sqlsrv_get_server_version( driver_dbh, return_value );
break;
}
case PDO_ATTR_CLIENT_VERSION:
{
core_sqlsrv_get_client_info( driver_dbh, return_value );
//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;
add_assoc_string(return_value, extver, &filever[0]);
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;
}
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
{
ZVAL_BOOL( return_value, driver_dbh->fetch_datetime );
break;
}
case SQLSRV_ATTR_FORMAT_DECIMALS:
{
ZVAL_BOOL( return_value, driver_dbh->format_decimals );
break;
}
case SQLSRV_ATTR_DECIMAL_PLACES:
{
ZVAL_LONG( return_value, driver_dbh->decimal_places );
break;
}
#if PHP_VERSION_ID >= 70200
case PDO_ATTR_DEFAULT_STR_PARAM:
{
ZVAL_LONG(return_value, (driver_dbh->use_national_characters == 0) ? PDO_PARAM_STR_CHAR : PDO_PARAM_STR_NATL);
break;
}
#endif
default:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
break;
}
}
#if PHP_VERSION_ID < 80100
return 1;
#else
return 1;
#endif
}
catch( core::CoreException& ) {
#if PHP_VERSION_ID < 80100
return 0;
#else
return -1;
#endif
}
}
// 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.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info)
#else
void pdo_sqlsrv_dbh_return_error(_In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, _Out_ zval *info)
#endif
{
SQLSRV_ASSERT( dbh != NULL || stmt != NULL, "Either dbh or stmt must not be NULL to dereference the error." );
sqlsrv_error* ctx_error = NULL;
if( stmt ) {
ctx_error = static_cast<sqlsrv_stmt*>( stmt->driver_data )->last_error();
}
else {
ctx_error = static_cast<sqlsrv_conn*>( dbh->driver_data )->last_error();
}
pdo_sqlsrv_retrieve_context_error( ctx_error, info );
#if PHP_VERSION_ID < 80100
return 1;
#endif
}
// 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. (if PHP_VERSION_ID < 80100)
// Returning NULL indicates an error condition. The input "name" MIGHT be NULL
#if PHP_VERSION_ID < 80100
char * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len)
#else
zend_string * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_ const zend_string *name)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
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<sqlsrv_stmt> driver_stmt;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ASSERT( driver_dbh != NULL, "pdo_sqlsrv_dbh_last_id: driver_data object was NULL." );
char idSTR[LAST_INSERT_ID_BUFF_LEN] = { '\0' };
char* str = NULL;
SQLLEN cbID = 0;
try {
sqlsrv_malloc_auto_ptr<SQLWCHAR> wsql_string;
unsigned int wsql_len;
if (name == NULL) {
wsql_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_CHAR, LAST_INSERT_ID_QUERY, sizeof(LAST_INSERT_ID_QUERY), &wsql_len);
} else {
char buffer[LAST_INSERT_ID_QUERY_MAX_LEN] = { '\0' };
#if PHP_VERSION_ID < 80100
snprintf(buffer, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, name);
#else
snprintf(buffer, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, ZSTR_VAL(name));
#endif
wsql_string = utf16_string_from_mbcs_string(SQLSRV_ENCODING_CHAR, buffer, sizeof(buffer), &wsql_len);
}
CHECK_CUSTOM_ERROR(wsql_string == 0, driver_stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
// temp PDO statement used for error handling if something happens
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<pdo_sqlsrv_stmt>, NULL /*options_ht*/, NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt );
driver_stmt->set_func( __FUNCTION__ );
// execute the last insert id query
core::SQLExecDirectW( driver_stmt, wsql_string );
core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 );
SQLRETURN r = core::SQLGetData(driver_stmt, 1, SQL_C_CHAR, idSTR, LAST_INSERT_ID_BUFF_LEN, &cbID, false);
CHECK_CUSTOM_ERROR((!SQL_SUCCEEDED(r) || cbID == SQL_NULL_DATA || cbID == SQL_NO_TOTAL), driver_stmt,
PDO_SQLSRV_ERROR_LAST_INSERT_ID) {
throw core::CoreException();
}
driver_stmt->~sqlsrv_stmt();
} catch( core::CoreException& ) {
// restore error handling to its previous mode
dbh->error_mode = prev_err_mode;
// 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<const char*>( driver_stmt->last_error()->sqlstate ));
driver_dbh->set_last_error( driver_stmt->last_error() );
if( driver_stmt ) {
driver_stmt->~sqlsrv_stmt();
}
#if PHP_VERSION_ID < 80100
*len = 0;
str = reinterpret_cast<char*>(sqlsrv_malloc(0, sizeof(char), 1)); // return an empty string with a null terminator
str[0] = '\0';
return str;
#else
return ZSTR_EMPTY_ALLOC();
#endif
}
// restore error handling to its previous mode
dbh->error_mode = prev_err_mode;
// copy the last ID string and return it
str = reinterpret_cast<char*>(sqlsrv_malloc(cbID, sizeof(char), 1)); // include space for null terminator
strcpy_s(str, cbID + 1, idSTR);
#if PHP_VERSION_ID < 80100
*len = static_cast<size_t>(cbID);
return str;
#else
zend_string *zstr = zend_string_init(str, cbID, 0);
sqlsrv_free(str);
return zstr;
#endif
}
// pdo_sqlsrv_dbh_quote
// 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.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const char* unquoted, _In_ size_t unquoted_len, _Outptr_result_buffer_(*quoted_len) char **quoted, _Out_ size_t* quoted_len,
enum pdo_param_type paramtype )
#else
zend_string* pdo_sqlsrv_dbh_quote(_Inout_ pdo_dbh_t* dbh, _In_ const zend_string *unquoted, _In_ enum pdo_param_type paramtype)
#endif
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
SQLSRV_ENCODING encoding = SQLSRV_ENCODING_CHAR;
bool use_national_char_set = false;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>(dbh->driver_data);
SQLSRV_ASSERT(driver_dbh != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was NULL.");
// get the current object in PHP; this distinguishes pdo_sqlsrv_dbh_quote being called from:
// 1. PDO::quote() - object name is PDO
// 2. PDOStatement::execute() - object name is PDOStatement
zend_execute_data* execute_data = EG( current_execute_data );
zval *object = getThis();
// iterate through parents to find "PDOStatement"
bool is_statement = false;
if ( object ) {
zend_class_entry* curr_class = ( Z_OBJ_P( object ))->ce;
while ( curr_class != NULL ) {
if ( strcmp( reinterpret_cast<const char*>( curr_class->name->val ), "PDOStatement" ) == 0 ) {
is_statement = true;
break;
}
curr_class = curr_class->parent;
}
}
// only change the encoding if quote is called from the statement level (which should only be called when a statement
// is prepared with emulate prepared on)
if (is_statement) {
pdo_stmt_t *stmt = Z_PDO_STMT_P(object);
SQLSRV_ASSERT(stmt != NULL, "pdo_sqlsrv_dbh_quote: stmt object was null");
// set the encoding to be the encoding of the statement otherwise set to be the encoding of the dbh
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was null");
encoding = driver_stmt->encoding();
if (encoding == SQLSRV_ENCODING_INVALID || encoding == SQLSRV_ENCODING_DEFAULT) {
pdo_sqlsrv_dbh* stmt_driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>(stmt->driver_data);
encoding = stmt_driver_dbh->encoding();
}
// get the placeholder at the current position in driver_stmt->placeholders ht
// Normally it's not a good idea to alter the internal pointer in a hashed array
// (see pull request 634 on GitHub) but in this case this is for internal use only
zval* placeholder = NULL;
if ((placeholder = zend_hash_get_current_data(driver_stmt->placeholders)) != NULL && zend_hash_move_forward(driver_stmt->placeholders) == SUCCESS && stmt->bound_params != NULL) {
pdo_bound_param_data* param = NULL;
if (Z_TYPE_P(placeholder) == IS_STRING) {
param = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_params, Z_STR_P(placeholder)));
}
else if (Z_TYPE_P(placeholder) == IS_LONG) {
param = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_params, Z_LVAL_P(placeholder)));
}
if (NULL != param) {
SQLSRV_ENCODING param_encoding = static_cast<SQLSRV_ENCODING>(Z_LVAL(param->driver_params));
if (param_encoding != SQLSRV_ENCODING_INVALID) {
encoding = param_encoding;
}
}
}
}
use_national_char_set = (driver_dbh->use_national_characters == 1 || encoding == SQLSRV_ENCODING_UTF8);
#if PHP_VERSION_ID >= 70200
if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
use_national_char_set = true;
}
if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
use_national_char_set = false;
}
#endif
if (encoding == SQLSRV_ENCODING_BINARY) {
#if PHP_VERSION_ID < 80100
*quoted_len = (unquoted_len * 2) + 2; // each character will be converted to 2 hex digits and prepend '0x' to the result
*quoted = reinterpret_cast<char*>(sqlsrv_malloc(*quoted_len, sizeof(char), 1)); // include space for null terminator
memset(*quoted, '\0', *quoted_len + 1);
unsigned int pos = 0;
(*quoted)[pos++] = '0';
(*quoted)[pos++] = 'x';
for (size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index) {
// On success, snprintf returns the total number of characters written
// On failure, a negative number is returned
// The generated string has a length of at most len - 1, so
// len is 3 (2 hex digits + 1)
// Requires "& 0x000000FF", or snprintf will translate "0x90" to "0xFFFFFF90"
int n = snprintf((char*)(*quoted + pos), 3, "%02X", unquoted[index] & 0x000000FF);
if (n < 0) {
// Something went wrong, simply return 0 (failure)
return 0;
}
pos += 2;
}
return 1;
#else
size_t unquoted_len = ZSTR_LEN(unquoted);
const char *unquoted_str = ZSTR_VAL(unquoted);
sqlsrv_malloc_auto_ptr<char> quoted;
size_t quoted_len = (unquoted_len * 2) + 2; // each character will be converted to 2 hex digits and prepend '0x' to the result
quoted = reinterpret_cast<char*>(sqlsrv_malloc(quoted_len, sizeof(char), 1)); // include space for null terminator
memset(quoted, '\0', quoted_len + 1);
unsigned int pos = 0;
quoted[pos++] = '0';
quoted[pos++] = 'x';
char *p = quoted;
for (size_t index = 0; index < unquoted_len && unquoted_str[index] != '\0'; ++index) {
// On success, snprintf returns the total number of characters written
// On failure, a negative number is returned
// The generated string has a length of at most len - 1, so
// len is 3 (2 hex digits + 1)
// Requires "& 0x000000FF", or snprintf will translate "0x90" to "0xFFFFFF90"
int n = snprintf((char*)(p + pos), 3, "%02X", unquoted_str[index] & 0x000000FF);
if (n < 0) {
// Something went wrong, simply return NULL (failure)
return NULL;
}
pos += 2;
}
zend_string* zstr = zend_string_init(quoted, quoted_len, 0);
return zstr;
#endif
}
else {
// The minimum number of single quotes needed is 2 -- the initial start and end quotes
// Add the letter N before the initial quote if the encoding is UTF8
int quotes_needed = (use_national_char_set) ? 3 : 2;
char c = '\'';
#if PHP_VERSION_ID < 80100
std::string tmp_str(unquoted, unquoted_len); // Copy all unquoted_len characters from unquoted
#else
size_t unquoted_len = ZSTR_LEN(unquoted);
const char *unquoted_str = ZSTR_VAL(unquoted);
std::string tmp_str(unquoted_str, unquoted_len); // Copy all unquoted_len characters from unquoted
#endif
std::size_t found = tmp_str.find(c); // Find the first single quote
while (found != std::string::npos) {
tmp_str.insert(found + 1, 1, c); // Insert an additional single quote
found = tmp_str.find(c, found + 2); // Find the next single quote
}
size_t len = tmp_str.length();
#if PHP_VERSION_ID < 80100
*quoted_len = quotes_needed + len; // The new length should be number of quotes plus the length of tmp_str
*quoted = reinterpret_cast<char*>(sqlsrv_malloc(*quoted_len, sizeof(char), 1)); // include space for null terminator
memset(*quoted, '\0', *quoted_len + 1);
char *p = *quoted;
#else
sqlsrv_malloc_auto_ptr<char> quoted;
size_t quoted_len = quotes_needed + len; // length returned to the caller should not account for null terminator
quoted = reinterpret_cast<char*>(sqlsrv_malloc(quoted_len, sizeof(char), 1)); // include space for null terminator
memset(quoted, '\0', quoted_len + 1);
char *p = quoted;
#endif
size_t pos = 0;
if (use_national_char_set) { // Insert the letter N if the encoding is UTF8
*(p + (pos++)) = 'N';
}
*(p + (pos++)) = c; // Add the initial quote
tmp_str.copy(p + pos, len, 0); // Copy tmp_str to *quoted
pos += len;
*(p + pos) = c; // Add the end quote
#if PHP_VERSION_ID < 80100
return 1;
#else
zend_string* zstr = zend_string_init(quoted, quoted_len, 0);
return zstr;
#endif
}
}
// This method is not implemented by this driver.
pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( _Inout_ pdo_dbh_t *dbh, int kind )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
sqlsrv_conn* driver_conn = reinterpret_cast<sqlsrv_conn*>( dbh->driver_data );
// As per documentation, simply return false if the method does not exist
// https://www.php.net/manual/en/function.is-callable.php
// But user can call PDO::errorInfo() to check the error message if necessary
CHECK_CUSTOM_WARNING_AS_ERROR(true, driver_conn, PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED);
return NULL; // return NULL for PDO to take care of the rest
}
namespace {
// Maps the PDO driver specific statement option/attribute constants to the core layer
// statement option/attribute constants.
void add_stmt_option_key(_Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ HashTable* options_ht,
_Inout_ zval* data)
{
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;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE;
break;
case SQLSRV_ATTR_FORMAT_DECIMALS:
option_key = PDO_STMT_OPTION_FORMAT_DECIMALS;
break;
case SQLSRV_ATTR_DECIMAL_PLACES:
option_key = PDO_STMT_OPTION_DECIMAL_PLACES;
break;
case SQLSRV_ATTR_DATA_CLASSIFICATION:
option_key = PDO_STMT_OPTION_DATA_CLASSIFICATION;
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);
}
}
// 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( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht )
{
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;
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 );
} ZEND_HASH_FOREACH_END();
}
}
catch( core::CoreException& ) {
throw;
}
}
void pdo_bool_conn_str_func::func( _In_ connection_option const* option, _Inout_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str )
{
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*/, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn,
std::string& /*conn_str*/ )
{
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<SQLPOINTER>( out_val ), SQL_IS_UINTEGER );
}
catch( core::CoreException& ) {
throw;
}
}
} // namespace

View file

@ -1,346 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_init.cpp
//
// Contents: initialization routines for PDO_SQLSRV
//
// Microsoft Drivers 5.10 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.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_pdo_sqlsrv.h"
}
#include "php_pdo_sqlsrv_int.h"
#ifdef COMPILE_DL_PDO_SQLSRV
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE();
#endif
ZEND_GET_MODULE(g_pdo_sqlsrv)
#endif
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_pdo_henv_cp;
sqlsrv_context* g_pdo_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( _In_z_ char const* name, _In_ long value );
void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( _In_z_ char const* name, _In_z_ char const* value );
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( _Inout_ zval* elem ) {
pdo_error* error_to_ignore = reinterpret_cast<pdo_error*>( 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_severity_checker(pdo_severity_check);
REGISTER_INI_ENTRIES();
PDO_LOG_NOTICE("pdo_sqlsrv: entering minit");
// initialize list of pdo errors
g_pdo_errors_ht = reinterpret_cast<HashTable*>( 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 );
}
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_UNCOMMITTED", PDOTxnIsolationValues::READ_UNCOMMITTED );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_COMMITTED", PDOTxnIsolationValues::READ_COMMITTED );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_REPEATABLE_READ", PDOTxnIsolationValues::REPEATABLE_READ );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SERIALIZABLE", PDOTxnIsolationValues::SERIALIZABLE );
REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SNAPSHOT", PDOTxnIsolationValues::SNAPSHOT );
// retrieve the handles for the environments
core_sqlsrv_minit( &g_pdo_henv_cp, &g_pdo_henv_ncp, pdo_sqlsrv_handle_env_error, "PHP_MINIT_FUNCTION for pdo_sqlsrv" );
}
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_pdo_henv_cp, *g_pdo_henv_ncp );
}
catch( ... ) {
PDO_LOG_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
#ifndef _WIN32
// if necessary, set locale from the environment for ODBC, which MUST be done before any connection
int set_locale = PDO_SQLSRV_G(set_locale_info);
if (set_locale == 2) {
setlocale(LC_ALL, "");
PDO_LOG_NOTICE("pdo_sqlsrv: setlocale LC_ALL");
}
else if (set_locale == 1) {
setlocale(LC_CTYPE, "");
PDO_LOG_NOTICE("pdo_sqlsrv: setlocale LC_CTYPE");
}
else {
PDO_LOG_NOTICE("pdo_sqlsrv: setlocale NONE");
}
#endif
PDO_LOG_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 );
PDO_LOG_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( _In_z_ char const* name, _In_ long value )
{
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");
zend_declare_class_constant_long(zend_class, const_cast<char*>(name), strnlen_s(name), value);
}
void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( _In_z_ char const* name, _In_z_ char const* value )
{
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");
zend_declare_class_constant_string(zend_class, const_cast<char*>(name), strnlen_s(name), const_cast<char*>(value));
}
// 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 },
{ "SQLSRV_ATTR_FETCHES_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE },
{ "SQLSRV_ATTR_FORMAT_DECIMALS" , SQLSRV_ATTR_FORMAT_DECIMALS },
{ "SQLSRV_ATTR_DECIMAL_PLACES" , SQLSRV_ATTR_DECIMAL_PLACES },
{ "SQLSRV_ATTR_DATA_CLASSIFICATION" , SQLSRV_ATTR_DATA_CLASSIFICATION },
// 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<int>(SQLSRV_CURSOR_BUFFERED) },
{ NULL , 0 } // terminate the table
};
}
// DllMain for the extension.
#ifdef _WIN32
// Only needed if extension is built shared
#ifdef COMPILE_DL_PDO_SQLSRV
BOOL WINAPI DllMain( _In_ HINSTANCE hinstDLL, _In_ 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
#endif

View file

@ -1,450 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_parser.cpp
//
// Contents: Implements a parser to parse the PDO DSN.
//
// Copyright Microsoft Corporation
//
// Microsoft Drivers 5.10 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.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_pdo_sqlsrv.h"
}
#include "php_pdo_sqlsrv_int.h"
// Constructor
conn_string_parser:: conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht )
{
this->orig_str = dsn;
this->len = len;
this->element_ht = conn_options_ht;
this->pos = -1;
this->ctx = &ctx;
this->current_key = 0;
this->current_key_name = NULL;
}
sql_string_parser:: sql_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* sql_str, _In_ int len, _In_ HashTable* placeholders_ht )
{
this->orig_str = sql_str;
this->len = len;
this->element_ht = placeholders_ht;
this->pos = -1;
this->ctx = &ctx;
this->current_key = 0;
}
// Move to the next character
inline bool 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 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 string_parser::is_white_space( _In_ 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( _In_reads_(len) const char* str, _Inout_ int len )
{
const char* end = str + ( len - 1 );
while(( this->is_white_space( *end ) ) && (len > 0) ) {
len--;
end--;
}
return len;
}
// Discard white spaces.
bool string_parser::discard_white_spaces()
{
if( this->is_eos() ) {
return false;
}
while( this->is_white_space( this->orig_str[pos] )) {
if( !next() )
return false;
}
return true;
}
// Add a key-value pair to the hashtable
void string_parser::add_key_value_pair( _In_reads_(len) const char* value, _In_ int len )
{
zval value_z;
ZVAL_UNDEF( &value_z );
if( len == 0 ) {
ZVAL_STRINGL( &value_z, "", 0);
}
else {
ZVAL_STRINGL( &value_z, const_cast<char*>( value ), len );
}
core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z );
}
// Add a key-value pair to the hashtable with int value
void sql_string_parser::add_key_int_value_pair( _In_ unsigned int value ) {
zval value_z;
ZVAL_LONG( &value_z, value );
core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z );
}
// Validate a given DSN keyword.
void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len )
{
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<char> key_name;
key_name = static_cast<char*>( 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<char*>( key_name ) );
}
inline bool sql_string_parser::is_placeholder_char( char c )
{
// placeholder only accepts numbers, upper and lower case alphabets and underscore
if (( c >= '0' && c <= '9' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) || c == '_' ) {
return true;
}
return false;
}
// Primary function which parses the connection string/DSN.
void conn_string_parser:: parse_conn_string( void )
{
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->orig_str[pos] != '=' ) {
if( !next() ) {
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY ); //EOS
}
}
this->validate_key( &( this->orig_str[start_pos] ), ( pos - start_pos ) );
state = Value;
break;
}
case Value:
{
SQLSRV_ASSERT(( this->orig_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->orig_str[pos] == ';' ) {
add_key_value_pair( NULL, 0 );
if( this->is_eos() ) {
break; // EOS
}
else {
// this->orig_str[pos] == ';'
state = NextKeyValuePair;
}
}
// if LCB
else if( this->orig_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->orig_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->orig_str[pos] != ';' ) {
if( ! next() ) {
break; //EOS
}
}
if( !this->is_eos() && this->orig_str[pos] == ';' ) {
// semi-colon encountered, so go to next key-value pair
state = NextKeyValuePair;
}
add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos );
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->orig_str[start_pos] ), this->pos - start_pos );
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->orig_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->orig_str[pos] )) {
if( ! this->discard_white_spaces() ) {
//EOS
add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos );
break;
}
}
// if semi-colon than go to next key-value pair
if ( this->orig_str[pos] == ';' ) {
add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos );
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->orig_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->orig_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;
}
}
// Primary function which parses out the named placeholders from a sql string.
void sql_string_parser::parse_sql_string( void ) {
try {
int start_pos = -1;
while ( !this->is_eos() ) {
// if pos is -1, then reading from a string is an initialized read
if ( pos == -1 ) {
next();
}
// skip until a '"', '\'', ':' or '?'
char sym;
while ( this->orig_str[pos] != '"' && this->orig_str[pos] != '\'' && this->orig_str[pos] != ':' && this->orig_str[pos] != '?' && !this->is_eos() ) {
next();
}
sym = this->orig_str[pos];
// if '"' or '\'', skip until the next '"' or '\'' respectively
if ( sym == '"' || sym == '\'' ) {
next();
while ( this->orig_str[pos] != sym && !this->is_eos() ) {
next();
}
}
// if ':', store string placeholder in the placeholders hashtable
else if ( sym == ':' ) {
start_pos = this->pos;
next();
// keep going until the next space or line break
// while (!is_white_space(this->orig_str[pos]) && !this->is_eos()) {
while ( is_placeholder_char( this->orig_str[pos] )) {
next();
}
add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos );
discard_white_spaces();
// if an '=' is right after a placeholder, it means the placeholder is for output parameters
// and emulate prepare does not support output parameters
if (this->orig_str[pos] == '=') {
THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED);
}
this->current_key++;
}
// if '?', store long placeholder into the placeholders hashtable
else if ( sym == '?' ) {
next();
// add dummy value to placeholders ht to keep count of the number of placeholders
add_key_int_value_pair( this->current_key );
discard_white_spaces();
if (this->orig_str[pos] == '=') {
THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED);
}
this->current_key++;
}
}
}
catch ( pdo::PDOException& ) {
throw;
}
}

View file

@ -1,1572 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_stmt.cpp
//
// Contents: Implements the PDOStatement object for the PDO_SQLSRV
//
// Microsoft Drivers 5.10 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.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_pdo_sqlsrv.h"
}
#include "php_pdo_sqlsrv_int.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 ( _In_ 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( _Inout_ sqlsrv_stmt* driver_stmt, _In_ enum pdo_param_type pdo_type )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>(driver_stmt);
SQLSRV_ASSERT(pdo_stmt != NULL, "pdo_type_to_sqlsrv_php_type: pdo_stmt object was null");
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:
if (pdo_stmt->fetch_datetime) {
return SQLSRV_PHPTYPE_DATETIME;
} else {
// 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( _In_ 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( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
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 );
}
void set_stmt_cursor_scroll_type( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
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<long>( Z_LVAL_P( value_z ) );
core_sqlsrv_set_scrollable( stmt, odbc_cursor_type );
return;
}
// Sets the statement encoding. Default encoding on the statement
// implies use the connection's encoding.
void set_stmt_encoding( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
// 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<SQLSRV_ENCODING>( attr_value ));
break;
default:
THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_ENCODING );
break;
}
}
zval convert_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _Inout_ void** in_val, _In_opt_ SQLLEN field_len )
{
zval out_zval;
ZVAL_UNDEF(&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<int**>(in_val)));
}
else {
ZVAL_DOUBLE(&out_zval, **(reinterpret_cast<double**>(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<char*>(*in_val), field_len);
sqlsrv_free(*in_val);
}
break;
}
case SQLSRV_PHPTYPE_DATETIME:
convert_datetime_string_to_zval(stmt, static_cast<char*>(*in_val), field_len, out_zval);
sqlsrv_free(*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( _Inout_ pdo_stmt_t *stmt );
int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt );
int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orientation ori,
_In_ zend_long offset );
int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
_Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type );
int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno );
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
_Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees );
#else
int pdo_sqlsrv_stmt_get_col_data(_Inout_ pdo_stmt_t *stmt, _In_ int colno, _Inout_ zval *result, _Inout_ enum pdo_param_type *type);
#endif
int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val );
int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value );
int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value );
int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt );
int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt );
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_pdo_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
set_stmt_cursors( stmt, value_z );
}
void stmt_option_encoding:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
set_stmt_encoding( stmt, value_z );
}
void stmt_option_direct_query:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->direct_query = zend_is_true(value_z);
}
void stmt_option_cursor_scroll_type:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
set_stmt_cursor_scroll_type( stmt, value_z );
}
void stmt_option_emulate_prepares:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_stmt_t *pdo_stmt = static_cast<pdo_stmt_t*>( stmt->driver() );
pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL;
}
void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->fetch_numeric = zend_is_true(value_z);
}
void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->fetch_datetime = zend_is_true(value_z);
}
// log a function entry point
#define PDO_LOG_STMT_ENTRY \
{ \
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data ); \
if (driver_stmt != NULL) driver_stmt->set_func( __FUNCTION__ ); \
core_sqlsrv_register_severity_checker(pdo_severity_check); \
LOG(SEV_NOTICE, "%1!s!: entering", __FUNCTION__); \
}
// PDO SQLSRV statement destructor
pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void )
{
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<void*>( const_cast<char*>( 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( _Inout_ pdo_stmt_t *stmt )
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_close_cursor: pdo_stmt object was null" );
sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_close_cursor: driver_data object was null" );
// to "close the cursor" means we make the statement ready for execution again. To do this, we
// skip all the result sets on the current statement.
// If the statement has not been executed there are no next results to iterate over.
if ( driver_stmt && driver_stmt->executed == true )
{
while( driver_stmt && driver_stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( driver_stmt );
}
}
}
catch( core::CoreException& ) {
return 0;
}
catch( ... ) {
DIE( "pdo_sqlsrv_stmt_close_cursor: Unknown exception occurred while advancing 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( _Inout_ pdo_stmt_t *stmt, _In_ int colno)
{
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_ASSERT( stmt->driver_data != NULL, "pdo_sqlsrv_stmt_describe_col: driver_data object was NULL." );
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;
try {
core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data ), colno );
}
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 );
// 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;
#if PHP_VERSION_ID < 80100
// Set the param_type
column_data->param_type = PDO_PARAM_ZVAL;
#endif
// store the field data for use by pdo_sqlsrv_stmt_get_col_data
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "Invalid driver statement in pdo_sqlsrv_stmt_describe_col" );
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( _Inout_ pdo_stmt_t *stmt )
{
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( 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;
}
// occasionally stmt->dbh->driver_data is already freed and reset but its driver_data is not
if (stmt->dbh != NULL && stmt->dbh->driver_data == NULL) {
stmt->driver_data = NULL;
return 1;
}
if ( driver_stmt->placeholders != NULL ) {
zend_hash_destroy( driver_stmt->placeholders );
FREE_HASHTABLE( driver_stmt->placeholders );
driver_stmt->placeholders = NULL;
}
(( sqlsrv_stmt* )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( _Inout_ pdo_stmt_t *stmt )
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
try {
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( 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 && driver_stmt->executed && !driver_stmt->past_next_result_end ) {
while( driver_stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( driver_stmt, 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<unsigned int>( 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) {
// reset the placeholders hashtable internal in case the user reexecutes a statement
// Normally it's not a good idea to alter the internal pointer in a hashed array
// (see pull request 634 on GitHub) but in this case this is for internal use only
zend_hash_internal_pointer_reset(driver_stmt->placeholders);
#if PHP_VERSION_ID < 80100
query = stmt->active_query_string;
query_len = static_cast<unsigned int>(stmt->active_query_stringlen);
#else
query = ZSTR_VAL(stmt->active_query_string);
query_len = ZSTR_LEN(stmt->active_query_string);
#endif
}
// The query timeout setting is inherited from the corresponding connection attribute, but
// the user may have changed the query timeout setting again before this via
// PDOStatement::setAttribute()
driver_stmt->set_query_timeout();
SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt, query, query_len );
if ( execReturn == SQL_NO_DATA ) {
stmt->column_count = 0;
stmt->row_count = 0;
driver_stmt->column_count = 0;
driver_stmt->row_count = 0;
}
else {
if (driver_stmt->column_count == ACTIVE_NUM_COLS_INVALID) {
stmt->column_count = core::SQLNumResultCols( driver_stmt );
driver_stmt->column_count = stmt->column_count;
}
else {
stmt->column_count = driver_stmt->column_count;
}
if (driver_stmt->row_count == ACTIVE_NUM_ROWS_INVALID) {
// return the row count regardless if there are any rows or not
stmt->row_count = core::SQLRowCount( driver_stmt );
driver_stmt->row_count = stmt->row_count;
}
else {
stmt->row_count = driver_stmt->row_count;
}
}
// 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( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orientation ori,
_In_ zend_long offset)
{
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<pdo_sqlsrv_stmt*>( 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 ) {
#if PHP_VERSION_ID < 80100
driver_stmt->bound_column_param_types =
reinterpret_cast<pdo_param_type*>( sqlsrv_malloc( stmt->column_count, sizeof( pdo_param_type ), 0 ));
std::fill( driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count,
PDO_PARAM_ZVAL );
#else
// TODO: possibly no longer need bound_column_param_types?? default to PDO_PARAM_STR???
driver_stmt->bound_column_param_types =
reinterpret_cast<pdo_param_type*>(sqlsrv_malloc(stmt->column_count, sizeof(pdo_param_type), 0));
std::fill(driver_stmt->bound_column_param_types, driver_stmt->bound_column_param_types + stmt->column_count,
PDO_PARAM_STR);
#endif
}
for( long i = 0; i < stmt->column_count; ++i ) {
#if PHP_VERSION_ID < 80100
if (NULL== (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, i))) &&
(NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) {
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;
}
#else
if (NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, i))) &&
(NULL == (bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) {
continue;
}
// TODO: possibly no longer need bound_column_param_types??
driver_stmt->bound_column_param_types[i] = bind_data->param_type;
#endif
}
}
SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori );
bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset );
// 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
// The if condition was changed from
// `driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY`
// because it caused SQLRowCount to be called at each fetch if using a non-forward cursor
// which is unnecessary and a performance hit
if( driver_stmt->past_fetch_end || driver_stmt->cursor_type == SQL_CURSOR_DYNAMIC) {
stmt->row_count = core::SQLRowCount( driver_stmt );
driver_stmt->row_count = stmt->row_count;
// 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.");
}
// Should not have reached here but adding this due to compilation warnings
return 0;
}
// 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.
#if PHP_VERSION_ID < 80100
int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
_Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees)
#else
int pdo_sqlsrv_stmt_get_col_data(_Inout_ pdo_stmt_t *stmt, _In_ int colno, _Inout_ zval *result_z, _Inout_ enum pdo_param_type *type)
#endif
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
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<pdo_sqlsrv_stmt*>(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;
}
#if PHP_VERSION_ID < 80100
// Let PDO free the memory after use.
* caller_frees = 1;
#endif
// translate the pdo type to a type the core layer understands
sqlsrv_phptype sqlsrv_php_type;
SQLSRV_ASSERT(colno >= 0 && colno < static_cast<int>(driver_stmt->current_meta_data.size()),
"Invalid column number in pdo_sqlsrv_stmt_get_col_data");
// set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding
// save the php type for next use
sqlsrv_php_type = driver_stmt->sql_type_to_php_type(
static_cast<SQLINTEGER>(driver_stmt->current_meta_data[colno]->field_type),
static_cast<SQLUINTEGER>(driver_stmt->current_meta_data[colno]->field_size),
true);
driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
// if a column is bound to a type different than the column type, figure out a way to convert it to the
// type they want
#if PHP_VERSION_ID < 80100
if (stmt->bound_columns && driver_stmt->bound_column_param_types[colno] != PDO_PARAM_ZVAL) {
#else
if (stmt->bound_columns) {
#endif
sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type(driver_stmt,
driver_stmt->bound_column_param_types[colno]
);
pdo_bound_param_data* bind_data = NULL;
bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_index_find_ptr(stmt->bound_columns, colno));
if (bind_data == NULL) {
// can't find by index then try searching by name
bind_data = reinterpret_cast<pdo_bound_param_data*>(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[colno].name));
}
if (bind_data != NULL && !Z_ISUNDEF(bind_data->driver_params)) {
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;
}
}
// save the php type for the bound column
driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
}
SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID;
#if PHP_VERSION_ID < 80100
core_sqlsrv_get_field(driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast<void**>(ptr)),
reinterpret_cast<SQLLEN*>(len), true, &sqlsrv_phptype_out);
if (ptr) {
zval* zval_ptr = reinterpret_cast<zval*>(sqlsrv_malloc(sizeof(zval)));
*zval_ptr = convert_to_zval(driver_stmt, sqlsrv_phptype_out, reinterpret_cast<void**>(ptr), *len);
*ptr = reinterpret_cast<char*>(zval_ptr);
*len = sizeof(zval);
}
#else
SQLLEN len = 0;
void *ptr = NULL;
core_sqlsrv_get_field(driver_stmt, colno, sqlsrv_php_type, false, ptr, &len, true, &sqlsrv_phptype_out);
if (ptr) {
*result_z = convert_to_zval(driver_stmt, sqlsrv_phptype_out, &ptr, len);
}
#endif
return 1;
}
catch ( core::CoreException& ) {
return 0;
}
catch ( ... ) {
DIE ("pdo_sqlsrv_stmt_get_col_data: Unexpected exception occurred.");
}
// Should not have reached here but adding this due to compilation warnings
return 0;
}
// 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( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val)
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
pdo_sqlsrv_stmt* driver_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_set_attr: driver_data object was null" );
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 );
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 );
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 );
break;
case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE:
driver_stmt->fetch_numeric = zend_is_true(val);
break;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
driver_stmt->fetch_datetime = zend_is_true(val);
break;
case SQLSRV_ATTR_FORMAT_DECIMALS:
driver_stmt->format_decimals = zend_is_true(val);
break;
case SQLSRV_ATTR_DECIMAL_PLACES:
core_sqlsrv_set_decimal_places(driver_stmt, val);
break;
case SQLSRV_ATTR_DATA_CLASSIFICATION:
driver_stmt->data_classification = zend_is_true(val);
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( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value )
{
PDO_RESET_STMT_ERROR;
PDO_VALIDATE_STMT;
PDO_LOG_STMT_ENTRY;
pdo_sqlsrv_stmt* driver_stmt = static_cast<pdo_sqlsrv_stmt*>( 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;
}
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
{
ZVAL_BOOL( return_value, driver_stmt->fetch_datetime );
break;
}
case SQLSRV_ATTR_FORMAT_DECIMALS:
{
ZVAL_BOOL( return_value, driver_stmt->format_decimals );
break;
}
case SQLSRV_ATTR_DECIMAL_PLACES:
{
ZVAL_LONG( return_value, driver_stmt->decimal_places );
break;
}
case SQLSRV_ATTR_DATA_CLASSIFICATION:
{
ZVAL_BOOL(return_value, driver_stmt->data_classification);
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:
// FAILURE for failure, SUCCESS for success.
int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value)
{
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( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." );
sqlsrv_stmt* driver_stmt = static_cast<sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: stmt->driver_data was null");
// Based on PDOStatement::getColumnMeta API, this should return FALSE
// if the requested column does not exist in the result set, or if
// no result set exists. Thus, do not use SQLSRV_ASSERT, which causes
// the script to fail right away. Instead, log this warning if logging
// is enabled
if (colno < 0 || colno >= stmt->column_count || stmt->columns == NULL) {
LOG( SEV_WARNING, "Invalid column number %1!d!", colno );
return FAILURE;
}
// initialize the array to nothing, as PDO requires us to create it
array_init(return_value);
field_meta_data* core_meta_data;
// metadata should have been saved earlier
SQLSRV_ASSERT(colno < driver_stmt->current_meta_data.size(), "pdo_sqlsrv_stmt_get_col_meta: Metadata vector out of sync with column numbers");
core_meta_data = driver_stmt->current_meta_data[colno];
// add the following fields: flags, native_type, driver:decl_type, table
if (driver_stmt->data_classification) {
core_sqlsrv_sensitivity_metadata(driver_stmt);
// initialize the column data classification array
zval data_classification;
ZVAL_UNDEF(&data_classification);
array_init(&data_classification);
data_classification::fill_column_sensitivity_array(driver_stmt, (SQLSMALLINT)colno, &data_classification);
add_assoc_zval(return_value, "flags", &data_classification);
}
else {
add_assoc_long(return_value, "flags", 0);
}
// get the name of the data type
char field_type_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'};
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, &not_used );
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] = {'\0'};
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 );
add_assoc_string( return_value, "table", table_name );
#if PHP_VERSION_ID < 80100
if( stmt->columns && stmt->columns[colno].param_type == PDO_PARAM_ZVAL ) {
add_assoc_long( return_value, "pdo_type", pdo_type );
}
#else
if (stmt->columns) {
add_assoc_long(return_value, "pdo_type", pdo_type);
}
#endif
}
catch( core::CoreException& ) {
zval_ptr_dtor(return_value);
return FAILURE;
}
catch(...) {
zval_ptr_dtor(return_value);
DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." );
}
return SUCCESS;
}
// 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( _Inout_ pdo_stmt_t *stmt )
{
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<pdo_sqlsrv_stmt*>( stmt->driver_data );
SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" );
core_sqlsrv_next_result( static_cast<sqlsrv_stmt*>( stmt->driver_data ) );
// clear the current meta data since the new result will generate new meta data
driver_stmt->clean_up_results_metadata();
// if there are no more result sets, return that it failed.
if( driver_stmt->past_next_result_end == true ) {
return 0;
}
stmt->column_count = core::SQLNumResultCols( driver_stmt );
// return the row count regardless if there are any rows or not
stmt->row_count = core::SQLRowCount( driver_stmt );
driver_stmt->column_count = stmt->column_count;
driver_stmt->row_count = stmt->row_count;
}
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( _Inout_ pdo_stmt_t *stmt,
_Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type)
{
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:
{
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
if (driver_stmt->conn->ce_option.enabled) {
if (driver_stmt->direct_query) {
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED);
}
if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED);
}
}
if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE && (param->param_type & PDO_PARAM_INPUT_OUTPUT)) {
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED);
}
}
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<sqlsrv_stmt*>( 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, 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 the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
// and the SQLSRV_PHPTYPE_* constant
// vso 2829: derive the pdo_type for input/output parameter as well
// also check if the user has specified PARAM_STR_NATL or PARAM_STR_CHAR for string params
int pdo_type = param->param_type;
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;
pdo_type = param->param_type & ~PDO_PARAM_INPUT_OUTPUT;
}
else {
direction = SQL_PARAM_OUTPUT;
}
}
// check if the user has specified the character set to use, take it off but ignore
#if PHP_VERSION_ID >= 70200
if ((pdo_type & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
pdo_type = pdo_type & ~PDO_PARAM_STR_NATL;
LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_NATL set but is ignored.");
}
if ((pdo_type & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
pdo_type = pdo_type & ~PDO_PARAM_STR_CHAR;
LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_CHAR set but is ignored.");
}
#endif
// if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
// and the SQLSRV_PHPTYPE_* constant
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
switch (pdo_type) {
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( &param->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:
{
zval *zv = &param->parameter;
if (Z_ISREF_P(zv)) {
ZVAL_DEREF(zv);
}
// Table-valued parameters are input-only
CHECK_CUSTOM_ERROR(Z_TYPE_P(zv) == IS_ARRAY, driver_stmt, SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY) {
throw pdo::PDOException();
}
// For other types, simply throw the following error
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
break;
}
case SQLSRV_PHPTYPE_INT:
column_size = SQLSRV_UNKNOWN_SIZE;
break;
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();
}
// Beginning with PHP7.2 the user can specify whether to use PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL
// But this extended type will be ignored in real prepared statements, so the encoding deliberately
// set in the statement or driver options will still take precedence
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<SQLSRV_ENCODING>( 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<SQLUSMALLINT>( param->paramno ), direction, &(param->parameter) , php_out_type, encoding,
sql_type, column_size, decimal_digits);
}
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;
}
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( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ 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." );
}
sqlsrv_phptype.typeinfo.encoding = local_encoding;
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 = SQLSRV_ENCODING_CHAR;
}
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 = SQLSRV_ENCODING_CHAR;
}
break;
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
case SQL_TYPE_TIMESTAMP:
if ( this->fetch_datetime ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
}
break;
case SQL_BIGINT:
case SQL_DECIMAL:
case SQL_NUMERIC:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
break;
case SQL_CHAR:
case SQL_GUID:
case SQL_WCHAR:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
case SQL_SS_VARIANT:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
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;
}

View file

@ -1,705 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: pdo_util.cpp
//
// Contents: Utility functions used by both connection or statement functions
//
// Microsoft Drivers 5.10 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.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_pdo_sqlsrv.h"
}
#include "php_pdo_sqlsrv_int.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 = static_cast<const int>( strnlen_s( WARNING_TEMPLATE ) - strnlen_s( "%1!s!%2!d!%3!s!" ));
// Returns a sqlsrv_error for a given error code.
sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code);
// build the object and throw the PDO exception
void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error);
void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _Inout_ sqlsrv_error_auto_ptr& error, _Inout_ char* error_code, _In_opt_ va_list* print_args);
void add_remaining_errors_to_array (_In_ sqlsrv_error const* error, _Inout_ zval* array_z);
}
// 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 for SQL Server to "
"communicate with SQL Server. Access the following URL to download the ODBC Driver for SQL Server "
"for %1!s!: "
"https://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 }
},
{
PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED,
{ IMSSP, (SQLCHAR*) "Statement with emulate prepare on does not support output or input_output parameters.", -72, false }
},
{
SQLSRV_ERROR_CE_DRIVER_REQUIRED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server (or above) for %1!s!.", -78, true }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! was specified for Driver option.", -79, true }
},
{
SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND,
{ IMSSP, (SQLCHAR*) "The specified ODBC Driver is not found.", -80, false }
},
{
PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED,
{ IMSSP, (SQLCHAR*) "Parameterized statement with attribute PDO::SQLSRV_ATTR_DIRECT_QUERY is not supported in a Column Encryption enabled Connection.", -81, false }
},
{
PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED,
{ IMSSP, (SQLCHAR*) "Parameterized statement with attribute PDO::ATTR_EMULATE_PREPARES is not supported in a Column Encryption enabled Connection.", -82, false }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -83, false }
},
{
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
{ IMSSP, (SQLCHAR*) "Error converting a double (value out of range) to an integer.", -84, false }
},
{
SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*) "Invalid option for the KeyStoreAuthentication keyword. Only KeyVaultPassword or KeyVaultClientSecret is allowed.", -85, false }
},
{
SQLSRV_ERROR_AKV_AUTH_MISSING,
{ IMSSP, (SQLCHAR*) "The authentication method for Azure Key Vault is missing. KeyStoreAuthentication must be set to KeyVaultPassword or KeyVaultClientSecret.", -86, false }
},
{
SQLSRV_ERROR_AKV_NAME_MISSING,
{ IMSSP, (SQLCHAR*) "The username or client Id for Azure Key Vault is missing.", -87, false }
},
{
SQLSRV_ERROR_AKV_SECRET_MISSING,
{ IMSSP, (SQLCHAR*) "The password or client secret for Azure Key Vault is missing.", -88, false }
},
{
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -89, false}
},
{
SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.", -90, false}
},
{
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false}
},
{
SQLSRV_ERROR_INVALID_DECIMAL_PLACES,
{ IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -92, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION,
{ IMSSP, (SQLCHAR*) "The statement must be executed to retrieve Data Classification Sensitivity Metadata.", -94, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE,
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata. If the driver and the server both support the Data Classification feature, check whether the query returns columns with classification information.", -95, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -96, true}
},
{
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID,
{ IMSSP, (SQLCHAR*) "Invalid extended string type specified. PDO_ATTR_DEFAULT_STR_PARAM can be either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL.", -97, false}
},
{
SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating a string for Table-Valued Param %1!d! Column %2!d! to UTF-16: %3!s!", -98, true }
},
{
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
{ IMSSP, (SQLCHAR*) "An invalid type for Table-Valued Param %1!d! Column %2!d! was specified", -99, true }
},
{
SQLSRV_ERROR_TVP_FETCH_METADATA,
{ IMSSP, (SQLCHAR*) "Failed to get metadata for Table-Valued Param %1!d!", -100, true }
},
{
SQLSRV_ERROR_TVP_INVALID_INPUTS,
{ IMSSP, (SQLCHAR*) "Invalid inputs for Table-Valued Param %1!d!", -101, true }
},
{
SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME,
{ IMSSP, (SQLCHAR*) "Expect a non-empty string for a Type Name for Table-Valued Param %1!d!", -102, true }
},
{
SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE,
{ IMSSP, (SQLCHAR*) "For Table-Valued Param %1!d! the number of values in a row is expected to be %2!d!", -103, true }
},
{
SQLSRV_ERROR_TVP_STRING_KEYS,
{ IMSSP, (SQLCHAR*) "Associative arrays not allowed for Table-Valued Param %1!d!", -104, true }
},
{
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
{ IMSSP, (SQLCHAR*) "Expect an array for each row for Table-Valued Param %1!d!", -105, true }
},
{
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
{ IMSSP, (SQLCHAR*) "You cannot return data in a table-valued parameter. Table-valued parameters are input-only.", -106, false }
},
{ UINT_MAX, {} }
};
bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args )
{
SQLSRV_ASSERT((ctx != NULL), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null");
pdo_dbh_t* dbh = reinterpret_cast<pdo_dbh_t*>(ctx.driver());
SQLSRV_ASSERT((dbh != NULL), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null");
sqlsrv_error_auto_ptr error;
format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args);
// error_mode is valid because PDO API has already taken care of invalid ones
if (!warning && dbh->error_mode == PDO_ERRMODE_EXCEPTION) {
pdo_sqlsrv_throw_exception(error);
}
ctx.set_last_error(error);
// 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( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args )
{
pdo_dbh_t* dbh = reinterpret_cast<pdo_dbh_t*>( ctx.driver());
SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" );
sqlsrv_error_auto_ptr error;
format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args);
// error_mode is valid because PDO API has already taken care of invalid ones
if (!warning) {
if (dbh->error_mode == PDO_ERRMODE_EXCEPTION) {
pdo_sqlsrv_throw_exception(error);
}
else if (dbh->error_mode == PDO_ERRMODE_WARNING) {
size_t msg_len = strnlen_s(reinterpret_cast<const char*>(error->native_message)) + SQL_SQLSTATE_BUFSIZE
+ MAX_DIGITS + WARNING_MIN_LENGTH + 1;
sqlsrv_malloc_auto_ptr<char> msg;
msg = static_cast<char*>(sqlsrv_malloc(msg_len));
core_sqlsrv_format_message(msg, static_cast<unsigned int>(msg_len), WARNING_TEMPLATE, error->sqlstate, error->native_code,
error->native_message);
php_error(E_WARNING, "%s", msg.get());
}
}
ctx.set_last_error(error);
// return error ignored = true for warnings.
return (warning ? true : false);
}
// PDO error handler for the statement context.
bool pdo_sqlsrv_handle_stmt_error(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args)
{
pdo_stmt_t* pdo_stmt = reinterpret_cast<pdo_stmt_t*>(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;
format_or_get_all_errors(ctx, sqlsrv_error_code, error, pdo_stmt->error_code, print_args);
// error_mode is valid because PDO API has already taken care of invalid ones
if (!warning && pdo_stmt->dbh->error_mode == PDO_ERRMODE_EXCEPTION) {
pdo_sqlsrv_throw_exception(error);
}
ctx.set_last_error(error);
// 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( _In_ sqlsrv_error const* last_error, _Out_ 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<char*>( last_error->native_message ));
add_remaining_errors_to_array (last_error, pdo_zval);
}
}
// check the global variable of pdo_sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool pdo_severity_check(_In_ unsigned int severity)
{
return ((severity & PDO_SQLSRV_G(pdo_log_severity)));
}
namespace {
// Workaround for name collision problem between the SQLSRV and PDO_SQLSRV drivers on Mac
// Place get_error_message into the anonymous namespace in pdo_util.cpp
sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code) {
sqlsrv_error_const *error_message = NULL;
int zr = (error_message = reinterpret_cast<sqlsrv_error_const*>(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;
}
void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error)
{
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" );
#if PHP_VERSION_ID >= 80000
zend_object *zendobj = Z_OBJ_P(&ex_obj);
#endif
sqlsrv_malloc_auto_ptr<char> ex_msg;
size_t ex_msg_len = strnlen_s(reinterpret_cast<const char*>(error->native_message)) + SQL_SQLSTATE_BUFSIZE +
12 + 1; // 12 = "SQLSTATE[]: "
ex_msg = reinterpret_cast<char*>(sqlsrv_malloc(ex_msg_len));
snprintf(ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message);
#if PHP_VERSION_ID < 80000
zend_update_property_string(ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof(EXCEPTION_PROPERTY_MSG) - 1, ex_msg);
zend_update_property_string(ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof(EXCEPTION_PROPERTY_CODE) - 1, reinterpret_cast<char*>(error->sqlstate));
#else
zend_update_property_string(ex_class, zendobj, EXCEPTION_PROPERTY_MSG, sizeof(EXCEPTION_PROPERTY_MSG) - 1, ex_msg);
zend_update_property_string(ex_class, zendobj, EXCEPTION_PROPERTY_CODE, sizeof(EXCEPTION_PROPERTY_CODE) - 1, reinterpret_cast<char*>(error->sqlstate));
#endif
zval ex_error_info;
ZVAL_UNDEF( &ex_error_info );
array_init( &ex_error_info );
add_next_index_string( &ex_error_info, reinterpret_cast<char*>( error->sqlstate ));
add_next_index_long( &ex_error_info, error->native_code );
add_next_index_string( &ex_error_info, reinterpret_cast<char*>( error->native_message ));
add_remaining_errors_to_array (error, &ex_error_info);
//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
#if PHP_VERSION_ID < 80000
zend_update_property(ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof(EXCEPTION_PROPERTY_ERRORINFO) - 1, &ex_error_info);
#else
zend_update_property(ex_class, zendobj, EXCEPTION_PROPERTY_ERRORINFO, sizeof(EXCEPTION_PROPERTY_ERRORINFO) - 1, &ex_error_info);
#endif
//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 );
}
void add_remaining_errors_to_array (_In_ sqlsrv_error const* error, _Inout_ zval* array_z)
{
if (error->next != NULL && PDO_SQLSRV_G(report_additional_errors)) {
sqlsrv_error *p = error->next;
while (p != NULL) {
add_next_index_string(array_z, reinterpret_cast<char*>(p->sqlstate));
add_next_index_long(array_z, p->native_code);
add_next_index_string(array_z, reinterpret_cast<char*>(p->native_message));
p = p-> next;
}
}
}
void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _Inout_ sqlsrv_error_auto_ptr& error, _Inout_ char* error_code, _In_opt_ va_list* print_args)
{
if (sqlsrv_error_code != SQLSRV_ERROR_ODBC) {
core_sqlsrv_format_driver_error(ctx, get_error_message(sqlsrv_error_code), error, SEV_ERROR, print_args);
strcpy_s(error_code, sizeof(pdo_error_type), reinterpret_cast<const char*>(error->sqlstate));
}
else {
bool result = core_sqlsrv_get_odbc_error(ctx, 1, error, SEV_ERROR, true);
if (result) {
// Check if there exist more errors
int rec_number = 2;
sqlsrv_error_auto_ptr err;
sqlsrv_error *p = error;
do {
result = core_sqlsrv_get_odbc_error(ctx, rec_number++, err, SEV_ERROR, true);
if (result) {
p->next = err.get();
err.transferred();
p = p->next;
}
} while (result);
}
// core_sqlsrv_get_odbc_error() returns the error_code of size SQL_SQLSTATE_BUFSIZE,
// which is the same size as pdo_error_type
strcpy_s(error_code, sizeof(pdo_error_type), reinterpret_cast<const char*>(error->sqlstate));
}
}
}

View file

@ -1,67 +0,0 @@
#ifndef PHP_PDO_SQLSRV_H
#define PHP_PDO_SQLSRV_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: php_pdo_sqlsrv.h
//
// Contents: Declarations for the extension
//
// Microsoft Drivers 5.10 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.h"
//*********************************************************************************************************************************
// Global variables
//*********************************************************************************************************************************
// request level variables
ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv)
unsigned int pdo_log_severity;
zend_long client_buffer_max_size;
short report_additional_errors;
#ifndef _WIN32
zend_long set_locale_info;
#endif
ZEND_END_MODULE_GLOBALS(pdo_sqlsrv)
ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv);
//*********************************************************************************************************************************
// 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
#endif /* PHP_PDO_SQLSRV_H */

View file

@ -1,417 +0,0 @@
#ifndef PHP_PDO_SQLSRV_INT_H
#define PHP_PDO_SQLSRV_INT_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: php_pdo_sqlsrv_int.h
//
// Contents: Internal declarations for the extension
//
// Microsoft Drivers 5.10 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 <vector>
#include <map>
//*********************************************************************************************************************************
// Global variables
//*********************************************************************************************************************************
// henv context for creating connections
extern sqlsrv_context* g_pdo_henv_cp;
extern sqlsrv_context* g_pdo_henv_ncp;
// used for getting the version information
extern HMODULE g_sqlsrv_hmodule;
// 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_PDO_SQLSRV_MORE_ERRORS "report_additional_errors"
#define INI_PREFIX "pdo_sqlsrv."
#ifndef _WIN32
#define INI_PDO_SET_LOCALE_INFO "set_locale_info"
#endif
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, pdo_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 )
STD_PHP_INI_ENTRY(INI_PREFIX INI_PDO_SQLSRV_MORE_ERRORS, "1", PHP_INI_ALL, OnUpdateLong, report_additional_errors, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals)
#ifndef _WIN32
STD_PHP_INI_ENTRY(INI_PREFIX INI_PDO_SET_LOCALE_INFO, "2", PHP_INI_ALL, OnUpdateLong, set_locale_info,
zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals)
#endif
PHP_INI_END()
//*********************************************************************************************************************************
// Constants and Types
//*********************************************************************************************************************************
// sqlsrv driver specific PDO attributes
enum PDO_SQLSRV_ATTR {
// The 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,
SQLSRV_ATTR_FETCHES_DATETIME_TYPE,
SQLSRV_ATTR_FORMAT_DECIMALS,
SQLSRV_ATTR_DECIMAL_PLACES,
SQLSRV_ATTR_DATA_CLASSIFICATION
};
// 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";
}
//*********************************************************************************************************************************
// Initialization
//*********************************************************************************************************************************
// Basic string parser
class string_parser
{
protected:
const char* orig_str;
sqlsrv_context* ctx;
int len;
int pos;
unsigned int current_key;
HashTable* element_ht;
inline bool next(void);
inline bool is_eos(void);
inline bool is_white_space( _In_ char c );
bool discard_white_spaces(void);
void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len );
};
//*********************************************************************************************************************************
// PDO DSN Parser
//*********************************************************************************************************************************
// Parser class used to parse DSN connection string.
class conn_string_parser : private string_parser
{
enum States
{
FirstKeyValuePair,
Key,
Value,
ValueContent1,
ValueContent2,
RCBEncountered,
NextKeyValuePair,
};
private:
const char* current_key_name;
int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len );
void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len);
public:
conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht );
void parse_conn_string( void );
};
//*********************************************************************************************************************************
// PDO Query Parser
//*********************************************************************************************************************************
// Parser class used to parse DSN named placeholders.
class sql_string_parser : private string_parser
{
private:
bool is_placeholder_char(char);
public:
void add_key_int_value_pair( _In_ unsigned int value );
sql_string_parser(_In_ sqlsrv_context& ctx, _In_ const char* sql_str, _In_ int len, _In_ HashTable* placeholder_ht);
void parse_sql_string(void);
};
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
extern const connection_option PDO_CONN_OPTS[];
int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options);
// 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;
bool fetch_numeric;
bool fetch_datetime;
bool format_decimals;
short decimal_places;
short use_national_characters;
bool emulate_prepare;
pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver );
};
//*********************************************************************************************************************************
// Statement
//*********************************************************************************************************************************
struct stmt_option_encoding : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_pdo_scrollable : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_direct_query : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_cursor_scroll_type : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_emulate_prepares : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_fetch_numeric : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
struct stmt_option_fetch_datetime : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
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( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv ) :
sqlsrv_stmt( c, handle, e, drv ),
direct_query( false ),
direct_query_subst_string( NULL ),
direct_query_subst_string_len( 0 ),
placeholders(NULL),
bound_column_param_types( NULL ),
fetch_numeric( false ),
fetch_datetime( false )
{
pdo_sqlsrv_dbh* db = static_cast<pdo_sqlsrv_dbh*>( c );
direct_query = db->direct_query;
fetch_numeric = db->fetch_numeric;
fetch_datetime = db->fetch_datetime;
format_decimals = db->format_decimals;
decimal_places = db->decimal_places;
query_timeout = db->query_timeout;
}
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( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ 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
HashTable* placeholders; // hashtable of named placeholders to keep track of params ordering in emulate prepare
pdo_param_type* bound_column_param_types;
bool fetch_numeric;
bool fetch_datetime;
};
//*********************************************************************************************************************************
// 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( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args );
bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args );
bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ int warning,
_In_opt_ va_list* print_args );
// common routine to transfer a sqlsrv_context's error to a PDO zval
void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Out_ zval* pdo_zval );
// reset the errors from the last operation
inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh )
{
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;
zval_ptr_dtor( &dbh->query_stmt_zval );
}
// 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<sqlsrv_conn*>( dbh->driver_data );
if( ctx->last_error() ) {
ctx->last_error().reset();
}
}
#define PDO_LOG_NOTICE(message) \
core_sqlsrv_register_severity_checker(pdo_severity_check); \
LOG(SEV_NOTICE, message);
#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh );
inline void pdo_reset_stmt_error( _Inout_ 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<sqlsrv_stmt*>( 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,
PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED,
PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED,
PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED,
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID
};
extern pdo_error PDO_ERRORS[];
#define THROW_PDO_ERROR( ctx, custom, ... ) \
call_error_handler( ctx, custom, 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
// check the global variable of pdo_sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool pdo_severity_check(_In_ unsigned int severity);
#endif /* PHP_PDO_SQLSRV_INT_H */

View file

@ -1,1585 +0,0 @@
//-----------------------------------------------------------------------------
// File: FormattedPrint.cpp
//
//
// Contents: Contains functions for handling Windows format strings
// and UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.10 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 <FormattedPrint.h>
#include <errno.h>
#include <iconv.h>
#include "StringFunctions.h"
// XPLAT_ODBC_TODO VSTS 819733 - MPlat: Reconcile std c++ usage between platforms
#include <vector>
#include <algorithm>
#include "sal_def.h"
#define PTR_IS_INT64 1
// SQL Server does not have a long double type
#define LONGDOUBLE_IS_DOUBLE 1
typedef double _LONGDOUBLE;
// 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( 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 _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);
}
// 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] = {'\0'};
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
chars_printed = snprintf( buf, bufSize, local_fmt, *dbl );
assert( 0 < chars_printed && chars_printed < bufSize );
# 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;
}
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<char> * 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 = {'\0'};
WCHAR wchar; /* temp wchar_t */
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 */
/* 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 */
int buffersize = BUFFERSIZE; /* size of text.sz (used only for the call to _cfltcvt) */
/* 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)strnlen_s(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] = {'\0'};
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 */
}
// 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<void *>(int64Val);
#else
return reinterpret_cast<void *>(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;
}
// 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] = {'\0'};
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;
}
// 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 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<const char *>(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] = {'\0'};
chars_printed = FormatMessageToBufferA( reinterpret_cast<const char *>(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<const char *>(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;
}
//--------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_vsnprintf( char * buffer, size_t count, const char * format, va_list args )
{
BufferOutput<char> 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;
}
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;
}

View file

@ -1,224 +0,0 @@
//-----------------------------------------------------------------------------
// File: FormattedPrint.h
//
// Contents: Contains functions for handling Windows format strings
// and UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.10 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"
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<T>
{
protected:
bool ShouldOutput( const int * pCumulativeOutputCount, int count ) const
{
assert( NULL != pCumulativeOutputCount );
return ( (0 <= *pCumulativeOutputCount) && (0 < count) );
}
};
int FormattedPrintA( IFormattedPrintOutput<char> * output, const char *format, va_list argptr );
template< typename T >
class BufferOutput : public FormattedOutput<T>
{
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<T>::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<T>::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<T>::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<T>
{
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<T>::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<T>::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<T>::ShouldOutput( pCumulativeOutputCount, count ) )
{
assert( NULL != pch );
*pCumulativeOutputCount += count;
if ( (size_t)count != fwrite( pch, sizeof(T), count, m_file ) )
*pCumulativeOutputCount = -1;
}
}
};
#endif // _FORMATTEDPRINT_H_

View file

@ -1,150 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: StringFunctions.cpp
//
// Contents: Contains functions for handling UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.10 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;
}
return 0;
}
size_t strnlen_s(const char * _Str, size_t _MaxCount)
{
return (_Str==0) ? 0 : strnlen(_Str, _MaxCount);
}
//
// End copy functions
//----------------------------------------------------------------------------

View file

@ -1,41 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: StringFunctions.h
//
// Contents: Contains functions for handling UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.10 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);
size_t strnlen_s(const char * _Str, size_t _MaxCount = INT_MAX);
// Copy
#define memcpy_s mplat_memcpy_s
#define strcat_s mplat_strcat_s
#define strcpy_s mplat_strcpy_s
#endif // _STRINGFUNCTIONS_H_

View file

@ -1,1207 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_conn.cpp
//
// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv
//
// Microsoft Drivers 5.10 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 <php.h>
#ifdef _WIN32
#include <psapi.h>
#include <windows.h>
#include <winver.h>
#endif // _WIN32
#include <sstream>
#include <vector>
#ifndef _WIN32
#include <sys/utsname.h>
#include <odbcinst.h>
#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;
// length for name of keystore used in CEKeyStoreData
const int MAX_CE_NAME_LEN = 260;
// ODBC driver name
const char ODBC_DRIVER_NAME[] = "ODBC Driver %d 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( _Inout_ sqlsrv_conn* conn, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options_ht, _In_ const connection_option valid_conn_opts[],
void* driver,_Inout_ std::string& connection_string );
void determine_server_version( _Inout_ sqlsrv_conn* conn );
const char* get_processor_arch( void );
connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ const char* key, _In_ SQLULEN key_len );
void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str );
void load_azure_key_vault( _Inout_ sqlsrv_conn* conn );
void configure_azure_key_vault( sqlsrv_conn* conn, BYTE config_attr, const DWORD config_value, size_t key_size);
void configure_azure_key_vault( sqlsrv_conn* conn, BYTE config_attr, const char* config_value, size_t key_size);
std::string get_ODBC_driver_name(_In_ ODBC_DRIVER driver);
#ifndef _WIN32
bool core_search_odbc_driver_unix(_In_ ODBC_DRIVER driver);
#endif
}
// 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( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_context& henv_ncp, _In_ driver_conn_factory conn_factory,
_Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[],
_In_ void* driver, _In_z_ const char* driver_func )
{
SQLRETURN r;
std::string conn_str;
conn_str.reserve( DEFAULT_CONN_STR_LEN );
sqlsrv_malloc_auto_ptr<sqlsrv_conn> conn;
bool is_pooled = false;
#ifdef _WIN32
sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv
is_pooled = true;
#else
sqlsrv_context* henv = &henv_ncp; // by default do not use the connection pooling henv
is_pooled = false;
#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;
is_pooled = true;
}
#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;
is_pooled = false;
}
}
}
#endif // !_WIN32
SQLHANDLE temp_conn_h;
core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h );
conn = conn_factory( temp_conn_h, err, driver );
conn->set_func( driver_func );
build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str );
// In non-Windows environment, unixODBC 2.3.4 and unixODBC 2.3.1 return different error states when an ODBC driver exists or not
// Therefore, it is unreliable to check for a certain sql state error
// In Windows, we try to connect with ODBC driver first and rely on the returned error code to try connecting with other supported ODBC drivers
if (conn->driver_version != ODBC_DRIVER::VER_UNKNOWN) {
// if column encryption is enabled, must use ODBC driver 17 or above
CHECK_CUSTOM_ERROR(conn->ce_option.enabled && conn->driver_version == ODBC_DRIVER::VER_13, conn, SQLSRV_ERROR_CE_DRIVER_REQUIRED, get_processor_arch()) {
throw core::CoreException();
}
#ifndef _WIN32
// check if the ODBC driver actually exists, if not, throw an exception
CHECK_CUSTOM_ERROR(!core_search_odbc_driver_unix(conn->driver_version), conn, SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND) {
throw core::CoreException();
}
// if the driver exists, connect
r = core_odbc_connect(conn, conn_str, is_pooled);
#else
// try to connect with the specified ODBC driver
r = core_odbc_connect(conn, conn_str, is_pooled);
// if the specified ODBC driver does not exist, the error code is "IM002" (i.e. Data source name not found)
CHECK_CUSTOM_ERROR(core_compare_error_state(conn, r, "IM002"), conn, SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND) {
throw core::CoreException();
}
#endif
}
else {
// ODBC driver not specified, so check ODBC 17 first then ODBC 18 and/or ODBC 13
// If column encryption is enabled, check up to ODBC 18
ODBC_DRIVER drivers[] = { ODBC_DRIVER::VER_17, ODBC_DRIVER::VER_18, ODBC_DRIVER::VER_13 };
ODBC_DRIVER last_version = (conn->ce_option.enabled) ? ODBC_DRIVER::VER_18 : ODBC_DRIVER::VER_13;
ODBC_DRIVER version = ODBC_DRIVER::VER_UNKNOWN;
for (auto &d : drivers) {
std::string driver_name = get_ODBC_driver_name(d);
#ifndef _WIN32
if (core_search_odbc_driver_unix(d)) {
// now append the driver name to the connection string
common_conn_str_append_func(ODBCConnOptions::Driver, driver_name.c_str(), driver_name.length(), conn_str);
r = core_odbc_connect(conn, conn_str, is_pooled);
break;
}
#else
std::string conn_str_driver = conn_str; // use a copy of conn_str instead
common_conn_str_append_func(ODBCConnOptions::Driver, driver_name.c_str(), driver_name.length(), conn_str_driver);
r = core_odbc_connect(conn, conn_str_driver, is_pooled);
if (SQL_SUCCEEDED(r) || !core_compare_error_state(conn, r, "IM002")) {
// something else went wrong, exit the loop now other than ODBC driver not found
break;
}
#endif
else if (d == last_version) {
// if column encryption is enabled, throw the exception related to column encryption
CHECK_CUSTOM_ERROR(conn->ce_option.enabled, conn, SQLSRV_ERROR_CE_DRIVER_REQUIRED, get_processor_arch()) {
throw core::CoreException();
}
// here it means that none of the supported ODBC drivers is found
CHECK_CUSTOM_ERROR(true, conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) {
throw core::CoreException();
}
}
}
}
// time to free the access token, if not null
if (conn->azure_ad_access_token) {
memset(conn->azure_ad_access_token->data, 0, conn->azure_ad_access_token->dataSize); // clear the memory
conn->azure_ad_access_token.reset();
}
CHECK_SQL_ERROR( r, conn ) {
throw core::CoreException();
}
CHECK_SQL_WARNING_AS_ERROR( r, conn ) {
throw core::CoreException();
}
// After load_azure_key_vault, reset AKV related variables regardless
load_azure_key_vault(conn);
conn->ce_option.akv_reset();
// determine the version of the server we're connected to. The server version is left in the
// 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 );
#ifndef _WIN32
}
#endif // !_WIN32
}
catch( std::bad_alloc& ) {
conn_str.clear();
conn->invalidate();
DIE( "C++ memory allocation failure building the connection string." );
}
catch( std::out_of_range const& ex ) {
conn_str.clear();
LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
conn->invalidate();
throw;
}
catch( std::length_error const& ex ) {
conn_str.clear();
LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
conn->invalidate();
throw;
}
catch( core::CoreException& ) {
conn->ce_option.akv_reset();
conn_str.clear();
conn->invalidate();
throw;
}
conn_str.clear();
sqlsrv_conn* return_conn = conn;
conn.transferred();
return return_conn;
}
// core_compare_error_state
// This method compares the error state to the one specified
// Parameters:
// conn - the connection structure on which we establish the connection
// rc - ODBC return code
// Return - a boolean flag that indicates if the error states are the same
bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ const char* error_state )
{
if( SQL_SUCCEEDED( rc ) )
return false;
SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {'\0'};
SQLSMALLINT len;
SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
return ( SQL_SUCCEEDED(sr) && ! strcmp(error_state, reinterpret_cast<char*>( state ) ) );
}
// core_odbc_connect
// calls odbc connect API to establish the connection to server
// Parameters:
// conn - The connection structure on which we establish the connection
// conn_str - Connection string
// is_pooled - indicate whether it is a pooled connection
// Return - SQLRETURN status returned by SQLDriverConnect
SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _In_ bool is_pooled )
{
SQLRETURN r = SQL_SUCCESS;
sqlsrv_malloc_auto_ptr<SQLWCHAR> wconn_string;
unsigned int wconn_len = static_cast<unsigned int>( conn_str.length() + 1 ) * sizeof( SQLWCHAR );
// Set the desired data classification version before connecting, but older ODBC drivers will generate a warning message 'Driver's SQLSetConnectAttr failed'
SQLSetConnectAttr(conn->handle(), SQL_COPT_SS_DATACLASSIFICATION_VERSION, reinterpret_cast<SQLPOINTER>(data_classification::VERSION_RANK_AVAILABLE), SQL_IS_POINTER);
// We only support UTF-8 encoding for connection string.
// Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW
wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast<unsigned int>( conn_str.length() ), &wconn_len, true );
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 (is_pooled) {
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, wconn_string, static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
}
#else
r = SQLDriverConnectW( conn->handle(), NULL, wconn_string, static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
#endif // !_WIN32
// clear the connection string from memory
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
return r;
}
// 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( _Inout_ sqlsrv_conn* conn )
{
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_OFF ),
SQL_IS_UINTEGER );
}
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( _Inout_ sqlsrv_conn* conn )
{
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." );
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
SQL_IS_UINTEGER );
}
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( _Inout_ sqlsrv_conn* conn )
{
try {
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." );
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK );
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
SQL_IS_UINTEGER );
}
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( _Inout_opt_ sqlsrv_conn* conn )
{
// 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 );
}
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( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len )
{
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<SQLWCHAR> wsql_string;
unsigned int wsql_len = 0;
if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) {
wsql_string = reinterpret_cast<SQLWCHAR*>( 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<const char*>( sql ), static_cast<int>( 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<SQLWCHAR*>( wsql_string.get() ), wsql_len );
// if AE is enabled, get meta data for all parameters before binding them
if( stmt->conn->ce_option.enabled ) {
SQLSMALLINT num_params;
core::SQLNumParams( stmt, &num_params);
for( int i = 0; i < num_params; i++ ) {
param_meta_data param;
core::SQLDescribeParam(stmt, i + 1, &(param.sql_type), &(param.column_size), &(param.decimal_digits), &(param.nullable));
stmt->params_container.params_meta_ae.push_back(param);
}
}
}
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( _Inout_ sqlsrv_conn* conn, _Inout_ zval* server_version )
{
try {
char buffer[INFO_BUFFER_LEN] = "";
SQLSMALLINT buffer_len = 0;
core::SQLGetInfo(conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len);
core::sqlsrv_zval_stringl(server_version, buffer, buffer_len);
} 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( _Inout_ sqlsrv_conn* conn, _Out_ zval *server_info )
{
try {
char buffer[INFO_BUFFER_LEN] = "";
SQLSMALLINT buffer_len = 0;
// Get the database name
core::SQLGetInfo(conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len);
// initialize the array
array_init(server_info);
add_assoc_string(server_info, "CurrentDatabase", buffer);
// Get the server version
core::SQLGetInfo(conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len);
add_assoc_string(server_info, "SQLServerVersion", buffer);
// Get the server name
core::SQLGetInfo(conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len);
add_assoc_string(server_info, "SQLServerName", buffer);
} 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( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_info )
{
try {
char buffer[INFO_BUFFER_LEN] = "";
SQLSMALLINT buffer_len = 0;
// Get the ODBC driver's dll name
core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len );
// initialize the array
array_init(client_info);
#ifndef _WIN32
add_assoc_string(client_info, "DriverName", buffer);
#else
add_assoc_string(client_info, "DriverDllName", buffer);
#endif // !_WIN32
// Get the ODBC driver's ODBC version
core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len );
add_assoc_string(client_info, "DriverODBCVer", buffer);
// Get the OBDC driver's version
core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len );
add_assoc_string(client_info, "DriverVer", buffer);
} 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( _Inout_ const char* value, _Inout_ size_t value_len )
{
if (value_len == 0) {
return true;
}
if (value_len == 1) {
return (value[0] != '}');
}
const char *pstr = value;
if (value_len > 0 && value[0] == '{' && value[value_len - 1] == '}') {
pstr = ++value;
value_len -= 2;
}
const char *pch = strchr(pstr, '}');
size_t i = 0;
while (pch != NULL && i < value_len) {
i = pch - pstr + 1;
if (i == value_len || (i < value_len && pstr[i] != '}')) {
return false;
}
i++; // skip the brace
pch = strchr(pch + 2, '}'); // continue searching
}
return true;
}
// *** internal connection functions and classes ***
namespace {
connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ SQLULEN key,
_In_ const connection_option conn_opts[] )
{
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( _Inout_ sqlsrv_conn* conn, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options, _In_ const connection_option valid_conn_opts[],
void* driver, _Inout_ std::string& connection_string )
{
bool mars_mentioned = false;
connection_option const* conn_opt;
bool access_token_used = false;
bool authentication_option_used = zend_hash_index_exists(options, SQLSRV_CONN_OPTION_AUTHENTICATION);
try {
// Since connection options access token and authentication cannot coexist, check if both of them are used.
// If access token is specified, check UID and PWD as well.
// No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers
if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) {
bool invalidOptions = false;
// UID and PWD have to be NULLs... throw an exception as long as the user has specified any of them in the connection string,
// even if they may be empty strings. Likewise if the keyword Authentication exists
if (uid != NULL || pwd != NULL || authentication_option_used) {
invalidOptions = true;
}
CHECK_CUSTOM_ERROR(invalidOptions, conn, SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN ) {
throw core::CoreException();
}
access_token_used = true;
}
// Check if Authentication is ActiveDirectoryMSI because we have to handle this case differently
// https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview
bool activeDirectoryMSI = false;
bool activeDirectoryIntegrated = false;
if (authentication_option_used) {
const char aadMSIoption[] = "ActiveDirectoryMSI";
const char addIntegratedOption[] = "ActiveDirectoryIntegrated";
zval* auth_option = NULL;
auth_option = zend_hash_index_find(options, SQLSRV_CONN_OPTION_AUTHENTICATION);
char* option = NULL;
if (auth_option != NULL) {
option = Z_STRVAL_P(auth_option);
}
if (option != NULL) {
// Check if the user is using ActiveDirectoryMSI or ActiveDirectoryIntegrated
if (!stricmp(option, aadMSIoption)) {
activeDirectoryMSI = true;
}
else if (!stricmp(option, addIntegratedOption)) {
activeDirectoryIntegrated = true;
}
}
}
// Add the server name
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string );
// Check uid when Authentication is ActiveDirectoryMSI
// uid can be specified when using user-assigned identity
if (activeDirectoryMSI) {
if (uid != NULL && strnlen_s(uid) > 0) {
bool escaped = core_is_conn_opt_value_escaped(uid, strnlen_s(uid));
CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) {
throw core::CoreException();
}
common_conn_str_append_func(ODBCConnOptions::UID, uid, strnlen_s(uid), connection_string);
}
}
// If uid is not present then we use trusted connection -- but not when connecting
// using the access token or Authentication is ActiveDirectoryMSI
// ActiveDirectoryIntegrated does not need UID or PWD
if (!access_token_used && !activeDirectoryMSI && !activeDirectoryIntegrated) {
if (uid == NULL || strnlen_s(uid) == 0) {
connection_string += CONNECTION_OPTION_NO_CREDENTIALS; // "Trusted_Connection={Yes};"
}
else {
bool escaped = core_is_conn_opt_value_escaped(uid, strnlen_s(uid));
CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) {
throw core::CoreException();
}
common_conn_str_append_func(ODBCConnOptions::UID, uid, strnlen_s(uid), connection_string);
// 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, strnlen_s(pwd));
CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) {
throw core::CoreException();
}
common_conn_str_append_func(ODBCConnOptions::PWD, pwd, strnlen_s(pwd), connection_string);
}
}
}
// 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 );
if( index == SQLSRV_CONN_OPTION_MARS ) {
mars_mentioned = true;
}
conn_opt->func( conn_opt, data, conn, connection_string );
} ZEND_HASH_FOREACH_END();
// MARS on if not explicitly turned off
if( !mars_mentioned ) {
connection_string += CONNECTION_OPTION_MARS_ON;
}
}
catch( core::CoreException& ) {
conn->ce_option.akv_reset();
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 )
{
// processor architectures
const char* PROCESSOR_ARCH[] = {"x86", "x64", "arm64"};
#ifdef _WIN32
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
switch (sys_info.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_INTEL:
return PROCESSOR_ARCH[0];
case PROCESSOR_ARCHITECTURE_AMD64:
return PROCESSOR_ARCH[1];
default:
DIE("Unsupported Windows processor architecture.");
return NULL;
}
#elif defined(__arm64__)
return PROCESSOR_ARCH[2];
#elif defined(__x86_64__)
return PROCESSOR_ARCH[1];
#else
DIE("Unsupported processor architecture.");
return NULL;
#endif // _WIN32
}
// 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( _Inout_ sqlsrv_conn* conn )
{
SQLSMALLINT info_len;
char p[INFO_BUFFER_LEN] = {'\0'};
core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len );
errno = 0;
char version_major_str[3] = {'\0'};
SERVER_VERSION version_major;
memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 );
version_major_str[2] = {'\0'};
version_major = static_cast<SERVER_VERSION>( 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 load_azure_key_vault(_Inout_ sqlsrv_conn* conn)
{
// If column encryption is not enabled simply do nothing. Otherwise, check if Azure Key Vault
// is required for encryption or decryption. Note, in order to load and configure Azure Key Vault,
// all fields in conn->ce_option must be defined.
if (!conn->ce_option.enabled || !conn->ce_option.akv_required)
return;
CHECK_CUSTOM_ERROR(conn->ce_option.akv_mode == -1, conn, SQLSRV_ERROR_AKV_AUTH_MISSING) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR(!conn->ce_option.akv_id, conn, SQLSRV_ERROR_AKV_NAME_MISSING) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR(!conn->ce_option.akv_secret, conn, SQLSRV_ERROR_AKV_SECRET_MISSING) {
throw core::CoreException();
}
char *akv_id = conn->ce_option.akv_id.get();
char *akv_secret = conn->ce_option.akv_secret.get();
size_t id_len = strnlen_s(akv_id);
size_t key_size = strnlen_s(akv_secret);
configure_azure_key_vault(conn, AKV_CONFIG_FLAGS, conn->ce_option.akv_mode, 0);
configure_azure_key_vault(conn, AKV_CONFIG_PRINCIPALID, akv_id, id_len);
configure_azure_key_vault(conn, AKV_CONFIG_AUTHSECRET, akv_secret, key_size);
}
void configure_azure_key_vault(sqlsrv_conn* conn, BYTE config_attr, const DWORD config_value, size_t key_size)
{
BYTE akv_data[sizeof(CEKEYSTOREDATA) + sizeof(DWORD) + 1];
CEKEYSTOREDATA *pData = reinterpret_cast<CEKEYSTOREDATA*>(akv_data);
char akv_name[] = "AZURE_KEY_VAULT";
unsigned int name_len = 15;
unsigned int wname_len = 0;
sqlsrv_malloc_auto_ptr<SQLWCHAR> wakv_name;
wakv_name = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, akv_name, name_len, &wname_len);
CHECK_CUSTOM_ERROR(wakv_name == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE) {
throw core::CoreException();
}
pData->name = (wchar_t *)wakv_name.get();
pData->data[0] = config_attr;
pData->dataSize = sizeof(config_attr) + sizeof(config_value);
*reinterpret_cast<DWORD*>(&pData->data[1]) = config_value;
core::SQLSetConnectAttr(conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast<SQLPOINTER>(pData), SQL_IS_POINTER);
}
void configure_azure_key_vault(sqlsrv_conn* conn, BYTE config_attr, const char* config_value, size_t key_size)
{
BYTE akv_data[sizeof(CEKEYSTOREDATA) + MAX_CE_NAME_LEN];
CEKEYSTOREDATA *pData = reinterpret_cast<CEKEYSTOREDATA*>(akv_data);
char akv_name[] = "AZURE_KEY_VAULT";
unsigned int name_len = 15;
unsigned int wname_len = 0;
sqlsrv_malloc_auto_ptr<SQLWCHAR> wakv_name;
wakv_name = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, akv_name, name_len, &wname_len);
CHECK_CUSTOM_ERROR(wakv_name == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE) {
throw core::CoreException();
}
pData->name = (wchar_t *)wakv_name.get();
pData->data[0] = config_attr;
pData->dataSize = 1 + key_size;
memcpy_s(pData->data + 1, key_size * sizeof(char), config_value, key_size);
core::SQLSetConnectAttr(conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast<SQLPOINTER>(pData), SQL_IS_POINTER);
}
void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str )
{
// 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 }.
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 += "};";
}
std::string get_ODBC_driver_name(_In_ ODBC_DRIVER driver)
{
const short BUFFER_LEN = sizeof(ODBC_DRIVER_NAME);
char driver_name[BUFFER_LEN] = { '\0' };
snprintf(driver_name, BUFFER_LEN, ODBC_DRIVER_NAME, static_cast<int>(driver));
return driver_name;
}
#ifndef _WIN32
// core_search_odbc_driver_unix
// This method is meant to be used in a non-Windows environment,
// searching for a particular ODBC driver name in the odbcinst.ini file
// Parameters:
// driver - a valid value in enum ODBC_DRIVER
// Return - a boolean flag that indicates if the specified driver version is found or not
bool core_search_odbc_driver_unix(_In_ ODBC_DRIVER driver)
{
char szBuf[DEFAULT_CONN_STR_LEN + 1] = { '\0' }; // use a large enough buffer size
WORD cbBufMax = DEFAULT_CONN_STR_LEN;
WORD cbBufOut;
char *pszBuf = szBuf;
// get all the names of the installed drivers delimited by null characters
if (!SQLGetInstalledDrivers(szBuf, cbBufMax, &cbBufOut))
return false;
// search for the derived ODBC driver name based on the given version
std::string driver_name = get_ODBC_driver_name(driver);
do
{
if (strstr(pszBuf, driver_name.c_str()) != 0)
return true;
// get the next driver
pszBuf = strchr(pszBuf, '\0') + 1;
} while (pszBuf[1] != '\0'); // end when there are two consecutive null characters
return false;
}
#endif // !_WIN32
} // namespace
// simply add the parsed value to the connection string
void conn_str_append_func::func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str )
{
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 );
}
// 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*/ )
{
}
void driver_set_func::func(_In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str)
{
const char* val_str = Z_STRVAL_P(value);
size_t val_len = Z_STRLEN_P(value);
// Check if curly brackets are used, if so, trim them for matching
if (val_len > 0 && val_str[0] == '{' && val_str[val_len - 1] == '}') {
++val_str;
val_len -= 2;
}
// Check if the user provided driver_option matches any of the acceptable driver names
std::string driver_option(val_str, val_len);
ODBC_DRIVER drivers[] = { ODBC_DRIVER::VER_17, ODBC_DRIVER::VER_18, ODBC_DRIVER::VER_13 };
conn->driver_version = ODBC_DRIVER::VER_UNKNOWN;
for (auto &d : drivers) {
std::string name = get_ODBC_driver_name(d);
if (!driver_option.compare(name)) {
conn->driver_version = d;
break;
}
}
CHECK_CUSTOM_ERROR(conn->driver_version == ODBC_DRIVER::VER_UNKNOWN, conn, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, Z_STRVAL_P(value)) {
throw core::CoreException();
}
// Append this driver option to the connection string
common_conn_str_append_func(ODBCConnOptions::Driver, driver_option.c_str(), driver_option.length(), conn_str);
}
void column_encryption_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str )
{
convert_to_string( value );
const char* value_str = Z_STRVAL_P( value );
// Column Encryption is disabled by default, but if it is present and not
// explicitly set to disabled or enabled, the ODBC driver will assume the
// user is providing an attestation protocol and URL for enclave support.
// For our purposes we need only set ce_option.enabled to true if not disabled.
conn->ce_option.enabled = false;
if ( stricmp(value_str, "disabled" )) {
conn->ce_option.enabled = true;
}
conn_str += option->odbc_name;
conn_str += "=";
conn_str += value_str;
conn_str += ";";
}
void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str)
{
SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "Azure Key Vault keywords accept only strings.");
const char *value_str = Z_STRVAL_P(value);
size_t value_len = Z_STRLEN_P(value);
CHECK_CUSTOM_ERROR(value_len <= 0, conn, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE) {
throw core::CoreException();
}
switch (option->conn_option_key)
{
case SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION:
{
if (!stricmp(value_str, "KeyVaultPassword")) {
conn->ce_option.akv_mode = AKVCFG_AUTHMODE_PASSWORD;
} else if (!stricmp(value_str, "KeyVaultClientSecret")) {
conn->ce_option.akv_mode = AKVCFG_AUTHMODE_CLIENTKEY;
} else {
CHECK_CUSTOM_ERROR(1, conn, SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION) {
throw core::CoreException();
}
}
conn->ce_option.akv_required = true;
break;
}
case SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID:
case SQLSRV_CONN_OPTION_KEYSTORE_SECRET:
{
// Create a new string to save a copy of the zvalue
char *pValue = static_cast<char*>(sqlsrv_malloc(value_len + 1));
memcpy_s(pValue, value_len + 1, value_str, value_len);
pValue[value_len] = '\0'; // this makes sure there will be no trailing garbage
// This will free the existing memory block before assigning the new pointer -- the user might set the value(s) more than once
if (option->conn_option_key == SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID) {
conn->ce_option.akv_id = pValue;
} else {
conn->ce_option.akv_secret = pValue;
}
conn->ce_option.akv_required = true;
break;
}
default:
SQLSRV_ASSERT(false, "ce_akv_str_set_func: Invalid AKV option!");
break;
}
}
// 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(_Inout_ 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." );
std::string val_str = Z_STRVAL_P(value_z);
std::string whitespaces(" \t\f\v\n\r");
// Trim white spaces
std::size_t found = val_str.find_last_not_of(whitespaces);
if (found != std::string::npos)
val_str.erase(found + 1);
const char TRUE_VALUE_1[] = "true";
const char TRUE_VALUE_2[] = "1";
if (!val_str.compare(TRUE_VALUE_1) || !val_str.compare(TRUE_VALUE_2)) {
return 1; // true
}
return 0; // false
}
void access_token_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str )
{
SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "An access token must be a byte string.");
size_t value_len = Z_STRLEN_P(value);
CHECK_CUSTOM_ERROR(value_len <= 0, conn, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN) {
throw core::CoreException();
}
const char* value_str = Z_STRVAL_P( value );
// The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from
// an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also
// bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the
// SQL_COPT_SS_ACCESS_TOKEN connection attribute to a pointer to an ACCESSTOKEN structure
//
// typedef struct AccessToken
// {
// unsigned int dataSize;
// char data[];
// } ACCESSTOKEN;
//
// NOTE: The ODBC Driver version 13.1 only supports this authentication on Windows.
//
// A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte,
// similar to a UCS-2 string containing only ASCII characters
//
// See https://docs.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token
size_t dataSize = 2 * value_len;
sqlsrv_malloc_auto_ptr<ACCESSTOKEN> accToken;
accToken = reinterpret_cast<ACCESSTOKEN*>(sqlsrv_malloc(sizeof(ACCESSTOKEN) + dataSize));
ACCESSTOKEN *pAccToken = accToken.get();
SQLSRV_ASSERT(pAccToken != NULL, "Something went wrong when trying to allocate memory for the access token.");
pAccToken->dataSize = dataSize;
// Expand access token with padding bytes
for (size_t i = 0, j = 0; i < dataSize; i += 2, j++) {
pAccToken->data[i] = value_str[j];
pAccToken->data[i+1] = 0;
}
core::SQLSetConnectAttr(conn, SQL_COPT_SS_ACCESS_TOKEN, reinterpret_cast<SQLPOINTER>(pAccToken), SQL_IS_POINTER);
// Save the pointer because SQLDriverConnect() will use it to make connection to the server
conn->azure_ad_access_token = pAccToken;
accToken.transferred();
}

View file

@ -1,155 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_init.cpp
//
// Contents: common initialization routines shared by PDO and sqlsrv
//
// Microsoft Drivers 5.10 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;
bool isVistaOrGreater;
// 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( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_context** henv_ncp, _In_ error_callback err, _In_z_ const char* driver_func )
{
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.
isVistaOrGreater = IsWindowsVistaOrGreater( );
#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<SQLPOINTER>( SQL_OV_ODBC3 ), SQL_IS_INTEGER
);
// disable connection pooling
core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast<SQLPOINTER>( SQL_CP_OFF ),
SQL_IS_UINTEGER );
// 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<SQLPOINTER>( SQL_OV_ODBC3 ), SQL_IS_INTEGER);
// enable connection pooling
core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast<SQLPOINTER>( SQL_CP_ONE_PER_HENV ),
SQL_IS_UINTEGER );
}
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( _Inout_ sqlsrv_context& henv_cp, _Inout_ 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;
}

View file

@ -1,1581 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_results.cpp
//
// Contents: Result sets
//
// Microsoft Drivers 5.10 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 <functional>
#include <sstream>
#ifndef _WIN32
#include <type_traits>
#endif // !_WIN32
using namespace core;
namespace {
// *** internal types ***
#if defined(_MSC_VER)
#pragma warning(disable:4200)
#endif
// *** internal constants ***
const int INITIAL_LOB_FIELD_LEN = 2048; // base allocation size when retrieving a LOB field
// *** internal functions ***
// return an integral type rounded up to a certain number
template <int align, typename T>
T align_to( _In_ 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 <int align, typename T>
T* align_to( _In_ T* ptr )
{
size_t p_value = (size_t) ptr;
return align_to<align, size_t>( p_value );
}
// set the nth bit of the bitstream starting at ptr
void set_bit( _In_ void* ptr, _In_ unsigned int bit )
{
unsigned char* null_bits = reinterpret_cast<unsigned char*>( ptr );
null_bits += bit >> 3;
*null_bits |= 1 << ( 7 - ( bit & 0x7 ));
}
// retrieve the nth bit from the bitstream starting at ptr
bool get_bit( _In_ void* ptr, _In_ unsigned int bit )
{
unsigned char* null_bits = reinterpret_cast<unsigned char*>( 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( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_buffered_result_set::meta_data& meta,
_In_ zend_long mem_used );
// dtor for each row in the cache
void cache_row_dtor( _In_ zval* data );
size_t get_float_precision( _In_ SQLLEN buffer_length, _In_ 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 <typename Number>
SQLRETURN get_string_from_stream( _In_ Number number_data, _Out_ std::basic_string<char> &str_num, _In_ size_t precision, _Out_ sqlsrv_error_auto_ptr& last_error)
{
//std::locale loc( std::locale(""), new std::num_put<char> ); // By default, SQL Server doesn't take user's locale into consideration
std::locale loc;
std::basic_ostringstream<char> os;
os.precision( precision );
os.imbue( loc );
auto itert = std::ostreambuf_iterator<char>( os.rdbuf() );
std::use_facet< std::num_put<char>>(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 <typename Char>
SQLRETURN copy_buffer( _Out_writes_bytes_to_opt_(out_buffer_lenth, out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, _In_reads_bytes_opt_(out_buffer_length) std::basic_string<Char> &str, _Out_ 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 <typename Char, typename Number>
SQLRETURN number_to_string( _In_ Number* number_data, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, _Inout_ sqlsrv_error_auto_ptr& last_error )
{
size_t precision = 0;
#ifdef _WIN32
std::basic_ostringstream<Char> 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<Char>::_Iter( os.rdbuf()), os, ' ', *number_data );
std::basic_string<Char>& 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<char> str_num;
SQLRETURN r;
if ( std::is_integral<Number>::value )
{
long num_data = *number_data;
r = get_string_from_stream<long>( num_data, str_num, precision, last_error );
}
else
{
precision = get_float_precision( buffer_length, sizeof( Char ));
r = get_string_from_stream<double>( *number_data, str_num, precision, last_error );
}
if ( r == SQL_ERROR ) return SQL_ERROR;
if ( std::is_same<Char, char16_t>::value )
{
std::basic_string<char16_t> str;
for (const auto &mb : str_num )
{
size_t cch = SystemLocale::NextChar( CP_ACP, &mb ) - &mb;
if ( cch > 0 )
{
WCHAR ch16;
DWORD rc;
size_t cchActual = SystemLocale::ToUtf16( CP_ACP, &mb, cch, &ch16, 1, &rc);
if (cchActual > 0)
{
str.push_back ( ch16 );
}
}
}
return copy_buffer<char16_t>( buffer, buffer_length, out_buffer_length, str, last_error );
}
return copy_buffer<char>( buffer, buffer_length, out_buffer_length, str_num, last_error );
#endif // _WIN32
}
#ifndef _WIN32
std::string getUTF8StringFromString( _In_z_ const SQLWCHAR* source )
{
// convert to regular character string first
char c_str[4] = "";
SQLLEN i = 0;
std::string str;
while ( source[i] )
{
memset( c_str, 0, sizeof( c_str ) );
int cch = 0;
errno_t err = mplat_wctomb_s( &cch, c_str, sizeof( c_str ), source[i++] );
if ( cch > 0 && err == ERROR_SUCCESS )
{
str.append( std::string( c_str, cch ) );
}
}
return str;
}
std::string getUTF8StringFromString( _In_z_ const char* source )
{
return std::string( source );
}
#endif // !_WIN32
// "closure" for the hash table destructor
struct row_dtor_closure {
sqlsrv_buffered_result_set* results;
BYTE* row_data;
row_dtor_closure( _In_ sqlsrv_buffered_result_set* st, _In_ BYTE* row ) :
results( st ), row_data( row )
{
}
};
sqlsrv_error* odbc_get_diag_rec( _In_ sqlsrv_stmt* odbc, _In_ SQLSMALLINT record_number )
{
SQLWCHAR wsql_state[SQL_SQLSTATE_BUFSIZE] = {L'\0'};
SQLWCHAR wnative_message[SQL_MAX_ERROR_MESSAGE_LENGTH + 1] = {L'\0'};
SQLINTEGER native_code;
SQLSMALLINT wnative_message_len = 0;
SQLSRV_ASSERT(odbc != NULL, "odbc_get_diag_rec: sqlsrv_stmt* odbc was null.");
SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message,
SQL_MAX_ERROR_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<SQLCHAR> 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<SQLCHAR> 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( _In_ sqlsrv_stmt* stmt ) :
odbc( stmt )
{
}
// ODBC result set
// This object simply wraps ODBC function calls
sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( _In_ sqlsrv_stmt* stmt ) :
sqlsrv_result_set( stmt )
{
}
sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void )
{
}
SQLRETURN sqlsrv_odbc_result_set::fetch( _In_ SQLSMALLINT orientation, _In_ SQLLEN offset )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLFetchScroll( odbc, orientation, offset );
}
SQLRETURN sqlsrv_odbc_result_set::get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type,
_Out_writes_opt_(buffer_length) SQLPOINTER buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length,
_In_ bool handle_warning )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning );
}
SQLRETURN sqlsrv_odbc_result_set::get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier,
_Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length,
_Inout_ SQLSMALLINT* out_buffer_length )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length,
out_buffer_length );
}
sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( _In_ 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( void )
{
SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
return core::SQLRowCount( odbc );
}
// Buffered result set
// This class holds a result set in memory
sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stmt ) :
sqlsrv_result_set( stmt ),
cache(NULL),
col_count(0),
current(0),
last_field_index(-1),
read_so_far(0),
temp_length(0)
{
col_count = core::SQLNumResultCols( stmt );
// 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_buffered_result_set::meta_data*>( sqlsrv_malloc( col_count *
sizeof( sqlsrv_buffered_result_set::meta_data )));
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 );
offset = align_to<sizeof(SQLPOINTER)>( 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<SQLLEN*>( &meta[i].length ) );
meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space
offset += meta[i].length;
break;
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_SS_VARIANT:
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<SQLLEN*>( &meta[i].length ) );
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_SS_VARIANT:
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;
// 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*/ );
try {
while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 ) != SQL_NO_DATA ) {
// allocate the row buffer
sqlsrv_malloc_auto_ptr<unsigned char> rowAuto;
rowAuto = static_cast<unsigned char*>( sqlsrv_malloc( offset ));
unsigned char* row = rowAuto.get();
memset( row, 0, offset );
// read the fields into the row buffer
for( SQLSMALLINT i = 0; i < col_count; ++i ) {
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<SQLPOINTER*>( &row[meta[i].offset] );
*lob_addr = read_lob_field( stmt, i, meta[i], mem_used );
// a NULL pointer means NULL field
if( *lob_addr == NULL ) {
*out_buffer_length = SQL_NULL_DATA;
}
else {
*out_buffer_length = **reinterpret_cast<SQLLEN**>( 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<SQLLEN*>( row + meta[i].offset );
core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length,
false );
}
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 );
}
break;
default:
SQLSRV_ASSERT( false, "Unknown C type" );
break;
}
if( *out_buffer_length == SQL_NULL_DATA ) {
set_bit( row, i );
}
}
row_count++;
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) );
rowAuto.transferred();
}
}
catch( core::CoreException& ) {
// free the rows
if( cache ) {
zend_hash_destroy( cache );
FREE_HASHTABLE( cache );
cache = NULL;
}
throw;
}
}
sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void )
{
// free the rows
if( cache ) {
zend_hash_destroy( cache );
FREE_HASHTABLE( cache );
cache = NULL;
}
}
SQLRETURN sqlsrv_buffered_result_set::fetch( _Inout_ SQLSMALLINT orientation, _Inout_opt_ SQLLEN offset )
{
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();
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() || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) {
current = row_count() + 1;
return SQL_NO_DATA;
}
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type,
_Out_writes_bytes_opt_(buffer_length) SQLPOINTER buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length,
bool handle_warning )
{
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
switch (meta[field_index].c_type) {
case SQL_C_CHAR:
switch (target_type) {
case SQL_C_CHAR: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::system_to_wide_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::string_to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::string_to_long(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_WCHAR:
switch (target_type) {
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::wide_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::wstring_to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::wstring_to_long(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_BINARY:
switch (target_type) {
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::binary_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::binary_to_wide_string(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_LONG:
switch (target_type) {
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::long_to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::long_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_DOUBLE:
switch (target_type) {
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::double_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::double_to_long(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
default:
break;
}
// Should not have reached here, return an error
last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error)))
sqlsrv_error((SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0);
return SQL_ERROR;
}
SQLRETURN sqlsrv_buffered_result_set::get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier,
_Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length,
_Inout_ SQLSMALLINT* out_buffer_length )
{
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<row_dtor_closure*>(zend_hash_index_find_ptr(cache, static_cast<zend_ulong>(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( _In_ 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( void )
{
last_error = NULL;
if ( cache ) {
return zend_hash_num_elements( cache );
}
else {
// returning -1 to represent getting the rowcount of an empty result set
return -1;
}
}
// private functions
template <typename Char>
SQLRETURN binary_to_string( _Inout_ SQLCHAR* field_data, _Inout_ SQLLEN& read_so_far, _Out_writes_z_(*out_buffer_length) void* buffer,
_In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length,
_Inout_ sqlsrv_error_auto_ptr& out_error )
{
// The 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);
// TO convert a binary to a system string or a binary to a wide string, the buffer size minus
// 'extra' is ideally multiples of 2 or 4 (depending on Char), but calculating to_copy_hex below
// takes care of this.
// 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<SQLLEN*>( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra;
// Will 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<Char*>(buffer);
BYTE* b = reinterpret_cast<BYTE*>(field_data + read_so_far);
// to_copy contains the number of bytes to copy, so we divide the number in half (or quarter)
// to get the maximum number of hex digits to copy
SQLLEN to_copy_hex = static_cast<SQLLEN>(floor(to_copy / (2 * extra)));
for( SQLLEN 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<Char>(0);
}
else {
reinterpret_cast<char*>( buffer )[0] = '\0';
}
return r;
}
SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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<SQLCHAR**>( &row[meta[field_index].offset] ) + sizeof( SQLULEN );
}
else {
field_data = &row[meta[field_index].offset] + sizeof( SQLULEN );
}
return binary_to_string<char>( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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<SQLCHAR**>( &row[meta[field_index].offset] ) + sizeof( SQLULEN );
}
else {
field_data = &row[meta[field_index].offset] + sizeof( SQLULEN );
}
return binary_to_string<WCHAR>( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error );
}
SQLRETURN sqlsrv_buffered_result_set::double_to_long( _In_ SQLSMALLINT field_index, _Inout_updates_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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<double*>( &row[meta[field_index].offset] );
LONG* long_data = reinterpret_cast<LONG*>( 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<LONG>( *double_data );
*out_buffer_length = sizeof( LONG );
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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<double*>( &row[meta[field_index].offset] );
SQLRETURN r = SQL_SUCCESS;
#ifdef _WIN32
r = number_to_string<char>( double_data, buffer, buffer_length, out_buffer_length, last_error );
#else
r = number_to_string<char, double>( double_data, buffer, buffer_length, out_buffer_length, last_error );
#endif // _WIN32
return r;
}
SQLRETURN sqlsrv_buffered_result_set::long_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ 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<double*>( buffer );
LONG* long_data = reinterpret_cast<LONG*>( &row[meta[field_index].offset] );
*double_data = static_cast<LONG>( *long_data );
*out_buffer_length = sizeof( double );
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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<LONG*>( &row[meta[field_index].offset] );
SQLRETURN r = SQL_SUCCESS;
#ifdef _WIN32
r = number_to_string<char>( long_data, buffer, buffer_length, out_buffer_length, last_error );
#else
r = number_to_string<char, LONG>( long_data, buffer, buffer_length, out_buffer_length, last_error );
#endif // _WIN32
return r;
}
SQLRETURN sqlsrv_buffered_result_set::string_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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<char*>( &row[meta[field_index].offset] ) + sizeof( SQLULEN );
double* number_data = reinterpret_cast<double*>(buffer);
try {
*number_data = std::stod(std::string(string_data));
} catch (const std::logic_error& ) {
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(double);
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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<SQLWCHAR*>( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR );
double* number_data = reinterpret_cast<double*>(buffer);
try {
#ifdef _WIN32
*number_data = std::stod(std::wstring(string_data));
#else
*number_data = std::stod(getUTF8StringFromString(string_data));
#endif // _WIN32
} catch (const std::logic_error& ) {
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(double);
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::string_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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<char*>( &row[meta[field_index].offset] ) + sizeof( SQLULEN );
LONG* number_data = reinterpret_cast<LONG*>(buffer);
try {
*number_data = std::stol(std::string(string_data));
} catch (const std::logic_error& ) {
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(LONG);
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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<SQLWCHAR*>( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR );
LONG* number_data = reinterpret_cast<LONG*>(buffer);
try {
#ifdef _WIN32
*number_data = std::stol(std::wstring(string_data));
#else
*number_data = std::stol(getUTF8StringFromString(string_data));
#endif // _WIN32
} catch (const std::logic_error& ) {
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(LONG);
return SQL_SUCCESS;
}
SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ 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<SQLLEN**>( &row[meta[field_index].offset] );
field_data = *reinterpret_cast<SQLCHAR**>( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) + read_so_far;
}
else {
field_len = *reinterpret_cast<SQLLEN*>( &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<SQLLEN*>( 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<int>(to_copy),
static_cast<LPWSTR>(buffer), static_cast<int>(to_copy));
#else
int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast<int>(to_copy),
static_cast<LPWSTR>(buffer), static_cast<int>(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<WCHAR*>( buffer )[0] = L'\0';
}
return r;
}
SQLRETURN sqlsrv_buffered_result_set::to_same_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ 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<SQLCHAR**>( &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<SQLLEN*>( 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<SQLCHAR*>( buffer ) + to_copy, buffer_length, L"\0", extra );
}
return r;
}
SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( _In_ SQLSMALLINT field_index, _Inout_updates_bytes_to_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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<SQLLEN**>( &row[meta[field_index].offset] );
field_data = *reinterpret_cast<SQLCHAR**>( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) + read_so_far;
}
else {
field_len = *reinterpret_cast<SQLLEN*>( &row[meta[field_index].offset] );
field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ) + read_so_far;
}
if ( field_len == 0 ) { // empty string, no need for conversion
*out_buffer_length = 0;
return SQL_SUCCESS;
}
// allocate enough to handle WC -> DBCS conversion if it happens
temp_string = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( field_len, sizeof(char), sizeof(char)));
#ifndef _WIN32
temp_length = SystemLocale::FromUtf16( CP_ACP, (LPCWSTR) field_data, static_cast<int>(field_len / sizeof(WCHAR)),
(LPSTR) temp_string.get(), static_cast<int>(field_len) );
#else
BOOL default_char_used = FALSE;
char default_char = '?';
temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, static_cast<int>(field_len / sizeof(WCHAR)),
(LPSTR) temp_string.get(), static_cast<int>(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( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ 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( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ 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<LONG*>( &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( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ 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<double*>( &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( _In_ zval* data )
{
row_dtor_closure* cl = reinterpret_cast<row_dtor_closure*>( 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<void**>( &row[result_set->col_meta_data(i).offset] );
sqlsrv_free( out_of_row_data );
}
}
sqlsrv_free( row );
sqlsrv_free( cl );
}
SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_buffered_result_set::meta_data& meta,
_In_ zend_long mem_used )
{
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_LOB_FIELD_LEN;
sqlsrv_malloc_auto_ptr<char> buffer;
buffer = static_cast<char*>( sqlsrv_malloc( INITIAL_LOB_FIELD_LEN + extra + sizeof( SQLULEN )));
SQLRETURN r = SQL_SUCCESS;
SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {'\0'};
SQLLEN last_field_len = 0;
bool full_length_returned = false;
do {
output_buffer_len = reinterpret_cast<SQLULEN*>( 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*/ );
// 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
);
if( !is_truncated_warning( state )) {
break;
}
}
SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" );
already_read += to_read - already_read;
// 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();
}
to_read = last_field_len;
buffer.resize( to_read + extra + sizeof( SQLULEN ));
output_buffer_len = reinterpret_cast<SQLULEN*>( 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 {
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<SQLULEN*>( 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;
}
}

View file

@ -1,2755 +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 5.10 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 // !_WIN32
#include "FormattedPrint.h"
#include "StringFunctions.h"
#else
#include "VersionHelpers.h"
#endif
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef PHP_WIN32
#define PHP_SQLSRV_API __declspec(dllexport)
#else
#define PHP_SQLSRV_API
#endif
#define stricmp strcasecmp
#define strnicmp strncasecmp
#define strnlen_s(s) strnlen_s(s, INT_MAX)
#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.h>
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 <sql.h>
#include <sqlext.h>
#endif // _WIN32
#if !defined(SQL_GUID)
// imported from sqlext.h
#define SQL_GUID (-11)
#endif
#if !defined(WC_ERR_INVALID_CHARS)
// imported from winnls.h as it isn't included by 5.3.0
#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars
#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 <deque>
#include <map>
#include <string>
#include <algorithm>
#include <limits>
#include <cassert>
#include <memory>
#include <vector>
// included for SQL Server specific constants
#include "msodbcsql.h"
#ifdef _WIN32
#include <strsafe.h>
#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_TYPE_SIZE = 0;
const int SQL_SERVER_MAX_PARAMS = 2100;
const int SQL_SERVER_MAX_MONEY_SCALE = 4;
// increase the maximum message length to accommodate for the long error returned for operand type clash
// or for conversion of a long string
const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 2;
// max size of a date time string when converting from a DateTime object to a string
const int MAX_DATETIME_STRING_LEN = 256;
// identifier for whether or not we have obtained the number of rows and columns
// of a result
const short ACTIVE_NUM_COLS_INVALID = -99;
const long ACTIVE_NUM_ROWS_INVALID = -99;
// 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;
// the message returned by ODBC Driver for SQL Server
const char ODBC_CONNECTION_BUSY_ERROR[] = "Connection is busy with results for another command";
// 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,
SQLSRV_PHPTYPE_TABLE,
MAX_SQLSRV_PHPTYPE, // highest value for a php type
SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid
};
// 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;
// default value of decimal places (no formatting required)
const short NO_CHANGE_DECIMAL_PLACES = -1;
// default value for national character set strings (user did not specify any preference)
const short CHARSET_PREFERENCE_NOT_SPECIFIED = -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 <bool b>
struct sqlsrv_static_assert;
template <>
struct sqlsrv_static_assert<true> { _In_ static const int value = 1; };
#define SQLSRV_STATIC_ASSERT( c ) (sqlsrv_static_assert<(c) != 0>() )
//*********************************************************************************************************************************
// Logging
//*********************************************************************************************************************************
// log_callback
// a driver specific callback for checking if the messages are qualified to be logged:
// severity - severity of the message: notice, warning, or error
typedef bool (*severity_callback)(_In_ unsigned int severity);
// each driver must register a severity checker callback for logging to work according to the INI settings
void core_sqlsrv_register_severity_checker(_In_ severity_callback driver_checker);
// a simple wrapper around a PHP error logging function.
void write_to_log( _In_ unsigned int severity, _In_ const char* msg, ... );
// a macro to make it convenient to use the function.
#define LOG( severity, msg, ...) write_to_log( severity, 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( _In_opt_ 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( _In_ size_t size, _In_ const char* file, _In_ 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( _In_ size_t element_count, _In_ size_t element_size, _In_ size_t extra, _In_ const char* file, _In_ 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, _In_ size_t size, _In_ const char* file, _In_ 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( _Inout_ void* ptr, _In_ const char* file, _In_ 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
// Unlike their C standard library's counterparts the Zend Engine's memory management functions
// emalloc or erealloc won't return NULL in case of an problem while allocating the requested
// memory but bail out and terminate the current request.
// Check www.phpinternalsbook.com/php7/memory_management/zend_memory_manager.html for details
inline void* sqlsrv_malloc( _In_ size_t size )
{
return emalloc( size );
}
inline void* sqlsrv_malloc( _In_ size_t element_count, _In_ size_t element_size, _In_ 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" );
}
// safeguard against anomalous calculation or any arithmetic overflow
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( _Inout_ void* buffer, _In_ size_t size )
{
return erealloc( buffer, size );
}
inline void sqlsrv_free( _Inout_ void* ptr )
{
efree( ptr );
}
#endif
// trait class that allows us to assign const types to an auto_ptr
template <typename T>
struct remove_const {
typedef T type;
};
template <typename T>
struct remove_const<const T*> {
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<typename T>
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<typename U>
struct rebind {
typedef sqlsrv_allocator<U> other;
};
inline sqlsrv_allocator() {}
inline ~sqlsrv_allocator() {}
inline sqlsrv_allocator( sqlsrv_allocator const& ) {}
template<typename U>
inline sqlsrv_allocator( sqlsrv_allocator<U> const& ) {}
// address (doesn't work if the class defines operator&)
inline pointer address( _In_ reference r )
{
return &r;
}
inline const_pointer address( _In_ const_reference r )
{
return &r;
}
// memory allocation/deallocation
inline pointer allocate( _In_ size_type cnt,
typename std::allocator<void>::const_pointer = 0 )
{
return reinterpret_cast<pointer>( sqlsrv_malloc(cnt, sizeof (T), 0));
}
inline void deallocate( _Inout_ pointer p, size_type )
{
sqlsrv_free(p);
}
// size
inline size_type max_size( void ) const
{
return std::numeric_limits<size_type>::max() / sizeof(T);
}
// object construction/destruction
inline void construct( _In_ pointer p, _In_ const T& t )
{
new(p) T(t);
}
inline void destroy( _Inout_ pointer p )
{
p->~T();
}
// equality operators
inline bool operator==( sqlsrv_allocator const& )
{
return true;
}
inline bool operator!=( _In_ 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 <typename T, typename Subclass>
class sqlsrv_auto_ptr {
public:
sqlsrv_auto_ptr( void ) : _ptr( NULL )
{
}
~sqlsrv_auto_ptr( void )
{
static_cast<Subclass*>(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<T*>::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[]( _In_ 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[]( _In_ 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[]( _In_ 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[]( _In_ 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[]( _In_ 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( _In_opt_ T* ptr ) :
_ptr( ptr )
{
}
sqlsrv_auto_ptr( _Inout_opt_ sqlsrv_auto_ptr& src )
{
if( _ptr ) {
static_cast<Subclass*>(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=( _In_opt_ T* ptr )
{
static_cast<Subclass*>( 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 <typename T>
class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> > {
public:
sqlsrv_malloc_auto_ptr( void ) :
sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >( NULL )
{
}
sqlsrv_malloc_auto_ptr( _Inout_opt_ const sqlsrv_malloc_auto_ptr& src ) :
sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >( src )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( _In_opt_ T* ptr = NULL )
{
if( sqlsrv_auto_ptr<T,sqlsrv_malloc_auto_ptr<T> >::_ptr )
sqlsrv_free( (void*) sqlsrv_auto_ptr<T,sqlsrv_malloc_auto_ptr<T> >::_ptr );
sqlsrv_auto_ptr<T,sqlsrv_malloc_auto_ptr<T> >::_ptr = ptr;
}
T* operator=( _In_opt_ T* ptr )
{
return sqlsrv_auto_ptr<T, sqlsrv_malloc_auto_ptr<T> >::operator=( ptr );
}
void operator=( _Inout_opt_ sqlsrv_malloc_auto_ptr<T>& 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( _In_ size_t new_size )
{
sqlsrv_auto_ptr<T,sqlsrv_malloc_auto_ptr<T> >::_ptr = reinterpret_cast<T*>( sqlsrv_realloc( sqlsrv_auto_ptr<T,sqlsrv_malloc_auto_ptr<T> >::_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<HashTable, hash_auto_ptr> {
public:
hash_auto_ptr( void ) :
sqlsrv_auto_ptr<HashTable, hash_auto_ptr>( NULL )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( _In_opt_ HashTable* ptr = NULL )
{
if (_ptr != NULL) {
zend_hash_destroy(_ptr);
FREE_HASHTABLE(_ptr);
}
_ptr = ptr;
}
HashTable* operator=( _In_opt_ HashTable* ptr )
{
return sqlsrv_auto_ptr<HashTable, hash_auto_ptr>::operator=( ptr );
}
private:
hash_auto_ptr( _In_ HashTable const& hash );
hash_auto_ptr( _In_ 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<zval, zval_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( _In_opt_ zval* ptr = NULL )
{
if( _ptr )
zval_ptr_dtor(_ptr );
_ptr = ptr;
}
zval* operator=( _In_opt_ zval* ptr )
{
return sqlsrv_auto_ptr<zval, zval_auto_ptr>::operator=( ptr );
}
private:
zval_auto_ptr( _In_ 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 {
struct sqlsrv_error *next; // Only used in pdo_sqlsrv for additional errors (as a linked list)
sqlsrv_error( void )
{
sqlstate = NULL;
native_message = NULL;
native_code = -1;
format = false;
next = NULL;
}
sqlsrv_error( _In_ SQLCHAR* sql_state, _In_ SQLCHAR* message, _In_ SQLINTEGER code, _In_ bool printf_format = false)
{
sqlstate = reinterpret_cast<SQLCHAR*>(sqlsrv_malloc(SQL_SQLSTATE_BUFSIZE));
native_message = reinterpret_cast<SQLCHAR*>(sqlsrv_malloc(SQL_MAX_ERROR_MESSAGE_LENGTH + 1));
strcpy_s(reinterpret_cast<char*>(sqlstate), SQL_SQLSTATE_BUFSIZE, reinterpret_cast<const char*>(sql_state));
strcpy_s(reinterpret_cast<char*>(native_message), SQL_MAX_ERROR_MESSAGE_LENGTH + 1, reinterpret_cast<const char*>(message));
native_code = code;
format = printf_format;
next = NULL;
}
sqlsrv_error( _In_ sqlsrv_error_const const& prototype )
{
sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format );
}
~sqlsrv_error( void )
{
reset();
}
void reset() {
if (sqlstate != NULL) {
sqlsrv_free(sqlstate);
sqlstate = NULL;
}
if (native_message != NULL) {
sqlsrv_free(native_message);
native_message = NULL;
}
if (next != NULL) {
next->reset(); // free the next sqlsrv_error, and so on
sqlsrv_free(next);
next = NULL;
}
}
};
// an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete
class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr > {
public:
sqlsrv_error_auto_ptr( void ) :
sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_auto_ptr >( NULL )
{
}
sqlsrv_error_auto_ptr( _Inout_opt_ sqlsrv_error_auto_ptr const& src ) :
sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_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( _In_opt_ sqlsrv_error* ptr = NULL )
{
if( _ptr ) {
_ptr->~sqlsrv_error();
sqlsrv_free( (void*) _ptr );
}
_ptr = ptr;
}
sqlsrv_error* operator=( _In_opt_ sqlsrv_error* ptr )
{
return sqlsrv_auto_ptr<sqlsrv_error, sqlsrv_error_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=( _Inout_opt_ 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)( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ int error, _In_opt_ 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( _In_opt_ SQLSMALLINT type, _In_ error_callback e, _In_opt_ void* drv, _In_ 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( _In_ SQLHANDLE h, _In_opt_ SQLSMALLINT t, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding = SQLSRV_ENCODING_INVALID ) :
handle_( h ),
handle_type_( t ),
name_( NULL ),
err_( e ),
driver_( drv ),
last_error_(),
encoding_( encoding )
{
}
sqlsrv_context( _In_ 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( _In_z_ const char* f )
{
name_ = f;
}
void set_last_error( _In_ 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( _In_ 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( _In_ 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
};
// 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( _In_ const char* iana, _In_ unsigned int code_page, _In_ bool not_for_conn = false ):
iana( iana ), iana_len( strnlen_s( iana )), code_page( code_page ), not_for_connection( not_for_conn )
{
}
};
//*********************************************************************************************************************************
// Initialization
//*********************************************************************************************************************************
// variables set during initialization
extern bool isVistaOrGreater; // used to determine if OS is Vista or Greater
extern HashTable* g_encodings; // encodings supported by this driver
void core_sqlsrv_minit( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_context** henv_ncp, _In_ error_callback err, _In_z_ const char* driver_func );
void core_sqlsrv_mshutdown( _Inout_ sqlsrv_context& henv_cp, _Inout_ sqlsrv_context& henv_ncp );
// environment context used by sqlsrv_connect for when a connection error occurs.
struct sqlsrv_henv {
sqlsrv_context ctx;
sqlsrv_henv( _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ 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.
// ODBC 17 is the default
enum class ODBC_DRIVER : int
{
VER_17 = 17,
VER_18 = 18,
VER_13 = 13,
VER_UNKNOWN = 0
};
// forward decl
struct sqlsrv_stmt;
struct stmt_option;
// This holds the various details of column encryption.
struct col_encryption_option {
bool enabled; // column encryption enabled, false by default
SQLINTEGER akv_mode;
sqlsrv_malloc_auto_ptr<char> akv_id;
sqlsrv_malloc_auto_ptr<char> akv_secret;
bool akv_required;
col_encryption_option() : enabled( false ), akv_mode(-1), akv_required( false )
{
}
void akv_reset()
{
akv_id.reset();
akv_secret.reset();
akv_required = false;
akv_mode = -1;
}
};
// *** connection resource structure ***
// 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
col_encryption_option ce_option; // holds the details of what are required to enable column encryption
ODBC_DRIVER driver_version; // version of ODBC driver
sqlsrv_malloc_auto_ptr<ACCESSTOKEN> azure_ad_access_token;
// initialize with default values
sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding ) :
sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding )
{
server_version = SERVER_VERSION_UNKNOWN;
driver_version = ODBC_DRIVER::VER_UNKNOWN;
}
// 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,
SQLSRV_STMT_OPTION_DATE_AS_STRING,
SQLSRV_STMT_OPTION_FORMAT_DECIMALS,
SQLSRV_STMT_OPTION_DECIMAL_PLACES,
SQLSRV_STMT_OPTION_DATA_CLASSIFICATION,
// Driver specific connection options
SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000,
};
namespace ODBCConnOptions {
const char APP[] = "APP";
const char AccessToken[] = "AccessToken";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char Authentication[] = "Authentication";
const char Driver[] = "Driver";
const char CharacterSet[] = "CharacterSet";
const char ConnectionPooling[] = "ConnectionPooling";
const char Language[] = "Language";
const char ColumnEncryption[] = "ColumnEncryption";
const char ConnectRetryCount[] = "ConnectRetryCount";
const char ConnectRetryInterval[] = "ConnectRetryInterval";
const char Database[] = "Database";
const char Encrypt[] = "Encrypt";
const char Failover_Partner[] = "Failover_Partner";
const char KeyStoreAuthentication[] = "KeyStoreAuthentication";
const char KeyStorePrincipalId[] = "KeyStorePrincipalId";
const char KeyStoreSecret[] = "KeyStoreSecret";
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 TransparentNetworkIPResolution[] = "TransparentNetworkIPResolution";
const char WSID[] = "WSID";
const char UID[] = "UID";
const char PWD[] = "PWD";
const char SERVER[] = "Server";
const char ComputePool[] = "ComputePool";
const char HostNameInCertificate[] = "HostNameInCertificate";
}
enum SQLSRV_CONN_OPTIONS {
SQLSRV_CONN_OPTION_INVALID,
SQLSRV_CONN_OPTION_APP,
SQLSRV_CONN_OPTION_ACCESS_TOKEN,
SQLSRV_CONN_OPTION_CHARACTERSET,
SQLSRV_CONN_OPTION_CONN_POOLING,
SQLSRV_CONN_OPTION_LANGUAGE,
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,
SQLSRV_CONN_OPTION_AUTHENTICATION,
SQLSRV_CONN_OPTION_COLUMNENCRYPTION,
SQLSRV_CONN_OPTION_DRIVER,
SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER,
SQLSRV_CONN_OPTION_CEKEYSTORE_NAME,
SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY,
SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION,
SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID,
SQLSRV_CONN_OPTION_KEYSTORE_SECRET,
SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION,
SQLSRV_CONN_OPTION_CONN_RETRY_COUNT,
SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL,
SQLSRV_CONN_OPTION_COMPUTE_POOL,
SQLSRV_CONN_OPTION_HOSTNAME_IN_CERT,
// 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_MIXED,
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 );
};
// connection attribute functions
// simply add the parsed value to the connection string
struct conn_str_append_func {
static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str );
};
struct conn_null_func {
static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ );
};
struct column_encryption_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str );
};
struct driver_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str );
};
struct ce_akv_str_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str );
};
struct access_token_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str );
};
// factory to create a connection (since they are subclassed to instantiate statements)
typedef sqlsrv_conn* (*driver_conn_factory)( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv );
// *** connection functions ***
sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_context& henv_ncp, _In_ driver_conn_factory conn_factory,
_Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[],
_In_ void* driver, _In_z_ const char* driver_func );
SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _In_ bool is_pooled );
void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn );
void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len );
void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn );
void core_sqlsrv_commit( _Inout_ sqlsrv_conn* conn );
void core_sqlsrv_rollback( _Inout_ sqlsrv_conn* conn );
void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval* server_info );
void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval *server_version );
void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_info );
bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t value_len );
size_t core_str_zval_is_true( _Inout_ zval* str_zval );
bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN r, _In_ const char* error_state );
//*********************************************************************************************************************************
// Statement
//*********************************************************************************************************************************
struct stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, _In_ zval* /*value_z*/ );
};
struct stmt_option_query_timeout : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z );
};
struct stmt_option_send_at_exec : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z );
};
struct stmt_option_buffered_query_limit : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z );
};
struct stmt_option_date_as_string : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z );
};
struct stmt_option_format_decimals : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z );
};
struct stmt_option_decimal_places : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z );
};
struct stmt_option_data_classification : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z );
};
// 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<stmt_option_functor> 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( _In_opt_ zval* str_z, _In_ 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 );
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"
// *** parameter metadata struct ***
struct param_meta_data
{
SQLSMALLINT sql_type;
SQLSMALLINT decimal_digits;
SQLSMALLINT nullable;
SQLULEN column_size;
param_meta_data() : sql_type(0), decimal_digits(0), column_size(0), nullable(0)
{
}
~param_meta_data()
{
}
SQLSMALLINT get_sql_type() { return sql_type; }
SQLSMALLINT get_decimal_digits() { return decimal_digits; }
SQLSMALLINT get_nullable() { return nullable; }
SQLULEN get_column_size() { return column_size; }
};
// *** parameter struct used for SQLBindParameter ***
struct sqlsrv_param
{
SQLUSMALLINT param_pos; // 0-based - the position in the parameters of the statement
SQLSMALLINT direction;
SQLSMALLINT c_data_type;
SQLSMALLINT sql_data_type;
SQLULEN column_size;
SQLSMALLINT decimal_digits;
SQLPOINTER buffer;
SQLLEN buffer_length;
SQLLEN strlen_or_indptr;
int param_php_type;
SQLSRV_ENCODING encoding;
bool was_null; // false by default - the original parameter was a NULL zval
zval placeholder_z; // A temp zval for binding any input parameter value, including simple data types, wide input string (UTF-16 buffer), the datetime strings, etc.
zval* param_ptr_z; // NULL by default - points to the original parameter or its reference
std::size_t num_bytes_read; // 0 by default - number of bytes processed so far (for an empty PHP stream, an empty string is sent to the server)
php_stream* param_stream; // NULL by default - used to send stream data from an input parameter to the server
sqlsrv_param(_In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT dir, _In_ SQLSRV_ENCODING enc, _In_ SQLSMALLINT sql_type, _In_ SQLULEN col_size, _In_ SQLSMALLINT dec_digits) :
c_data_type(0), buffer(NULL), buffer_length(0), strlen_or_indptr(0), param_pos(param_num), direction(dir), encoding(enc), sql_data_type(sql_type),
column_size(col_size), decimal_digits(dec_digits), param_php_type(0), was_null(false), param_ptr_z(NULL), num_bytes_read(0), param_stream(NULL)
{
ZVAL_UNDEF(&placeholder_z);
}
void copy_param_meta_ae(_Inout_ zval* param_z, _In_ param_meta_data& meta); // Only used when Always Encrypted is enabled
virtual ~sqlsrv_param(){ release_data(); }
virtual void release_data();
bool derive_string_types_sizes(_In_ zval* param_z);
bool preprocess_datetime_object(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
// The following methods change the member placeholder_z, and both will return false if conversions fail
bool convert_input_str_to_utf16(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
bool convert_datetime_to_string(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
virtual bool prepare_param(_In_ zval* param_ref, _Inout_ zval* param_z);
virtual void process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
virtual void process_null_param(_Inout_ zval* param_z);
virtual void process_bool_param(_Inout_ zval* param_z);
virtual void process_long_param(_Inout_ zval* param_z);
virtual void process_double_param(_Inout_ zval* param_z);
virtual void process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
virtual void process_resource_param(_Inout_ zval* param_z);
virtual void process_object_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
virtual void bind_param(_Inout_ sqlsrv_stmt* stmt);
// The following methods are used to supply data to the server via SQLPutData
virtual void init_data_from_zval(_Inout_ sqlsrv_stmt* stmt);
virtual bool send_data_packet(_Inout_ sqlsrv_stmt* stmt);
};
// *** output / inout parameter struct used for SQLBindParameter, inheriting sqlsrv_param ***
struct sqlsrv_param_inout : public sqlsrv_param
{
SQLSRV_PHPTYPE php_out_type; // Used to convert output param when necessary
bool was_bool; // false by default - the original parameter was a boolean zval
sqlsrv_stmt* stmt; // NULL by default - points to the statement object mainly for error processing
sqlsrv_param_inout(_In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT dir, _In_ SQLSRV_ENCODING enc, _In_ SQLSMALLINT sql_type,
_In_ SQLULEN col_size, _In_ SQLSMALLINT dec_digits, SQLSRV_PHPTYPE php_out_type) :
sqlsrv_param(param_num, dir, enc, sql_type, col_size, dec_digits),
php_out_type(php_out_type), was_bool(false), stmt(NULL)
{
}
virtual ~sqlsrv_param_inout() { param_ptr_z = NULL; }
virtual void release_data() { param_ptr_z = NULL; }
virtual bool prepare_param(_In_ zval* param_ref, _Inout_ zval* param_z);
virtual void process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
virtual void process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
// Called when the output parameter is ready to be finalized, using the value stored in param_ptr_z
void finalize_output_value();
// Resize the output string buffer based on its properties and whether it is a numeric type
void resize_output_string_buffer(_Inout_ zval* param_z, _In_ bool is_numeric_type);
// A helper method called by finalize_output_value() to finalize output string parameters
void finalize_output_string();
};
// *** Table-valued parameter struct used for SQLBindParameter, inheriting sqlsrv_param
// *** A sqlsrv_param_tvp can be representing a table-valued parameter itself or one of
// *** its constituent columns. When it is a table-valued parameter, tvp_columns cannot
// *** be empty. When it is a TVP column, parent_tvp points to its table-valued parameter
// *** and tvp_columns must be empty. The member param_pos refers to the ordinal position
// *** of this column in the corresponding table type.
struct sqlsrv_param_tvp : public sqlsrv_param
{
std::map<SQLUSMALLINT, sqlsrv_param_tvp*> tvp_columns; // The constituent columns of the table-valued parameter
sqlsrv_param_tvp* parent_tvp; // For a TVP column to reference to the table-valued parameter. NULL if this is the TVP itself.
int num_rows; // The total number of rows
int current_row; // A counter to keep track of which row is to be processed
sqlsrv_param_tvp(_In_ SQLUSMALLINT param_num, _In_ SQLSRV_ENCODING enc, _In_ SQLSMALLINT sql_type, _In_ SQLULEN col_size, _In_ SQLSMALLINT dec_digits, _In_ sqlsrv_param_tvp* tvp) :
sqlsrv_param(param_num, SQL_PARAM_INPUT, enc, sql_type, col_size, dec_digits), num_rows(0), current_row(0), parent_tvp(tvp)
{
ZVAL_UNDEF(&placeholder_z);
}
virtual ~sqlsrv_param_tvp() { release_data(); }
virtual void release_data();
virtual void bind_param(_Inout_ sqlsrv_stmt* stmt);
virtual void process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
// The following methods are used to supply data to the server post execution
virtual void init_data_from_zval(_Inout_ sqlsrv_stmt* stmt) {}
virtual bool send_data_packet(_Inout_ sqlsrv_stmt* stmt);
// Change the column encoding based on the sql data type
static void sql_type_to_encoding(_In_ SQLSMALLINT sql_type, _Inout_ SQLSRV_ENCODING* encoding);
// The following methods are only applicable to a table-valued parameter or its individual columns
int parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
void get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ zend_string* table_type_name, _In_ zend_string* schema_name);
void process_param_column_value(_Inout_ sqlsrv_stmt* stmt);
void process_null_param_value(_Inout_ sqlsrv_stmt* stmt);
void populate_cell_placeholder(_Inout_ sqlsrv_stmt* stmt, _In_ int ordinal);
void send_string_data_in_batches(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z);
};
// *** a container of all parameters used for SQLBindParameter ***
struct sqlsrv_params_container
{
std::vector<param_meta_data> params_meta_ae; // Empty by default - only used when Always Encrypted is enabled
std::map<SQLUSMALLINT, sqlsrv_param*> input_params; // map of pointers to the input params with their ordinal positions as keys
std::map<SQLUSMALLINT, sqlsrv_param*> output_params; // map of pointers to the output / inout params with their ordinal positions as keys
sqlsrv_param* current_param; // Null by default - points to a sqlsrv_param object used for sending stream data
sqlsrv_params_container() { current_param = NULL; }
~sqlsrv_params_container() { params_meta_ae.clear(); clean_up_param_data(); }
sqlsrv_param* find_param(_In_ SQLUSMALLINT param_num, _In_ bool is_input);
void insert_param(_In_ SQLUSMALLINT param_num, _In_ sqlsrv_param* new_param)
{
if (new_param->direction == SQL_PARAM_INPUT) {
input_params[param_num] = new_param;
} else {
output_params[param_num] = new_param;
}
}
void remove_params(std::map<SQLUSMALLINT, sqlsrv_param*>& params_map)
{
std::map<SQLUSMALLINT, sqlsrv_param*>::iterator it1;
for (it1 = params_map.begin(); it1 != params_map.end(); ++it1) {
sqlsrv_param* ptr = it1->second;
if (ptr) {
ptr->release_data();
sqlsrv_free(ptr);
}
}
params_map.clear();
}
void clean_up_param_data(_In_opt_ bool only_input = false);
void finalize_output_parameters();
// The following functions are used to supply data to the server post execution
bool get_next_parameter(_Inout_ sqlsrv_stmt* stmt);
bool send_next_packet(_Inout_ sqlsrv_stmt* stmt);
void send_all_packets(_Inout_ sqlsrv_stmt* stmt)
{
while (get_next_parameter(stmt)) {
while (current_param->send_data_packet(stmt)) {}
}
}
};
namespace data_classification {
const size_t VERSION_RANK_AVAILABLE = 2; // Rank info is available when data classification version is 2+
const int RANK_NOT_DEFINED = -1;
// *** data classficiation metadata structures and helper methods -- to store and/or process the sensitivity classification data ***
struct name_id_pair;
struct sensitivity_metadata;
void name_id_pair_free(_Inout_ name_id_pair * pair);
void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector<name_id_pair*, sqlsrv_allocator<name_id_pair*>>* pairs, _Inout_ unsigned char **pptr);
void parse_column_sensitivity_props(_Inout_ sensitivity_metadata* meta, _Inout_ unsigned char **pptr, _In_ bool getRankInfo);
USHORT fill_column_sensitivity_array(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Inout_ zval *column_data);
struct name_id_pair {
UCHAR name_len;
sqlsrv_malloc_auto_ptr<char> name;
UCHAR id_len;
sqlsrv_malloc_auto_ptr<char> id;
name_id_pair() : name_len(0), id_len(0)
{
}
~name_id_pair()
{
}
};
struct label_infotype_pair {
USHORT label_idx;
USHORT infotype_idx;
int rank; // Default value is "not defined"
label_infotype_pair() : label_idx(0), infotype_idx(0), rank(RANK_NOT_DEFINED)
{
}
};
struct column_sensitivity {
USHORT num_pairs;
std::vector<label_infotype_pair> label_info_pairs;
column_sensitivity() : num_pairs(0)
{
}
~column_sensitivity()
{
label_info_pairs.clear();
}
};
struct sensitivity_metadata {
USHORT num_labels;
std::vector<name_id_pair*, sqlsrv_allocator<name_id_pair*>> labels;
USHORT num_infotypes;
std::vector<name_id_pair*, sqlsrv_allocator<name_id_pair*>> infotypes;
USHORT num_columns;
std::vector<column_sensitivity> columns_sensitivity;
int rank; // Default value is "not defined"
sensitivity_metadata() : num_labels(0), num_infotypes(0), num_columns(0), rank(RANK_NOT_DEFINED)
{
}
~sensitivity_metadata()
{
reset();
}
void reset();
};
} // namespace data_classification
// forward decls
struct sqlsrv_result_set;
struct field_meta_data;
// *** Statement resource structure ***
struct sqlsrv_stmt : public sqlsrv_context {
void free_param_data( void );
virtual void new_result_set( void );
// free sensitivity classification metadata
void clean_up_sensitivity_metadata();
// free resultset metadata
void clean_up_results_metadata();
// set query timeout
void set_query_timeout();
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
short column_count; // Number of columns in the current result set obtained from SQLNumResultCols
long row_count; // Number of rows in the current result set obtained from SQLRowCount
unsigned long query_timeout; // maximum allowed statement execution time
zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB)
bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings
bool format_decimals; // false by default but the user can set this to true to add the missing leading zeroes and/or control number of decimal digits to show
short decimal_places; // indicates number of decimals shown in fetched results (-1 by default, which means no change to number of decimal digits)
bool data_classification; // false by default but the user can set this to true to retrieve data classification sensitivity metadata
bool send_streams_at_exec; // send all stream data right after execution before returning
zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals
zval col_cache; // Used by get_field_as_string not to call SQLColAttribute() after every fetch.
zval active_stream; // the currently active stream reading data from the database
sqlsrv_params_container params_container; // holds all parameters and references used for SQLBindParameter
// meta data for current result set
std::vector<field_meta_data*, sqlsrv_allocator<field_meta_data*>> current_meta_data;
// meta data for data classification
sqlsrv_malloc_auto_ptr<data_classification::sensitivity_metadata> current_sensitivity_metadata;
sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv );
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( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ) = 0;
};
// *** field metadata struct ***
struct field_meta_data {
sqlsrv_malloc_auto_ptr<SQLCHAR> field_name;
SQLSMALLINT field_name_len;
SQLSMALLINT field_type;
SQLULEN field_size;
SQLULEN field_precision;
SQLSMALLINT field_scale;
SQLSMALLINT field_is_nullable;
bool field_is_money_type;
sqlsrv_phptype sqlsrv_php_type;
field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0),
field_scale (0), field_is_nullable(0), field_is_money_type(false)
{
reset_php_type();
}
~field_meta_data()
{
}
void reset_php_type()
{
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
}
};
// *** 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 );
// *** statement functions ***
sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht,
_In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver );
void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z,
_In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size,
_Inout_ SQLSMALLINT decimal_digits);
SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql = NULL, _In_ int sql_len = 0 );
field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno );
bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLULEN fetch_offset );
void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_phptype sqlsrv_phptype, _In_ bool prefer_string,
_Outref_result_bytebuffer_maybenull_(*field_length) void*& field_value, _Inout_ SQLLEN* field_length, _In_ bool cache_field,
_Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out);
bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt );
void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt, _In_ bool finalize_output_params = true, _In_ bool throw_on_errors = true );
void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type );
void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z );
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt, _In_opt_ bool get_all = false);
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z );
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit );
void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z);
void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt );
//*********************************************************************************************************************************
// 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( _In_ sqlsrv_stmt* );
virtual ~sqlsrv_result_set( void ) { }
virtual bool cached( int field_index ) = 0;
virtual SQLRETURN fetch( _Inout_ SQLSMALLINT fetch_orientation, _Inout_opt_ SQLLEN fetch_offset ) = 0;
virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type,
_Out_writes_bytes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length,
bool handle_warning )= 0;
virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier,
_Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length,
_Inout_ SQLSMALLINT* out_buffer_length ) = 0;
virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number ) = 0;
virtual SQLLEN row_count( void ) = 0;
};
struct sqlsrv_odbc_result_set : public sqlsrv_result_set {
explicit sqlsrv_odbc_result_set( _In_ sqlsrv_stmt* );
virtual ~sqlsrv_odbc_result_set( void );
virtual bool cached( int field_index ) { return false; }
virtual SQLRETURN fetch( _In_ SQLSMALLINT fetch_orientation, _In_ SQLLEN fetch_offset );
virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type,
_Out_writes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length,
_In_ bool handle_warning );
virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier,
_Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length,
_Inout_ SQLSMALLINT* out_buffer_length );
virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number );
virtual SQLLEN row_count( void );
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( _Inout_ sqlsrv_stmt* odbc );
virtual ~sqlsrv_buffered_result_set( void );
virtual bool cached( int field_index ) { return true; }
virtual SQLRETURN fetch( _Inout_ SQLSMALLINT fetch_orientation, _Inout_opt_ SQLLEN fetch_offset );
virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type,
_Out_writes_bytes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length,
bool handle_warning );
virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier,
_Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length,
_Inout_ SQLSMALLINT* out_buffer_length );
virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number );
virtual SQLLEN row_count( void );
// 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
sqlsrv_malloc_auto_ptr<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<SQLCHAR> temp_string; // temp buffer to hold a converted field while in use
SQLLEN temp_length; // number of bytes in the temp conversion buffer
// string conversion functions
SQLRETURN binary_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
SQLRETURN binary_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
SQLRETURN system_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN to_binary_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN to_same_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
SQLRETURN wide_to_system_string( _In_ SQLSMALLINT field_index, _Inout_updates_bytes_to_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
// long conversion functions
SQLRETURN to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length );
SQLRETURN long_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
SQLRETURN long_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
SQLRETURN long_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Out_ SQLLEN* out_buffer_length );
// double conversion functions
SQLRETURN to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length );
SQLRETURN double_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
SQLRETURN double_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
SQLRETURN double_to_long( _In_ SQLSMALLINT field_index, _Inout_updates_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
SQLRETURN string_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
SQLRETURN wstring_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
SQLRETURN wstring_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ 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( _In_ SQLSRV_ENCODING encoding, _Inout_updates_z_(len) char** string, _Inout_ SQLLEN& len);
bool validate_string( _In_ char* string, _In_ SQLLEN& len);
bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_(cchInLen) const SQLWCHAR* inString, _In_ SQLINTEGER cchInLen, _Inout_updates_bytes_(cchOutLen) char** outString, _Out_ SQLLEN& cchOutLen );
SQLWCHAR* utf16_string_from_mbcs_string( _In_ SQLSRV_ENCODING php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len, bool use_strict_conversion = false );
void convert_datetime_string_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_opt_ char* input, _In_ SQLLEN length, _Inout_ zval& out_zval);
//*********************************************************************************************************************************
// Error handling routines and Predefined Errors
//*********************************************************************************************************************************
enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_ODBC,
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_CE_DRIVER_REQUIRED,
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND,
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,
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION,
SQLSRV_ERROR_AKV_AUTH_MISSING,
SQLSRV_ERROR_AKV_NAME_MISSING,
SQLSRV_ERROR_AKV_SECRET_MISSING,
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN,
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
SQLSRV_ERROR_INVALID_DECIMAL_PLACES,
SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION,
SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE,
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE,
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
SQLSRV_ERROR_TVP_FETCH_METADATA,
SQLSRV_ERROR_TVP_INVALID_INPUTS,
SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME,
SQLSRV_ERROR_TVP_STRING_KEYS,
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE,
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
// Driver specific error codes starts from here.
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
};
// 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( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error,
_In_ logging_severity severity, _In_opt_ bool check_warning = false );
// format and return a driver specfic error
void core_sqlsrv_format_driver_error( _In_ sqlsrv_context& ctx, _In_ sqlsrv_error_const const* custom_error,
_Out_ sqlsrv_error_auto_ptr& formatted_error, _In_ logging_severity severity, _In_opt_ 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( _Inout_ 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( _Out_ char* output_buffer, _In_ unsigned output_len, _In_opt_ 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( _Inout_ sqlsrv_context& ctx, _In_ unsigned long sqlsrv_error_code, _In_ int warning, ... )
{
va_list print_params;
va_start( print_params, warning );
bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning, &print_params );
va_end( print_params );
return ignored;
}
inline bool call_error_handler( _Inout_ sqlsrv_context* ctx, _In_ unsigned long sqlsrv_error_code, _In_ int warning, ... )
{
va_list print_params;
va_start( print_params, warning );
bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning, &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( _In_ SQLCHAR* state )
{
#if defined(ZEND_DEBUG)
if( state == NULL || strnlen_s( reinterpret_cast<char*>( 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, /*warning*/0, ## __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, /*warning*/1, ## __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, /*warning*/1, ## __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, 0, ##__VA_ARGS__ ); \
} \
else if( result == SQL_SUCCESS_WITH_INFO ) { \
ignored = call_error_handler( context, SQLSRV_ERROR_ODBC, 1, ##__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, /*warning*/0, ## __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( _Inout_ sqlsrv_stmt* stmt, _In_ SQLRETURN r )
{
// Skip this if not SQL_ERROR -
// 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 (r == SQL_ERROR) {
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 );
if (rtemp == SQL_SUCCESS_WITH_INFO && len > SQL_MAX_MESSAGE_LENGTH) {
// if the error message is this long, then it must not be the mars message
// defined as ODBC_CONNECTION_BUSY_ERROR -- so return here and continue the
// regular error handling
return;
}
CHECK_SQL_ERROR_OR_WARNING( rtemp, stmt ) {
throw CoreException();
}
// the message returned by ODBC Driver for SQL Server
const std::string connection_busy_error( ODBC_CONNECTION_BUSY_ERROR );
const std::string returned_error( reinterpret_cast<char*>( err_msg ));
if(( returned_error.find( connection_busy_error ) != std::string::npos )) {
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( _Inout_ sqlsrv_context* ctx, _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier,
_Out_writes_opt_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length,
_Out_opt_ SQLSMALLINT* out_buffer_length )
{
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( _In_ SQLSMALLINT HandleType, _Inout_ sqlsrv_context& InputHandle,
_Out_ SQLHANDLE* OutputHandlePtr )
{
SQLRETURN r;
r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr );
CHECK_SQL_ERROR_OR_WARNING( r, InputHandle ) {
throw CoreException();
}
}
inline void SQLBindParameter( _Inout_ sqlsrv_stmt* stmt,
_In_ SQLUSMALLINT ParameterNumber,
_In_ SQLSMALLINT InputOutputType,
_In_ SQLSMALLINT ValueType,
_In_ SQLSMALLINT ParameterType,
_In_ SQLULEN ColumnSize,
_In_ SQLSMALLINT DecimalDigits,
_Inout_opt_ SQLPOINTER ParameterValuePtr,
_Inout_ SQLLEN BufferLength,
_Inout_ SQLLEN * StrLen_Or_IndPtr
)
{
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 SQLCloseCursor( _Inout_ sqlsrv_stmt* stmt )
{
SQLRETURN r = ::SQLCloseCursor( stmt->handle() );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLColAttribute( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLUSMALLINT field_identifier,
_Out_writes_bytes_opt_(buffer_length) SQLPOINTER field_type_char, _In_ SQLSMALLINT buffer_length,
_Out_opt_ SQLSMALLINT* out_buffer_length, _Out_opt_ SQLLEN* field_type_num )
{
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( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLUSMALLINT field_identifier,
_Out_writes_bytes_opt_(buffer_length) SQLPOINTER field_type_char, _In_ SQLSMALLINT buffer_length,
_Out_opt_ SQLSMALLINT* out_buffer_length, _Out_opt_ SQLLEN* field_type_num )
{
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( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Out_writes_opt_(col_name_length) SQLCHAR* col_name, _In_ SQLSMALLINT col_name_length,
_Out_opt_ SQLSMALLINT* col_name_length_out, _Out_opt_ SQLSMALLINT* data_type, _Out_opt_ SQLULEN* col_size,
_Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable )
{
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( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Out_writes_opt_(col_name_length) SQLWCHAR* col_name, _In_ SQLSMALLINT col_name_length,
_Out_opt_ SQLSMALLINT* col_name_length_out, _Out_opt_ SQLSMALLINT* data_type, _Out_opt_ SQLULEN* col_size,
_Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable )
{
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 SQLDescribeParam( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT paramno, _Out_opt_ SQLSMALLINT* data_type, _Out_opt_ SQLULEN* col_size,
_Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable )
{
SQLRETURN r;
r = ::SQLDescribeParam( stmt->handle(), paramno, data_type, col_size, decimal_digits, nullable );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLNumParams( _Inout_ sqlsrv_stmt* stmt, _Out_opt_ SQLSMALLINT* num_params)
{
SQLRETURN r;
r = ::SQLNumParams( stmt->handle(), num_params );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLEndTran( _In_ SQLSMALLINT handleType, _Inout_ sqlsrv_conn* conn, _In_ SQLSMALLINT completionType )
{
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( _Inout_ sqlsrv_stmt* stmt, _In_ char* sql )
{
SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast<SQLCHAR*>( sql ), SQL_NTS );
check_for_mars_error( stmt, r );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline SQLRETURN SQLExecDirectW( _Inout_ sqlsrv_stmt* stmt, _In_ SQLWCHAR* wsql )
{
SQLRETURN r;
r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast<SQLWCHAR*>( wsql ), SQL_NTS );
check_for_mars_error( stmt, r );
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( _Inout_ sqlsrv_stmt* stmt )
{
SQLRETURN r;
r = ::SQLExecute( stmt->handle() );
check_for_mars_error( stmt, r );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline SQLRETURN SQLFetchScroll( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLLEN fetch_offset )
{
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( _Inout_ sqlsrv_context& ctx )
{
SQLRETURN r;
r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {}
}
inline void SQLGetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _Out_writes_opt_(buf_len) void* value_ptr, _In_ SQLINTEGER buf_len, _Out_opt_ SQLINTEGER* str_len)
{
SQLRETURN r;
r = ::SQLGetStmtAttr( stmt->handle(), attr, value_ptr, buf_len, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline SQLRETURN SQLGetData( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type,
_Out_writes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_opt_ SQLLEN* out_buffer_length,
_In_ bool handle_warning )
{
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( _Inout_ sqlsrv_conn* conn, _In_ SQLUSMALLINT info_type, _Out_writes_bytes_opt_(buffer_len) SQLPOINTER info_value, _In_ SQLSMALLINT buffer_len,
_Out_opt_ SQLSMALLINT* str_len )
{
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( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT data_type )
{
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( _Inout_ sqlsrv_stmt* stmt )
{
SQLRETURN r = ::SQLMoreResults( stmt->handle() );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline SQLSMALLINT SQLNumResultCols( _Inout_ sqlsrv_stmt* stmt )
{
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( _Inout_ sqlsrv_stmt* stmt, _Out_opt_ SQLPOINTER* value_ptr_ptr )
{
SQLRETURN r;
r = ::SQLParamData( stmt->handle(), value_ptr_ptr );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
return r;
}
inline void SQLPrepareW( _Inout_ sqlsrv_stmt* stmt, _In_reads_(sql_len) SQLWCHAR * sql, _In_ SQLINTEGER sql_len )
{
SQLRETURN r;
r = ::SQLPrepareW( stmt->handle(), sql, sql_len );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline void SQLPutData( _Inout_ sqlsrv_stmt* stmt, _In_reads_(strlen_or_ind) SQLPOINTER data_ptr, _In_ SQLLEN strlen_or_ind )
{
SQLRETURN r;
r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
inline SQLLEN SQLRowCount( _Inout_ sqlsrv_stmt* stmt )
{
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( _Inout_ sqlsrv_context& ctx, _In_ SQLINTEGER attr, _In_reads_bytes_opt_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len )
{
SQLRETURN r;
r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {
throw CoreException();
}
}
inline void SQLSetDescField( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT rec_num, _In_ SQLSMALLINT fld_id, _In_reads_bytes_opt_( str_len ) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len )
{
SQLRETURN r;
SQLHDESC hIpd = NULL;
core::SQLGetStmtAttr( stmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0 );
if( value_ptr ) {
r = ::SQLSetDescField( hIpd, rec_num, fld_id, value_ptr, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw CoreException();
}
}
}
inline void SQLSetEnvAttr( _Inout_ sqlsrv_context& ctx, _In_ SQLINTEGER attr, _In_reads_bytes_opt_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len )
{
SQLRETURN r;
r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len );
CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {
throw CoreException();
}
}
inline void SQLSetConnectAttr( _Inout_ sqlsrv_conn* conn, _In_ SQLINTEGER attribute, _In_reads_bytes_opt_(value_len) SQLPOINTER value_ptr, _In_ SQLINTEGER value_len )
{
SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len );
CHECK_SQL_ERROR_OR_WARNING( r, conn ) {
throw CoreException();
}
}
inline void SQLSetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _In_reads_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len )
{
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( _Inout_ zval* value_z, _In_reads_(str_len) const char* str, _In_ 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);
}
}
inline void sqlsrv_php_stream_from_zval_no_verify( _Inout_ sqlsrv_context& ctx, _Outref_result_maybenull_ php_stream*& stream, _In_opt_ zval* stream_z )
{
// 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( _In_ sqlsrv_context& ctx, _In_ HashTable* ht, _Outref_result_maybenull_ zval*& output_data)
{
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( _Inout_ sqlsrv_context& ctx, _In_ HashTable* ht, _Outref_result_maybenull_ void*& output_data)
{
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( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index )
{
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( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_ zval* data_z )
{
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( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_ void* pData)
{
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( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_reads_bytes_(size) void* pData, _In_ std::size_t size)
{
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( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zval* data )
{
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( _Inout_ sqlsrv_context& ctx, _In_ HashTable* ht, _In_reads_bytes_(data_size) void* data, _In_ size_t data_size)
{
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( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ void* data)
{
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, _Inout_ HashTable* ht, _Inout_ uint32_t initial_size,
_In_ dtor_func_t dtor_fn, _In_ zend_bool persistent )
{
::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent);
}
template <typename Statement>
sqlsrv_stmt* allocate_stmt( _In_ sqlsrv_conn* conn, _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver )
{
return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver );
}
template <typename Connection>
sqlsrv_conn* allocate_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver )
{
return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver );
}
} // namespace core
template <unsigned int Attr>
struct str_conn_attr_func {
static void func( connection_option const* /*option*/, zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_STRVAL_P( value )), static_cast<SQLINTEGER>( Z_STRLEN_P( value )) );
}
catch ( core::CoreException& ) {
throw;
}
}
};
#endif // CORE_SQLSRV_H

View file

@ -1,3684 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_stmt.cpp
//
// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv
//
// Microsoft Drivers 5.10 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 <sstream>
#include <vector>
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( _In_reads_bytes_opt_(field_len) void* field_value, _In_ SQLLEN field_len, _In_ sqlsrv_phptype t )
: type( t )
{
// if the value is NULL, then just record a NULL pointer
// field_len may be equal to SQL_NULL_DATA even when field_value is not null
if( field_value != NULL && field_len != SQL_NULL_DATA) {
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
};
// Used to cache display size and SQL type of a column in get_field_as_string()
struct col_cache {
SQLLEN sql_type;
SQLLEN display_size;
col_cache( _In_ SQLLEN col_sql_type, _In_ SQLLEN col_display_size )
{
sql_type = col_sql_type;
display_size = col_display_size;
}
};
const int INITIAL_FIELD_STRING_LEN = 2048; // base allocation size when retrieving a string field
const char DECIMAL_POINT = '.';
const int SQL_SERVER_DECIMAL_MAXIMUM_PRECISION = 38; // 38 is the maximum length of a stringified decimal number
// 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 more explanations they need in their definitions
void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size );
size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) const char* buffer, _In_ size_t buffer_end );
void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype
sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len);
void col_cache_dtor( _Inout_ zval* data_z );
void field_cache_dtor( _Inout_ zval* data_z );
int round_up_decimal_numbers(_Inout_ char* buffer, _In_ int decimal_pos, _In_ int decimals_places, _In_ int offset, _In_ int lastpos);
void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len);
void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type,
_Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len );
stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] );
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
void adjustDecimalPrecision(_Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits);
bool is_a_numeric_type(_In_ SQLSMALLINT sql_type);
bool is_a_string_type(_In_ SQLSMALLINT sql_type);
}
// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier.
sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv ) :
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 ),
column_count( ACTIVE_NUM_COLS_INVALID ),
row_count( ACTIVE_NUM_ROWS_INVALID ),
query_timeout( QUERY_TIMEOUT_INVALID ),
date_as_string(false),
format_decimals(false), // no formatting needed
decimal_places(NO_CHANGE_DECIMAL_PLACES), // the default is no formatting to resultset required
data_classification(false),
buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ),
send_streams_at_exec( true )
{
ZVAL_UNDEF( &active_stream );
// initialize the col cache
array_init(&col_cache);
core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL(col_cache), 5 /* # of buckets */, col_cache_dtor, 0 /*persistent*/ );
// initialize the field cache
array_init(&field_cache);
core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/);
}
// desctructor for sqlsrv statement.
sqlsrv_stmt::~sqlsrv_stmt( void )
{
if( Z_TYPE( active_stream ) != IS_UNDEF ) {
close_active_stream( this );
}
// delete any current results
if( current_results ) {
current_results->~sqlsrv_result_set();
efree( current_results );
current_results = NULL;
}
// delete sensivity data
clean_up_sensitivity_metadata();
// clean up metadata
clean_up_results_metadata();
invalidate();
zval_ptr_dtor( &col_cache );
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( void )
{
params_container.clean_up_param_data();
zend_hash_clean( Z_ARRVAL( col_cache ));
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( void )
{
this->fetch_called = false;
this->has_rows = false;
this->past_next_result_end = false;
this->past_fetch_end = false;
this->last_field_index = -1;
this->column_count = ACTIVE_NUM_COLS_INVALID;
this->row_count = ACTIVE_NUM_ROWS_INVALID;
// delete any current results
if( current_results ) {
current_results->~sqlsrv_result_set();
efree( current_results );
current_results = NULL;
}
// delete sensivity data
clean_up_sensitivity_metadata();
// reset sqlsrv php type in meta data
size_t num_fields = this->current_meta_data.size();
for (size_t f = 0; f < num_fields; f++) {
this->current_meta_data[f]->reset_php_type();
}
// create a new result set
if( cursor_type == SQLSRV_CURSOR_BUFFERED ) {
sqlsrv_malloc_auto_ptr<sqlsrv_buffered_result_set> result;
result = reinterpret_cast<sqlsrv_buffered_result_set*> ( sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ) ) );
new ( result.get() ) sqlsrv_buffered_result_set( this );
current_results = result.get();
result.transferred();
}
else {
current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this );
}
}
// free sensitivity classification metadata
void sqlsrv_stmt::clean_up_sensitivity_metadata()
{
if (current_sensitivity_metadata) {
current_sensitivity_metadata->~sensitivity_metadata();
current_sensitivity_metadata.reset();
}
}
// internal helper function to free meta data structures allocated
void meta_data_free(_Inout_ field_meta_data* meta)
{
meta->field_name.reset();
sqlsrv_free(meta);
}
void sqlsrv_stmt::clean_up_results_metadata()
{
std::for_each(current_meta_data.begin(), current_meta_data.end(), meta_data_free);
current_meta_data.clear();
column_count = ACTIVE_NUM_COLS_INVALID;
row_count = ACTIVE_NUM_ROWS_INVALID;
}
void sqlsrv_stmt::set_query_timeout()
{
if (query_timeout == QUERY_TIMEOUT_INVALID || query_timeout < 0) {
return;
}
core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>((SQLLEN)query_timeout), SQL_IS_UINTEGER);
}
// 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( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht,
_In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver )
{
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> stmt;
SQLHANDLE stmt_h = SQL_NULL_HANDLE;
sqlsrv_stmt* return_stmt = NULL;
try {
core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h );
stmt = stmt_factory( conn, stmt_h, err, driver );
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 && valid_stmt_opts ) {
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 );
// 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 );
} ZEND_HASH_FOREACH_END();
}
// The query timeout setting is inherited from the corresponding connection attribute, but
// the user may override that the query timeout setting using the statement option.
// In any case, set query timeout using the latest value
stmt->set_query_timeout();
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( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z,
_In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size,
_Inout_ SQLSMALLINT decimal_digits)
{
// 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();
}
// Dereference the parameter if necessary
zval* param_ref = param_z;
if (Z_ISREF_P(param_z)) {
ZVAL_DEREF(param_z);
}
sqlsrv_param* param_ptr = stmt->params_container.find_param(param_num, (direction == SQL_PARAM_INPUT));
try {
if (param_ptr == NULL) {
sqlsrv_malloc_auto_ptr<sqlsrv_param> new_param;
if (direction == SQL_PARAM_INPUT) {
// Check if it's a Table-Valued Parameter first
if (Z_TYPE_P(param_z) == IS_ARRAY) {
new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param_tvp))) sqlsrv_param_tvp(param_num, encoding, SQL_SS_TABLE, 0, 0, NULL);
} else {
new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param))) sqlsrv_param(param_num, direction, encoding, sql_type, column_size, decimal_digits);
}
} else if (direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT) {
new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param_inout))) sqlsrv_param_inout(param_num, direction, encoding, sql_type, column_size, decimal_digits, php_out_type);
} else {
SQLSRV_ASSERT(false, "sqlsrv_params_container::insert_param - Invalid parameter direction.");
}
stmt->params_container.insert_param(param_num, new_param);
param_ptr = new_param;
new_param.transferred();
} else if (direction == SQL_PARAM_INPUT
&& param_ptr->sql_data_type != SQL_SS_TABLE
&& param_ptr->strlen_or_indptr == SQL_NULL_DATA) {
// reset the followings for regular input parameters if it was bound as a null param before
param_ptr->sql_data_type = sql_type;
param_ptr->column_size = column_size;
param_ptr->strlen_or_indptr = 0;
}
SQLSRV_ASSERT(param_ptr != NULL, "core_sqlsrv_bind_param: param_ptr is null. Something went wrong.");
bool result = param_ptr->prepare_param(param_ref, param_z);
if (!result && direction == SQL_PARAM_INPUT_OUTPUT) {
CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1) {
throw core::CoreException();
}
}
// If Always Encrypted is enabled, transfer the known param meta data if applicable, which might alter param_z for decimal types
if (stmt->conn->ce_option.enabled) {
if (param_ptr->sql_data_type == SQL_UNKNOWN_TYPE || param_ptr->column_size == SQLSRV_UNKNOWN_SIZE) {
// meta data parameters are always sorted based on parameter number
param_ptr->copy_param_meta_ae(param_z, stmt->params_container.params_meta_ae[param_num]);
}
}
// Get all necessary values to prepare for SQLBindParameter
param_ptr->process_param(stmt, param_z);
param_ptr->bind_param(stmt);
// When calling SQLDescribeParam() on a parameter targeting a Datetime column, the return values for ParameterType, ColumnSize and DecimalDigits are SQL_TYPE_TIMESTAMP, 23, and 3 respectively.
// For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error.
// This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns.
// To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column.
// Note this must be called after SQLBindParameter() or SQLSetDescField() may fail.
// VSO BUG 2693: how to correctly distinguish datetime from datetime2(3)? Both have the same decimal_digits and column_size
if (stmt->conn->ce_option.enabled && param_ptr->sql_data_type == SQL_TYPE_TIMESTAMP) {
if (param_ptr->decimal_digits == 3) {
core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER);
} else if (param_ptr->decimal_digits == 0 && param_ptr->column_size == 16) {
core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER);
}
}
}
catch( core::CoreException& e ){
stmt->free_param_data();
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
SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ int sql_len )
{
SQLRETURN r = SQL_ERROR;
try {
// close the stream to release the resource
close_active_stream( stmt );
if( sql ) {
sqlsrv_malloc_auto_ptr<SQLWCHAR> wsql_string;
unsigned int wsql_len = 0;
if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) {
wsql_string = reinterpret_cast<SQLWCHAR*>( 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<const char*>( 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 );
}
else {
r = core::SQLExecute( stmt );
}
// 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 ) {
core_sqlsrv_send_stream_packet(stmt, true);
}
stmt->new_result_set();
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 ))) {
stmt->params_container.finalize_output_parameters();
}
return r;
}
catch( core::CoreException& e ) {
// if the statement executed but failed in a subsequent operation before returning,
// we need to remove all the parameters and cancel the statement
stmt->params_container.clean_up_param_data();
if( stmt->executed ) {
SQLCancel( stmt->handle() );
// stmt->executed = false; should this be reset if something fails?
}
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( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLULEN fetch_offset )
{
// 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 {
// first check if the end of all results has been reached
CHECK_CUSTOM_ERROR(stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END) {
throw core::CoreException();
}
// clear the field cache of the previous fetch
zend_hash_clean( Z_ARRVAL( stmt->field_cache ));
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();
}
// First time only
if ( !stmt->fetch_called ) {
SQLSMALLINT has_fields;
if (stmt->column_count != ACTIVE_NUM_COLS_INVALID) {
has_fields = stmt->column_count;
} else {
has_fields = core::SQLNumResultCols( stmt );
stmt->column_count = has_fields;
}
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 );
// 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 );
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;
}
stmt->fetch_called = false; // reset this flag
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( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno )
{
// pre-condition check
SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." );
sqlsrv_malloc_auto_ptr<field_meta_data> meta_data;
sqlsrv_malloc_auto_ptr<SQLWCHAR> 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<SQLWCHAR*>( 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 ) );
}
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;
}
}
if (meta_data->field_type == SQL_DECIMAL) {
// Check if it is money type -- get the name of the data type
char field_type_name[SS_MAXCOLNAMELEN] = {'\0'};
SQLSMALLINT out_buff_len;
SQLLEN not_used;
core::SQLColAttribute(stmt, colno + 1, SQL_DESC_TYPE_NAME, field_type_name,
sizeof( field_type_name ), &out_buff_len, &not_used);
if (!strcmp(field_type_name, "money") || !strcmp(field_type_name, "smallmoney")) {
meta_data->field_is_money_type = true;
}
}
// Set the field name length
meta_data->field_name_len = static_cast<SQLSMALLINT>( field_name_len );
field_meta_data* result_field_meta_data = meta_data;
meta_data.transferred();
return result_field_meta_data;
}
void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt )
{
sqlsrv_malloc_auto_ptr<unsigned char> dcbuf;
DWORD dcVersion = 0;
SQLINTEGER dclen = 0, dcIRD = 0;
SQLINTEGER dclenout = 0;
SQLHANDLE ird;
SQLRETURN r;
try {
if (!stmt->data_classification) {
return;
}
if (stmt->current_sensitivity_metadata) {
// Already cached, so return
return;
}
CHECK_CUSTOM_ERROR(!stmt->executed, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION) {
throw core::CoreException();
}
// Reference: https://docs.microsoft.com/sql/connect/odbc/data-classification
// To retrieve sensitivity classfication data, the first step is to retrieve the IRD(Implementation Row Descriptor) handle by
// calling SQLGetStmtAttr with SQL_ATTR_IMP_ROW_DESC statement attribute
r = ::SQLGetStmtAttr(stmt->handle(), SQL_ATTR_IMP_ROW_DESC, reinterpret_cast<SQLPOINTER*>(&ird), SQL_IS_POINTER, 0);
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in getting Implementation Row Descriptor handle." );
throw core::CoreException();
}
// First call to get dclen
r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION, reinterpret_cast<SQLPOINTER>(dcbuf.get()), 0, &dclen);
if (r != SQL_SUCCESS || dclen == 0) {
// log the error first
LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in calling SQLGetDescFieldW first time." );
// If this fails, check if it is the "Invalid Descriptor Field error"
SQLRETURN rc;
SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {'\0'};
SQLSMALLINT len;
rc = ::SQLGetDiagField(SQL_HANDLE_DESC, ird, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len);
CHECK_SQL_ERROR_OR_WARNING(rc, stmt) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR(!strcmp("HY091", reinterpret_cast<char*>(state)), stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "Check if ODBC driver or the server supports the Data Classification feature.") {
throw core::CoreException();
}
}
// Call again to read SQL_CA_SS_DATA_CLASSIFICATION data
dcbuf = static_cast<unsigned char*>(sqlsrv_malloc(dclen * sizeof(char)));
r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION, reinterpret_cast<SQLPOINTER>(dcbuf.get()), dclen, &dclenout);
if (r != SQL_SUCCESS) {
LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in calling SQLGetDescFieldW again." );
CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "SQLGetDescFieldW failed unexpectedly") {
throw core::CoreException();
}
}
// Start parsing the data (blob)
using namespace data_classification;
// If make it this far, must be using ODBC 17.2 or above. Prior to ODBC 17.4, checking Data Classification version will fail.
// When the function is successful and the version is right, rank info is available for retrieval
bool getRankInfo = false;
r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION_VERSION, reinterpret_cast<SQLPOINTER>(&dcVersion), SQL_IS_INTEGER, &dcIRD);
if (r == SQL_SUCCESS && dcVersion >= VERSION_RANK_AVAILABLE) {
getRankInfo = true;
}
// Start parsing the data (blob)
unsigned char *dcptr = dcbuf;
sqlsrv_malloc_auto_ptr<sensitivity_metadata> sensitivity_meta;
sensitivity_meta = new (sqlsrv_malloc(sizeof(sensitivity_metadata))) sensitivity_metadata();
// Parse the name id pairs for labels first then info types
parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_labels, &sensitivity_meta->labels, &dcptr);
parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_infotypes, &sensitivity_meta->infotypes, &dcptr);
// Next parse the sensitivity properties
parse_column_sensitivity_props(sensitivity_meta, &dcptr, getRankInfo);
unsigned char *dcend = dcbuf;
dcend += dclen;
CHECK_CUSTOM_ERROR(dcptr != dcend, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "Metadata parsing ends unexpectedly") {
throw core::CoreException();
}
stmt->current_sensitivity_metadata = sensitivity_meta;
sensitivity_meta.transferred();
} catch (core::CoreException& e) {
throw e;
}
}
// 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( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_phptype sqlsrv_php_type_in, _In_ bool prefer_string,
_Outref_result_bytebuffer_maybenull_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len, _In_ bool cache_field,
_Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out)
{
try {
// close the stream to release the resource
close_active_stream(stmt);
// if the field has been retrieved before, return the previous result
field_cache* cached = NULL;
if (NULL != ( cached = static_cast<field_cache*>( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast<zend_ulong>( field_index ))))) {
// 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<char*>( field_value )[cached->len] = '\0';
}
*field_len = cached->len;
if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>(cached->type.typeinfo.type); }
}
return;
}
sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in;
// 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<field_cache*>( 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 );
// 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) {
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_sqlsrv_get_field - meta data vector not in sync" );
// Get the corresponding php type from the sql type and then save the result for later
if (stmt->current_meta_data[field_index]->sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID) {
SQLLEN sql_field_type = 0;
SQLLEN sql_field_len = 0;
sql_field_type = stmt->current_meta_data[field_index]->field_type;
if (stmt->current_meta_data[field_index]->field_precision > 0) {
sql_field_len = stmt->current_meta_data[field_index]->field_precision;
}
else {
sql_field_len = stmt->current_meta_data[field_index]->field_size;
}
sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast<SQLINTEGER>(sql_field_type), static_cast<SQLUINTEGER>(sql_field_len), prefer_string);
stmt->current_meta_data[field_index]->sqlsrv_php_type = sqlsrv_php_type;
}
else {
// use the previously saved php type
sqlsrv_php_type = stmt->current_meta_data[field_index]->sqlsrv_php_type;
}
}
// 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_PHPTYPE>( sqlsrv_php_type.typeinfo.type );
// Retrieve the data
core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len );
// 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) );
}
}
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( _Inout_ sqlsrv_stmt* stmt )
{
SQLSMALLINT num_cols;
SQLLEN rows_affected;
if (stmt->column_count != ACTIVE_NUM_COLS_INVALID) {
num_cols = stmt->column_count;
}
else {
// Use SQLNumResultCols to determine if we have rows or not
num_cols = core::SQLNumResultCols( stmt );
stmt->column_count = num_cols;
}
if (stmt->row_count != ACTIVE_NUM_ROWS_INVALID) {
rows_affected = stmt->row_count;
}
else {
// Use SQLRowCount to determine if there is a rows status waiting
rows_affected = core::SQLRowCount( stmt );
stmt->row_count = rows_affected;
}
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( _Inout_ sqlsrv_stmt* stmt, _In_ bool finalize_output_params, _In_ 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 );
//Clear column sql types and sql display sizes.
zend_hash_clean( Z_ARRVAL( stmt->col_cache ));
SQLRETURN r;
if( throw_on_errors ) {
r = core::SQLMoreResults( stmt );
}
else {
r = SQLMoreResults( stmt->handle() );
}
if( r == SQL_NO_DATA ) {
if( finalize_output_params ) {
// if we're finished processing result sets, handle the output parameters
stmt->params_container.finalize_output_parameters();
}
// mark we are past the end of all results
stmt->past_next_result_end = true;
return;
}
stmt->new_result_set();
}
catch( core::CoreException& e ) {
SQLCancel( stmt->handle() );
throw e;
}
}
//Calls SQLSetStmtAttr to set a cursor.
void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type )
{
try {
switch( cursor_type ) {
case SQL_CURSOR_STATIC:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER );
break;
case SQL_CURSOR_DYNAMIC:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER );
break;
case SQL_CURSOR_KEYSET_DRIVEN:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER );
break;
case SQL_CURSOR_FORWARD_ONLY:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER );
break;
case SQLSRV_CURSOR_BUFFERED:
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER );
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( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z )
{
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 ) );
}
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit )
{
if( limit <= 0 ) {
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT );
}
stmt->buffered_query_limit = limit;
}
// 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( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z )
{
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 ) );
}
// Save the query timeout setting for processing later
stmt->query_timeout = static_cast<long>(Z_LVAL_P(value_z));
}
catch( core::CoreException& ) {
throw;
}
}
void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z)
{
try {
// first check if the input is an integer
CHECK_CUSTOM_ERROR(Z_TYPE_P(value_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_DECIMAL_PLACES) {
throw core::CoreException();
}
zend_long decimal_places = Z_LVAL_P(value_z);
if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) {
// ignore decimal_places because it is out of range
decimal_places = NO_CHANGE_DECIMAL_PLACES;
}
stmt->decimal_places = static_cast<short>(decimal_places);
}
catch( core::CoreException& ) {
throw;
}
}
// 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 will be thrown.
// Parameters:
// stmt - query to send the next packet for
// get_all - send stream data all at once (false by default)
// Returns:
// true if more data remains to be sent, false if all data processed
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt, _In_opt_ bool get_all /*= false*/)
{
bool bMore = false;
try {
if (get_all) {
// send stream data all at once (so no more after this)
stmt->params_container.send_all_packets(stmt);
} else {
bMore = stmt->params_container.send_next_packet(stmt);
}
if (!bMore) {
// All resources parameters are sent, so it's time to clean up
stmt->params_container.clean_up_param_data(true);
}
} catch (core::CoreException& e) {
stmt->free_param_data();
SQLFreeStmt(stmt->handle(), SQL_RESET_PARAMS);
SQLCancel(stmt->handle());
throw e;
}
return bMore;
}
void stmt_option_functor::operator()( _Inout_ sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, _In_ zval* /*value_z*/ )
{
// This implementation should never get called.
DIE( "Not implemented." );
}
void stmt_option_query_timeout:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z )
{
core_sqlsrv_set_query_timeout( stmt, value_z );
}
void stmt_option_send_at_exec:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
// zend_is_true does not fail. It either returns true or false.
stmt->send_streams_at_exec = (zend_is_true(value_z));
}
void stmt_option_buffered_query_limit:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
core_sqlsrv_set_buffered_query_limit( stmt, value_z );
}
void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z )
{
stmt->date_as_string = zend_is_true(value_z);
}
void stmt_option_format_decimals:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z )
{
stmt->format_decimals = zend_is_true(value_z);
}
void stmt_option_decimal_places:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z )
{
core_sqlsrv_set_decimal_places(stmt, value_z);
}
void stmt_option_data_classification:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z )
{
stmt->data_classification = zend_is_true(value_z);
}
// 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 )
{
// 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( _In_ SQLSMALLINT 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;
}
bool is_a_numeric_type(_In_ SQLSMALLINT sql_type)
{
switch (sql_type) {
case SQL_BIGINT:
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_REAL:
case SQL_DECIMAL:
case SQL_NUMERIC:
return true;
}
return false;
}
bool is_a_string_type(_In_ SQLSMALLINT sql_type)
{
switch (sql_type) {
case SQL_BIGINT:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_SS_VARIANT:
case SQL_SS_UDT:
case SQL_GUID:
case SQL_SS_XML:
case SQL_CHAR:
case SQL_WCHAR:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
return true;
}
return false;
}
void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size )
{
try {
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:
case SQL_SS_VARIANT:
{
// unixODBC 2.3.1 requires wide calls to support pooling
core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size );
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 );
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( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) const char* buffer, _In_ size_t buffer_end )
{
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, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype
sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len )
{
try {
close_active_stream( stmt );
// 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<SQLLEN> field_value_temp;
field_value_temp = static_cast<SQLLEN*>( sqlsrv_malloc( sizeof( SQLLEN )));
*field_value_temp = 0;
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( SQLLEN ),
field_len, true /*handle_warning*/ );
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<double> field_value_temp;
field_value_temp = static_cast<double*>( sqlsrv_malloc( sizeof( double )));
*field_value_temp = 0.0;
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ),
field_len, true /*handle_warning*/ );
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 );
break;
}
// Reference: https://docs.microsoft.com/sql/odbc/reference/appendixes/sql-to-c-timestamp
// Retrieve the datetime data as a string, which may be cached for later use.
// The string is converted to a DateTime object only when it is required to
// be returned as a zval.
case SQLSRV_PHPTYPE_DATETIME:
{
sqlsrv_malloc_auto_ptr<char> field_value_temp;
SQLLEN field_len_temp = 0;
field_value_temp = static_cast<char*>(sqlsrv_malloc(MAX_DATETIME_STRING_LEN));
memset(field_value_temp, '\0', MAX_DATETIME_STRING_LEN);
SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_CHAR, field_value_temp, MAX_DATETIME_STRING_LEN, &field_len_temp, true);
if (r == SQL_NO_DATA || field_len_temp == SQL_NULL_DATA) {
field_value_temp.reset();
field_len_temp = 0;
}
CHECK_CUSTOM_ERROR((r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index) {
throw core::CoreException();
}
field_value = field_value_temp;
field_value_temp.transferred();
*field_len = field_len_temp;
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;
SQLSMALLINT sql_type;
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_get_field_common - meta data vector not in sync" );
sql_type = stmt->current_meta_data[field_index]->field_type;
CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) {
throw core::CoreException();
}
// For a sqlsrv stream, only REPORT_ERRORS may be used. For "mode", the 'b' option
// is ignored on POSIX systems, which treat text and binary files the same. Yet, the
// 'b' option might be important in other systems.
// For details check https://www.php.net/manual/en/internals2.ze1.streams.php
stream = php_stream_open_wrapper("sqlsrv://sqlncli10", "rb", REPORT_ERRORS, NULL);
CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) {
throw core::CoreException();
}
ss = static_cast<sqlsrv_stream*>( stream->abstract );
ss->stmt = stmt;
ss->field_index = field_index;
ss->sql_type = static_cast<SQLUSMALLINT>( sql_type );
ss->encoding = static_cast<SQLSRV_ENCODING>( 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<void*>( 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;
}
}
void col_cache_dtor( _Inout_ zval* data_z )
{
col_cache* cache = static_cast<col_cache*>( Z_PTR_P( data_z ));
sqlsrv_free( cache );
}
void field_cache_dtor( _Inout_ zval* data_z )
{
field_cache* cache = static_cast<field_cache*>( Z_PTR_P( data_z ));
if( cache->value )
{
sqlsrv_free( cache->value );
}
sqlsrv_free( cache );
}
// To be called for formatting decimal / numeric fetched values from finalize_output_parameters() and/or get_field_as_string()
void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len)
{
// In SQL Server, the default maximum precision of numeric and decimal data types is 38
//
// Note: decimals_places is NO_CHANGE_DECIMAL_PLACES by default, which means no formatting on decimal data is necessary
// This function assumes stmt->format_decimals is true, so it first checks if it is necessary to add the leading zero.
//
// Likewise, if decimals_places is larger than the field scale, decimals_places wil be ignored. This is to ensure the
// number of decimals adheres to the column field scale. If smaller, the output value may be rounded up.
//
// Note: it's possible that the decimal data does not contain a decimal point because the field scale is 0.
// Thus, first check if the decimal point exists. If not, no formatting necessary, regardless of
// format_decimals and decimals_places
//
// Check if it's a negative number and if necessary to add the leading zero
short is_negative = (*field_value == '-') ? 1 : 0;
char *src = field_value + is_negative;
bool add_leading_zero = false;
// If the decimal point is not found, simply return
char *pt = strchr(src, DECIMAL_POINT);
if (pt == NULL) {
return;
}
else if (pt == src) {
add_leading_zero = true;
}
SQLSMALLINT scale = decimals_places;
if (scale > field_scale) {
scale = field_scale;
}
char buffer[50] = " "; // A buffer with TWO blank spaces, as leeway
int offset = 1 + is_negative; // for cases like 9.* to 10.* and the minus sign if needed
int src_length = strnlen_s(src);
if (add_leading_zero) {
buffer[offset++] = '0'; // leading zero added
}
// Copy the original numerical value to the buffer
memcpy_s(buffer + offset, src_length, src, src_length);
int last_pos = src_length + offset;
// If no need to adjust decimal places, skip formatting
if (decimals_places != NO_CHANGE_DECIMAL_PLACES) {
int num_decimals = src_length - (pt - src) - 1;
if (num_decimals > scale) {
last_pos = round_up_decimal_numbers(buffer, (pt - src) + offset, scale, offset, last_pos);
}
}
// Remove the extra white space if not used. For a negative number,
// the first pos is always a space
offset = is_negative;
char *p = buffer + offset;
while (*p++ == ' ') {
offset++;
}
if (is_negative) {
buffer[--offset] = '-';
}
int len = last_pos - offset;
memcpy_s(field_value, len, buffer + offset, len);
field_value[len] = '\0';
*field_len = len;
}
void get_field_as_string(_Inout_ sqlsrv_stmt *stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type,
_Inout_updates_bytes_(*field_len) void *&field_value, _Inout_ SQLLEN *field_len)
{
SQLRETURN r;
SQLSMALLINT c_type;
SQLSMALLINT sql_field_type = 0;
SQLSMALLINT extra = 0;
SQLLEN field_len_temp = 0;
SQLLEN sql_display_size = 0;
char* field_value_temp = NULL;
unsigned int initial_field_len = INITIAL_FIELD_STRING_LEN;
try {
DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING,
"Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" );
col_cache* cached = NULL;
if ( NULL != ( cached = static_cast< col_cache* >( zend_hash_index_find_ptr( Z_ARRVAL( stmt->col_cache ), static_cast< zend_ulong >( field_index ))))) {
sql_field_type = cached->sql_type;
sql_display_size = cached->display_size;
}
else {
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "get_field_as_string - meta data vector not in sync" );
sql_field_type = stmt->current_meta_data[field_index]->field_type;
// Calculate the field size.
calc_string_size( stmt, field_index, sql_field_type, sql_display_size );
col_cache cache( sql_field_type, sql_display_size );
core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) );
}
// Determine the correct encoding
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.
if (sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_BINARY) {
c_type = SQL_C_BINARY;
extra = 0;
} else {
c_type = SQL_C_CHAR;
extra = sizeof(SQLCHAR);
// For numbers, no need to convert
if (sqlsrv_php_type.typeinfo.encoding == CP_UTF8 && !is_a_numeric_type(sql_field_type)) {
c_type = SQL_C_WCHAR;
extra = sizeof(SQLWCHAR);
sql_display_size = (sql_display_size * sizeof(SQLWCHAR));
}
}
// If this is a large type, then read the first chunk to get the actual length from SQLGetData
// The user may use "SET TEXTSIZE" to specify the size of varchar(max), nvarchar(max),
// varbinary(max), text, ntext, and image data returned by a SELECT statement.
// For varbinary(max), varchar(max) and nvarchar(max), sql_display_size will be 0, regardless
if (sql_display_size == 0 ||
(sql_field_type == SQL_WLONGVARCHAR || sql_field_type == SQL_LONGVARCHAR || sql_field_type == SQL_LONGVARBINARY)) {
field_len_temp = initial_field_len;
field_value_temp = static_cast<char*>(sqlsrv_malloc(field_len_temp + extra + 1));
r = stmt->current_results->get_data(field_index + 1, c_type, field_value_temp, (field_len_temp + extra), &field_len_temp, false /*handle_warning*/);
} else {
field_len_temp = sql_display_size;
field_value_temp = static_cast<char*>(sqlsrv_malloc(sql_display_size + extra + 1));
// get the data
r = stmt->current_results->get_data(field_index + 1, c_type, field_value_temp, sql_display_size + extra, &field_len_temp, false /*handle_warning*/);
}
CHECK_CUSTOM_ERROR((r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index) {
throw core::CoreException();
}
if (field_len_temp == SQL_NULL_DATA) {
field_value = NULL;
sqlsrv_free(field_value_temp);
return;
}
if (r == SQL_SUCCESS_WITH_INFO) {
SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { L'\0' };
SQLSMALLINT len = 0;
stmt->current_results->get_diag_field(1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len);
if (is_truncated_warning(state)) {
SQLLEN chunk_field_len = 0;
// for XML (and possibly other conditions) the field length returned is not the real field length, so
// in every pass, we double the allocation size to retrieve all the contents.
if (field_len_temp == SQL_NO_TOTAL) {
// reset the field_len_temp
field_len_temp = initial_field_len;
do {
SQLLEN buffer_len = field_len_temp;
// Double the size.
field_len_temp *= 2;
field_value_temp = static_cast<char*>(sqlsrv_realloc(field_value_temp, field_len_temp + extra + 1));
field_len_temp -= buffer_len;
// Get the rest of the data
r = stmt->current_results->get_data(field_index + 1, c_type, field_value_temp + buffer_len,
field_len_temp + extra, &chunk_field_len, false /*handle_warning*/);
// the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL
// so we calculate the actual length of the string with that.
if (chunk_field_len != SQL_NO_TOTAL)
field_len_temp += chunk_field_len;
else
field_len_temp += buffer_len;
if (r == SQL_SUCCESS_WITH_INFO) {
core::SQLGetDiagField(stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len);
}
} while (r == SQL_SUCCESS_WITH_INFO && is_truncated_warning(state));
} // if (field_len_temp == SQL_NO_TOTAL)
else {
// The field length (or its estimate) is returned, thus no need to double the allocation size.
// Allocate field_len_temp (which is the field length retrieved from the first SQLGetData) but with some padding
// because there is a chance that the estimated field_len_temp is not accurate enough
SQLLEN buffer_len = 50;
field_value_temp = static_cast<char*>(sqlsrv_realloc(field_value_temp, field_len_temp + buffer_len + 1));
field_len_temp -= initial_field_len;
// Get the rest of the data
r = stmt->current_results->get_data(field_index + 1, c_type, field_value_temp + initial_field_len,
field_len_temp + buffer_len, &chunk_field_len, false /*handle_warning*/);
field_len_temp = initial_field_len + chunk_field_len;
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
throw core::CoreException();
}
// Reallocate field_value_temp next
field_value_temp = static_cast<char*>(sqlsrv_realloc(field_value_temp, field_len_temp + extra + 1));
}
} // if (is_truncated_warning(state))
} // if (r == SQL_SUCCESS_WITH_INFO)
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
throw core::CoreException();
}
if (c_type == SQL_C_WCHAR) {
bool converted = convert_string_from_utf16_inplace(static_cast<SQLSRV_ENCODING>(sqlsrv_php_type.typeinfo.encoding),
&field_value_temp, field_len_temp);
CHECK_CUSTOM_ERROR(!converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
}
if (stmt->format_decimals && (sql_field_type == SQL_DECIMAL || sql_field_type == SQL_NUMERIC)) {
// number of decimal places only affect money / smallmoney fields
SQLSMALLINT decimal_places = (stmt->current_meta_data[field_index]->field_is_money_type) ? stmt->decimal_places : NO_CHANGE_DECIMAL_PLACES;
format_decimal_numbers(decimal_places, stmt->current_meta_data[field_index]->field_scale, field_value_temp, &field_len_temp);
}
// finalized the returned values and set field_len to 0 if field_len_temp is negative (which may happen with unixODBC connection pooling)
field_value = field_value_temp;
*field_len = (field_len_temp > 0) ? field_len_temp : 0;
// prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP
// runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode.
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and add 1 to fill the null terminator
if (field_len_temp > 0) {
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, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] )
{
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( _In_ 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( _In_ sqlsrv_phptype type )
{
switch( type.typeinfo.type ) {
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_TABLE:
return true;
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM:
{
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;
}
void adjustDecimalPrecision(_Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits)
{
char* value = Z_STRVAL_P(param_z);
int value_len = Z_STRLEN_P(param_z);
// If the length is greater than maxDecimalStrLen, do not convert the string
// 6 is derived from: 1 for the decimal point; 1 for sign of the number; 1 for 'e' or 'E' (scientific notation);
// 1 for sign of scientific exponent; 2 for length of scientific exponent
const int MAX_DECIMAL_STRLEN = SQL_SERVER_DECIMAL_MAXIMUM_PRECISION + 6;
if (value_len > MAX_DECIMAL_STRLEN) {
return;
}
// If std::stold() succeeds, 'index' is the position of the first character after the numerical value
long double d = 0;
size_t index;
try {
d = std::stold(std::string(value), &index);
}
catch (const std::logic_error& ) {
return; // invalid input caused the conversion to throw an exception
}
if (index < value_len) {
return; // the input contains something else apart from the numerical value
}
// Navigate to the first digit or the decimal point
short is_negative = (d < 0) ? 1 : 0;
char *src = value + is_negative;
while (*src != DECIMAL_POINT && !isdigit(static_cast<unsigned int>(*src))) {
src++;
}
// Check if the value is in scientific notation
char *exp = strchr(src, 'E');
if (exp == NULL) {
exp = strchr(src, 'e');
}
// Find the decimal point
char *pt = strchr(src, DECIMAL_POINT);
char buffer[50] = " "; // A buffer with 2 blank spaces, as leeway
int offset = 1 + is_negative; // The position to start copying the original numerical value
if (exp == NULL) {
if (pt == NULL) {
return; // decimal point not found
}
int src_length = strnlen_s(src);
int num_decimals = src_length - (pt - src) - 1;
if (num_decimals <= decimal_digits) {
return; // no need to adjust number of decimals
}
memcpy_s(buffer + offset, src_length, src, src_length);
round_up_decimal_numbers(buffer, (pt - src) + offset, decimal_digits, offset, src_length + offset);
}
else {
int power = atoi(exp+1);
if (abs(power) > SQL_SERVER_DECIMAL_MAXIMUM_PRECISION) {
return; // Out of range, so let the server handle this
}
int num_decimals = 0;
if (power == 0) {
// Simply chop off the exp part
int length = (exp - src);
memcpy_s(buffer + offset, length, src, length);
if (pt != NULL) {
// Adjust decimal places only if decimal point is found and number of decimals more than decimal_digits
num_decimals = exp - pt - 1;
if (num_decimals > decimal_digits) {
round_up_decimal_numbers(buffer, (pt - src) + offset, decimal_digits, offset, length + offset);
}
}
} else {
int oldpos = 0;
if (pt == NULL) {
oldpos = exp - src; // Decimal point not found, use the exp sign
}
else {
oldpos = pt - src;
num_decimals = exp - pt - 1;
if (power > 0 && num_decimals <= power) {
return; // The result will be a whole number, do nothing and return
}
}
// Derive the new position for the decimal point in the buffer
int newpos = oldpos + power;
if (power > 0) {
newpos = newpos + offset;
if (num_decimals == 0) {
memset(buffer + offset + oldpos, '0', power); // Fill parts of the buffer with zeroes first
}
else {
buffer[newpos] = DECIMAL_POINT;
}
}
else {
// The negative "power" part shows exactly how many places to move the decimal point.
// Whether to pad zeroes depending on the original position of the decimal point pos.
if (newpos <= 0) {
// If newpos is negative or zero, pad zeroes (size of '0.' + places to move) in the buffer
short numzeroes = 2 + abs(newpos);
memset(buffer + offset, '0', numzeroes);
newpos = offset + 1; // The new decimal position should be offset + '0'
buffer[newpos] = DECIMAL_POINT; // Replace that '0' with the decimal point
offset = numzeroes + offset; // Short offset now in the buffer
}
else {
newpos = newpos + offset;
buffer[newpos] = DECIMAL_POINT;
}
}
// Start copying the content to the buffer until the exp sign or one more digit after decimal_digits
char *p = src;
int idx = offset;
int lastpos = newpos + decimal_digits + 1;
while (p != exp && idx <= lastpos) {
if (*p == DECIMAL_POINT) {
p++;
continue;
}
if (buffer[idx] == DECIMAL_POINT) {
idx++;
}
buffer[idx++] = *p;
p++;
}
// Round up is required only when number of decimals is more than decimal_digits
num_decimals = idx - newpos - 1;
if (num_decimals > decimal_digits) {
round_up_decimal_numbers(buffer, newpos, decimal_digits, offset, idx);
}
}
}
// Set the minus sign if negative
if (is_negative) {
buffer[0] = '-';
}
zend_string* zstr = zend_string_init(buffer, strnlen_s(buffer), 0);
zend_string_release(Z_STR_P(param_z));
ZVAL_NEW_STR(param_z, zstr);
}
int round_up_decimal_numbers(_Inout_ char* buffer, _In_ int decimal_pos, _In_ int num_decimals, _In_ int offset, _In_ int lastpos)
{
// This helper method assumes the 'buffer' has some extra blank spaces at the beginning without the minus '-' sign.
// We want the rounding to be consistent with php number_format(), http://php.net/manual/en/function.number-format.php
// as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is
// followed by 5 or above.
int pos = decimal_pos + num_decimals + 1;
if (pos < lastpos) {
short n = buffer[pos] - '0';
if (n >= 5) {
// Start rounding up - starting from the digit left of pos all the way to the first digit
bool carry_over = true;
for (short p = pos - 1; p >= offset && carry_over; p--) {
if (buffer[p] == DECIMAL_POINT) {
continue;
}
n = buffer[p] - '0';
carry_over = (++n == 10);
if (n == 10) {
n = 0;
}
buffer[p] = '0' + n;
}
if (carry_over) {
buffer[offset - 1] = '1';
}
}
if (num_decimals == 0) {
buffer[decimal_pos] = '\0';
return decimal_pos;
}
else {
buffer[pos] = '\0';
return pos;
}
}
// Do nothing and just return
return lastpos;
}
} // end of anonymous namespace
////////////////////////////////////////////////////////////////////////////////////////////////
//
// *** implementations of structures used for SQLBindParameter ***
//
void sqlsrv_param::release_data()
{
if (Z_TYPE(placeholder_z) == IS_STRING) {
zend_string_release(Z_STR(placeholder_z));
}
ZVAL_UNDEF(&placeholder_z);
buffer = NULL;
param_stream = NULL;
num_bytes_read = 0;
param_ptr_z = NULL;
}
void sqlsrv_param::copy_param_meta_ae(_Inout_ zval* param_z, _In_ param_meta_data& meta)
{
// Always Encrypted (AE) enabled - copy the meta data from SQLDescribeParam()
sql_data_type = meta.sql_type;
column_size = meta.column_size;
decimal_digits = meta.decimal_digits;
// Due to strict rules of AE, convert long to double if the sql type is decimal (numeric)
if (Z_TYPE_P(param_z) == IS_LONG && (sql_data_type == SQL_DECIMAL || sql_data_type == SQL_NUMERIC)) {
convert_to_double(param_z);
}
}
bool sqlsrv_param::prepare_param(_In_ zval* param_ref, _Inout_ zval* param_z)
{
// For input parameters, check if the original parameter was null
was_null = (Z_TYPE_P(param_z) == IS_NULL);
return true;
}
// Derives the ODBC C type constant that matches the PHP type and/or the encoding given
// If SQL type or column size is unknown, derives the appropriate values as well using the provided param zval and encoding
void sqlsrv_param::process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
// Get param php type
param_php_type = Z_TYPE_P(param_z);
switch (param_php_type) {
case IS_NULL:
process_null_param(param_z);
break;
case IS_TRUE:
case IS_FALSE:
process_bool_param(param_z);
break;
case IS_LONG:
process_long_param(param_z);
break;
case IS_DOUBLE:
process_double_param(param_z);
break;
case IS_STRING:
process_string_param(stmt, param_z);
break;
case IS_RESOURCE:
process_resource_param(param_z);
break;
case IS_OBJECT:
process_object_param(stmt, param_z);
break;
case IS_ARRAY:
default:
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_pos + 1);
break;
}
}
void sqlsrv_param::process_null_param(_Inout_ zval* param_z)
{
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
// Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases,
// if the server type is a binary type, then 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_varchar works fine.
// It must be varchar with column size 0 for ISNULL to work properly.
sql_data_type = (encoding == SQLSRV_ENCODING_BINARY) ? SQL_BINARY : SQL_VARCHAR;
}
c_data_type = (encoding == SQLSRV_ENCODING_BINARY) ? SQL_C_BINARY : SQL_C_CHAR;
if (column_size == SQLSRV_UNKNOWN_SIZE) {
column_size = (encoding == SQLSRV_ENCODING_BINARY) ? 1 : 0;
decimal_digits = 0;
}
buffer = NULL;
buffer_length = 0;
strlen_or_indptr = SQL_NULL_DATA;
}
void sqlsrv_param::process_bool_param(_Inout_ zval* param_z)
{
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
sql_data_type = SQL_INTEGER;
}
c_data_type = SQL_C_SLONG;
// The column size and decimal digits are by default 0
// Ignore column_size and decimal_digits because they will be inferred by ODBC
// Convert the lval to 0 or 1
convert_to_long(param_z);
buffer = &param_z->value;
buffer_length = sizeof(Z_LVAL_P(param_z));
strlen_or_indptr = buffer_length;
}
void sqlsrv_param::process_long_param(_Inout_ zval* param_z)
{
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
//ODBC 64-bit long and integer type are 4 byte values.
if ((Z_LVAL_P(param_z) < INT_MIN) || (Z_LVAL_P(param_z) > INT_MAX)) {
sql_data_type = SQL_BIGINT;
} else {
sql_data_type = SQL_INTEGER;
}
}
// When binding any integer, the zend_long value and its length are used as the buffer
// and buffer length. When the buffer is 8 bytes use the corresponding C type for
// 8-byte integers
#ifdef ZEND_ENABLE_ZVAL_LONG64
c_data_type = SQL_C_SBIGINT;
#else
c_data_type = SQL_C_SLONG;
#endif
// The column size and decimal digits are by default 0
// Ignore column_size and decimal_digits because they will be inferred by ODBC
buffer = &param_z->value;
buffer_length = sizeof(Z_LVAL_P(param_z));
strlen_or_indptr = buffer_length;
}
void sqlsrv_param::process_double_param(_Inout_ zval* param_z)
{
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
sql_data_type = SQL_FLOAT;
}
// The column size and decimal digits are by default 0
// Ignore column_size and decimal_digits because they will be inferred by ODBC
c_data_type = SQL_C_DOUBLE;
buffer = &param_z->value;
buffer_length = sizeof(Z_DVAL_P(param_z));
strlen_or_indptr = buffer_length;
}
bool sqlsrv_param::derive_string_types_sizes(_In_ zval* param_z)
{
SQLSRV_ASSERT(encoding == SQLSRV_ENCODING_CHAR || encoding == SQLSRV_ENCODING_UTF8 || encoding == SQLSRV_ENCODING_BINARY, "Invalid encoding in sqlsrv_param::derive_string_types_sizes");
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
switch (encoding) {
case SQLSRV_ENCODING_CHAR:
sql_data_type = SQL_VARCHAR;
break;
case SQLSRV_ENCODING_BINARY:
sql_data_type = SQL_VARBINARY;
break;
case SQLSRV_ENCODING_UTF8:
sql_data_type = SQL_WVARCHAR;
break;
default:
break;
}
}
bool is_numeric = is_a_numeric_type(sql_data_type);
// Derive the C Data type next
switch (encoding) {
case SQLSRV_ENCODING_CHAR:
c_data_type = SQL_C_CHAR;
break;
case SQLSRV_ENCODING_BINARY:
c_data_type = SQL_C_BINARY;
break;
case SQLSRV_ENCODING_UTF8:
c_data_type = is_numeric ? SQL_C_CHAR : SQL_C_WCHAR;
break;
default:
break;
}
// Derive the column size also only if it is unknown
if (column_size == SQLSRV_UNKNOWN_SIZE) {
size_t char_size = (encoding == SQLSRV_ENCODING_UTF8) ? sizeof(SQLWCHAR) : sizeof(char);
SQLULEN byte_len = Z_STRLEN_P(param_z) * char_size;
if (byte_len > SQL_SERVER_MAX_FIELD_SIZE) {
column_size = SQL_SERVER_MAX_TYPE_SIZE;
} else {
column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size;
}
}
return is_numeric;
}
bool sqlsrv_param::convert_input_str_to_utf16(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
{
// This converts the string in param_z and stores the wide string in the member placeholder_z
char* str = Z_STRVAL_P(param_z);
SQLLEN str_length = Z_STRLEN_P(param_z);
if (str_length > 0) {
sqlsrv_malloc_auto_ptr<SQLWCHAR> wide_buffer;
unsigned int wchar_size = 0;
wide_buffer = utf16_string_from_mbcs_string(encoding, reinterpret_cast<const char*>(str), static_cast<int>(str_length), &wchar_size, true);
if (wide_buffer == 0) {
return false;
}
wide_buffer[wchar_size] = L'\0';
core::sqlsrv_zval_stringl(&placeholder_z, reinterpret_cast<char*>(wide_buffer.get()), wchar_size * sizeof(SQLWCHAR));
} else {
// If the string is empty, then nothing needs to be done
core::sqlsrv_zval_stringl(&placeholder_z, "", 0);
}
return true;
}
void sqlsrv_param::process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
bool is_numeric = derive_string_types_sizes(param_z);
// With AE, the precision of the decimal or numeric inputs have to match exactly as defined in the columns.
// Without AE, the derived default sql types will not be this specific. Thus, if sql_type is SQL_DECIMAL
// or SQL_NUMERIC, the user must have clearly specified it (using the SQLSRV driver) as SQL_DECIMAL or SQL_NUMERIC.
// In either case, the input passed into SQLBindParam requires matching scale (i.e., number of decimal digits).
if (sql_data_type == SQL_DECIMAL || sql_data_type == SQL_NUMERIC) {
adjustDecimalPrecision(param_z, decimal_digits);
}
if (!is_numeric && encoding == CP_UTF8) {
// Convert the input param value to wide string and save it for later
if (Z_STRLEN_P(param_z) > INT_MAX) {
LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded.");
throw core::CoreException();
}
// This changes the member placeholder_z to hold the wide string
bool converted = convert_input_str_to_utf16(stmt, param_z);
CHECK_CUSTOM_ERROR(!converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, param_pos + 1, get_last_error_message()) {
throw core::CoreException();
}
// Bind the wide string in placeholder_z
buffer = Z_STRVAL(placeholder_z);
buffer_length = Z_STRLEN(placeholder_z);
} else {
buffer = Z_STRVAL_P(param_z);
buffer_length = Z_STRLEN_P(param_z);
}
strlen_or_indptr = buffer_length;
}
void sqlsrv_param::process_resource_param(_Inout_ zval* param_z)
{
SQLSRV_ASSERT(encoding == SQLSRV_ENCODING_CHAR || encoding == SQLSRV_ENCODING_UTF8 || encoding == SQLSRV_ENCODING_BINARY, "Invalid encoding in sqlsrv_param::get_resource_param_info");
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
switch (encoding) {
case SQLSRV_ENCODING_CHAR:
sql_data_type = SQL_VARCHAR;
break;
case SQLSRV_ENCODING_BINARY:
sql_data_type = SQL_VARBINARY;
break;
case SQLSRV_ENCODING_UTF8:
sql_data_type = SQL_WVARCHAR;
break;
default:
break;
}
}
// The column_size will be inferred by ODBC unless it is SQLSRV_UNKNOWN_SIZE
if (column_size == SQLSRV_UNKNOWN_SIZE) {
column_size = 0;
}
switch (encoding) {
case SQLSRV_ENCODING_CHAR:
c_data_type = SQL_C_CHAR;
break;
case SQLSRV_ENCODING_BINARY:
c_data_type = SQL_C_BINARY;
break;
case SQLSRV_ENCODING_UTF8:
c_data_type = SQL_C_WCHAR;
break;
default:
break;
}
param_ptr_z = param_z;
buffer = reinterpret_cast<SQLPOINTER>(this);
buffer_length = 0;
strlen_or_indptr = SQL_DATA_AT_EXEC;
}
bool sqlsrv_param::convert_datetime_to_string(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
{
// This changes the member placeholder_z to hold the converted string of the datetime object
zval function_z;
zval format_z;
zval params[1];
ZVAL_UNDEF(&function_z);
ZVAL_UNDEF(&format_z);
ZVAL_UNDEF(params);
// If the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error'
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
// sql type, it lacks the timezone.
if (sql_data_type == SQL_SS_TIMESTAMPOFFSET) {
ZVAL_STRINGL(&format_z, DateTime::DATETIMEOFFSET_FORMAT, DateTime::DATETIMEOFFSET_FORMAT_LEN);
} else if (sql_data_type == SQL_TYPE_DATE) {
ZVAL_STRINGL(&format_z, DateTime::DATE_FORMAT, DateTime::DATE_FORMAT_LEN);
} else {
ZVAL_STRINGL(&format_z, DateTime::DATETIME_FORMAT, DateTime::DATETIME_FORMAT_LEN);
}
// call the DateTime::format member function to convert the object to a string that SQL Server understands
ZVAL_STRINGL(&function_z, "format", sizeof("format") - 1);
//core::sqlsrv_zval_stringl(&function_z, "format", sizeof("format") - 1);
params[0] = format_z;
// If placeholder_z is a string, release it first before assigning a new string value
if (Z_TYPE(placeholder_z) == IS_STRING && Z_STR(placeholder_z) != NULL) {
zend_string_release(Z_STR(placeholder_z));
}
// This is equivalent to the PHP code: $param_z->format($format_z); where param_z is the
// DateTime object and $format_z is the format string.
int zr = call_user_function(EG(function_table), param_z, &function_z, &placeholder_z, 1, params);
zend_string_release(Z_STR(format_z));
zend_string_release(Z_STR(function_z));
return (zr != FAILURE);
}
bool sqlsrv_param::preprocess_datetime_object(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
{
bool valid_class_name_found = false;
zend_class_entry *class_entry = Z_OBJCE_P(param_z);
while (class_entry != NULL) {
SQLSRV_ASSERT(class_entry->name != NULL, "sqlsrv_param::get_object_param_info -- class_entry->name is NULL.");
if (class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL &&
stricmp(class_entry->name->val, DateTime::DATETIME_CLASS_NAME) == 0) {
valid_class_name_found = true;
break;
} else {
// Check the parent
class_entry = class_entry->parent;
}
}
if (!valid_class_name_found) {
return false;
}
// Derive the param SQL type only if it is unknown
if (sql_data_type == SQL_UNKNOWN_TYPE) {
// For SQL Server 2005 or earlier, make it a SQLSRV_SQLTYPE_DATETIME.
// Otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET because these
// are the date types of the highest precision for the server
if (stmt->conn->server_version <= SERVER_VERSION_2005) {
sql_data_type = SQL_TYPE_TIMESTAMP;
} else {
sql_data_type = SQL_SS_TIMESTAMPOFFSET;
}
}
c_data_type = SQL_C_CHAR;
// Derive the column size also only if it is unknown
if (column_size == SQLSRV_UNKNOWN_SIZE) {
if (stmt->conn->server_version <= SERVER_VERSION_2005) {
column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION;
decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE;
} else {
column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION;
decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE;
}
}
return true;
}
void sqlsrv_param::process_object_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
// Assume the param refers to a DateTime object since it's the only type the drivers support.
// Verification occurs in the calling function as the drivers convert the DateTime object
// to a string before sending it to the server.
bool succeeded = preprocess_datetime_object(stmt, param_z);
if (succeeded) {
succeeded = convert_datetime_to_string(stmt, param_z);
}
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_pos + 1) {
throw core::CoreException();
}
buffer = Z_STRVAL(placeholder_z);
buffer_length = Z_STRLEN(placeholder_z) - 1;
strlen_or_indptr = buffer_length;
}
void sqlsrv_param::bind_param(_Inout_ sqlsrv_stmt* stmt)
{
if (was_null) {
strlen_or_indptr = SQL_NULL_DATA;
}
core::SQLBindParameter(stmt, param_pos + 1, direction, c_data_type, sql_data_type, column_size, decimal_digits, buffer, buffer_length, &strlen_or_indptr);
}
void sqlsrv_param::init_data_from_zval(_Inout_ sqlsrv_stmt* stmt)
{
// Get the stream from the param zval value
num_bytes_read = 0;
param_stream = NULL;
core::sqlsrv_php_stream_from_zval_no_verify(*stmt, param_stream, param_ptr_z);
}
bool sqlsrv_param::send_data_packet(_Inout_ sqlsrv_stmt* stmt)
{
// Check EOF first
if (php_stream_eof(param_stream)) {
// But return to the very beginning of param_stream since SQLParamData() may ask for the same data again
int ret = php_stream_seek(param_stream, 0, SEEK_SET);
if (ret != 0) {
LOG(SEV_ERROR, "PHP stream: stream seek failed.");
throw core::CoreException();
}
// Reset num_bytes_read
num_bytes_read = 0;
return false;
} else {
// Read the data from the stream, send it via SQLPutData and track how much is already sent.
char buffer[PHP_STREAM_BUFFER_SIZE + 1] = { '\0' };
std::size_t buffer_size = sizeof(buffer) - 3; // -3 to preserve enough space for a cut off UTF-8 character
std::size_t read = php_stream_read(param_stream, buffer, buffer_size);
if (read > UINT_MAX) {
LOG(SEV_ERROR, "PHP stream: buffer length exceeded.");
throw core::CoreException();
}
num_bytes_read += read;
if (read == 0) {
// Send an empty string, which is what a 0 length does.
char buff[1]; // Temp storage to hand to SQLPutData
core::SQLPutData(stmt, buff, 0);
} else if (read > 0) {
// If this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character
// then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it
// twice.
// If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion
// since all other MBCS supported by SQL Server are 2 byte maximum size.
if (encoding == CP_UTF8) {
// The size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is an
// expansion of 2x the UTF-8 size.
SQLWCHAR wbuffer[PHP_STREAM_BUFFER_SIZE + 1] = { L'\0' };
int wbuffer_size = static_cast<int>(sizeof(wbuffer) / sizeof(SQLWCHAR));
DWORD last_error_code = ERROR_SUCCESS;
// The buffer_size is the # of wchars. Set to buffer_size / 2
#ifndef _WIN32
int wsize = SystemLocale::ToUtf16Strict(encoding, buffer, static_cast<int>(read), wbuffer, wbuffer_size, &last_error_code);
#else
int wsize = MultiByteToWideChar(encoding, MB_ERR_INVALID_CHARS, buffer, static_cast<int>(read), wbuffer, wbuffer_size);
last_error_code = GetLastError();
#endif // !_WIN32
if (wsize == 0 && last_error_code == ERROR_NO_UNICODE_TRANSLATION) {
// This will calculate how many bytes were cut off from the last UTF-8 character and read that many more
// in, then reattempt the conversion. If it fails the second time, then an error is returned.
size_t need_to_read = calc_utf8_missing(stmt, buffer, read);
// read the missing bytes
size_t new_read = php_stream_read(param_stream, static_cast<char*>(buffer) + read, need_to_read);
// if the bytes couldn't be read, then we return an error
CHECK_CUSTOM_ERROR(new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, get_last_error_message(ERROR_NO_UNICODE_TRANSLATION)) {
throw core::CoreException();
}
// Try the conversion again with the complete character
#ifndef _WIN32
wsize = SystemLocale::ToUtf16Strict(encoding, buffer, static_cast<int>(read + new_read), wbuffer, static_cast<int>(sizeof(wbuffer) / sizeof(SQLWCHAR)));
#else
wsize = MultiByteToWideChar(encoding, MB_ERR_INVALID_CHARS, buffer, static_cast<int>(read + new_read), wbuffer, static_cast<int>(sizeof(wbuffer) / sizeof(wchar_t)));
#endif //!_WIN32
// something else must be wrong if it failed
CHECK_CUSTOM_ERROR(wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, get_last_error_message(ERROR_NO_UNICODE_TRANSLATION)) {
throw core::CoreException();
}
}
core::SQLPutData(stmt, wbuffer, wsize * sizeof(SQLWCHAR));
}
else {
core::SQLPutData(stmt, buffer, read);
} // NOT UTF8
} // read > 0
return true;
} // NOT EOF
}
bool sqlsrv_param_inout::prepare_param(_In_ zval* param_ref, _Inout_ zval* param_z)
{
// Save the output param reference now
param_ptr_z = param_ref;
int type = Z_TYPE_P(param_z);
was_null = (type == IS_NULL);
was_bool = (type == IS_TRUE || type == IS_FALSE);
if (direction == SQL_PARAM_INPUT_OUTPUT) {
// If the user asks for for a specific type for input and output, make sure the data type we send matches the data we
// type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so
// we always let that match if they want a string back.
bool matched = false;
switch (php_out_type) {
case SQLSRV_PHPTYPE_INT:
if (was_null || was_bool) {
convert_to_long(param_z);
}
matched = (Z_TYPE_P(param_z) == IS_LONG);
break;
case SQLSRV_PHPTYPE_FLOAT:
if (was_null) {
convert_to_double(param_z);
}
matched = (Z_TYPE_P(param_z) == IS_DOUBLE);
break;
case SQLSRV_PHPTYPE_STRING:
// anything can be converted to a string
convert_to_string(param_z);
matched = true;
break;
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_STREAM:
default:
SQLSRV_ASSERT(false, "sqlsrv_param_inout::prepare_param -- invalid type for an output parameter.");
break;
}
return matched;
} else if (direction == SQL_PARAM_OUTPUT) {
// If the user specifies a certain type for an output parameter, we have to convert the zval
// to that type so that when the buffer is filled, the type is correct. But first,
// should check if a LOB type is specified.
switch (php_out_type) {
case SQLSRV_PHPTYPE_INT:
convert_to_long(param_z);
break;
case SQLSRV_PHPTYPE_FLOAT:
convert_to_double(param_z);
break;
case SQLSRV_PHPTYPE_STRING:
convert_to_string(param_z);
break;
case SQLSRV_PHPTYPE_NULL:
case SQLSRV_PHPTYPE_DATETIME:
case SQLSRV_PHPTYPE_STREAM:
default:
SQLSRV_ASSERT(false, "sqlsrv_param_inout::prepare_param -- invalid type for an output parameter");
break;
}
return true;
} else {
SQLSRV_ASSERT(false, "sqlsrv_param_inout::prepare_param -- wrong param direction.");
}
return false;
}
// Derives the ODBC C type constant that matches the PHP type and/or the encoding given
// If SQL type or column size is unknown, derives the appropriate values as well using the provided param zval and encoding
void sqlsrv_param_inout::process_param(_Inout_ sqlsrv_stmt* stmt, zval* param_z)
{
// Get param php type NOW because the original parameter might have been converted beforehand
param_php_type = Z_TYPE_P(param_z);
switch (param_php_type) {
case IS_LONG:
process_long_param(param_z);
break;
case IS_DOUBLE:
process_double_param(param_z);
break;
case IS_STRING:
process_string_param(stmt, param_z);
break;
default:
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_pos + 1);
break;
}
// Save the pointer to the statement object
this->stmt = stmt;
}
void sqlsrv_param_inout::process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
bool is_numeric_type = derive_string_types_sizes(param_z);
buffer = Z_STRVAL_P(param_z);
buffer_length = Z_STRLEN_P(param_z);
if (ZSTR_IS_INTERNED(Z_STR_P(param_z))) {
// PHP 5.4 added interned strings, and since we obviously want to change that string here in some fashion,
// we reallocate the string if it's interned
core::sqlsrv_zval_stringl(param_z, static_cast<const char*>(buffer), buffer_length);
// reset buffer and its length
buffer = Z_STRVAL_P(param_z);
buffer_length = Z_STRLEN_P(param_z);
}
// If it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR)
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXT SQL type,
// convert it to wchar first
if (direction == SQL_PARAM_INPUT_OUTPUT &&
(c_data_type == SQL_C_WCHAR ||
(c_data_type == SQL_C_BINARY &&
(sql_data_type == SQL_WCHAR || sql_data_type == SQL_WVARCHAR || sql_data_type == SQL_WLONGVARCHAR)))) {
if (buffer_length > 0) {
sqlsrv_malloc_auto_ptr<SQLWCHAR> wide_buffer;
unsigned int wchar_size = 0;
wide_buffer = utf16_string_from_mbcs_string(SQLSRV_ENCODING_UTF8, reinterpret_cast<const char*>(buffer), static_cast<int>(buffer_length), &wchar_size);
CHECK_CUSTOM_ERROR(wide_buffer == 0, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, param_pos + 1, get_last_error_message()) {
throw core::CoreException();
}
wide_buffer[wchar_size] = L'\0';
core::sqlsrv_zval_stringl(param_z, reinterpret_cast<char*>(wide_buffer.get()), wchar_size * sizeof(SQLWCHAR));
buffer = Z_STRVAL_P(param_z);
buffer_length = Z_STRLEN_P(param_z);
}
}
strlen_or_indptr = buffer_length;
// Since this is an output string, assure there is enough space to hold the requested size and
// update all the variables accordingly (param_z, buffer, buffer_length, and strlen_or_indptr)
resize_output_string_buffer(param_z, is_numeric_type);
if (is_numeric_type) {
encoding = SQLSRV_ENCODING_CHAR;
}
// For output parameters, if we set the column_size to be same as the buffer_len,
// then if there is a truncation due to the data coming from the server being
// greater than the column_size, we don't get any truncation error. In order to
// avoid this silent truncation, we set the column_size to be "MAX" size for
// string types. This will guarantee that there is no silent truncation for
// output parameters.
// if column encryption is enabled, at this point the correct column size has been set by SQLDescribeParam
if (direction == SQL_PARAM_OUTPUT && !stmt->conn->ce_option.enabled) {
switch (sql_data_type) {
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_WVARCHAR:
column_size = SQL_SS_LENGTH_UNLIMITED;
break;
default:
break;
}
}
}
// Called when the output parameter is ready to be finalized, using the value stored in param_ptr_z
void sqlsrv_param_inout::finalize_output_value()
{
if (param_ptr_z == NULL) {
return;
}
zval* value_z = Z_REFVAL_P(param_ptr_z);
switch (Z_TYPE_P(value_z)) {
case IS_STRING:
finalize_output_string();
break;
case IS_LONG:
// For a long or a float, simply check if NULL was returned and if so, set the parameter to a PHP null
if (strlen_or_indptr == SQL_NULL_DATA) {
ZVAL_NULL(value_z);
} else if (was_bool) {
convert_to_boolean(value_z);
} else {
ZVAL_LONG(value_z, static_cast<int>(Z_LVAL_P(value_z)));
}
break;
case IS_DOUBLE:
// For a long or a float, simply check if NULL was returned and if so, set the parameter to a PHP null
if (strlen_or_indptr == SQL_NULL_DATA) {
ZVAL_NULL(value_z);
} else if (php_out_type == SQLSRV_PHPTYPE_INT) {
// First check if its value is out of range
double dval = Z_DVAL_P(value_z);
if (dval > INT_MAX || dval < INT_MIN) {
CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED) {
throw core::CoreException();
}
}
// Even if the output param is a boolean, still convert to a long
// integer first to take care of rounding
convert_to_long(value_z);
if (was_bool) {
convert_to_boolean(value_z);
}
}
break;
default:
SQLSRV_ASSERT(false, "Should not have reached here - invalid output parameter type in sqlsrv_param_inout::finalize_output_value.");
break;
}
value_z = NULL;
param_ptr_z = NULL; // Do not keep the reference now that the output param has been processed
}
// A helper method called by finalize_output_value() to finalize output string parameters
void sqlsrv_param_inout::finalize_output_string()
{
zval* value_z = Z_REFVAL_P(param_ptr_z);
// Adjust the length of the string to the value returned by SQLBindParameter in the strlen_or_indptr argument
if (strlen_or_indptr == 0) {
core::sqlsrv_zval_stringl(value_z, "", 0);
return;
}
if (strlen_or_indptr == SQL_NULL_DATA) {
zend_string_release(Z_STR_P(value_z));
ZVAL_NULL(value_z);
return;
}
// If there was more to output than buffer size to hold it, then throw a truncation error
SQLLEN str_len = strlen_or_indptr;
char* str = Z_STRVAL_P(value_z);
int null_size = 0;
switch (encoding) {
case SQLSRV_ENCODING_UTF8:
null_size = sizeof(SQLWCHAR); // The string isn't yet converted to UTF-8, still UTF-16
break;
case SQLSRV_ENCODING_SYSTEM:
null_size = sizeof(SQLCHAR);
break;
case SQLSRV_ENCODING_BINARY:
null_size = 0;
break;
default:
SQLSRV_ASSERT(false, "Should not have reached here - invalid encoding in sqlsrv_param_inout::process_output_string.");
break;
}
CHECK_CUSTOM_ERROR(str_len > (buffer_length - null_size), stmt, SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, param_pos + 1) {
throw core::CoreException();
}
// For ODBC 11+ see https://docs.microsoft.com/sql/relational-databases/native-client/features/odbc-driver-behavior-change-when-handling-character-conversions
// A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains data up to the
// original buffer_length and is NULL terminated.
// The IF statement can be true when using connection pooling with unixODBC 2.3.4.
if (str_len == SQL_NO_TOTAL) {
str_len = buffer_length - null_size;
}
if (encoding == SQLSRV_ENCODING_BINARY) {
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
// so we do that here if the length of the returned data is less than the original allocation. The
// original allocation null terminates the buffer already.
if (str_len < buffer_length) {
str[str_len] = '\0';
}
core::sqlsrv_zval_stringl(value_z, str, str_len);
}
else {
if (encoding != SQLSRV_ENCODING_CHAR) {
char* outString = NULL;
SQLLEN outLen = 0;
bool result = convert_string_from_utf16(encoding, reinterpret_cast<const SQLWCHAR*>(str), int(str_len / sizeof(SQLWCHAR)), &outString, outLen);
CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
if (stmt->format_decimals && (sql_data_type == SQL_DECIMAL || sql_data_type == SQL_NUMERIC)) {
format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, decimal_digits, outString, &outLen);
}
core::sqlsrv_zval_stringl(value_z, outString, outLen);
sqlsrv_free(outString);
}
else {
if (stmt->format_decimals && (sql_data_type == SQL_DECIMAL || sql_data_type == SQL_NUMERIC)) {
format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, decimal_digits, str, &str_len);
}
core::sqlsrv_zval_stringl(value_z, str, str_len);
}
}
value_z = NULL;
}
void sqlsrv_param_inout::resize_output_string_buffer(_Inout_ zval* param_z, _In_ bool is_numeric_type)
{
// Prerequisites: buffer, buffer_length, column_size, and strlen_or_indptr have been set to a known value
// Purpose:
// Verify there is enough space to hold the output string parameter, and allocate if necessary. The param_z
// is updated to contain the new buffer with the correct size and its reference is incremented, and all required
// values for SQLBindParameter will also be updated.
SQLLEN original_len = buffer_length;
SQLLEN expected_len;
SQLLEN buffer_null_extra;
SQLLEN elem_size;
// Calculate the size of each 'element' represented by column_size. WCHAR is the size of a wide char (2), and so is
// a N(VAR)CHAR/NTEXT field being returned as a binary field.
elem_size = (c_data_type == SQL_C_WCHAR ||
(c_data_type == SQL_C_BINARY &&
(sql_data_type == SQL_WCHAR || sql_data_type == SQL_WVARCHAR || sql_data_type == SQL_WLONGVARCHAR))) ? sizeof(SQLWCHAR) : sizeof(SQLCHAR);
// account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning
SQLULEN field_size = column_size;
// With AE enabled, column_size is already retrieved from SQLDescribeParam, but column_size
// does not include the negative sign or decimal place for numeric values
// VSO Bug 2913: without AE, the same can happen as well, in particular to decimals
// and numerics with precision/scale specified
if (is_numeric_type) {
// Include the possible negative sign
field_size += elem_size;
// Include the decimal dot for output params by adding elem_size
if (decimal_digits > 0) {
field_size += elem_size;
}
}
if (column_size == SQL_SS_LENGTH_UNLIMITED) {
field_size = SQL_SERVER_MAX_FIELD_SIZE / elem_size;
}
expected_len = field_size * elem_size + elem_size;
// Binary fields aren't null terminated, so we need to account for that in our buffer length calcuations
buffer_null_extra = (c_data_type == SQL_C_BINARY) ? elem_size : 0;
// Increment to include the null terminator since the Zend length doesn't include the null terminator
buffer_length += elem_size;
// if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new
// length.
if (buffer_length < expected_len) {
SQLSRV_ASSERT(expected_len >= expected_len - buffer_null_extra, "Integer overflow/underflow caused a corrupt field length.");
// allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since
// we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about
// not having a NULL terminator on a string.
zend_string* param_z_string = zend_string_realloc(Z_STR_P(param_z), expected_len, 0);
// A zval string len doesn't include the null. This calculates the length it should be
// regardless of whether the ODBC type contains the NULL or not.
// initialize the newly allocated space
char *p = ZSTR_VAL(param_z_string);
p = p + original_len;
memset(p, '\0', expected_len - original_len);
ZVAL_NEW_STR(param_z, param_z_string);
// buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the
// buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY
buffer_length = Z_STRLEN_P(param_z) - buffer_null_extra;
// Zend string length doesn't include the null terminator
ZSTR_LEN(Z_STR_P(param_z)) -= elem_size;
}
buffer = Z_STRVAL_P(param_z);
// The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which
// may be less than the size of the buffer since the output may be more than the input. If it is greater,
// then the error 22001 is returned by ODBC.
if (strlen_or_indptr > buffer_length - (elem_size - buffer_null_extra)) {
strlen_or_indptr = buffer_length - (elem_size - buffer_null_extra);
}
}
// Change the column encoding based on the sql data type
/*static*/ void sqlsrv_param_tvp::sql_type_to_encoding(_In_ SQLSMALLINT sql_type, _Inout_ SQLSRV_ENCODING* encoding)
{
switch (sql_type) {
case SQL_BIGINT:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_FLOAT:
case SQL_REAL:
*encoding = SQLSRV_ENCODING_CHAR;
break;
case SQL_BINARY:
case SQL_LONGVARBINARY:
case SQL_VARBINARY:
case SQL_SS_UDT:
*encoding = SQLSRV_ENCODING_BINARY;
break;
default:
// Do nothing
break;
}
}
void sqlsrv_param_tvp::get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ zend_string* table_type_name, _In_ zend_string* schema_name)
{
SQLHANDLE chstmt = SQL_NULL_HANDLE;
SQLRETURN rc;
SQLSMALLINT data_type, dec_digits;
SQLINTEGER col_size;
SQLLEN cb_data_type, cb_col_size, cb_dec_digits;
char* table_type = ZSTR_VAL(table_type_name);
core::SQLAllocHandle(SQL_HANDLE_STMT, *(stmt->conn), &chstmt);
rc = SQLSetStmtAttr(chstmt, SQL_SOPT_SS_NAME_SCOPE, (SQLPOINTER)SQL_SS_NAME_SCOPE_TABLE_TYPE, SQL_IS_UINTEGER);
CHECK_CUSTOM_ERROR(!SQL_SUCCEEDED(rc), stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) {
throw core::CoreException();
}
// Check table type name and see if the schema is specified. Otherwise, assume DBO
if (schema_name != NULL) {
char* schema = ZSTR_VAL(schema_name);
rc = SQLColumns(chstmt, NULL, 0, reinterpret_cast<SQLCHAR*>(schema), SQL_NTS, reinterpret_cast<SQLCHAR*>(table_type), SQL_NTS, NULL, 0);
} else {
rc = SQLColumns(chstmt, NULL, 0, NULL, SQL_NTS, reinterpret_cast<SQLCHAR*>(table_type), SQL_NTS, NULL, 0);
}
CHECK_CUSTOM_ERROR(!SQL_SUCCEEDED(rc), stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) {
throw core::CoreException();
}
SQLSRV_ENCODING stmt_encoding = (stmt->encoding() == SQLSRV_ENCODING_DEFAULT) ? stmt->conn->encoding() : stmt->encoding();
if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
SQLBindCol(chstmt, 5, SQL_C_SSHORT, &data_type, 0, &cb_data_type);
SQLBindCol(chstmt, 7, SQL_C_SLONG, &col_size, 0, &cb_col_size);
SQLBindCol(chstmt, 9, SQL_C_SSHORT, &dec_digits, 0, &cb_dec_digits);
SQLUSMALLINT pos = 0;
while (SQL_SUCCESS == rc) {
rc = SQLFetch(chstmt);
if (rc == SQL_NO_DATA) {
CHECK_CUSTOM_ERROR(tvp_columns.size() == 0, stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) {
throw core::CoreException();
}
break;
}
sqlsrv_malloc_auto_ptr<sqlsrv_param_tvp> param_ptr;
// The SQL data type is used to derive the column encoding
SQLSRV_ENCODING column_encoding = stmt_encoding;
sql_type_to_encoding(data_type, &column_encoding);
param_ptr = new (sqlsrv_malloc(sizeof(sqlsrv_param_tvp))) sqlsrv_param_tvp(pos, column_encoding, data_type, col_size, dec_digits, this);
param_ptr->num_rows = this->num_rows; // Each column inherits the number of rows from the TVP
tvp_columns[pos] = param_ptr.get();
param_ptr.transferred();
pos++;
}
} else {
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1);
}
SQLCloseCursor(chstmt);
SQLFreeHandle(SQL_HANDLE_STMT, chstmt);
}
void sqlsrv_param_tvp::release_data()
{
// Clean up tvp_columns
std::map<SQLUSMALLINT, sqlsrv_param_tvp*>::iterator it;
for (it = tvp_columns.begin(); it != tvp_columns.end(); ++it) {
sqlsrv_param_tvp* ptr = it->second;
if (ptr) {
ptr->release_data();
sqlsrv_free(ptr);
}
}
tvp_columns.clear();
sqlsrv_param::release_data();
}
void sqlsrv_param_tvp::process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
if (sql_data_type == SQL_SS_TABLE) {
// This is a table-valued parameter
param_php_type = IS_ARRAY;
c_data_type = SQL_C_DEFAULT;
// The decimal_digits must be 0 for TVP
decimal_digits = 0;
// The column_size for a TVP is the row array size
// The following method will verify the input array and also derive num_rows
this->num_rows = 0;
int num_columns = parse_tv_param_arrays(stmt, param_z);
column_size = num_rows;
strlen_or_indptr = (num_columns == 0)? SQL_DEFAULT_PARAM : SQL_DATA_AT_EXEC;
} else {
// This is one of the constituent columns of the table-valued parameter
// The column value of the first row is already saved in member variable param_ptr_z
process_param_column_value(stmt);
}
}
int sqlsrv_param_tvp::parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
// If this is not a table-valued parameter, simply return
if (sql_data_type != SQL_SS_TABLE) {
return 0;
}
// This method verifies if the table-valued parameter (i.e. param_z) provided by the user is valid.
// The number of columns in the given table-valued parameter is returned, which may be zero.
HashTable* inputs_ht = Z_ARRVAL_P(param_z);
zend_string *tvp_name = NULL;
zend_string *schema_name = NULL;
zval *tvp_data_z = NULL;
HashPosition pos;
zend_hash_internal_pointer_reset_ex(inputs_ht, &pos);
if (zend_hash_has_more_elements_ex(inputs_ht, &pos) == SUCCESS) {
zend_ulong num_index = -1;
size_t key_len = 0;
int key_type = zend_hash_get_current_key(inputs_ht, &tvp_name, &num_index);
if (key_type == HASH_KEY_IS_STRING) {
key_len = ZSTR_LEN(tvp_name);
tvp_data_z = zend_hash_get_current_data_ex(inputs_ht, &pos);
}
CHECK_CUSTOM_ERROR((key_type == HASH_KEY_IS_LONG || key_len == 0), stmt, SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME, param_pos + 1) {
throw core::CoreException();
}
}
// TODO: Find the docs page somewhere that says a TVP can not be null but it may have null columns??
CHECK_CUSTOM_ERROR(tvp_data_z == NULL || Z_TYPE_P(tvp_data_z) == IS_NULL || Z_TYPE_P(tvp_data_z) != IS_ARRAY, stmt, SQLSRV_ERROR_TVP_INVALID_INPUTS, param_pos + 1) {
throw core::CoreException();
}
// Save the TVP type name for SQLSetDescField later
buffer = ZSTR_VAL(tvp_name);
buffer_length = SQL_NTS;
// Check if schema is provided by the user
if (zend_hash_move_forward_ex(inputs_ht, &pos) == SUCCESS) {
zval *schema_z = zend_hash_get_current_data_ex(inputs_ht, &pos);
if (schema_z != NULL && Z_TYPE_P(schema_z) == IS_STRING) {
schema_name = Z_STR_P(schema_z);
ZVAL_NEW_STR(&placeholder_z, schema_name);
}
}
// Save the TVP multi-dim array data, which should be something like this
// [
// [r1c1, r1c2, r1c3],
// [r2c1, r2c2, r2c3],
// [r3c1, r3c2, r3c3]
// ]
param_ptr_z = tvp_data_z;
HashTable* rows_ht = Z_ARRVAL_P(tvp_data_z);
this->num_rows = zend_hash_num_elements(rows_ht);
if (this->num_rows == 0) {
// TVP has no data
return 0;
}
// Given the table type name, get its column meta data next
size_t total_num_columns = 0;
get_tvp_metadata(stmt, tvp_name, schema_name);
total_num_columns = tvp_columns.size();
// (1) Is the array empty?
// (2) Check individual rows and see if their sizes are consistent?
zend_ulong id = -1;
zend_string *key = NULL;
zval* row_z = NULL;
int num_columns = 0;
int type = HASH_KEY_NON_EXISTENT;
// Loop through the rows to check the number of columns
ZEND_HASH_FOREACH_KEY_VAL(rows_ht, id, key, row_z) {
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR(type == HASH_KEY_IS_STRING, stmt, SQLSRV_ERROR_TVP_STRING_KEYS, param_pos + 1) {
throw core::CoreException();
}
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
// Individual row must be an array
CHECK_CUSTOM_ERROR(Z_TYPE_P(row_z) != IS_ARRAY, stmt, SQLSRV_ERROR_TVP_ROW_NOT_ARRAY, param_pos + 1) {
throw core::CoreException();
}
// Are all the TVP's rows the same size
num_columns = zend_hash_num_elements(Z_ARRVAL_P(row_z));
CHECK_CUSTOM_ERROR(num_columns != total_num_columns, stmt, SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE, param_pos + 1, total_num_columns) {
throw core::CoreException();
}
} ZEND_HASH_FOREACH_END();
// Return the number of columns
return num_columns;
}
void sqlsrv_param_tvp::process_param_column_value(_Inout_ sqlsrv_stmt* stmt)
{
// This is one of the constituent columns of the table-valued parameter
// The corresponding column value of the TVP's first row is already saved in
// the member variable param_ptr_z, which may be a NULL value
zval *data_z = param_ptr_z;
param_php_type = is_a_string_type(sql_data_type) ? IS_STRING : Z_TYPE_P(data_z);
switch (param_php_type) {
case IS_TRUE:
case IS_FALSE:
case IS_LONG:
case IS_DOUBLE:
sqlsrv_param::process_param(stmt, data_z);
buffer = &placeholder_z.value; // use placeholder zval for binding later
break;
case IS_RESOURCE:
sqlsrv_param::process_resource_param(data_z);
break;
case IS_STRING:
case IS_OBJECT:
if (param_php_type == IS_STRING) {
derive_string_types_sizes(data_z);
} else {
// If preprocessing a datetime object fails, throw an error of invalid php type
bool succeeded = preprocess_datetime_object(stmt, data_z);
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE, parent_tvp->param_pos + 1, param_pos + 1) {
throw core::CoreException();
}
}
buffer = reinterpret_cast<SQLPOINTER>(this);
buffer_length = 0;
strlen_or_indptr = SQL_DATA_AT_EXEC;
break;
case IS_NULL:
process_null_param_value(stmt);
break;
default:
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE, parent_tvp->param_pos + 1, param_pos + 1);
break;
}
// Release the reference
param_ptr_z = NULL;
}
void sqlsrv_param_tvp::process_null_param_value(_Inout_ sqlsrv_stmt* stmt)
{
// This is one of the constituent columns of the table-valued parameter
// This method is called when the corresponding column value of the TVP's first row is NULL
// So keep looking in the subsequent rows and find the first non-NULL value in the same column
HashTable* rows_ht = Z_ARRVAL_P(parent_tvp->param_ptr_z);
zval* row_z = NULL;
zval* value_z = NULL;
int php_type = IS_NULL;
int row_id = 1; // Start from the second row
while ((row_z = zend_hash_index_find(rows_ht, row_id++)) != NULL) {
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
value_z = zend_hash_index_find(Z_ARRVAL_P(row_z), param_pos);
php_type = Z_TYPE_P(value_z);
if (php_type != IS_NULL) {
// Save this non-NULL value before calling process_param_column_value()
param_ptr_z = value_z;
process_param_column_value(stmt);
break;
}
}
if (php_type == IS_NULL) {
// This means that the entire column contains nothing but NULLs
sqlsrv_param::process_null_param(param_ptr_z);
}
}
void sqlsrv_param_tvp::bind_param(_Inout_ sqlsrv_stmt* stmt)
{
core::SQLBindParameter(stmt, param_pos + 1, direction, c_data_type, sql_data_type, column_size, decimal_digits, buffer, buffer_length, &strlen_or_indptr);
// No need to continue if this is one of the constituent columns of the table-valued parameter
if (sql_data_type != SQL_SS_TABLE) {
return;
}
if (num_rows == 0) {
// TVP has no data
return;
}
// Set Table-Valued parameter type name (and the schema where it is defined)
SQLHDESC hIpd = NULL;
core::SQLGetStmtAttr(stmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0);
if (buffer != NULL) {
// SQL_CA_SS_TYPE_NAME is optional for stored procedure calls, but it must be
// specified for SQL statements that are not procedure calls to enable the
// server to determine the type of the table-valued parameter.
char *tvp_name = reinterpret_cast<char *>(buffer);
SQLRETURN r = ::SQLSetDescField(hIpd, param_pos + 1, SQL_CA_SS_TYPE_NAME, reinterpret_cast<SQLCHAR*>(tvp_name), SQL_NTS);
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
throw core::CoreException();
}
}
if (Z_TYPE(placeholder_z) == IS_STRING) {
// If the table type for the table-valued parameter is defined in a different
// schema than the default, SQL_CA_SS_SCHEMA_NAME must be specified. If not,
// the server will not be able to determine the type of the table-valued parameter.
char * schema_name = Z_STRVAL(placeholder_z);
SQLRETURN r = ::SQLSetDescField(hIpd, param_pos + 1, SQL_CA_SS_SCHEMA_NAME, reinterpret_cast<SQLCHAR*>(schema_name), SQL_NTS);
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
throw core::CoreException();
}
// Free and reset the placeholder_z
zend_string_release(Z_STR(placeholder_z));
ZVAL_UNDEF(&placeholder_z);
}
// Bind the TVP columns one by one
// Register this object first using SQLSetDescField() for sending TVP data post execution
SQLHDESC desc;
core::SQLGetStmtAttr(stmt, SQL_ATTR_APP_PARAM_DESC, &desc, 0, 0);
SQLRETURN r = ::SQLSetDescField(desc, param_pos + 1, SQL_DESC_DATA_PTR, reinterpret_cast<SQLPOINTER>(this), 0);
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
throw core::CoreException();
}
// First set focus on this parameter
size_t ordinal = param_pos + 1;
core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(ordinal), SQL_IS_INTEGER);
// Bind the TVP columns
HashTable* rows_ht = Z_ARRVAL_P(param_ptr_z);
zval* row_z = zend_hash_index_find(rows_ht, 0);
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
HashTable* cols_ht = Z_ARRVAL_P(row_z);
zend_ulong id = -1;
zend_string *key = NULL;
zval* data_z = NULL;
int num_columns = 0;
// In case there are null values in the first row, have to loop
// through the entire first row of column values using the Zend macros.
ZEND_HASH_FOREACH_KEY_VAL(cols_ht, id, key, data_z) {
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR(type == HASH_KEY_IS_STRING, stmt, SQLSRV_ERROR_TVP_STRING_KEYS, param_pos + 1) {
throw core::CoreException();
}
// Assume the user has supplied data for all columns in the right order
SQLUSMALLINT pos = static_cast<SQLUSMALLINT>(id);
sqlsrv_param* column_param = tvp_columns[pos];
SQLSRV_ASSERT(column_param != NULL, "sqlsrv_param_tvp::bind_param -- column param should not be null");
// If data_z is NULL, will need to keep looking in the subsequent rows of
// the same column until a non-null value is found. Since Zend macros must be
// used to traverse the array items, nesting Zend macros in different directions
// does not work.
// Therefore, save data_z for later processing and binding.
column_param->param_ptr_z = data_z;
num_columns++;
} ZEND_HASH_FOREACH_END();
// Process the columns and bind each of them using the saved data
for (int i = 0; i < num_columns; i++) {
sqlsrv_param* column_param = tvp_columns[i];
column_param->process_param(stmt, NULL);
column_param->bind_param(stmt);
}
// Reset focus
core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(0), SQL_IS_INTEGER);
}
// For each of the constituent columns of the table-valued parameter, check its PHP type
// For pure scalar types, map the cell value (based on current_row and ordinal) to the
// member placeholder_z
void sqlsrv_param_tvp::populate_cell_placeholder(_Inout_ sqlsrv_stmt* stmt, _In_ int ordinal)
{
if (sql_data_type == SQL_SS_TABLE || ordinal >= num_rows) {
return;
}
zval* row_z = NULL;
HashTable* values_ht = NULL;
zval* value_z = NULL;
int type = IS_NULL;
switch (param_php_type) {
case IS_TRUE:
case IS_FALSE:
case IS_LONG:
case IS_DOUBLE:
// Find the row from the TVP data based on ordinal
row_z = zend_hash_index_find(Z_ARRVAL_P(parent_tvp->param_ptr_z), ordinal);
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
// Now find the column value based on param_pos
value_z = zend_hash_index_find(Z_ARRVAL_P(row_z), param_pos);
type = Z_TYPE_P(value_z);
// First check if value_z is NULL
if (type == IS_NULL) {
ZVAL_NULL(&placeholder_z);
strlen_or_indptr = SQL_NULL_DATA;
} else {
// Once the placeholder is bound with the correct value from the array, update current_row
if (param_php_type == IS_DOUBLE) {
if (type != IS_DOUBLE) {
// If value_z type is different from param_php_type convert first
convert_to_double(value_z);
}
strlen_or_indptr = sizeof(Z_DVAL_P(value_z));
ZVAL_DOUBLE(&placeholder_z, Z_DVAL_P(value_z));
} else {
if (type != IS_LONG) {
// If value_z type is different from param_php_type convert first
// Even for boolean values
convert_to_long(value_z);
}
strlen_or_indptr = sizeof(Z_LVAL_P(value_z));
ZVAL_LONG(&placeholder_z, Z_LVAL_P(value_z));
}
}
current_row++;
break;
default:
// Do nothing for non-scalar types
break;
}
}
// If this is the table-valued parameter, loop through each parameter column
// and populate the cell's placeholder_z.
// If this is one of the constituent columns of the table-valued parameter,
// call SQLPutData() to send the cell value to the server (based on current_row
// and param_pos)
bool sqlsrv_param_tvp::send_data_packet(_Inout_ sqlsrv_stmt* stmt)
{
if (sql_data_type != SQL_SS_TABLE) {
// This is one of the constituent columns of the table-valued parameter
// Check current_row first
if (current_row >= num_rows) {
return false;
}
// Find the row from the TVP data based on current_row
zval* row_z = zend_hash_index_find(Z_ARRVAL_P(parent_tvp->param_ptr_z), current_row);
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
// Now find the column value based on param_pos
zval* value_z = zend_hash_index_find(Z_ARRVAL_P(row_z), param_pos);
// First check if value_z is NULL
if (Z_TYPE_P(value_z) == IS_NULL) {
core::SQLPutData(stmt, NULL, SQL_NULL_DATA);
current_row++;
} else {
switch (param_php_type) {
case IS_RESOURCE:
{
num_bytes_read = 0;
param_stream = NULL;
// Get the stream from the zval value
core::sqlsrv_php_stream_from_zval_no_verify(*stmt, param_stream, value_z);
// Keep sending the packets until EOF is reached
while (sqlsrv_param::send_data_packet(stmt)) {
}
current_row++;
}
break;
case IS_OBJECT:
{
// This method updates placeholder_z as a string
bool succeeded = convert_datetime_to_string(stmt, value_z);
// Conversion failed so assume the input was an invalid PHP type
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE, parent_tvp->param_pos + 1, param_pos + 1) {
throw core::CoreException();
}
core::SQLPutData(stmt, Z_STRVAL(placeholder_z), SQL_NTS);
current_row++;
}
break;
case IS_STRING:
{
int type = Z_TYPE_P(value_z);
if (type != IS_STRING) {
convert_to_string(value_z);
}
SQLLEN value_len = Z_STRLEN_P(value_z);
if (value_len == 0) {
// If it's an empty string
core::SQLPutData(stmt, Z_STRVAL_P(value_z), 0);
} else {
if (encoding == CP_UTF8 && !is_a_numeric_type(sql_data_type)) {
if (value_len > INT_MAX) {
LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded.");
throw core::CoreException();
}
// This method would change the member placeholder_z
bool succeeded = convert_input_str_to_utf16(stmt, value_z);
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE, parent_tvp->param_pos + 1, param_pos + 1, get_last_error_message()) {
throw core::CoreException();
}
send_string_data_in_batches(stmt, &placeholder_z);
} else {
send_string_data_in_batches(stmt, value_z);
}
}
current_row++;
}
break;
default:
// Do nothing for basic types as they should be processed elsewhere
break;
}
} // else not IS_NULL
} else {
// This is the table-valued parameter
if (current_row < num_rows) {
// Loop through the table parameter columns and populate each cell's placeholder whenever applicable
for (size_t i = 0; i < tvp_columns.size(); i++) {
tvp_columns[i]->populate_cell_placeholder(stmt, current_row);
}
// This indicates a TVP row is available
core::SQLPutData(stmt, reinterpret_cast<SQLPOINTER>(1), 1);
current_row++;
} else {
// This indicates there is no more TVP row
core::SQLPutData(stmt, reinterpret_cast<SQLPOINTER>(0), 0);
}
}
// Return false to indicate that the current row has been sent
return false;
}
// A helper method for sending large string data in batches
void sqlsrv_param_tvp::send_string_data_in_batches(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z)
{
SQLLEN len = Z_STRLEN_P(value_z);
SQLLEN batch = (encoding == CP_UTF8) ? PHP_STREAM_BUFFER_SIZE / sizeof(SQLWCHAR) : PHP_STREAM_BUFFER_SIZE;
char* p = Z_STRVAL_P(value_z);
while (len > batch) {
core::SQLPutData(stmt, p, batch);
len -= batch;
p += batch;
}
// Put final batch
core::SQLPutData(stmt, p, len);
}
void sqlsrv_params_container::clean_up_param_data(_In_opt_ bool only_input/* = false*/)
{
current_param = NULL;
remove_params(input_params);
if (!only_input) {
remove_params(output_params);
}
}
// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output
// parameters will be present until all results are processed (since output parameters can depend on results
// while being processed). This function updates the lengths of output parameter strings from the strlen_or_indptr
// argument passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary.
// If a NULL was returned by SQL Server to any output parameter, set the parameter to NULL as well
void sqlsrv_params_container::finalize_output_parameters()
{
std::map<SQLUSMALLINT, sqlsrv_param*>::iterator it;
for (it = output_params.begin(); it != output_params.end(); ++it) {
sqlsrv_param_inout* ptr = dynamic_cast<sqlsrv_param_inout*>(it->second);
if (ptr) {
ptr->finalize_output_value();
}
}
}
sqlsrv_param* sqlsrv_params_container::find_param(_In_ SQLUSMALLINT param_num, _In_ bool is_input)
{
try {
if (is_input) {
return input_params.at(param_num);
} else {
return output_params.at(param_num);
}
} catch (std::out_of_range&) {
// not found
return NULL;
}
}
bool sqlsrv_params_container::get_next_parameter(_Inout_ sqlsrv_stmt* stmt)
{
// Get the param ptr when binding the resource parameter
SQLPOINTER param = NULL;
SQLRETURN r = core::SQLParamData(stmt, &param);
// If no more data, all the bound parameters have been exhausted, so return false (done)
if (SQL_SUCCEEDED(r) || r == SQL_NO_DATA) {
// Done now, reset current_param
current_param = NULL;
return false;
} else if (r == SQL_NEED_DATA) {
if (param != NULL) {
current_param = reinterpret_cast<sqlsrv_param*>(param);
SQLSRV_ASSERT(current_param != NULL, "sqlsrv_params_container::get_next_parameter - The parameter requested is missing!");
current_param->init_data_from_zval(stmt);
} else {
// Do not reset current_param when param is NULL, because
// it means that data is expected from the existing current_param
}
}
return true;
}
// The following helper method sends one stream packet at a time, if available
bool sqlsrv_params_container::send_next_packet(_Inout_ sqlsrv_stmt* stmt)
{
if (current_param == NULL) {
// If current_stream is NULL, either this is the first time checking or the previous parameter
// is done. In either case, MUST call get_next_parameter() to see if there is any more
// parameter requested by ODBC. Otherwise, "Function sequence error" will result, meaning the
// ODBC functions are called out of the order required by the ODBC Specification
if (get_next_parameter(stmt) == false) {
return false;
}
}
// The helper method send_stream_packet() returns false when EOF is reached
if (current_param && current_param->send_data_packet(stmt) == false) {
// Now that EOF has been reached, reset current_param for next round
// Bear in mind that SQLParamData might request the same stream resource again
current_param = NULL;
}
// Returns true regardless such that either get_next_parameter() will be called or next packet will be sent
return true;
}

View file

@ -1,303 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_stream.cpp
//
// Contents: Implementation of PHP streams for reading SQL Server data
//
// Microsoft Drivers 5.10 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( _Inout_ php_stream* stream, int /*close_handle*/ )
{
sqlsrv_stream* ss = static_cast<sqlsrv_stream*>( stream->abstract );
SQLSRV_ASSERT( ss != NULL && ss->stmt != 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.
#if PHP_VERSION_ID >= 70400
ssize_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count)
#else
size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count)
#endif
{
SQLLEN read = 0;
SQLSMALLINT c_type = SQL_C_CHAR;
char* get_data_buffer = buf;
sqlsrv_malloc_auto_ptr<char> temp_buf;
sqlsrv_stream* ss = static_cast<sqlsrv_stream*>( stream->abstract );
SQLSRV_ASSERT( ss != NULL && ss->stmt != 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<char*>( 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;
}
// Warnings will be handled below
SQLRETURN r = ss->stmt->current_results->get_data(ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read, false /*handle_warning*/);
CHECK_SQL_ERROR( r, ss->stmt ) {
stream->eof = 1;
throw core::CoreException();
}
// If the stream returns no data or NULL data, mark the "end of the stream" and return
if( r == SQL_NO_DATA || read == SQL_NULL_DATA) {
stream->eof = 1;
return 0;
}
// If the stream returns data less than the count requested then we are at the "end of the stream" but continue processing
if (static_cast<size_t>(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] = {L'\0'};
SQLSMALLINT len = 0;
ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
if( read == SQL_NO_TOTAL ) {
SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it "
"did not occur." );
}
// 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.
// But when fetching binary fields as chars (wide chars), each byte is represented as 2 hex characters,
// each takes the size of a char (wide char). Note that BufferLength may not be multiples of 2 or 4.
bool is_binary = (ss->sql_type == SQL_BINARY || ss->sql_type == SQL_VARBINARY || ss->sql_type == SQL_LONGVARBINARY);
// 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
size_t char_size = sizeof(SQLCHAR);
switch( c_type ) {
case SQL_C_BINARY:
read = count;
break;
case SQL_C_WCHAR:
char_size = sizeof(SQLWCHAR);
if (is_binary) {
// Each binary byte read will be 2 hex wide chars in the buffer
SQLLEN num_bytes_read = static_cast<SQLLEN>(floor((count - char_size) / (2 * char_size)));
read = num_bytes_read * char_size * 2 ;
} else {
read = (count % 2 == 0 ? count - 2 : count - 3);
}
break;
case SQL_C_CHAR:
if (is_binary) {
read = ((count - char_size) % 2 == 0 ? count - char_size : count - char_size - 1);
} else {
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( isVistaOrGreater ) {
// 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<LPCWSTR>( temp_buf.get() ),
static_cast<int>(read >> 1), buf, static_cast<int>(count), NULL, NULL );
#else
int enc_len = WideCharToMultiByte( ss->encoding, flags, reinterpret_cast<LPCWSTR>( temp_buf.get() ),
static_cast<int>(read >> 1), buf, static_cast<int>(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 static_cast<size_t>( read );
}
catch (core::CoreException&) {
#if PHP_VERSION_ID >= 70400
return -1;
#else
return 0;
#endif
}
catch (...) {
LOG(SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught.");
#if PHP_VERSION_ID >= 70400
return -1;
#else
return 0;
#endif
}
}
// 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( _In_opt_ php_stream_wrapper* wrapper, _In_ const char*, _In_ const char* mode,
_In_opt_ int options, _In_ zend_string **, php_stream_context* STREAMS_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<sqlsrv_stream> ss;
ss = static_cast<sqlsrv_stream*>( sqlsrv_malloc( sizeof( sqlsrv_stream )));
memset( ss, 0, sizeof( sqlsrv_stream ));
// The function core_get_field_common() is changed to pass REPORT_ERRORS for
// php_stream_open_wrapper(). Whether the error flag is toggled or cleared,
// the argument "options" will be zero.
// For details check this pull request: https://github.com/php/php-src/pull/6190
if (options != 0) {
php_stream_wrapper_log_error(wrapper, options, "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
};

View file

@ -1,708 +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 5.10 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 {
severity_callback g_driver_severity;
// *** internal constants ***
// buffer used to hold a formatted log message prior to actually logging it.
const int LOG_MSG_SIZE = 2048;
// 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] = {'\0'}; // 2k to hold the error messages
// routine used by utf16_string_from_mbcs_string
unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string,
_In_ unsigned int mbcs_len,
_Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string,
_In_ unsigned int utf16_len, bool use_strict_conversion = false );
// invoked by write_to_log() when the message severity qualifies to be logged
// msg - the message to log in a FormatMessage style formatting
// print_args - args to the message
void log_activity(_In_opt_ const char* msg, _In_opt_ va_list* print_args)
{
char log_msg[LOG_MSG_SIZE] = { '\0' };
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);
}
}
// 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( _In_ unsigned int severity, _In_ const char* msg, ...)
{
SQLSRV_ASSERT(g_driver_severity != NULL, "Must register a driver checker function.");
if (!g_driver_severity(severity)) {
return;
}
va_list args;
va_start( args, msg );
log_activity(msg, &args);
va_end( args );
}
void core_sqlsrv_register_severity_checker(_In_ severity_callback driver_checker)
{
g_driver_severity = driver_checker;
}
// 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( _In_ SQLSRV_ENCODING encoding, _Inout_updates_z_(len) char** string, _Inout_ 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<const SQLWCHAR*>(*string), int(len / sizeof(SQLWCHAR)), &outString, outLen );
if (result)
{
sqlsrv_free( *string );
*string = outString;
len = outLen;
}
return result;
}
bool validate_string( _In_ char* string, _In_ 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( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_(cchInLen) const SQLWCHAR* inString, _In_ SQLINTEGER cchInLen, _Inout_updates_bytes_(cchOutLen) char** outString, _Out_ 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<char*>( sqlsrv_malloc ( 1 ) );
*outString[0] = '\0';
cchOutLen = 0;
return true;
}
#ifndef _WIN32
// Allocate enough space to hold the largest possible number of bytes for UTF-8 conversion
// instead of calling FromUtf16, for performance reasons
cchOutLen = 4 * cchInLen;
#else
// 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 && isVistaOrGreater ) {
// Vista (and later) will detect invalid UTF-16 characters and raise an error.
flags = WC_ERR_INVALID_CHARS;
}
// Calculate the number of output bytes required - no performance hit here because
// WideCharToMultiByte is highly optimised
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<char*>( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ ));
memset(newString, '\0', cchOutLen+1);
#ifndef _WIN32
int rc = SystemLocale::FromUtf16Strict( encoding, inString, cchInLen, newString, static_cast<int>(cchOutLen));
#else
int rc = WideCharToMultiByte( encoding, flags, inString, cchInLen, newString, static_cast<int>(cchOutLen), NULL, NULL );
#endif // !_WIN32
if( rc == 0 ) {
cchOutLen = 0;
sqlsrv_free( newString );
return false;
}
char* newString2 = reinterpret_cast<char*>( sqlsrv_malloc( rc + 1 /* NULL char*/ ));
memset(newString2, '\0', rc+1);
memcpy_s(newString2, rc, newString, rc);
sqlsrv_free( newString );
*outString = newString2;
cchOutLen = rc;
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( _In_ SQLSRV_ENCODING php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, _In_ unsigned int mbcs_len,
_Out_ unsigned int* utf16_len, bool use_strict_conversion )
{
*utf16_len = (mbcs_len + 1);
SQLWCHAR* utf16_string = reinterpret_cast<SQLWCHAR*>( sqlsrv_malloc( *utf16_len * sizeof( SQLWCHAR )));
*utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, utf16_string, *utf16_len, use_strict_conversion );
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;
}
// Converts an input (assuming a datetime string) to a zval containing a PHP DateTime object.
// If the input is null, this simply returns a NULL zval. If anything wrong occurs during conversion,
// an exception will be thrown.
void convert_datetime_string_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_opt_ char* input, _In_ SQLLEN length, _Inout_ zval& out_zval)
{
if (input == NULL) {
ZVAL_NULL(&out_zval);
return;
}
zval params[1];
zval value_temp_z;
zval function_z;
// Initialize all zval variables
ZVAL_UNDEF(&out_zval);
ZVAL_UNDEF(&value_temp_z);
ZVAL_UNDEF(&function_z);
ZVAL_UNDEF(params);
// Convert the datetime string to a PHP DateTime object
ZVAL_STRINGL(&value_temp_z, input, length);
ZVAL_STRINGL(&function_z, "date_create", sizeof("date_create") - 1);
params[0] = value_temp_z;
if (call_user_function(EG(function_table), NULL, &function_z, &out_zval, 1,
params) == FAILURE) {
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED);
}
zend_string_free(Z_STR(value_temp_z));
zend_string_free(Z_STR(function_z));
}
// 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( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, _In_ logging_severity severity, _In_opt_ bool check_warning /* = false */)
{
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_ERROR_MESSAGE_LENGTH + 1] = {L'\0'};
SQLSRV_ENCODING enc = ctx.encoding();
switch( h_type ) {
case SQL_HANDLE_STMT:
{
sqlsrv_stmt* stmt = static_cast<sqlsrv_stmt*>( &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_ERROR_MESSAGE_LENGTH + 1, &wmessage_len );
// don't use the CHECK* macros here since it will trigger reentry into the error handling system
// removed the workaround for Mac users with unixODBC 2.3.4 when connection pooling is enabled (PDO SQLSRV), for two reasons:
// (1) not recommended to use connection pooling with unixODBC < 2.3.7
// (2) the problem was not reproducible with unixODBC 2.3.7
if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
return false;
}
// We need to calculate number of characters
SQLINTEGER 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;
if (r == SQL_SUCCESS_WITH_INFO && wmessage_len > SQL_MAX_ERROR_MESSAGE_LENGTH) {
// note that wmessage_len is the number of characters required for the error message --
// create a new buffer big enough for this lengthy error message
sqlsrv_malloc_auto_ptr<SQLWCHAR> wnative_message_str;
SQLSMALLINT expected_len = wmessage_len * sizeof(SQLWCHAR);
SQLSMALLINT returned_len = 0;
wnative_message_str = reinterpret_cast<SQLWCHAR*>(sqlsrv_malloc(expected_len));
memset(wnative_message_str, '\0', expected_len);
SQLRETURN rtemp = ::SQLGetDiagFieldW(h_type, h, record_number, SQL_DIAG_MESSAGE_TEXT, wnative_message_str, wmessage_len, &returned_len);
if (!SQL_SUCCEEDED(rtemp) || returned_len != expected_len) {
// something went wrong
return false;
}
convert_string_from_utf16(enc, wnative_message_str, wmessage_len, (char**)&error->native_message, message_len);
} else {
convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len);
}
if (message_len == 0 && error->native_message == NULL) {
// something went wrong
return false;
}
break;
}
// Only overrides 'severity' if 'check_warning' is true (false by default)
if (check_warning) {
// The character string value returned for an SQLSTATE consists of a two-character class value
// followed by a three-character subclass value. A class value of "01" indicates a warning.
// https://docs.microsoft.com/sql/odbc/reference/appendixes/appendix-a-odbc-error-codes?view=sql-server-ver15
if (error->sqlstate[0] == '0' && error->sqlstate[1] == '1') {
severity = SEV_WARNING;
}
}
// 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( _In_ sqlsrv_context& ctx, _In_ sqlsrv_error_const const* custom_error,
_Out_ sqlsrv_error_auto_ptr& formatted_error, _In_ logging_severity severity, _In_opt_ va_list* args )
{
// allocate space for the formatted message
formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error();
formatted_error->sqlstate = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE ));
formatted_error->native_message = reinterpret_cast<SQLCHAR*>( sqlsrv_malloc( SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ));
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast<LPSTR>( custom_error->native_message ), 0, 0,
reinterpret_cast<LPSTR>( formatted_error->native_message ), SQL_MAX_ERROR_MESSAGE_LENGTH, args );
if( rc == 0 ) {
strcpy_s( reinterpret_cast<char*>( formatted_error->native_message ), SQL_MAX_ERROR_MESSAGE_LENGTH,
reinterpret_cast<char*>( INTERNAL_FORMAT_ERROR ));
}
strcpy_s( reinterpret_cast<char*>( formatted_error->sqlstate ), SQL_SQLSTATE_BUFSIZE,
reinterpret_cast<char*>( 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( _Out_ char* output_buffer, _In_ unsigned output_len, _In_opt_ const char* format, ... )
{
va_list format_args;
va_start( format_args, format );
DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast<LPSTR>(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( _Inout_ 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( _In_opt_ 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, "%s", reinterpret_cast<const char*>(INTERNAL_FORMAT_ERROR));
}
php_error(E_ERROR, "%s", 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( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string,
_In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string,
_In_ unsigned int utf16_len, bool use_strict_conversion )
{
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;
if (use_strict_conversion) {
required_len = SystemLocale::ToUtf16Strict( win_encoding, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len );
}
else {
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;
}
}
namespace data_classification {
const char* DATA_CLASS = "Data Classification";
const char* LABEL = "Label";
const char* INFOTYPE = "Information Type";
const char* NAME = "name";
const char* ID = "id";
const char* RANK = "rank";
void convert_sensivity_field(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_ENCODING encoding, _In_ unsigned char *ptr, _In_ int len, _Inout_updates_bytes_(field_name_len) char** field_name, _Out_ SQLLEN& field_name_len)
{
sqlsrv_malloc_auto_ptr<SQLWCHAR> temp_field_name;
int temp_field_len = len * sizeof(SQLWCHAR);
field_name_len = 0;
if (len == 0) {
*field_name = reinterpret_cast<char*>(sqlsrv_malloc(1));
*field_name[0] = '\0';
return;
}
temp_field_name = static_cast<SQLWCHAR*>(sqlsrv_malloc((len + 1) * sizeof(SQLWCHAR)));
memset(temp_field_name, L'\0', len + 1);
memcpy_s(temp_field_name, temp_field_len, ptr, temp_field_len);
bool converted = convert_string_from_utf16(encoding, temp_field_name, len, field_name, field_name_len);
CHECK_CUSTOM_ERROR(!converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
}
void name_id_pair_free(_Inout_ name_id_pair* pair)
{
if (pair->name) {
pair->name.reset();
}
if (pair->id) {
pair->id.reset();
}
sqlsrv_free(pair);
}
void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector<name_id_pair*, sqlsrv_allocator<name_id_pair*>>* pairs, _Inout_ unsigned char **pptr)
{
unsigned char *ptr = *pptr;
unsigned short npairs;
numpairs = npairs = *(reinterpret_cast<unsigned short*>(ptr));
SQLSRV_ENCODING encoding = ((stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding());
pairs->reserve(numpairs);
ptr += sizeof(unsigned short);
while (npairs--) {
int namelen, idlen;
unsigned char *nameptr, *idptr;
SQLLEN field_len;
sqlsrv_malloc_auto_ptr<name_id_pair> pair;
pair = new(sqlsrv_malloc(sizeof(name_id_pair))) name_id_pair();
sqlsrv_malloc_auto_ptr<char> name;
sqlsrv_malloc_auto_ptr<char> id;
namelen = *ptr++;
nameptr = ptr;
pair->name_len = namelen;
convert_sensivity_field(stmt, encoding, nameptr, namelen, (char**)&name, field_len);
pair->name = name;
ptr += namelen * 2;
idlen = *ptr++;
idptr = ptr;
ptr += idlen * 2;
pair->id_len = idlen;
convert_sensivity_field(stmt, encoding, idptr, idlen, (char**)&id, field_len);
pair->id = id;
pairs->push_back(pair.get());
pair.transferred();
}
*pptr = ptr;
}
void parse_column_sensitivity_props(_Inout_ sensitivity_metadata* meta, _Inout_ unsigned char **pptr, _In_ bool getRankInfo)
{
unsigned char *ptr = *pptr;
unsigned short ncols;
int queryrank, colrank;
// Get rank info
if (getRankInfo) {
queryrank = *(reinterpret_cast<long*>(ptr));
ptr += sizeof(int);
meta->rank = queryrank;
}
// Get number of columns
meta->num_columns = ncols = *(reinterpret_cast<unsigned short*>(ptr));
// Move forward
ptr += sizeof(unsigned short);
while (ncols--) {
unsigned short npairs = *(reinterpret_cast<unsigned short*>(ptr));
ptr += sizeof(unsigned short);
column_sensitivity column;
column.num_pairs = npairs;
while (npairs--) {
label_infotype_pair pair;
unsigned short labelidx, typeidx;
labelidx = *(reinterpret_cast<unsigned short*>(ptr));
ptr += sizeof(unsigned short);
typeidx = *(reinterpret_cast<unsigned short*>(ptr));
ptr += sizeof(unsigned short);
if (getRankInfo) {
colrank = *(reinterpret_cast<long*>(ptr));
ptr += sizeof(int);
pair.rank = colrank;
}
pair.label_idx = labelidx;
pair.infotype_idx = typeidx;
column.label_info_pairs.push_back(pair);
}
meta->columns_sensitivity.push_back(column);
}
*pptr = ptr;
}
USHORT fill_column_sensitivity_array(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Inout_ zval *return_array)
{
sensitivity_metadata* meta = stmt->current_sensitivity_metadata;
if (meta == NULL) {
return 0;
}
SQLSRV_ASSERT(colno >= 0 && colno < meta->num_columns, "fill_column_sensitivity_array: column number out of bounds");
zval data_classification;
ZVAL_UNDEF(&data_classification);
array_init(&data_classification);
USHORT num_pairs = meta->columns_sensitivity[colno].num_pairs;
if (num_pairs == 0) {
add_assoc_zval(return_array, DATA_CLASS, &data_classification);
return 0;
}
zval sensitivity_properties;
ZVAL_UNDEF(&sensitivity_properties);
array_init(&sensitivity_properties);
for (USHORT j = 0; j < num_pairs; j++) {
zval label_array, infotype_array;
ZVAL_UNDEF(&label_array);
ZVAL_UNDEF(&infotype_array);
array_init(&label_array);
array_init(&infotype_array);
USHORT labelidx = meta->columns_sensitivity[colno].label_info_pairs[j].label_idx;
USHORT typeidx = meta->columns_sensitivity[colno].label_info_pairs[j].infotype_idx;
int column_rank = meta->columns_sensitivity[colno].label_info_pairs[j].rank;
char *label = meta->labels[labelidx]->name;
char *label_id = meta->labels[labelidx]->id;
char *infotype = meta->infotypes[typeidx]->name;
char *infotype_id = meta->infotypes[typeidx]->id;
add_assoc_string(&label_array, NAME, label);
add_assoc_string(&label_array, ID, label_id);
add_assoc_zval(&sensitivity_properties, LABEL, &label_array);
add_assoc_string(&infotype_array, NAME, infotype);
add_assoc_string(&infotype_array, ID, infotype_id);
add_assoc_zval(&sensitivity_properties, INFOTYPE, &infotype_array);
// add column sensitivity rank info to sensitivity_properties
if (column_rank > RANK_NOT_DEFINED) {
add_assoc_long(&sensitivity_properties, RANK, column_rank);
}
// add the pair of sensitivity properties to data_classification
add_next_index_zval(&data_classification, &sensitivity_properties);
}
// add query sensitivity rank info to data_classification
int query_rank = meta->rank;
if (query_rank > RANK_NOT_DEFINED) {
add_assoc_long(&data_classification, RANK, query_rank);
}
// add data classfication as associative array
add_assoc_zval(return_array, DATA_CLASS, &data_classification);
return num_pairs;
}
void sensitivity_metadata::reset()
{
std::for_each(labels.begin(), labels.end(), name_id_pair_free);
labels.clear();
std::for_each(infotypes.begin(), infotypes.end(), name_id_pair_free);
infotypes.clear();
columns_sensitivity.clear();
}
} // namespace data_classification

View file

@ -1,397 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: Globalization.h
//
// Contents: Contains functions for handling Windows format strings
// and UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.10 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 <errno.h>
#include <iconv.h>
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;
}
};
class EncodingConverter
{
UINT m_dstCodePage;
UINT m_srcCodePage;
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<DestType> * 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<DestType> & dest,
iconv_buffer<SrcType> & 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;
}
}
//if a shift sequence is encountered, we need to advance output buffer
iconv_ret = iconv( m_pCvtCache->GetIConv(), NULL, NULL, &dest.m_pBytes, &dest.m_nBytesLeft );
}
return cchDest - (dest.m_nBytesLeft / sizeof(DestType));
}
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 ( !IsValidIConv() )
return 0;
iconv_buffer<SrcType> src(
reinterpret_cast< char * >( const_cast< SrcType * >(srcBuffer) ),
cchSource );
size_t cchDest = cchSource;
AutoArray< DestType, AllocT > newDestBuffer( cchDest );
iconv_buffer<DestType> 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;
}
}
}
// 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 ( !IsValidIConv() )
return 0;
iconv_buffer<SrcType> src(
reinterpret_cast< char * >( const_cast< SrcType * >(srcBuffer) ),
cchSource );
if ( 0 < cchDest )
{
iconv_buffer<DestType> 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)] = {'\0'};
iconv_buffer<DestType> 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;
}
}
};
#endif // _GLOBALIZATION_H_

View file

@ -1,42 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: InterlockedAtomic.h
//
// Contents: Contains a portable abstraction for interlocked, atomic
// operations on int32_t and pointer types.
//
// Microsoft Drivers 5.10 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
//
// Always returns old value
// Sets to new value if old value equals compareTo
LONG InterlockedCompareExchange( LONG volatile * atomic, LONG newValue, LONG 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__

View file

@ -1,33 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: InterlockedAtomic_gcc.h
//
// Contents: Contains a portable abstraction for interlocked, atomic
// operations on int32_t and pointer types.
//
// Microsoft Drivers 5.10 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 InterlockedCompareExchange( LONG volatile * atomic, LONG newValue, LONG compareTo )
{
return __sync_val_compare_and_swap( atomic, compareTo, newValue );
}
#endif // __INTERLOCKEDATOMIC_GCC_H__

View file

@ -1,142 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: InterlockedSList.h
//
// Contents: Contains a portable abstraction for interlocked, singly
// linked list.
//
// Microsoft Drivers 5.10 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<USHORT>(slist->List.Depth);
}
#endif // __INTERLOCKEDSLIST_H__

View file

@ -1,263 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: Localization.hpp
//
// Contents: Contains portable classes for localization
//
// Microsoft Drivers 5.10 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 <time.h>
#include <assert.h>
#include "typedefs_for_linux.h"
#include <locale>
#define CP_UTF8 65001
#define CP_ISO8859_1 28591
#define CP_ISO8859_2 28592
#define CP_ISO8859_3 28593
#define CP_ISO8859_4 28594
#define CP_ISO8859_5 28595
#define CP_ISO8859_6 28596
#define CP_ISO8859_7 28597
#define CP_ISO8859_8 28598
#define CP_ISO8859_9 28599
#define CP_ISO8859_13 28603
#define CP_ISO8859_15 28605
#define CP_UTF16 1200
#define CP_ACP 0 // default to ANSI code page
bool _setLocale(const char * localeName, std::locale ** pLocale);
void setDefaultLocale(const char ** localeName, std::locale ** pLocale);
// 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;
}
};
class SystemLocale
{
public:
// -----------------------------------------------------------------------
// Public Static Functions
static const SystemLocale & Singleton();
static const int MINS_PER_HOUR = 60;
static const int MINS_PER_DAY = 24 * MINS_PER_HOUR;
// 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 );
// 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 );
// 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);
// CP1252 to UTF16 conversion which does not involve iconv
static size_t CP1252ToUtf16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode );
// UTF8/16 conversion which does not involve iconv
static size_t Utf8To16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode );
static size_t Utf8From16( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode );
static size_t Utf8To16Strict( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode );
static size_t Utf8From16Strict( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode );
// -----------------------------------------------------------------------
// Public Member Functions
// The Ansi code page, always UTF8 for Linux
UINT AnsiCP() const;
// Used for files (e.g. returns 437 on US Windows, UTF8 for Linux)
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 & );
std::locale * m_pLocale;
UINT m_uAnsiCP;
explicit SystemLocale( const char * localeName );
~SystemLocale();
static UINT ExpandSpecialCP( UINT codepage )
{
// skip SQLSRV_ENCODING_CHAR
return (codepage <= 3 ? Singleton().m_uAnsiCP : codepage);
}
// 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;
}
};
// ---------------------------------------------------------------------------
// Inlines
#include "globalization.h"
inline UINT SystemLocale::AnsiCP() const
{
return m_uAnsiCP;
}
inline UINT SystemLocale::MaxCharCchSize( UINT codepage )
{
codepage = ExpandSpecialCP( codepage );
switch ( codepage )
{
case CP_UTF8:
case 54936:
return 4;
case 932:
case 936:
case 949:
case 950:
case CP_UTF16:
return 2;
default:
return 1;
}
}
#endif // __LOCALIZATION_HPP__

View file

@ -1,1090 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: localizationimpl.cpp
//
// 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 5.10 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.
#ifdef __MUSL__
#define TRANSLIT ""
#else
#define TRANSLIT "//TRANSLIT"
#endif
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 },
{ 54936, "GB18030" TRANSLIT},
{ CP_ISO8859_1, "ISO8859-1" TRANSLIT },
{ CP_ISO8859_2, "ISO8859-2" TRANSLIT },
{ CP_ISO8859_3, "ISO8859-3" TRANSLIT },
{ CP_ISO8859_4, "ISO8859-4" TRANSLIT },
{ CP_ISO8859_5, "ISO8859-5" TRANSLIT },
{ CP_ISO8859_6, "ISO8859-6" TRANSLIT },
{ CP_ISO8859_7, "ISO8859-7" TRANSLIT },
{ CP_ISO8859_8, "ISO8859-8" TRANSLIT },
{ CP_ISO8859_9, "ISO8859-9" TRANSLIT },
{ CP_ISO8859_13, "ISO8859-13" TRANSLIT },
{ CP_ISO8859_15, "ISO8859-15" TRANSLIT },
{ 12000, "UTF-32LE" }
};
const size_t cp_iconv::g_cp_iconv_count = ARRAYSIZE(cp_iconv::g_cp_iconv);
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<IConvCache*>( InterlockedFlushSList(&m_Pool[dstIdx][srcIdx]) );
while ( NULL != pNode )
{
IConvCache * pNext = static_cast<IConvCache*>( 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<const IConvCache*>( 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<IConvCache*>( 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;
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 );
}
EncodingConverter::EncodingConverter( UINT dstCodePage, UINT srcCodePage )
: m_dstCodePage( dstCodePage ),
m_srcCodePage( srcCodePage ),
m_pCvtCache( NULL )
{
}
EncodingConverter::~EncodingConverter()
{
if ( NULL != m_pCvtCache )
{
IConvCachePool::ReturnCache( m_pCvtCache, m_dstCodePage, m_srcCodePage );
}
}
bool EncodingConverter::Initialize()
{
if ( !IsValidIConv() )
{
m_pCvtCache = IConvCachePool::BorrowCache( m_dstCodePage, m_srcCodePage );
}
return IsValidIConv();
}
#include <locale>
using namespace std;
#ifndef _countof
#define _countof(obj) (sizeof(obj)/sizeof(obj[0]))
#endif
const char* DEFAULT_LOCALES[] = {"en_US.UTF-8", "C"};
bool _setLocale(const char * localeName, std::locale ** pLocale)
{
try
{
*pLocale = new std::locale(localeName);
}
catch(const std::exception& e)
{
return false;
}
return true;
}
void setDefaultLocale(const char ** localeName, std::locale ** pLocale)
{
if(!localeName || !_setLocale(*localeName, pLocale))
{
int count = 0;
while(!_setLocale(DEFAULT_LOCALES[count], pLocale) && count < _countof(DEFAULT_LOCALES))
{
count++;
}
if(localeName)
*localeName = count < _countof(DEFAULT_LOCALES)?DEFAULT_LOCALES[count]:NULL;
}
}
SystemLocale::SystemLocale( const char * localeName )
: m_uAnsiCP(CP_UTF8)
, m_pLocale(NULL)
{
setDefaultLocale(&localeName, &m_pLocale);
// Mapping from locale charset to codepage
struct LocaleCP
{
const char* localeName;
UINT codePage;
};
#define CPxxx(cp) { "CP" #cp, cp }
#define ISO8859(n) { "ISO-8859-" #n, CP_ISO8859_ ## n }, \
{ "8859_" #n, CP_ISO8859_ ## n }, \
{ "ISO8859-" #n, CP_ISO8859_ ## n }, \
{ "ISO8859" #n, CP_ISO8859_ ## n }, \
{ "ISO_8859-" #n, CP_ISO8859_ ## n }, \
{ "ISO_8859_" #n, CP_ISO8859_ ## n }
const LocaleCP lcpTable[] = {
{ "utf8", CP_UTF8 },
{ "UTF-8", CP_UTF8 },
{ "BIG5", 950 },
{ "BIG5-HKSCS", 950 },
{ "gb18030", 54936 },
{ "gb2312", 936 },
{ "gbk", 936 },
CPxxx(1252), CPxxx(850), CPxxx(437), CPxxx(874), CPxxx(932), CPxxx(936), CPxxx(949), CPxxx(950),
CPxxx(1250), CPxxx(1251), CPxxx(1253), CPxxx(1254), CPxxx(1255), CPxxx(1256), CPxxx(1257), CPxxx(1258),
ISO8859(1), ISO8859(2), ISO8859(3), ISO8859(4), ISO8859(5), ISO8859(6),
ISO8859(7), ISO8859(8), ISO8859(9), ISO8859(13), ISO8859(15),
{ "UTF-32LE", 12000 }
};
if (localeName)
{
const char *charsetName = strchr(localeName, '.');
charsetName = charsetName ? charsetName + 1 : localeName;
for (const LocaleCP& lcp : lcpTable)
{
if (!strncasecmp(lcp.localeName, charsetName, strnlen_s(lcp.localeName)))
{
m_uAnsiCP = lcp.codePage;
return;
}
}
}
}
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(setlocale(LC_CTYPE, NULL));
return s_Default;
}
// Convert CP1252 to UTF-16 without requiring iconv or taking a lock.
// This is trivial because, except for the 80-9F range, CP1252 bytes
// directly map to the corresponding UTF-16 codepoint.
size_t SystemLocale::CP1252ToUtf16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode )
{
const static WCHAR s_1252Map[] =
{
0x20AC, 0x003F, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x003F, 0x017D, 0x003F,
0x003F, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x003F, 0x017E, 0x0178
};
const unsigned char *usrc = reinterpret_cast<const unsigned char*>(src);
const unsigned char *srcEnd = usrc + cchSrc;
const WCHAR *destEnd = dest + cchDest;
while(usrc < srcEnd && dest < destEnd)
{
DWORD ucode = *usrc++;
*dest++ = (ucode <= 127 || ucode >= 160) ? ucode : s_1252Map[ucode - 128];
}
pErrorCode && (*pErrorCode = (dest == destEnd && usrc != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS);
return cchDest - (destEnd - dest);
}
// Convert UTF-8 to UTF-16 without requiring iconv or taking a lock.
// 0abcdefg -> 0abcdefg 00000000
// 110abcde 10fghijk -> defghijk 00000abc
// 1110abcd 10efghij 10klmnop -> ijklmnop abcdefgh
// 11110abc 10defghi 10jklmno 10pqrstu -> cdfghijk 110110ab nopqrstu 11011lm
size_t SystemLocale::Utf8To16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode )
{
const unsigned char *usrc = reinterpret_cast<const unsigned char*>(src);
const unsigned char *srcEnd = usrc + cchSrc;
const WCHAR *destEnd = dest + cchDest;
DWORD dummyError;
if (!pErrorCode)
{
pErrorCode = &dummyError;
}
*pErrorCode = 0;
while(usrc < srcEnd && dest < destEnd)
{
DWORD ucode = *usrc++;
if(ucode <= 127) // Most common case for ASCII
{
*dest++ = ucode;
}
else if(ucode < 0xC0) // unexpected trailing byte 10xxxxxx
{
goto Invalid;
}
else if(ucode < 0xE0) // 110abcde 10fghijk
{
if (usrc >= srcEnd || *usrc < 0x80 || *usrc > 0xBF ||
(*dest = (ucode & 0x1F)<<6 | (*usrc++ & 0x3F)) < 0x80)
{
*dest = 0xFFFD;
}
dest++;
}
else if(ucode < 0xF0) // 1110abcd 10efghij 10klmnop
{
if (usrc >= srcEnd)
{
goto Invalid;
}
DWORD c1 = *usrc;
if (c1 < 0x80 || c1 > 0xBF)
{
goto Invalid;
}
usrc++;
if (usrc >= srcEnd)
{
goto Invalid;
}
DWORD c2 = *usrc;
if (c2 < 0x80 || c2 > 0xBF)
{
goto Invalid;
}
usrc++;
ucode = (ucode&15)<<12 | (c1&0x3F)<<6 | (c2&0x3F);
if (ucode < 0x800 || (ucode >= 0xD800 && ucode <= 0xDFFF))
{
goto Invalid;
}
*dest++ = ucode;
}
else if(ucode < 0xF8) // 11110abc 10defghi 10jklmno 10pqrstu
{
if (usrc >= srcEnd)
{
goto Invalid;
}
DWORD c1 = *usrc;
if (c1 < 0x80 || c1 > 0xBF)
{
goto Invalid;
}
usrc++;
if (usrc >= srcEnd)
{
goto Invalid;
}
DWORD c2 = *usrc;
if (c2 < 0x80 || c2 > 0xBF)
{
goto Invalid;
}
usrc++;
if (usrc >= srcEnd)
{
goto Invalid;
}
DWORD c3 = *usrc;
if (c3 < 0x80 || c3 > 0xBF)
{
goto Invalid;
}
usrc++;
ucode = (ucode&7)<<18 | (c1&0x3F)<<12 | (c2&0x3F)<<6 | (c3&0x3F);
if (ucode < 0x10000 // overlong encoding
|| ucode > 0x10FFFF // exceeds Unicode range
|| (ucode >= 0xD800 && ucode <= 0xDFFF)) // surrogate pairs
{
goto Invalid;
}
if (dest >= destEnd - 1)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return cchDest - (destEnd - dest);
}
ucode -= 0x10000;
// Lead surrogate
*dest++ = 0xD800 + (ucode >> 10);
// Trail surrogate
*dest++ = 0xDC00 + (ucode & 0x3FF);
}
else // invalid
{
Invalid:
*dest++ = 0xFFFD;
}
}
if (!*pErrorCode)
{
*pErrorCode = (dest == destEnd && usrc != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS;
}
return cchDest - (destEnd - dest);
}
size_t SystemLocale::Utf8To16Strict( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode )
{
const unsigned char *usrc = reinterpret_cast<const unsigned char*>(src);
const unsigned char *srcEnd = usrc + cchSrc;
const WCHAR *destEnd = dest + cchDest;
DWORD dummyError;
if (!pErrorCode)
{
pErrorCode = &dummyError;
}
*pErrorCode = 0;
while(usrc < srcEnd && dest < destEnd)
{
DWORD ucode = *usrc++;
if(ucode <= 127) // Most common case for ASCII
{
*dest++ = ucode;
}
else if(ucode < 0xC0) // unexpected trailing byte 10xxxxxx
{
goto Invalid;
}
else if(ucode < 0xE0) // 110abcde 10fghijk
{
if (usrc >= srcEnd || *usrc < 0x80 || *usrc > 0xBF ||
(*dest = (ucode & 0x1F)<<6 | (*usrc++ & 0x3F)) < 0x80)
{
goto Invalid;
}
dest++;
}
else if(ucode < 0xF0) // 1110abcd 10efghij 10klmnop
{
if (usrc >= srcEnd)
{
goto Invalid;
}
DWORD c1 = *usrc;
if (c1 < 0x80 || c1 > 0xBF)
{
goto Invalid;
}
usrc++;
if (usrc >= srcEnd)
{
goto Invalid;
}
DWORD c2 = *usrc;
if (c2 < 0x80 || c2 > 0xBF)
{
goto Invalid;
}
usrc++;
ucode = (ucode&15)<<12 | (c1&0x3F)<<6 | (c2&0x3F);
if (ucode < 0x800 || (ucode >= 0xD800 && ucode <= 0xDFFF))
{
goto Invalid;
}
*dest++ = ucode;
}
else if(ucode < 0xF8) // 11110abc 10defghi 10jklmno 10pqrstu
{
if (usrc >= srcEnd)
{
goto Invalid;
}
DWORD c1 = *usrc;
if (c1 < 0x80 || c1 > 0xBF)
{
goto Invalid;
}
usrc++;
if (usrc >= srcEnd)
{
goto Invalid;
}
DWORD c2 = *usrc;
if (c2 < 0x80 || c2 > 0xBF)
{
goto Invalid;
}
usrc++;
if (usrc >= srcEnd)
{
goto Invalid;
}
DWORD c3 = *usrc;
if (c3 < 0x80 || c3 > 0xBF)
{
goto Invalid;
}
usrc++;
ucode = (ucode&7)<<18 | (c1&0x3F)<<12 | (c2&0x3F)<<6 | (c3&0x3F);
if (ucode < 0x10000 // overlong encoding
|| ucode > 0x10FFFF // exceeds Unicode range
|| (ucode >= 0xD800 && ucode <= 0xDFFF)) // surrogate pairs
{
goto Invalid;
}
if (dest >= destEnd - 1)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return cchDest - (destEnd - dest);
}
ucode -= 0x10000;
// Lead surrogate
*dest++ = 0xD800 + (ucode >> 10);
// Trail surrogate
*dest++ = 0xDC00 + (ucode & 0x3FF);
}
else // invalid
{
Invalid:
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION;
return 0 ;
}
}
if (!*pErrorCode)
{
*pErrorCode = (dest == destEnd && usrc != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS;
}
return cchDest - (destEnd - dest);
}
size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode )
{
if (cchSrc < 0) {
if (NULL != pErrorCode)
*pErrorCode = ERROR_INVALID_PARAMETER;
return 0;
}
srcCodePage = ExpandSpecialCP( srcCodePage );
if ( dest )
{
if ( srcCodePage == CP_UTF8 )
{
return SystemLocale::Utf8To16( src, cchSrc, dest, cchDest, pErrorCode );
}
else if ( srcCodePage == 1252 )
{
return SystemLocale::CP1252ToUtf16( src, cchSrc, dest, cchDest, pErrorCode );
}
}
EncodingConverter cvt( CP_UTF16, srcCodePage );
if ( !cvt.Initialize() )
{
if ( NULL != pErrorCode )
*pErrorCode = ERROR_INVALID_PARAMETER;
return 0;
}
size_t cchSrcActual = (cchSrc < 0 ? (1+strnlen_s(src)) : cchSrc);
bool hasLoss = false;
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 )
{
if (cchSrc < 0) {
if (NULL != pErrorCode)
*pErrorCode = ERROR_INVALID_PARAMETER;
return 0;
}
srcCodePage = ExpandSpecialCP( srcCodePage );
if ( dest )
{
if ( srcCodePage == CP_UTF8 )
{
return SystemLocale::Utf8To16Strict( src, cchSrc, dest, cchDest, pErrorCode );
}
else if ( srcCodePage == 1252 )
{
return SystemLocale::CP1252ToUtf16( src, cchSrc, dest, cchDest, pErrorCode );
}
}
EncodingConverter cvt( CP_UTF16, srcCodePage );
if ( !cvt.Initialize() )
{
if ( NULL != pErrorCode )
*pErrorCode = ERROR_INVALID_PARAMETER;
return 0;
}
size_t cchSrcActual = (cchSrc < 0 ? (1+strnlen_s(src)) : cchSrc);
bool hasLoss = false;
return cvt.Convert( dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode );
}
size_t SystemLocale::Utf8From16( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode )
{
const WCHAR *srcEnd = src + cchSrc;
char *destEnd = dest + cchDest;
DWORD dummyError;
if (!pErrorCode)
{
pErrorCode = &dummyError;
}
*pErrorCode = 0;
// null dest is a special mode to calculate the output size required.
if (!dest)
{
size_t cbOut = 0;
while (src < srcEnd)
{
DWORD wch = *src++;
if (wch < 128) // most common case.
{
cbOut++;
}
else if (wch < 0x800) // 127 to 2047: 2 bytes
{
cbOut += 2;
}
else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes
{
cbOut += 3;
}
else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes
{
if (src >= srcEnd)
{
cbOut += 3; // lone surrogate at end
}
else if (*src < 0xDC00 || *src > 0xDFFF)
{
cbOut += 3; // low surrogate not followed by high
}
else
{
cbOut += 4;
}
}
else // unexpected trail surrogate
{
cbOut += 3;
}
}
return cbOut;
}
while ( src < srcEnd && dest < destEnd )
{
DWORD wch = *src++;
if (wch < 128) // most common case.
{
*dest++ = wch;
}
else if (wch < 0x800) // 127 to 2047: 2 bytes
{
if (destEnd - dest < 2)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return 0;
}
*dest++ = 0xC0 | (wch >> 6);
*dest++ = 0x80 | (wch & 0x3F);
}
else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes
{
if (destEnd - dest < 3)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return 0;
}
*dest++ = 0xE0 | (wch >> 12);
*dest++ = 0x80 | ((wch >> 6)&0x3F);
*dest++ = 0x80 | (wch &0x3F);
}
else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes
{
if (src >= srcEnd)
{
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end
if (destEnd - dest < 3)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return 0;
}
*dest++ = 0xEF;
*dest++ = 0xBF;
*dest++ = 0xBD;
continue;
}
if (*src < 0xDC00 || *src > 0xDFFF)
{
// low surrogate not followed by high
if (destEnd - dest < 3)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return 0;
}
*dest++ = 0xEF;
*dest++ = 0xBF;
*dest++ = 0xBD;
continue;
}
wch = 0x10000 + ((wch - 0xD800)<<10) + *src++ - 0xDC00;
if (destEnd - dest < 4)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return 0;
}
*dest++ = 0xF0 | (wch >> 18);
*dest++ = 0x80 | ((wch >>12)&0x3F);
*dest++ = 0x80 | ((wch >> 6)&0x3F);
*dest++ = 0x80 | (wch&0x3F);
}
else // unexpected trail surrogate
{
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end
if (destEnd - dest < 3)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return 0;
}
*dest++ = 0xEF;
*dest++ = 0xBF;
*dest++ = 0xBD;
}
}
if (!*pErrorCode)
{
*pErrorCode = (dest == destEnd && src != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS;
}
return *pErrorCode == ERROR_INSUFFICIENT_BUFFER ? 0 : cchDest - (destEnd - dest);
}
size_t SystemLocale::Utf8From16Strict( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode )
{
const WCHAR *srcEnd = src + cchSrc;
char *destEnd = dest + cchDest;
DWORD dummyError;
if (!pErrorCode)
{
pErrorCode = &dummyError;
}
*pErrorCode = 0;
// null dest is a special mode to calculate the output size required.
if (!dest)
{
size_t cbOut = 0;
while (src < srcEnd)
{
DWORD wch = *src++;
if (wch < 128) // most common case.
{
cbOut++;
}
else if (wch < 0x800) // 127 to 2047: 2 bytes
{
cbOut += 2;
}
else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes
{
cbOut += 3;
}
else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes
{
if (src >= srcEnd)
{
cbOut += 3; // lone surrogate at end
}
else if (*src < 0xDC00 || *src > 0xDFFF)
{
cbOut += 3; // low surrogate not followed by high
}
else
{
cbOut += 4;
}
}
else // unexpected trail surrogate
{
cbOut += 3;
}
}
return cbOut;
}
while ( src < srcEnd && dest < destEnd )
{
DWORD wch = *src++;
if (wch < 128) // most common case.
{
*dest++ = wch;
}
else if (wch < 0x800) // 127 to 2047: 2 bytes
{
if (destEnd - dest < 2)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return 0;
}
*dest++ = 0xC0 | (wch >> 6);
*dest++ = 0x80 | (wch & 0x3F);
}
else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes
{
if (destEnd - dest < 3)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return 0;
}
*dest++ = 0xE0 | (wch >> 12);
*dest++ = 0x80 | ((wch >> 6)&0x3F);
*dest++ = 0x80 | (wch &0x3F);
}
else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes
{
if (src >= srcEnd)
{
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end
if (destEnd - dest < 3)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
}
return 0;
}
if (*src < 0xDC00 || *src > 0xDFFF)
{
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // low surrogate not followed by high
if (destEnd - dest < 3)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
}
return 0;
}
wch = 0x10000 + ((wch - 0xD800)<<10) + *src++ - 0xDC00;
if (destEnd - dest < 4)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
return 0;
}
*dest++ = 0xF0 | (wch >> 18);
*dest++ = 0x80 | ((wch >>12)&0x3F);
*dest++ = 0x80 | ((wch >> 6)&0x3F);
*dest++ = 0x80 | (wch&0x3F);
}
else // unexpected trail surrogate
{
*pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end
if (destEnd - dest < 3)
{
*pErrorCode = ERROR_INSUFFICIENT_BUFFER;
}
return 0;
}
}
if (!*pErrorCode)
{
*pErrorCode = (dest == destEnd && src != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS;
}
return *pErrorCode == ERROR_INSUFFICIENT_BUFFER ? 0 : cchDest - (destEnd - dest);
}
size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode )
{
destCodePage = ExpandSpecialCP( destCodePage );
if ( destCodePage == CP_UTF8 )
{
pHasDataLoss && (*pHasDataLoss = 0);
return SystemLocale::Utf8From16( src, cchSrc < 0 ? 1+mplat_wcslen(src) : cchSrc, dest, cchDest, pErrorCode );
}
EncodingConverter cvt( destCodePage, CP_UTF16 );
if ( !cvt.Initialize() )
{
if ( NULL != pErrorCode )
*pErrorCode = ERROR_INVALID_PARAMETER;
return 0;
}
size_t cchSrcActual = (cchSrc < 0 ? (1+mplat_wcslen(src)) : cchSrc);
bool hasLoss = false;
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);
if ( destCodePage == CP_UTF8 )
{
pHasDataLoss && (*pHasDataLoss = 0);
return SystemLocale::Utf8From16Strict( src, cchSrc < 0 ? 1+mplat_wcslen(src) : cchSrc, dest, cchDest, pErrorCode );
}
EncodingConverter cvt(destCodePage, CP_UTF16);
if (!cvt.Initialize())
{
if (NULL != pErrorCode)
*pErrorCode = ERROR_INVALID_PARAMETER;
return 0;
}
size_t cchSrcActual = (cchSrc < 0 ? (1 + mplat_wcslen(src)) : cchSrc);
bool hasLoss = false;
return cvt.Convert(dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode);
}
char * SystemLocale::NextChar( UINT codepage, const char * start, size_t cchBytesLeft )
{
if ( NULL == start || '\0' == *start || 0 == cchBytesLeft )
return const_cast<char *>( start );
char first = *start;
codepage = ExpandSpecialCP( codepage );
if ( CP_UTF8 != codepage )
{
if ( !IsDBCSLeadByteEx(codepage, first) || '\0' == *(start+1) )
return const_cast<char *>( start+1 ); // single byte char or truncated double byte char
else
return const_cast<char *>( 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<char *>( 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<char *>( 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 );
}

View file

@ -1,466 +0,0 @@
#ifndef __msodbcsql_h__
#define __msodbcsql_h__
//---------------------------------------------------------------------------------------------------------------------------------
// File: msodbcsql.h
//
// Contents: Routines that use statement handles. This is a subset of the header file msodbcsql.h in the ODBC Driver.
//
// 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 5.10 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 1300
#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_PARAM_FOCUS (SQL_SOPT_SS_BASE+11)// Direct subsequent calls to parameter related methods to set properties on constituent columns/parameters of container types
#define SQL_SOPT_SS_NAME_SCOPE (SQL_SOPT_SS_BASE+12)// Sets name scope for subsequent catalog function calls
#define SQL_SOPT_SS_COLUMN_ENCRYPTION (SQL_SOPT_SS_BASE+13)// Sets the column encryption mode
// Define old names
#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING
#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)// Column Encryption Enabled or Disabled
#define SQL_COPT_SS_CEKEYSTOREPROVIDER (SQL_COPT_SS_BASE_EX+11)// Load a keystore provider or read the list of loaded keystore providers
#define SQL_COPT_SS_CEKEYSTOREDATA (SQL_COPT_SS_BASE_EX+12)// Communicate with loaded keystore providers
#define SQL_COPT_SS_TRUSTEDCMKPATHS (SQL_COPT_SS_BASE_EX+13)// List of trusted CMK paths
#define SQL_COPT_SS_CEKCACHETTL (SQL_COPT_SS_BASE_EX+14)// Symmetric Key Cache TTL
#define SQL_COPT_SS_AUTHENTICATION (SQL_COPT_SS_BASE_EX+15)// The authentication method used for the connection
#define SQL_COPT_SS_ACCESS_TOKEN (SQL_COPT_SS_BASE_EX+16)// The authentication access token used for the connection
/* SQLSetConnectAttr MS driver additional specific defines. */
#define SQL_COPT_SS_BASE_ADD 1400
#define SQL_COPT_SS_DATACLASSIFICATION_VERSION (SQL_COPT_SS_BASE_ADD + 0) // The flag to Set/Get DATACLASSIFICATION version support
// 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
// Data Classification
#define SQL_CA_SS_DATA_CLASSIFICATION (SQL_CA_SS_BASE+37) // retrieve data classification information
#define SQL_CA_SS_DATA_CLASSIFICATION_VERSION (SQL_CA_SS_BASE+38) // retrieve data classification version
#define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+38)
// 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)
// Defines for use with 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_CEKCACHETTL
#define SQL_CEKCACHETTL_DEFAULT 7200L // TTL value in seconds (2 hours)
//SQL_SOPT_SS_NAME_SCOPE
#define SQL_SS_NAME_SCOPE_TABLE 0L
#define SQL_SS_NAME_SCOPE_TABLE_TYPE 1L
#define SQL_SS_NAME_SCOPE_DEFAULT SQL_SS_NAME_SCOPE_TABLE
// SQL_COPT_SS_ENCRYPT
#define SQL_EN_OFF 0L
#define SQL_EN_ON 1L
// 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
// 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
// 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 AccessToken
{
unsigned int dataSize;
char data[];
} ACCESSTOKEN;
// Keystore Provider interface definition
typedef struct CEKeystoreContext
{
void *envCtx;
void *dbcCtx;
void *stmtCtx;
} CEKEYSTORECONTEXT;
typedef void errFunc(CEKEYSTORECONTEXT *ctx, const wchar_t *msg, ...);
#define IDS_MSG(x) ((const wchar_t*)(x))
typedef struct CEKeystoreProvider
{
wchar_t *Name;
int (__stdcall *Init)(CEKEYSTORECONTEXT *ctx, errFunc *onError);
int (__stdcall *Read)(CEKEYSTORECONTEXT *ctx, errFunc *onError, void *data, unsigned int *len);
int (__stdcall *Write)(CEKEYSTORECONTEXT *ctx, errFunc *onError, void *data, unsigned int len);
int (__stdcall *DecryptCEK)(
CEKEYSTORECONTEXT *ctx,
errFunc *onError,
const wchar_t *keyPath,
const wchar_t *alg,
unsigned char *ecek,
unsigned short ecekLen,
unsigned char **cekOut,
unsigned short *cekLen);
int(__stdcall *EncryptCEK)(
CEKEYSTORECONTEXT *ctx,
errFunc *onError,
const wchar_t *keyPath,
const wchar_t *alg,
unsigned char *cek,
unsigned short cekLen,
unsigned char **ecekOut,
unsigned short *ecekLen);
void (__stdcall *Free)();
} CEKEYSTOREPROVIDER;
// Communication between the driver and application via the CEKeystoreData structure
typedef struct CEKeystoreData
{
wchar_t *name;
unsigned int dataSize;
char data[];
} CEKEYSTOREDATA;
#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_AUTHMODE 0x0000000F
#define AKVCFG_AUTHMODE_ACCESSTOKEN 0
#define AKVCFG_AUTHMODE_CLIENTKEY 1
#define AKVCFG_AUTHMODE_PASSWORD 2
#define AKVCFG_AUTHMODE_INTEGRATED 3
#define AKVCFG_AUTHMODE_CERTIFICATE 4
#define AKVCFG_NOAUTORENEW 0x00000010
#define AKV_CONFIG_PRINCIPALID 1
#define AKV_CONFIG_AUTHSECRET 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
#define AKV_CONFIG_RESET 255
#endif // __msodbcsql_h__

View file

@ -1,809 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: sal_def.h
//
// Contents: Contains the minimal definitions to build on non-Windows platforms
//
// Microsoft Drivers 5.10 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_(size)
#define _In_reads_bytes_(size)
#define _In_reads_bytes_opt_(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_updates_(size)
#define _Inout_updates_bytes_(size)
#define _Inout_updates_bytes_to_(size,count)
#define _Inout_updates_z_(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_writes_bytes_opt_(size)
#define _Out_writes_bytes_to_opt_(size,count)
#define _Out_writes_opt_(size)
#define _Out_writes_z_(size)
#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 _Outptr_result_buffer_(size)
#define _Outref_result_bytebuffer_maybenull_(size)
#define _Outref_result_maybenull_
#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

View file

@ -1,62 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: typedefs_for_linux.h
//
// Microsoft Drivers 5.10 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 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
);
#define FormatMessage FormatMessageA
#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 ERROR_NO_UNICODE_TRANSLATION 1113L
#define ERROR_SUCCESS 0L
typedef int errno_t;
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);
char * mplat_cscpy(char * _Dst, const char * _Src);
BOOL IsDBCSLeadByteEx(__inn UINT CodePage, __inn BYTE TestChar);
typedef HINSTANCE HMODULE; /* HMODULEs can be used in place of HINSTANCEs */
size_t mplat_wcslen( const WCHAR * );
#endif // __linux_typedefs__

View file

@ -1,65 +0,0 @@
#ifndef VERSION_H
#define VERSION_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: version.h
// Contents: Version number constants
//
// Microsoft Drivers 5.10 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
// Increase Major number with backward incompatible breaking changes.
// Increase Minor with backward compatible new functionalities and API changes.
// Increase Patch for backward compatible fixes.
#define SQLVERSION_MAJOR 5
#define SQLVERSION_MINOR 10
#define SQLVERSION_PATCH 1
#define SQLVERSION_BUILD 0
// For previews, set this constant to 1, 2 and so on. Otherwise, set it to 0
#define PREVIEW 0
#define SEMVER_PRERELEASE
// Semantic versioning build metadata, build meta data is not counted in precedence order.
#define SEMVER_BUILDMETA
#if SQLVERSION_BUILD > 0
#undef SEMVER_BUILDMETA
#define SEMVER_BUILDMETA "+" STRINGIFY( SQLVERSION_BUILD )
#endif
// Main version, dot separated 3 digits, Major.Minor.Patch
#define VER_APIVERSION_STR STRINGIFY( SQLVERSION_MAJOR ) "." STRINGIFY( SQLVERSION_MINOR ) "." STRINGIFY( SQLVERSION_PATCH )
// Semantic versioning:
// For stable releases leave SEMVER_PRERELEASE empty
// Otherwise, for pre-releases, add '-' and change it to "beta" with a preview number
#if PREVIEW > 0
#undef SEMVER_PRERELEASE
#define SEMVER_PRERELEASE "beta" STRINGIFY(PREVIEW)
#define VER_FILEVERSION_STR VER_APIVERSION_STR "-" SEMVER_PRERELEASE SEMVER_BUILDMETA
#else
#define VER_FILEVERSION_STR VER_APIVERSION_STR SEMVER_PRERELEASE SEMVER_BUILDMETA
#endif
#define _FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_PATCH,SQLVERSION_BUILD
// PECL package version ('-' or '+' is not allowed) - to support Pickle do not use macros below
#define PHP_SQLSRV_VERSION "5.10.1"
#define PHP_PDO_SQLSRV_VERSION "5.10.1"
#endif // VERSION_H

View file

@ -1,192 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: xplat.h
//
// Contents: include for definition of Windows types for non-Windows platforms
//
// Microsoft Drivers 5.10 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 <iostream>
#include <string>
#include <errno.h>
#include <sql.h>
#include <sqlext.h>
#include <stdarg.h>
#include <cstdlib>
#include <cstdio>
#include <assert.h>
#include <string.h>
// Compiler specific items
#define _cdecl
#define __cdecl
#define __fastcall
#define _inline inline
#define __inline inline
#define __forceinline inline
#define __stdcall
#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
// GCC-specific definitions
#if defined(__GNUC__)
#define MPLAT_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
#endif // defined(__GNUC__)
// Needed to use the standard library min and max
#include <algorithm>
using std::min;
using std::max;
// 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 <stdint.h>.
#include <stdint.h> // 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;
typedef windowsLong_t LONG;
typedef windowsLongLong_t LONGLONG;
typedef windowsULongLong_t ULONGLONG;
#include <assert.h>
#include <stdlib.h>
#include "xplat_intsafe.h"
//-----------------------------------------------------------------------------
// Definitions for UnixODBC Driver Manager
// Define this to enable driver code to conditionalize around UnixODBC Driver
// Manager "quirks"...
#ifndef MPLAT_WWOWH
#define UNIXODBC
#endif
// End definitions for UnixODBC SQL headers
// ----------------------------------------------------------------------------
// WinNT.h
#define CONST const
#define VOID void
#define DLL_PROCESS_ATTACH 1
// Predeclared types from windef needed for remaining WinNT types
// to break circular dependency between WinNT.h and windef.h types.
typedef unsigned char BYTE;
typedef LONG HRESULT;
typedef char CHAR;
typedef CHAR *LPSTR;
#ifdef SQL_WCHART_CONVERT
typedef wchar_t WCHAR;
#else
typedef unsigned short WCHAR;
#endif
typedef WCHAR *LPWSTR;
typedef CONST WCHAR *LPCWSTR;
typedef CONST CHAR *LPCSTR;
typedef void *PVOID;
typedef PVOID HANDLE;
typedef unsigned short WORD;
#define RTL_NUMBER_OF_V1(A) (sizeof(A)/sizeof((A)[0]))
#define ARRAYSIZE(A) RTL_NUMBER_OF_V1(A)
// windef.h
typedef VOID *LPVOID;
typedef CONST void *LPCVOID;
typedef unsigned int UINT;
typedef unsigned char BYTE;
#define _LPCBYTE_DEFINED
typedef BOOL * LPBOOL;
typedef unsigned short WORD;
typedef unsigned short USHORT;
#define WINAPI // TODO __stdcall not portable?
typedef HANDLE HINSTANCE;
// 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
#ifndef IN
#define IN
#endif
#ifndef OUT
#define OUT
#endif
#ifndef OPTIONAL
#define OPTIONAL
#endif
//// ntdef.h
#define __unaligned
#ifndef UNALIGNED
#define UNALIGNED
#endif
//// ??
//typedef ULONG_PTR DWORD_PTR;
#define FALSE ((BOOL)0)
#define TRUE ((BOOL)1)
#include "xplat_winerror.h"
typedef void * HLOCAL;
HLOCAL LocalAlloc(UINT uFlags, SIZE_T uBytes);
HLOCAL LocalFree(HLOCAL hMem);
// End of xplat.h
#endif //__XPLAT_H__

View file

@ -1,62 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: xplat_intsafe.h
//
// Contents: This module defines helper functions to prevent
// integer overflow bugs.
//
// Microsoft Drivers 5.10 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 <limits.h>
//
// typedefs
//
typedef char CHAR;
typedef unsigned char BYTE;
typedef unsigned short USHORT;
typedef unsigned short WORD;
typedef int INT;
typedef unsigned int UINT;
typedef windowsLong_t LONG;
typedef windowsULong_t DWORD;
typedef windowsLongLong_t LONGLONG;
typedef windowsULongLong_t ULONGLONG;
typedef _W64 windowsLong_t LONG_PTR, *PLONG_PTR;
typedef _W64 windowsULong_t ULONG_PTR, *PULONG_PTR;
typedef LONG_PTR SSIZE_T;
typedef ULONG_PTR SIZE_T;
#define DWORD_MAX 0xffffffffUL
#endif // XPLAT_INTSAFE_H

View file

@ -1,131 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: xplat_winerror.h
//
// Contents: Contains the minimal definitions to build on non-Windows platforms
//
// Microsoft Drivers 5.10 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)
#endif // XPLAT_WINERROR_H

View file

@ -1,53 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: xplat_winnls.h
//
// Contents: Contains the minimal definitions to build on non-Windows platforms
//
// Microsoft Drivers 5.10 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 <stdlib.h>
#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;
typedef DWORD LCTYPE;
#define MB_ERR_INVALID_CHARS 0x00000008 // error for invalid chars
typedef WORD LANGID;
#define MAKELANGID(p, s) ((((WORD )(s)) << 10) | (WORD )(p))
#define LANG_NEUTRAL 0x00
#define SUBLANG_DEFAULT 0x01 // user default
BOOL
WINAPI
IsDBCSLeadByte(
__inn BYTE TestChar);
#endif // XPLAT_WINNLS_H

View file

@ -1,83 +0,0 @@
//----------------------------------------------------------------------------------------------------------------------------------
// File: template.rc
//
// Contents: Version resource
//
// Microsoft Drivers 5.10 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_PATCH, SQLVERSION_BUILD
PRODUCTVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_PATCH,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_PATCH, SQLVERSION_BUILD)
VALUE "InternalName", FILE_NAME "\0"
VALUE "LegalCopyright", "Copyright Microsoft Corporation.\0"
VALUE "OriginalFilename", FILE_NAME "\0"
VALUE "ProductName", "Microsoft Drivers for PHP for SQL Server\0"
VALUE "ProductVersion", STRVER3(SQLVERSION_MAJOR,SQLVERSION_MINOR, SQLVERSION_PATCH)
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

Binary file not shown.

View file

@ -0,0 +1 @@
be205b85e922ac291eef93e4bf8f1fbd419841f1