Compare commits

...

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

49 changed files with 1 additions and 22830 deletions

5
debian/changelog vendored
View file

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

78
debian/control vendored
View file

@ -1,78 +0,0 @@
Source: php-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
Vcs-Git: https://salsa.debian.org/php-team/pecl/php-sqlsrv.git
Vcs-Browser: https://salsa.debian.org/php-team/pecl/php-sqlsrv
Homepage: https://pecl.php.net/package/sqlsrv
Package: php-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 (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 SQLSRV driver.
.
This is empty package that depends on default PHP version.
Package: php-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 (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 SQLSRV driver.
.
This is empty package that depends on all PHP versions.
Package: php7.4-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 (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 SQLSRV driver.

33
debian/control.in vendored
View file

@ -1,33 +0,0 @@
Source: php-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
Vcs-Git: https://salsa.debian.org/php-team/pecl/php-sqlsrv.git
Vcs-Browser: https://salsa.debian.org/php-team/pecl/php-sqlsrv
Homepage: https://pecl.php.net/package/sqlsrv
Package: php-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 (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 SQLSRV driver.

26
debian/copyright vendored
View file

@ -1,26 +0,0 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: 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 +0,0 @@
mod debian/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)

2
debian/sqlsrv.ini vendored
View file

@ -1,2 +0,0 @@
; priority=20
extension=sqlsrv.so

3
debian/watch vendored
View file

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

View file

@ -1,121 +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>sqlsrv</name>
<channel>pecl.php.net</channel>
<summary>Microsoft Drivers for PHP for SQL Server (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 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:45</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="da4d3c1d4392f2e63b36250266cdf9b4" name="CREDITS" role="doc" />
<file md5sum="5367e8bd4b5c4f8034578525fbaff449" name="LICENSE" role="doc" />
<file md5sum="154f90f0c73a9fc2e1a7c06548a87a8a" name="config.m4" role="src" />
<file md5sum="ee5f439b8fcce700cbbe2fdeaf3c168b" name="config.w32" role="src" />
<file md5sum="225e7b2143a9c9cf89fe5b2b1021763e" name="conn.cpp" role="src" />
<file md5sum="271969b6a7a84917d01c0a65e7de056e" name="init.cpp" role="src" />
<file md5sum="4b70081ad33ad0df344072c9ac9c3a50" name="php_sqlsrv.h" role="src" />
<file md5sum="19cb3098e652003fbf923a7913920763" name="php_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="0890995e5cf829c078f95168a8c82e13" name="stmt.cpp" role="src" />
<file md5sum="8690cfbbaf8c391d42bb3355272d1943" name="template.rc" role="src" />
<file md5sum="e28c64727a2d671515a4537a4035d751" name="util.cpp" 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>sqlsrv</providesextension>
<extsrcrelease />
</package>

Binary file not shown.

View file

@ -0,0 +1 @@
1e800912656829eaa0525e5196c586ed42458bed

View file

@ -1 +0,0 @@
Microsoft Drivers for PHP for SQL Server

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,81 +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_ENABLE(sqlsrv, whether to enable sqlsrv functions,
[ --disable-sqlsrv Disable sqlsrv functions], yes)
if test "$PHP_SQLSRV" != "no"; then
sqlsrv_src_class="\
conn.cpp \
util.cpp \
init.cpp \
stmt.cpp \
"
shared_src_class="\
shared/core_conn.cpp \
shared/core_results.cpp \
shared/core_stream.cpp \
shared/core_init.cpp \
shared/core_stmt.cpp \
shared/core_util.cpp \
shared/FormattedPrint.cpp \
shared/localizationimpl.cpp \
shared/StringFunctions.cpp \
"
AC_MSG_CHECKING([for SQLSRV headers])
if test -f $srcdir/ext/pdo_sqlsrv/shared/core_sqlsrv.h && test "$PHP_PDO_SQLSRV" != "no" && test "$PHP_PDO_SQLSRV_SHARED" == "no"; then
pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/
shared_src_class=""
elif test -f $srcdir/ext/sqlsrv/shared/core_sqlsrv.h; then
sqlsrv_inc_path=$srcdir/ext/sqlsrv/shared/
elif test -f $srcdir/shared/core_sqlsrv.h; then
sqlsrv_inc_path=$srcdir/shared/
else
AC_MSG_ERROR([Cannot find SQLSRV headers])
fi
AC_MSG_RESULT($sqlsrv_inc_path)
CXXFLAGS="$CXXFLAGS -std=c++11"
CXXFLAGS="$CXXFLAGS -D_FORTIFY_SOURCE=2 -O2"
CXXFLAGS="$CXXFLAGS -fstack-protector"
HOST_OS_ARCH=`uname`
if test "${HOST_OS_ARCH}" = "Darwin"; then
SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-bind_at_load"
else
SQLSRV_SHARED_LIBADD="$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, SQLSRV_SHARED_LIBADD)
PHP_ADD_LIBRARY(odbc, 1, SQLSRV_SHARED_LIBADD)
PHP_ADD_LIBRARY(odbcinst, 1, SQLSRV_SHARED_LIBADD)
PHP_SUBST(SQLSRV_SHARED_LIBADD)
AC_DEFINE(HAVE_SQLSRV, 1, [ ])
PHP_ADD_INCLUDE([$sqlsrv_inc_path])
PHP_NEW_EXTENSION(sqlsrv, $sqlsrv_src_class $shared_src_class, $ext_shared,,-std=c++11)
PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1)
fi

View file

@ -1,55 +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_ENABLE("sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (SQLSRV driver)", "no");
if( PHP_SQLSRV != "no" ) {
sqlsrv_src_class = " conn.cpp init.cpp stmt.cpp util.cpp ";
shared_src_class = " core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp ";
if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") &&
CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")&&
CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_SQLSRV", configure_module_dirname + "\\shared")) {
if (PHP_SQLSRV_SHARED || PHP_PDO_SQLSRV == "no") {
ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "sqlsrv" );
}
CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_SQLSRV_ODBC");
CHECK_HEADER_ADD_INCLUDE("sqlext.h", "CFLAGS_SQLSRV_ODBC");
ADD_FLAG( "LDFLAGS_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug /guard:cf" );
ADD_FLAG( "CFLAGS_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" );
ADD_FLAG( "CFLAGS_SQLSRV", "/EHsc" );
ADD_FLAG( "CFLAGS_SQLSRV", "/GS" );
ADD_FLAG( "CFLAGS_SQLSRV", "/Zi" );
if (VCVERS >= 1913) {
ADD_FLAG("LDFLAGS_SQLSRV", "/d2:-guardspecload");
ADD_FLAG("CFLAGS_SQLSRV", "/Qspectre");
} else if (VCVERS == 1900) {
var subver1900 = probe_binary(PHP_CL).substr(6);
if (subver1900 >= 24241) {
ADD_FLAG("LDFLAGS_SQLSRV", "/d2:-guardspecload");
ADD_FLAG('CFLAGS_SQLSRV', "/Qspectre");
}
}
if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_SQLSRV", "/guard:cf /O2" );
EXTENSION("sqlsrv", sqlsrv_src_class , PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
} else {
WARNING("sqlsrv not enabled; libraries and headers not found");
}
}

View file

@ -1,1558 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: conn.cpp
//
// Contents: Routines that use connection handles
//
// 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_sqlsrv.h"
}
#include "php_sqlsrv_int.h"
#include <string>
#include <sstream>
// *** internal variables and constants ***
namespace {
const int MAX_CONN_VALSTRING_LEN = 256;
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_CONN;
struct date_as_string_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>( conn );
ss_conn->date_as_string = zend_is_true(value);
}
};
struct format_decimals_func
{
static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/)
{
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
ss_conn->format_decimals = zend_is_true(value);
}
};
struct decimal_places_func
{
static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/)
{
// first check if the input is an integer
if (Z_TYPE_P(value) != IS_LONG) {
THROW_SS_ERROR(conn, SQLSRV_ERROR_INVALID_DECIMAL_PLACES);
}
zend_long decimal_places = Z_LVAL_P(value);
if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) {
decimal_places = NO_CHANGE_DECIMAL_PLACES;
}
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
ss_conn->decimal_places = static_cast<short>(decimal_places);
}
};
struct srv_encrypt_set_func {
static void func(connection_option const* option, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn, std::string& conn_str)
{
std::string attr;
if (Z_TYPE_P(value_z) == IS_LONG) {
long val = Z_LVAL_P(value_z);
if (val == 1) {
attr = "yes";
} else if (val == 0) {
attr = "no";
} else {
attr = std::to_string(val);
}
} else if (Z_TYPE_P(value_z) == IS_TRUE || Z_TYPE_P(value_z) == IS_FALSE) {
attr = zend_is_true(value_z) ? "yes" : "no";
} else {
attr = Z_STRVAL_P(value_z);
}
char temp_str[MAX_CONN_VALSTRING_LEN];
snprintf(temp_str, MAX_CONN_VALSTRING_LEN, "%s={%s};", option->odbc_name, attr.c_str());
conn_str += temp_str;
}
};
struct conn_char_set_func {
static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
convert_to_string( value );
const char* encoding = Z_STRVAL_P( value );
size_t encoding_len = Z_STRLEN_P( value );
zend_ulong index = -1;
zend_string* key = NULL;
void* ss_encoding_temp = NULL;
ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, ss_encoding_temp ) {
sqlsrv_encoding* ss_encoding = reinterpret_cast<sqlsrv_encoding*>( ss_encoding_temp );
ss_encoding_temp = NULL;
if (!strnicmp( encoding, ss_encoding->iana, encoding_len )) {
if ( ss_encoding->not_for_connection ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding );
}
conn->set_encoding( static_cast<SQLSRV_ENCODING>(ss_encoding->code_page ));
return;
}
} ZEND_HASH_FOREACH_END();
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding );
}
};
struct bool_conn_str_func {
static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str )
{
char temp_str[MAX_CONN_VALSTRING_LEN];
snprintf(temp_str, MAX_CONN_VALSTRING_LEN, "%s={%s};", option->odbc_name, (zend_is_true(value) ? "yes" : "no"));
conn_str += temp_str;
}
};
struct 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_LONG, "An integer is expected for this keyword" )
char temp_str[MAX_CONN_VALSTRING_LEN];
snprintf(temp_str, MAX_CONN_VALSTRING_LEN, "%s={%ld};", option->odbc_name, Z_LVAL_P(value));
conn_str += temp_str;
}
};
template <unsigned int Attr>
struct int_conn_attr_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_LVAL_P( value )), SQL_IS_UINTEGER );
}
catch( core::CoreException& ) {
throw;
}
}
};
template <unsigned int Attr>
struct bool_conn_attr_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ )
{
try {
core::SQLSetConnectAttr(conn, Attr, reinterpret_cast<SQLPOINTER>((zend_long)zend_is_true(value)), SQL_IS_UINTEGER);
}
catch( core::CoreException& ) {
throw;
}
}
};
//// *** internal functions ***
void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn );
void validate_conn_options( _Inout_ sqlsrv_context& ctx, _In_ zval* user_options_z, _Inout_ char** uid, _Inout_ char** pwd,
_Inout_ HashTable* ss_conn_options_ht );
void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht );
void add_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len,
_Inout_ HashTable* options_ht, _Inout_ zval* data );
void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ HashTable* options_ht, _Inout_ zval* data );
int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z );
int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len );
}
// constants for parameters used by process_params function(s)
int ss_sqlsrv_conn::descriptor;
const char* ss_sqlsrv_conn::resource_name = "ss_sqlsrv_conn";
// connection specific parameter proccessing. Use the generic function specialised to return a connection
// resource.
#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \
rsrc = process_params<ss_sqlsrv_conn>( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, ##__VA_ARGS__ );\
if( rsrc == NULL ) { \
RETURN_FALSE; \
}
namespace SSStmtOptionNames {
const char QUERY_TIMEOUT[]= "QueryTimeout";
const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec";
const char SCROLLABLE[] = "Scrollable";
const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT;
const char DATE_AS_STRING[] = "ReturnDatesAsStrings";
const char FORMAT_DECIMALS[] = "FormatDecimals";
const char DECIMAL_PLACES[] = "DecimalPlaces";
const char DATA_CLASSIFICATION[] = "DataClassification";
}
namespace SSConnOptionNames {
// most of these strings are the same for both the sqlsrv_connect connection option
// and the name put into the connection string. MARS is the only one that's different.
const char APP[] = "APP";
const char AccessToken[] = "AccessToken";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char Authentication[] = "Authentication";
const char CharacterSet[] = "CharacterSet";
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 DecimalPlaces[] = "DecimalPlaces";
const char FormatDecimals[] = "FormatDecimals";
const char DateAsString[] = "ReturnDatesAsStrings";
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 PWD[] = "PWD";
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 UID[] = "UID";
const char WSID[] = "WSID";
const char ComputePool[] = "ComputePool";
const char HostNameInCertificate[] = "HostNameInCertificate";
}
enum SS_CONN_OPTIONS {
SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC,
SS_CONN_OPTION_FORMAT_DECIMALS,
SS_CONN_OPTION_DECIMAL_PLACES,
};
//List of all statement options supported by this driver
const stmt_option SS_STMT_OPTS[] = {
{
SSStmtOptionNames::QUERY_TIMEOUT,
sizeof( SSStmtOptionNames::QUERY_TIMEOUT ),
SQLSRV_STMT_OPTION_QUERY_TIMEOUT,
std::unique_ptr<stmt_option_query_timeout>( new stmt_option_query_timeout )
},
{
SSStmtOptionNames::SEND_STREAMS_AT_EXEC,
sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ),
SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC,
std::unique_ptr<stmt_option_send_at_exec>( new stmt_option_send_at_exec )
},
{
SSStmtOptionNames::SCROLLABLE,
sizeof( SSStmtOptionNames::SCROLLABLE ),
SQLSRV_STMT_OPTION_SCROLLABLE,
std::unique_ptr<stmt_option_ss_scrollable>( new stmt_option_ss_scrollable )
},
{
SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE,
sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ),
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
std::unique_ptr<stmt_option_buffered_query_limit>( new stmt_option_buffered_query_limit )
},
{
SSStmtOptionNames::DATE_AS_STRING,
sizeof( SSStmtOptionNames::DATE_AS_STRING ),
SQLSRV_STMT_OPTION_DATE_AS_STRING,
std::unique_ptr<stmt_option_date_as_string>( new stmt_option_date_as_string )
},
{
SSStmtOptionNames::FORMAT_DECIMALS,
sizeof( SSStmtOptionNames::FORMAT_DECIMALS ),
SQLSRV_STMT_OPTION_FORMAT_DECIMALS,
std::unique_ptr<stmt_option_format_decimals>( new stmt_option_format_decimals )
},
{
SSStmtOptionNames::DECIMAL_PLACES,
sizeof( SSStmtOptionNames::DECIMAL_PLACES),
SQLSRV_STMT_OPTION_DECIMAL_PLACES,
std::unique_ptr<stmt_option_decimal_places>( new stmt_option_decimal_places )
},
{
SSStmtOptionNames::DATA_CLASSIFICATION,
sizeof( SSStmtOptionNames::DATA_CLASSIFICATION ),
SQLSRV_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>{} },
};
// List of all connection options supported by this driver.
const connection_option SS_CONN_OPTS[] = {
{
SSConnOptionNames::APP,
sizeof( SSConnOptionNames::APP ),
SQLSRV_CONN_OPTION_APP,
ODBCConnOptions::APP,
sizeof( ODBCConnOptions::APP ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::AccessToken,
sizeof( SSConnOptionNames::AccessToken ),
SQLSRV_CONN_OPTION_ACCESS_TOKEN,
ODBCConnOptions::AccessToken,
sizeof( ODBCConnOptions::AccessToken),
CONN_ATTR_STRING,
access_token_set_func::func
},
{
SSConnOptionNames::ApplicationIntent,
sizeof( SSConnOptionNames::ApplicationIntent ),
SQLSRV_CONN_OPTION_APPLICATION_INTENT,
ODBCConnOptions::ApplicationIntent,
sizeof( ODBCConnOptions::ApplicationIntent ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::AttachDBFileName,
sizeof( SSConnOptionNames::AttachDBFileName ),
SQLSRV_CONN_OPTION_ATTACHDBFILENAME,
ODBCConnOptions::AttachDBFileName,
sizeof( ODBCConnOptions::AttachDBFileName ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::CharacterSet,
sizeof( SSConnOptionNames::CharacterSet ),
SQLSRV_CONN_OPTION_CHARACTERSET,
ODBCConnOptions::CharacterSet,
sizeof( ODBCConnOptions::CharacterSet ),
CONN_ATTR_STRING,
conn_char_set_func::func
},
{
SSConnOptionNames::Authentication,
sizeof( SSConnOptionNames::Authentication ),
SQLSRV_CONN_OPTION_AUTHENTICATION,
ODBCConnOptions::Authentication,
sizeof( ODBCConnOptions::Authentication ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::ConnectionPooling,
sizeof(SSConnOptionNames::ConnectionPooling),
SQLSRV_CONN_OPTION_CONN_POOLING,
ODBCConnOptions::ConnectionPooling,
sizeof(ODBCConnOptions::ConnectionPooling),
CONN_ATTR_BOOL,
conn_null_func::func
},
{
SSConnOptionNames::Language,
sizeof(SSConnOptionNames::Language),
SQLSRV_CONN_OPTION_LANGUAGE,
ODBCConnOptions::Language,
sizeof(ODBCConnOptions::Language),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::Driver,
sizeof(SSConnOptionNames::Driver),
SQLSRV_CONN_OPTION_DRIVER,
ODBCConnOptions::Driver,
sizeof(ODBCConnOptions::Driver),
CONN_ATTR_STRING,
driver_set_func::func
},
{
SSConnOptionNames::ColumnEncryption,
sizeof(SSConnOptionNames::ColumnEncryption),
SQLSRV_CONN_OPTION_COLUMNENCRYPTION,
ODBCConnOptions::ColumnEncryption,
sizeof(ODBCConnOptions::ColumnEncryption),
CONN_ATTR_STRING,
column_encryption_set_func::func
},
{
SSConnOptionNames::ConnectRetryCount,
sizeof( SSConnOptionNames::ConnectRetryCount ),
SQLSRV_CONN_OPTION_CONN_RETRY_COUNT,
ODBCConnOptions::ConnectRetryCount,
sizeof( ODBCConnOptions::ConnectRetryCount ),
CONN_ATTR_INT,
int_conn_str_func::func
},
{
SSConnOptionNames::ConnectRetryInterval,
sizeof( SSConnOptionNames::ConnectRetryInterval ),
SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL,
ODBCConnOptions::ConnectRetryInterval,
sizeof( ODBCConnOptions::ConnectRetryInterval ),
CONN_ATTR_INT,
int_conn_str_func::func
},
{
SSConnOptionNames::Database,
sizeof( SSConnOptionNames::Database ),
SQLSRV_CONN_OPTION_DATABASE,
ODBCConnOptions::Database,
sizeof( ODBCConnOptions::Database ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::Encrypt,
sizeof( SSConnOptionNames::Encrypt ),
SQLSRV_CONN_OPTION_ENCRYPT,
ODBCConnOptions::Encrypt,
sizeof( ODBCConnOptions::Encrypt ),
CONN_ATTR_MIXED,
srv_encrypt_set_func::func
},
{
SSConnOptionNames::Failover_Partner,
sizeof( SSConnOptionNames::Failover_Partner ),
SQLSRV_CONN_OPTION_FAILOVER_PARTNER,
ODBCConnOptions::Failover_Partner,
sizeof( ODBCConnOptions::Failover_Partner ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::KeyStoreAuthentication,
sizeof( SSConnOptionNames::KeyStoreAuthentication ),
SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION,
ODBCConnOptions::KeyStoreAuthentication,
sizeof( ODBCConnOptions::KeyStoreAuthentication ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
SSConnOptionNames::KeyStorePrincipalId,
sizeof( SSConnOptionNames::KeyStorePrincipalId ),
SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID,
ODBCConnOptions::KeyStorePrincipalId,
sizeof( ODBCConnOptions::KeyStorePrincipalId ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
SSConnOptionNames::KeyStoreSecret,
sizeof( SSConnOptionNames::KeyStoreSecret ),
SQLSRV_CONN_OPTION_KEYSTORE_SECRET,
ODBCConnOptions::KeyStoreSecret,
sizeof( ODBCConnOptions::KeyStoreSecret ),
CONN_ATTR_STRING,
ce_akv_str_set_func::func
},
{
SSConnOptionNames::LoginTimeout,
sizeof( SSConnOptionNames::LoginTimeout ),
SQLSRV_CONN_OPTION_LOGIN_TIMEOUT,
ODBCConnOptions::LoginTimeout,
sizeof( ODBCConnOptions::LoginTimeout ),
CONN_ATTR_INT,
int_conn_attr_func<SQL_ATTR_LOGIN_TIMEOUT>::func
},
{
SSConnOptionNames::MARS_Option,
sizeof( SSConnOptionNames::MARS_Option ),
SQLSRV_CONN_OPTION_MARS,
ODBCConnOptions::MARS_ODBC,
sizeof( ODBCConnOptions::MARS_ODBC ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::MultiSubnetFailover,
sizeof( SSConnOptionNames::MultiSubnetFailover ),
SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER,
ODBCConnOptions::MultiSubnetFailover,
sizeof( ODBCConnOptions::MultiSubnetFailover ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::QuotedId,
sizeof( SSConnOptionNames::QuotedId ),
SQLSRV_CONN_OPTION_QUOTED_ID,
ODBCConnOptions::QuotedId,
sizeof( ODBCConnOptions::QuotedId ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::TraceFile,
sizeof( SSConnOptionNames::TraceFile ),
SQLSRV_CONN_OPTION_TRACE_FILE,
ODBCConnOptions::TraceFile,
sizeof( ODBCConnOptions::TraceFile ),
CONN_ATTR_STRING,
str_conn_attr_func<SQL_ATTR_TRACEFILE>::func
},
{
SSConnOptionNames::TraceOn,
sizeof( SSConnOptionNames::TraceOn ),
SQLSRV_CONN_OPTION_TRACE_ON,
ODBCConnOptions::TraceOn,
sizeof( ODBCConnOptions::TraceOn ),
CONN_ATTR_BOOL,
bool_conn_attr_func<SQL_ATTR_TRACE>::func
},
{
SSConnOptionNames::TransactionIsolation,
sizeof( SSConnOptionNames::TransactionIsolation ),
SQLSRV_CONN_OPTION_TRANS_ISOLATION,
ODBCConnOptions::TransactionIsolation,
sizeof( ODBCConnOptions::TransactionIsolation ),
CONN_ATTR_INT,
int_conn_attr_func<SQL_COPT_SS_TXN_ISOLATION>::func
},
{
SSConnOptionNames::TrustServerCertificate,
sizeof( SSConnOptionNames::TrustServerCertificate ),
SQLSRV_CONN_OPTION_TRUST_SERVER_CERT,
ODBCConnOptions::TrustServerCertificate,
sizeof( ODBCConnOptions::TrustServerCertificate ),
CONN_ATTR_BOOL,
bool_conn_str_func::func
},
{
SSConnOptionNames::TransparentNetworkIPResolution,
sizeof(SSConnOptionNames::TransparentNetworkIPResolution),
SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION,
ODBCConnOptions::TransparentNetworkIPResolution,
sizeof(ODBCConnOptions::TransparentNetworkIPResolution),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::WSID,
sizeof( SSConnOptionNames::WSID ),
SQLSRV_CONN_OPTION_WSID,
ODBCConnOptions::WSID,
sizeof( ODBCConnOptions::WSID ),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::DateAsString,
sizeof( SSConnOptionNames::DateAsString ),
SS_CONN_OPTION_DATE_AS_STRING,
SSConnOptionNames::DateAsString,
sizeof( SSConnOptionNames::DateAsString ),
CONN_ATTR_BOOL,
date_as_string_func::func
},
{
SSConnOptionNames::FormatDecimals,
sizeof( SSConnOptionNames::FormatDecimals),
SS_CONN_OPTION_FORMAT_DECIMALS,
SSConnOptionNames::FormatDecimals,
sizeof( SSConnOptionNames::FormatDecimals),
CONN_ATTR_BOOL,
format_decimals_func::func
},
{
SSConnOptionNames::DecimalPlaces,
sizeof( SSConnOptionNames::DecimalPlaces),
SS_CONN_OPTION_DECIMAL_PLACES,
SSConnOptionNames::DecimalPlaces,
sizeof( SSConnOptionNames::DecimalPlaces),
CONN_ATTR_INT,
decimal_places_func::func
},
{
SSConnOptionNames::ComputePool,
sizeof(SSConnOptionNames::ComputePool),
SQLSRV_CONN_OPTION_COMPUTE_POOL,
ODBCConnOptions::ComputePool,
sizeof(ODBCConnOptions::ComputePool),
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::HostNameInCertificate,
sizeof(SSConnOptionNames::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
};
// sqlsrv_connect( string $serverName [, array $connectionInfo])
//
// Creates a connection resource and opens a connection. By default, the
// connection is attempted using Windows Authentication.
//
// Parameters
// $serverName: A string specifying the name of the server to which a connection
// is being established. An instance name (for example, "myServer\instanceName")
// or port number (for example, "myServer, 1521") can be included as part of
// this string. For a complete description of the options available for this
// parameter, see the Server keyword in the ODBC Driver Connection String
// Keywords section of Using Connection String Keywords with ODBC Driver 11 for SQL Server.
//
// $connectionInfo [OPTIONAL]: An associative array that contains connection
// attributes (for example, array("Database" => "AdventureWorks")).
//
// Return Value
// A PHP connection resource. If a connection cannot be successfully created and
// opened, false is returned
PHP_FUNCTION ( sqlsrv_connect )
{
LOG_FUNCTION( "sqlsrv_connect" );
g_ss_henv_cp->set_func(_FN_);
g_ss_henv_ncp->set_func(_FN_);
reset_errors();
const char* server = NULL;
zval* options_z = NULL;
char* uid = NULL;
char* pwd = NULL;
size_t server_len = 0;
zval conn_z;
ZVAL_UNDEF(&conn_z);
// get the server name and connection options
int result = zend_parse_parameters( ZEND_NUM_ARGS(), "s|a", &server, &server_len, &options_z );
CHECK_CUSTOM_ERROR(( result == FAILURE ), *g_ss_henv_cp, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, "sqlsrv_connect" ) {
RETURN_FALSE;
}
hash_auto_ptr ss_conn_options_ht;
hash_auto_ptr stmts;
ss_sqlsrv_conn* conn = NULL;
try {
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( ss_conn_options_ht );
core::sqlsrv_zend_hash_init( *g_ss_henv_cp, ss_conn_options_ht, 10 /* # of buckets */,
ZVAL_PTR_DTOR, 0 /*persistent*/ );
// Either of g_ss_henv_cp or g_ss_henv_ncp can be used to propagate the error.
::validate_conn_options( *g_ss_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht );
// call the core connect function
conn = static_cast<ss_sqlsrv_conn*>( core_sqlsrv_connect( *g_ss_henv_cp, *g_ss_henv_ncp, &core::allocate_conn<ss_sqlsrv_conn>,
server, uid, pwd, ss_conn_options_ht, ss_error_handler,
SS_CONN_OPTS, NULL, "sqlsrv_connect" ));
SQLSRV_ASSERT( conn != NULL, "sqlsrv_connect: Invalid connection returned. Exception should have been thrown." );
// create a bunch of statements
ALLOC_HASHTABLE( stmts );
core::sqlsrv_zend_hash_init( *g_ss_henv_cp, stmts, 5, NULL /* dtor */, 0 /* persistent */ );
// register the connection with the PHP runtime
ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name);
conn->stmts = stmts;
stmts.transferred();
RETURN_RES( Z_RES(conn_z) );
}
catch( core::CoreException& ) {
if( conn != NULL ) {
conn->invalidate();
}
RETURN_FALSE;
}
catch( ... ) {
DIE("sqlsrv_connect: Unknown exception caught.");
}
}
// sqlsrv_begin_transaction( resource $conn )
//
// Begins a transaction on a specified connection. The current transaction
// includes all statements on the specified connection that were executed after
// the call to sqlsrv_begin_transaction and before any calls to sqlsrv_rollback
// or sqlsrv_commit.
//
// The SQLSRV driver is in auto-commit mode by default. This means that all
// queries are automatically committed upon success unless they have been
// designated as part of an explicit transaction by using
// sqlsrv_begin_transaction.
//
// If sqlsrv_begin_transaction is called after a transaction has already been
// initiated on the connection but not completed by calling either sqlsrv_commit
// or sqlsrv_rollback, the call returns false and an Already in Transaction
// error is added to the error collection.
//
// Parameters
// $conn: The connection with which the transaction is associated.
//
// Return Value
// A Boolean value: true if the transaction was successfully begun. Otherwise, false.
PHP_FUNCTION( sqlsrv_begin_transaction )
{
LOG_FUNCTION( "sqlsrv_begin_transaction" );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
// Return false if already in transaction
CHECK_CUSTOM_ERROR(( conn->in_transaction == true ), *conn, SS_SQLSRV_ERROR_ALREADY_IN_TXN ) {
RETURN_FALSE;
}
try {
core_sqlsrv_begin_transaction( conn );
conn->in_transaction = true;
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE("sqlsrv_begin_transaction: Unknown exception caught.");
}
}
// sqlsrv_close( resource $conn )
// Closes the specified connection and releases associated resources.
//
// Parameters
// $conn: The connection to be closed. Null is a valid value parameter for this
// parameter. This allows the function to be called multiple times in a
// script. For example, if you close a connection in an error condition and
// close it again at the end of the script, the second call to sqlsrv_close will
// return true because the first call to sqlsrv_close (in the error condition)
// sets the connection resource to null.
//
// Return Value
// The Boolean value true unless the function is called with an invalid
// parameter. If the function is called with an invalid parameter, false is
// returned.
PHP_FUNCTION( sqlsrv_close )
{
LOG_FUNCTION( "sqlsrv_close" );
zval* conn_r = NULL;
ss_sqlsrv_conn* conn = NULL;
sqlsrv_context_auto_ptr error_ctx;
reset_errors();
try {
// dummy context to pass to the error handler
error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
error_ctx->set_func(_FN_);
if( zend_parse_parameters(ZEND_NUM_ARGS(), "r", &conn_r) == FAILURE ) {
// Check if it was a zval
int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "z", &conn_r );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
// if sqlsrv_close was called on a non-existent connection then we just return success.
if( Z_TYPE_P( conn_r ) == IS_NULL ) {
RETURN_TRUE;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
SQLSRV_ASSERT( conn_r != NULL, "sqlsrv_close: conn_r was null" );
conn = static_cast<ss_sqlsrv_conn*>( zend_fetch_resource( Z_RES_P( conn_r ), ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor ));
// if sqlsrv_close was called on an already closed connection then we just return success.
if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) {
RETURN_TRUE;
}
CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
conn->set_func(_FN_);
// cause any variables still holding a reference to this to be invalid so they cause
// an error when passed to a sqlsrv function. There's nothing we can do if the
// removal fails, so we just log it and move on.
#if PHP_VERSION_ID < 80000
if (zend_list_close(Z_RES_P(conn_r)) == FAILURE) {
LOG(SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P(conn_r));
}
#else
zend_list_close(Z_RES_P(conn_r));
#endif
// when conn_r is first parsed in zend_parse_parameters, conn_r becomes a zval that points to a zend_resource with a refcount of 2
// need to DELREF here so the refcount becomes 1 and conn_r can be appropriate destroyed by the garbage collector when it goes out of scope
// zend_list_close only destroy the resource pointed to by Z_RES_P( conn_r ), not the zend_resource itself
Z_TRY_DELREF_P(conn_r);
ZVAL_NULL( conn_r );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_close: Unknown exception caught." );
}
}
void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc )
{
// Without sqlsrv_close(), this function is invoked by php during the final clean up stage.
// To prevent memory/resource leaks, no more logging at this point.
//LOG_FUNCTION( "sqlsrv_conn_dtor" );
// get the structure
ss_sqlsrv_conn *conn = static_cast<ss_sqlsrv_conn*>( rsrc->ptr );
SQLSRV_ASSERT( conn != NULL, "sqlsrv_conn_dtor: connection was null");
conn->set_func(__func__);
// close all statements associated with the connection.
sqlsrv_conn_close_stmts( conn );
// close the connection itself.
core_sqlsrv_close( conn );
rsrc->ptr = NULL;
}
// sqlsrv_commit( resource $conn )
//
// Commits the current transaction on the specified connection and returns the
// connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or
// sqlsrv_commit.
// The SQLSRV driver is in auto-commit mode by
// default. This means that all queries are automatically committed upon success
// unless they have been designated as part of an explicit transaction by using
// sqlsrv_begin_transaction. If sqlsrv_commit is called on a connection that is
// not in an active transaction and that was initiated with
// sqlsrv_begin_transaction, the call returns false and a Not in Transaction
// error is added to the error collection.
//
// Parameters
// $conn: The connection on which the transaction is active.
//
// Return Value
// A Boolean value: true if the transaction was successfully committed. Otherwise, false.
PHP_FUNCTION( sqlsrv_commit )
{
LOG_FUNCTION( "sqlsrv_commit" );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
// Return false if not in transaction
CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) {
RETURN_FALSE;
}
try {
conn->in_transaction = false;
core_sqlsrv_commit( conn );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_commit: Unknown exception caught." );
}
}
// sqlsrv_rollback( resource $conn )
//
// Rolls back the current transaction on the specified connection and returns
// the connection to the auto-commit mode. The current transaction includes all
// statements on the specified connection that were executed after the call to
// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or
// sqlsrv_commit.
//
// The SQLSRV driver is in auto-commit mode by default. This
// means that all queries are automatically committed upon success unless they
// have been designated as part of an explicit transaction by using
// sqlsrv_begin_transaction.
//
// If sqlsrv_rollback is called on a connection that is not in an active
// transaction that was initiated with sqlsrv_begin_transaction, the call
// returns false and a Not in Transaction error is added to the error
// collection.
//
// Parameters
// $conn: The connection on which the transaction is active.
//
// Return Value
// A Boolean value: true if the transaction was successfully rolled back. Otherwise, false.
PHP_FUNCTION( sqlsrv_rollback )
{
LOG_FUNCTION( "sqlsrv_rollback" );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
// Return false if not in transaction
CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) {
RETURN_FALSE;
}
try {
conn->in_transaction = false;
core_sqlsrv_rollback( conn );
RETURN_TRUE;
}
catch( core::CoreException& ){
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_rollback: Unknown exception caught." );
}
}
// sqlsrv_client_info
// Returns the ODBC driver's dll name, version and the ODBC version. Also returns
// the version of this extension.
// Parameters:
// $conn - The connection resource by which the client and server are connected.
PHP_FUNCTION( sqlsrv_client_info )
{
LOG_FUNCTION( "sqlsrv_client_info" );
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS( conn, "r", _FN_, 0 );
try {
core_sqlsrv_get_client_info(conn, return_value);
// Add the sqlsrv driver's file version
//Declarations below eliminate compiler warnings about string constant to char* conversions
const char* extver = "ExtensionVer";
std::string filever = VER_FILEVERSION_STR;
add_assoc_string(return_value, extver, &filever[0]);
} catch (core::CoreException&) {
RETURN_FALSE;
} catch (...) {
DIE("sqlsrv_client_info: Unknown exception caught.");
}
}
// sqlsrv_server_info( resource $conn )
//
// Returns information about the server.
//
// Parameters
// $conn: The connection resource by which the client and server are connected.
//
// Return Value
// An associative array with the following keys:
// CurrentDatabase
// The database currently being targeted.
// SQLServerVersion
// The version of SQL Server.
// SQLServerName
// The name of the server.
PHP_FUNCTION( sqlsrv_server_info )
{
try {
LOG_FUNCTION("sqlsrv_server_info");
ss_sqlsrv_conn* conn = NULL;
PROCESS_PARAMS(conn, "r", _FN_, 0);
core_sqlsrv_get_server_info(conn, return_value);
} catch (core::CoreException&) {
RETURN_FALSE;
} catch (...) {
DIE("sqlsrv_server_info: Unknown exception caught.");
}
}
// sqlsrv_prepare( resource $conn, string $tsql [, array $params [, array $options]])
//
// Creates a statement resource associated with the specified connection. A statement
// resource returned by sqlsrv_prepare may be executed multiple times by sqlsrv_execute.
// In between each execution, the values may be updated by changing the value of the
// variables bound. Output parameters cannot be relied upon to contain their results until
// all rows are processed.
//
// Parameters
// $conn: The connection resource associated with the created statement.
//
// $tsql: The Transact-SQL expression that corresponds to the created statement.
//
// $params [OPTIONAL]: An array of values that correspond to parameters in a
// parameterized query. Each parameter may be specified as:
// $value | array($value [, $direction [, $phpType [, $sqlType]]])
// When given just a $value, the direction is default input, and phptype is the value
// given, with the sql type inferred from the php type.
//
// $options [OPTIONAL]: An associative array that sets query properties. The
// table below lists the supported keys and corresponding values:
// QueryTimeout
// Sets the query timeout in seconds. By default, the driver will wait
// indefinitely for results.
// SendStreamParamsAtExec
// Configures the driver to send all stream data at execution (true), or to
// send stream data in chunks (false). By default, the value is set to
// true. For more information, see sqlsrv_send_stream_data.
//
// Return Value
// A statement resource. If the statement resource cannot be created, false is returned.
PHP_FUNCTION( sqlsrv_prepare )
{
LOG_FUNCTION( "sqlsrv_prepare" );
sqlsrv_malloc_auto_ptr<ss_sqlsrv_stmt> stmt;
ss_sqlsrv_conn* conn = NULL;
char *sql = NULL;
zend_long sql_len = 0;
zval* params_z = NULL;
zval* options_z = NULL;
hash_auto_ptr ss_stmt_options_ht;
zval stmt_z;
ZVAL_UNDEF(&stmt_z);
PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, &params_z, &options_z );
try {
if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) {
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( ss_stmt_options_ht );
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */,
ZVAL_PTR_DTOR, 0 /*persistent*/ );
validate_stmt_options( *conn, options_z, ss_stmt_options_ht );
}
if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( sql == NULL ) {
DIE( "sqlsrv_prepare: sql string was null." );
}
stmt = static_cast<ss_sqlsrv_stmt*>( core_sqlsrv_create_stmt( conn, core::allocate_stmt<ss_sqlsrv_stmt>,
ss_stmt_options_ht, SS_STMT_OPTS,
ss_error_handler, NULL ) );
core_sqlsrv_prepare( stmt, sql, sql_len );
if (params_z) {
stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval));
ZVAL_COPY(stmt->params_z, params_z);
}
stmt->prepared = true;
// register the statement with the PHP runtime
ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name );
// store the resource id with the connection so the connection
// can release this statement when it closes.
zend_long next_index = zend_hash_next_free_element( conn->stmts );
core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z);
stmt->conn_index = next_index;
// the statement is now registered with EG( regular_list )
stmt.transferred();
RETURN_RES(Z_RES(stmt_z));
}
catch( core::CoreException& ) {
if( stmt ) {
stmt->conn = NULL;
stmt->~ss_sqlsrv_stmt();
}
if (!Z_ISUNDEF(stmt_z)) {
free_stmt_resource(&stmt_z);
}
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_prepare: Unknown exception caught." );
}
}
// sqlsrv_query( resource $conn, string $tsql [, array $params [, array $options]])
//
// Creates a statement resource associated with the specified connection. The statement
// is immediately executed and may not be executed again using sqlsrv_execute.
//
// Parameters
// $conn: The connection resource associated with the created statement.
//
// $tsql: The Transact-SQL expression that corresponds to the created statement.
//
// $params [OPTIONAL]: An array of values that correspond to parameters in a
// parameterized query. Each parameter may be specified as:
// $value | array($value [, $direction [, $phpType [, $sqlType]]])
// When given just a $value, the direction is default input, and phptype is the value
// given, with the sql type inferred from the php type.
//
// $options [OPTIONAL]: An associative array that sets query properties. The
// table below lists the supported keys and corresponding values:
// QueryTimeout
// Sets the query timeout in seconds. By default, the driver will wait
// indefinitely for results.
// SendStreamParamsAtExec
// Configures the driver to send all stream data at execution (true), or to
// send stream data in chunks (false). By default, the value is set to
// true. For more information, see sqlsrv_send_stream_data.
//
// Return Value
// A statement resource. If the statement resource cannot be created, false is returned.
PHP_FUNCTION( sqlsrv_query )
{
LOG_FUNCTION( "sqlsrv_query" );
ss_sqlsrv_conn* conn = NULL;
sqlsrv_malloc_auto_ptr<ss_sqlsrv_stmt> stmt;
char* sql = NULL;
hash_auto_ptr ss_stmt_options_ht;
size_t sql_len = 0;
zval* options_z = NULL;
zval* params_z = NULL;
zval stmt_z;
ZVAL_UNDEF(&stmt_z);
PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, &params_z, &options_z );
try {
// check for statement options
if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) {
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( ss_stmt_options_ht );
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */, ZVAL_PTR_DTOR,
0 /*persistent*/ );
validate_stmt_options( *conn, options_z, ss_stmt_options_ht );
}
if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) {
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
if( sql == NULL ) {
DIE( "sqlsrv_query: sql string was null." );
}
stmt = static_cast<ss_sqlsrv_stmt*>( core_sqlsrv_create_stmt( conn, core::allocate_stmt<ss_sqlsrv_stmt>,
ss_stmt_options_ht, SS_STMT_OPTS,
ss_error_handler, NULL ) );
if( params_z ) {
stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval));
ZVAL_COPY(stmt->params_z, params_z);
}
stmt->set_func( "sqlsrv_query" );
bind_params( stmt );
// execute the statement
core_sqlsrv_execute( stmt, sql, static_cast<int>( sql_len ) );
// register the statement with the PHP runtime
ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name);
// store the resource id with the connection so the connection
// can release this statement when it closes.
zend_ulong next_index = zend_hash_next_free_element( conn->stmts );
core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z);
stmt->conn_index = next_index;
stmt.transferred();
RETURN_RES(Z_RES(stmt_z));
}
catch( core::CoreException& ) {
if( stmt ) {
stmt->conn = NULL; // tell the statement that it isn't part of the connection so it doesn't try to remove itself
stmt->~ss_sqlsrv_stmt();
}
if (!Z_ISUNDEF(stmt_z)) {
free_stmt_resource(&stmt_z);
}
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_query: Unknown exception caught." );
}
}
void free_stmt_resource( _Inout_ zval* stmt_z )
{
#if PHP_VERSION_ID < 80000
if (FAILURE == zend_list_close(Z_RES_P(stmt_z))) {
LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z));
}
#else
zend_list_close(Z_RES_P(stmt_z));
#endif
ZVAL_NULL( stmt_z );
zval_ptr_dtor(stmt_z);
}
// internal connection functions
namespace {
// must close all statement handles opened by this connection before closing the connection
// no errors are returned, since close should always succeed
void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn )
{
//pre-condition check
SQLSRV_ASSERT(( conn->handle() != NULL ), "sqlsrv_conn_close_stmts: Connection handle is NULL. Trying to destroy an "
"already destroyed connection.");
SQLSRV_ASSERT(( conn->stmts ), "sqlsrv_conn_close_stmts: Connection doesn't contain a statement array." );
// loop through the stmts hash table and destroy each stmt resource so we can close the
// ODBC connection
zval* rsrc_ptr = NULL;
ZEND_HASH_FOREACH_VAL( conn->stmts, rsrc_ptr ) {
try {
int zr = ( rsrc_ptr ) != NULL ? SUCCESS : FAILURE;
CHECK_ZEND_ERROR( zr, *conn, SQLSRV_ERROR_ZEND_HASH ) {
throw core::CoreException();
}
}
catch( core::CoreException& ) {
DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" );
}
// see if the statement is still valid, and if not skip to the next one
// presumably this should never happen because if it's in the list, it should still be valid
// by virtue that a statement resource should remove itself from its connection when it is
// destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource
// and move to the next one.
ss_sqlsrv_stmt* stmt = NULL;
stmt = static_cast<ss_sqlsrv_stmt*>( Z_RES_VAL_P( rsrc_ptr ));
if( stmt == NULL || Z_RES_TYPE_P( rsrc_ptr ) != ss_sqlsrv_stmt::descriptor ) {
LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves"
" from the connection so this shouldn't be out of sync." );
continue;
}
// delete the statement by deleting it from Zend's resource list, which will force its destruction
stmt->conn = NULL;
// this would call the destructor on the statement.
// There's nothing we can do if the removal fails, so we just log it and move on.
#if PHP_VERSION_ID < 80000
if (zend_list_close(Z_RES_P(rsrc_ptr)) == FAILURE) {
LOG(SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", Z_RES_HANDLE_P(rsrc_ptr));
}
#else
zend_list_close(Z_RES_P(rsrc_ptr));
#endif
} ZEND_HASH_FOREACH_END();
zend_hash_destroy( conn->stmts );
FREE_HASHTABLE( conn->stmts );
conn->stmts = NULL;
}
int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z )
{
for( int i=0; SS_CONN_OPTS[i].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i )
{
if( key_len == SS_CONN_OPTS[i].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[i].sqlsrv_name )) {
switch( SS_CONN_OPTS[i].value_type ) {
case CONN_ATTR_BOOL:
// bool attributes can be either strings to be appended to the connection string
// as yes or no or integral connection attributes. This will have to be reworked
// if we ever introduce a boolean connection option that maps to a string connection
// attribute.
break;
case CONN_ATTR_MIXED:
break;
case CONN_ATTR_INT:
{
CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
SS_CONN_OPTS[i].sqlsrv_name )
{
throw ss::SSException();
}
break;
}
case CONN_ATTR_STRING:
{
CHECK_CUSTOM_ERROR( Z_TYPE_P( value_z ) != IS_STRING, ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
SS_CONN_OPTS[i].sqlsrv_name ) {
throw ss::SSException();
}
char* value = Z_STRVAL_P( value_z );
size_t value_len = Z_STRLEN_P( value_z );
bool escaped = core_is_conn_opt_value_escaped( value, value_len );
CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[i].sqlsrv_name ) {
throw ss::SSException();
}
break;
}
case CONN_ATTR_INVALID:
SQLSRV_ASSERT(false, "Should not have reached CONN_ATTR_INVALID.");
break;
}
return SS_CONN_OPTS[i].conn_option_key;
}
}
return SQLSRV_CONN_OPTION_INVALID;
}
int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len )
{
for( int i = 0; SS_STMT_OPTS[i].key != SQLSRV_STMT_OPTION_INVALID; ++i )
{
if( key_len == SS_STMT_OPTS[i].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[i].name )) {
return SS_STMT_OPTS[i].key;
}
}
return SQLSRV_STMT_OPTION_INVALID;
}
void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len,
_Inout_ HashTable* options_ht, _Inout_ zval* data )
{
int option_key = ::get_stmt_option_key( key, key_len );
CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, ZSTR_VAL( key ) ) {
throw ss::SSException();
}
Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too.
core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data );
}
void add_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len,
_Inout_ HashTable* options_ht, _Inout_ zval* data )
{
int option_key = ::get_conn_option_key( ctx, key, key_len, data );
CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, ZSTR_VAL( key ) ) {
throw ss::SSException();
}
Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too.
core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data );
}
// Iterates through the list of statement options provided by the user and validates them
// against the list of supported statement options by this driver. After validation
// creates a Hashtable of statement options to be sent to the core layer for processing.
void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht )
{
try {
if( stmt_options ) {
HashTable* options_ht = Z_ARRVAL_P( stmt_options );
zend_ulong int_key = -1;
zend_string *key = NULL;
zval* data = NULL;
ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) {
int type = HASH_KEY_NON_EXISTENT;
size_t key_len = 0;
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
if (type != HASH_KEY_IS_STRING) {
CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) {
throw core::CoreException();
}
}
else if ( key != NULL ) {
key_len = ZSTR_LEN( key ) + 1;
add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data );
}
else {
DIE( "validate_stmt_options: key was null." );
}
} ZEND_HASH_FOREACH_END();
}
}
catch( core::CoreException& ) {
throw;
}
}
// Iterates through the list of connection options provided by the user and validates them
// against the predefined list of supported connection options by this driver. After validation
// creates a Hashtable of connection options to be sent to the core layer for processing.
void validate_conn_options( _Inout_ sqlsrv_context& ctx, _In_ zval* user_options_z, _Inout_ char** uid, _Inout_ char** pwd, _Inout_ HashTable* ss_conn_options_ht )
{
try {
if( user_options_z ) {
HashTable* options_ht = Z_ARRVAL_P( user_options_z );
zend_ulong int_key = -1;
zend_string *key = NULL;
zval* data = NULL;
ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) {
int type = HASH_KEY_NON_EXISTENT;
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR(( Z_TYPE_P( data ) == IS_NULL || Z_TYPE_P( data ) == IS_UNDEF ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key) {
throw ss::SSException();
}
CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) {
throw ss::SSException();
}
if ( key != NULL ) {
// Length of the key string does not include the null terminator in PHP7, +1 has to be added
size_t key_len = ZSTR_LEN( key ) + 1;
if ( key_len == sizeof( SSConnOptionNames::UID ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::UID )) {
*uid = Z_STRVAL_P( data );
}
else if ( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::PWD )) {
*pwd = Z_STRVAL_P( data );
}
else {
::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data );
}
}
else {
DIE( "validate_conn_options: key was null." );
}
} ZEND_HASH_FOREACH_END();
}
}
catch( core::CoreException& ) {
throw;
}
}
} // namespace

View file

@ -1,736 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: init.cpp
// Contents: initialization routines 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.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_sqlsrv.h"
}
#include "php_sqlsrv_int.h"
#ifdef COMPILE_DL_SQLSRV
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE();
#endif
ZEND_GET_MODULE(g_sqlsrv)
#endif
extern "C" {
ZEND_DECLARE_MODULE_GLOBALS(sqlsrv);
}
// module global variables (initialized in minit and freed in mshutdown)
HashTable* g_ss_errors_ht = NULL;
// special list of warnings to ignore even if warnings are treated as errors
HashTable* g_ss_warnings_to_ignore_ht = NULL;
// encodings we understand
HashTable* g_ss_encodings_ht = NULL;
// Destructors called by Zend for each element in the hashtable
void sqlsrv_error_const_dtor( _Inout_ zval* element );
void sqlsrv_encoding_dtor( _Inout_ zval* element );
// henv context for creating connections
sqlsrv_context* g_ss_henv_cp;
sqlsrv_context* g_ss_henv_ncp;
namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_INIT;
}
// argument info structures for functions, arranged alphabetically.
// see zend_API.h in the PHP sources for more information about these macros
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_begin_transaction_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_cancel_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_close_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_client_info_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_commit_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_configure_arginfo, 0, 0, 2 )
ZEND_ARG_INFO( 0, setting )
ZEND_ARG_INFO( 0, value )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_connect_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, server_name )
ZEND_ARG_ARRAY_INFO( 0, connection_info, 0 )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_errors_arginfo, 0, 0, 0 )
ZEND_ARG_INFO( 0, errors_and_or_warnings )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_execute_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, row )
ZEND_ARG_INFO( 0, offset )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, fetch_type )
ZEND_ARG_INFO( 0, row )
ZEND_ARG_INFO( 0, offset )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_object_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, class_name )
ZEND_ARG_INFO( 0, ctor_params )
ZEND_ARG_INFO( 0, row )
ZEND_ARG_INFO( 0, offset )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_field_metadata_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_free_stmt_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_config_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, setting )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_get_field_arginfo, 0, 0, 2 )
ZEND_ARG_INFO( 0, stmt )
ZEND_ARG_INFO( 0, field_index )
ZEND_ARG_INFO( 0, get_as_type )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_has_rows_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_next_result_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_num_fields_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_num_rows_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_prepare_arginfo, 0, 0, 2 )
ZEND_ARG_INFO( 0, conn )
ZEND_ARG_INFO( 0, tsql )
ZEND_ARG_INFO( 0, params )
ZEND_ARG_INFO( 0, options )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_query_arginfo, 0, 0, 2 )
ZEND_ARG_INFO( 0, conn )
ZEND_ARG_INFO( 0, tsql )
ZEND_ARG_INFO( 0, params )
ZEND_ARG_INFO( 0, options )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX( sqlsrv_rollback_arginfo, 0, 0, 1 )
ZEND_ARG_INFO( 0, conn )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_rows_affected_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_send_stream_data_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_server_info_arginfo, 0 )
ZEND_ARG_INFO( 0, stmt )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_size_arginfo, 0 )
ZEND_ARG_INFO( 0, size )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_sqltype_precision_scale_arginfo, 0 )
ZEND_ARG_INFO( 0, precision )
ZEND_ARG_INFO( 0, scale )
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO( sqlsrv_phptype_encoding_arginfo, 0 )
ZEND_ARG_INFO( 0, encoding )
ZEND_END_ARG_INFO()
// function table with associated arginfo structures
zend_function_entry sqlsrv_functions[] = {
PHP_FE( sqlsrv_connect, sqlsrv_connect_arginfo )
PHP_FE( sqlsrv_close, sqlsrv_close_arginfo )
PHP_FE( sqlsrv_commit, sqlsrv_commit_arginfo )
PHP_FE( sqlsrv_begin_transaction, sqlsrv_begin_transaction_arginfo )
PHP_FE( sqlsrv_rollback, sqlsrv_rollback_arginfo )
PHP_FE( sqlsrv_errors, sqlsrv_errors_arginfo )
PHP_FE( sqlsrv_configure, sqlsrv_configure_arginfo )
PHP_FE( sqlsrv_get_config, sqlsrv_get_config_arginfo )
PHP_FE( sqlsrv_prepare, sqlsrv_prepare_arginfo )
PHP_FE( sqlsrv_execute, sqlsrv_execute_arginfo )
PHP_FE( sqlsrv_query, sqlsrv_query_arginfo )
PHP_FE( sqlsrv_fetch, sqlsrv_fetch_arginfo )
PHP_FE( sqlsrv_get_field, sqlsrv_get_field_arginfo )
PHP_FE( sqlsrv_fetch_array, sqlsrv_fetch_array_arginfo )
PHP_FE( sqlsrv_fetch_object, sqlsrv_fetch_object_arginfo )
PHP_FE( sqlsrv_has_rows, sqlsrv_has_rows_arginfo )
PHP_FE( sqlsrv_num_fields, sqlsrv_num_fields_arginfo )
PHP_FE( sqlsrv_next_result, sqlsrv_next_result_arginfo )
PHP_FE( sqlsrv_num_rows, sqlsrv_num_rows_arginfo )
PHP_FE( sqlsrv_rows_affected, sqlsrv_rows_affected_arginfo )
PHP_FE( SQLSRV_PHPTYPE_STREAM, sqlsrv_phptype_encoding_arginfo )
PHP_FE( SQLSRV_PHPTYPE_STRING, sqlsrv_phptype_encoding_arginfo )
PHP_FE( sqlsrv_client_info, sqlsrv_client_info_arginfo )
PHP_FE( sqlsrv_server_info, sqlsrv_server_info_arginfo )
PHP_FE( sqlsrv_cancel, sqlsrv_cancel_arginfo )
PHP_FE( sqlsrv_free_stmt, sqlsrv_free_stmt_arginfo )
PHP_FE( sqlsrv_field_metadata, sqlsrv_field_metadata_arginfo )
PHP_FE( sqlsrv_send_stream_data, sqlsrv_send_stream_data_arginfo )
PHP_FE( SQLSRV_SQLTYPE_BINARY, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_CHAR, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_DECIMAL, sqlsrv_sqltype_precision_scale_arginfo )
PHP_FE( SQLSRV_SQLTYPE_NCHAR, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_NUMERIC, sqlsrv_sqltype_precision_scale_arginfo )
PHP_FE( SQLSRV_SQLTYPE_NVARCHAR, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_VARBINARY, sqlsrv_sqltype_size_arginfo )
PHP_FE( SQLSRV_SQLTYPE_VARCHAR, sqlsrv_sqltype_size_arginfo )
{NULL, NULL, NULL} // end of the table
};
// the structure returned to Zend that exposes the extension to the Zend engine.
// this structure is defined in zend_modules.h in the PHP sources
zend_module_entry g_sqlsrv_module_entry =
{
STANDARD_MODULE_HEADER,
"sqlsrv",
sqlsrv_functions, // exported function table
// initialization and shutdown functions
PHP_MINIT(sqlsrv),
PHP_MSHUTDOWN(sqlsrv),
PHP_RINIT(sqlsrv),
PHP_RSHUTDOWN(sqlsrv),
PHP_MINFO(sqlsrv),
// version of the extension. Matches the version resource of the extension dll
VER_FILEVERSION_STR,
PHP_MODULE_GLOBALS(sqlsrv),
NULL,
NULL,
NULL,
STANDARD_MODULE_PROPERTIES_EX
};
// Module initialization
// This function is called once per execution of the Zend engine
// We use it to:
// 1) Register our constants. See MSDN or the function below for the exact constants
// we register.
// 2) Register our resource types (connection, statement, and stream types)
// 3) Allocate the environment handles for ODBC connections (1 for non pooled
// connections and 1 for pooled connections)
// 4) Register our INI entries. See MSDN or php_sqlsrv.h for our supported INI entries
PHP_MINIT_FUNCTION(sqlsrv)
{
// SQLSRV_UNUSED( type );
core_sqlsrv_register_severity_checker(ss_severity_check);
// our global variables are initialized in the RINIT function
#if defined(ZTS)
if( ts_allocate_id( &sqlsrv_globals_id,
sizeof( zend_sqlsrv_globals ),
(ts_allocate_ctor) NULL,
(ts_allocate_dtor) NULL ) == 0 )
return FAILURE;
ZEND_TSRMLS_CACHE_UPDATE();
#endif
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ));
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long ));
REGISTER_INI_ENTRIES();
LOG_FUNCTION( "PHP_MINIT_FUNCTION for php_sqlsrv" );
REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ERRORS", SQLSRV_ERR_ERRORS, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_ERR_WARNINGS", SQLSRV_ERR_WARNINGS, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_ERR_ALL", SQLSRV_ERR_ALL, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_OFF", 0, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_INIT", LOG_INIT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_CONN", LOG_CONN, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_STMT", LOG_STMT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_UTIL", LOG_UTIL, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SYSTEM_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ERROR", SEV_ERROR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_WARNING", SEV_WARNING, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_NOTICE", SEV_NOTICE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_LOG_SEVERITY_ALL", -1, CONST_PERSISTENT | CONST_CS ); // -1 so that all the bits are set
// register connection resource
ss_sqlsrv_conn::descriptor = zend_register_list_destructors_ex( sqlsrv_conn_dtor, NULL, "SQL Server Connection",
module_number );
if( ss_sqlsrv_conn::descriptor == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: connection resource registration failed", _FN_ );
return FAILURE;
}
// register statement resources
ss_sqlsrv_stmt::descriptor = zend_register_list_destructors_ex( sqlsrv_stmt_dtor, NULL, "SQL Server Statement", module_number );
if( ss_sqlsrv_stmt::descriptor == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: statement resource regisration failed", _FN_ );
return FAILURE;
}
sqlsrv_sqltype constant_type;
REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_NUMERIC", SQLSRV_FETCH_NUMERIC, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_ASSOC", SQLSRV_FETCH_ASSOC, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_FETCH_BOTH", SQLSRV_FETCH_BOTH, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_NULL", SQLSRV_PHPTYPE_NULL, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_INT", SQLSRV_PHPTYPE_INT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_FLOAT", SQLSRV_PHPTYPE_FLOAT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_DATETIME", SQLSRV_PHPTYPE_DATETIME, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_TABLE", SQLSRV_PHPTYPE_TABLE, CONST_PERSISTENT | CONST_CS);
std::string bin = "binary";
std::string chr = "char";
REGISTER_STRING_CONSTANT( "SQLSRV_ENC_BINARY", &bin[0], CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_ENC_CHAR", &chr[0], CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_NO", 0, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_YES", 1, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_NULLABLE_UNKNOWN", 2, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIGINT", SQL_BIGINT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_BIT", SQL_BIT, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
constant_type.typeinfo.size = 23;
constant_type.typeinfo.scale = 3;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_FLOAT", SQL_FLOAT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_IMAGE", SQL_LONGVARBINARY, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_INT", SQL_INTEGER, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_DECIMAL;
constant_type.typeinfo.size = 19;
constant_type.typeinfo.scale = 4;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_MONEY", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_NTEXT", SQL_WLONGVARCHAR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TEXT", SQL_LONGVARCHAR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_REAL", SQL_REAL, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
constant_type.typeinfo.size = 16;
constant_type.typeinfo.scale = 0;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLDATETIME", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLINT", SQL_SMALLINT, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_DECIMAL;
constant_type.typeinfo.size = 10;
constant_type.typeinfo.scale = 4;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_SMALLMONEY", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_BINARY;
constant_type.typeinfo.size = 8;
constant_type.typeinfo.scale = 0;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIMESTAMP", constant_type.value, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TINYINT", SQL_TINYINT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UDT", SQL_SS_UDT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TABLE", SQL_SS_TABLE, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UNIQUEIDENTIFIER", SQL_GUID, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_XML", SQL_SS_XML, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_DATE;
constant_type.typeinfo.size = 10;
constant_type.typeinfo.scale = 0;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATE", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_SS_TIME2;
constant_type.typeinfo.size = 16;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIME", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_SS_TIMESTAMPOFFSET;
constant_type.typeinfo.size = 34;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIMEOFFSET", constant_type.value, CONST_PERSISTENT | CONST_CS );
constant_type.typeinfo.type = SQL_TYPE_TIMESTAMP;
constant_type.typeinfo.size = 27;
constant_type.typeinfo.scale = 7;
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_DATETIME2", constant_type.value, CONST_PERSISTENT | CONST_CS );
// These constant are defined to provide type checking (type ==SQLSRV_SQLTYPE_DECIMAL).
// There are functions with the same name which accept parameters and is used in binding paramters.
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_DECIMAL", SQL_DECIMAL, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NUMERIC", SQL_NUMERIC, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_CHAR", SQL_CHAR, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NCHAR", SQL_WCHAR, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_VARCHAR", SQL_VARCHAR, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_NVARCHAR", SQL_WVARCHAR, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_BINARY", SQL_BINARY, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT("SQLSRV_SQLTYPE_VARBINARY", SQL_VARBINARY, CONST_PERSISTENT | CONST_CS);
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_IN", SQL_PARAM_INPUT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_OUT", SQL_PARAM_OUTPUT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_PARAM_INOUT", SQL_PARAM_INPUT_OUTPUT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_UNCOMMITTED", SQL_TXN_READ_UNCOMMITTED, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_READ_COMMITTED", SQL_TXN_READ_COMMITTED, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_REPEATABLE_READ", SQL_TXN_REPEATABLE_READ, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SERIALIZABLE", SQL_TXN_SERIALIZABLE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_TXN_SNAPSHOT", SQL_TXN_SS_SNAPSHOT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_NEXT", SQL_FETCH_NEXT, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_PRIOR", SQL_FETCH_PRIOR, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_FIRST", SQL_FETCH_FIRST, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_LAST", SQL_FETCH_LAST, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_ABSOLUTE", SQL_FETCH_ABSOLUTE, CONST_PERSISTENT | CONST_CS );
REGISTER_LONG_CONSTANT( "SQLSRV_SCROLL_RELATIVE", SQL_FETCH_RELATIVE, CONST_PERSISTENT | CONST_CS );
std::string fwd = "forward";
std::string stc = "static";
std::string dyn = "dynamic";
std::string key = "keyset";
std::string buf = "buffered";
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_FORWARD", &fwd[0], CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_STATIC", &stc[0], CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_DYNAMIC", &dyn[0], CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_KEYSET", &key[0], CONST_PERSISTENT | CONST_CS );
REGISTER_STRING_CONSTANT( "SQLSRV_CURSOR_CLIENT_BUFFERED", &buf[0], CONST_PERSISTENT | CONST_CS );
try {
// initialize list of warnings to ignore
g_ss_warnings_to_ignore_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
zend_hash_init( g_ss_warnings_to_ignore_ht, 6, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 );
sqlsrv_error_const error_to_ignore;
// changed database warning
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 5701;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// changed language warning
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 5703;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// option value changed
error_to_ignore.sqlstate = (SQLCHAR*)"01S02";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = -1;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// cursor operation conflict
error_to_ignore.sqlstate = (SQLCHAR*)"01001";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = -1;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// null value eliminated in set function
error_to_ignore.sqlstate = (SQLCHAR*)"01003";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = -1;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// SQL Azure warning: This session has been assigned a tracing id of ..
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 40608;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem( g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
// Full-text search condition contained noise words warning
error_to_ignore.sqlstate = (SQLCHAR*)"01000";
error_to_ignore.native_message = NULL;
error_to_ignore.native_code = 9927;
error_to_ignore.format = false;
if (NULL == zend_hash_next_index_insert_mem(g_ss_warnings_to_ignore_ht, (void*)&error_to_ignore, sizeof(sqlsrv_error_const))) {
throw ss::SSException();
}
}
catch( ss::SSException& ) {
LOG( SEV_ERROR, "PHP_MINIT: warnings hash table failure" );
return FAILURE;
}
try {
// supported encodings
g_ss_encodings_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
zend_hash_init( g_ss_encodings_ht, 3, NULL /*use standard hash function*/, sqlsrv_encoding_dtor /*resource destructor*/, 1 /*persistent*/ );
sqlsrv_encoding sql_enc_char( "char", SQLSRV_ENCODING_CHAR );
if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_char, sizeof( sqlsrv_encoding ))) {
throw ss::SSException();
}
sqlsrv_encoding sql_enc_bin( "binary", SQLSRV_ENCODING_BINARY, true );
if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_bin, sizeof( sqlsrv_encoding ))) {
throw ss::SSException();
}
sqlsrv_encoding sql_enc_utf8( "utf-8", CP_UTF8 );
if (NULL == zend_hash_next_index_insert_mem( g_ss_encodings_ht, (void*)&sql_enc_utf8, sizeof( sqlsrv_encoding ))) {
throw ss::SSException();
}
}
catch( ss::SSException& ) {
LOG( SEV_ERROR, "PHP_RINIT: encodings hash table failure" );
return FAILURE;
}
// initialize list of sqlsrv errors
g_ss_errors_ht = reinterpret_cast<HashTable*>( pemalloc( sizeof( HashTable ), 1 ));
::zend_hash_init( g_ss_errors_ht, 50, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 );
for( int i = 0; SS_ERRORS[i].error_code != UINT_MAX; ++i ) {
if (NULL == ::zend_hash_index_update_mem( g_ss_errors_ht, SS_ERRORS[i].error_code,
&( SS_ERRORS[i].sqlsrv_error ), sizeof( SS_ERRORS[i].sqlsrv_error ))) {
LOG( SEV_ERROR, "%1!s!: Failed to insert data into sqlsrv errors hashtable.", _FN_ );
return FAILURE;
}
}
if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper ) == FAILURE ) {
LOG( SEV_ERROR, "%1!s!: stream registration failed", _FN_ );
return FAILURE;
}
try {
// retrieve the handles for the environments
core_sqlsrv_minit( &g_ss_henv_cp, &g_ss_henv_ncp, ss_error_handler, "PHP_MINIT_FUNCTION for sqlsrv" );
}
catch( core::CoreException& ) {
return FAILURE;
}
catch( ... ) {
LOG( SEV_ERROR, "PHP_RINIT: Unknown exception caught." );
return FAILURE;
}
return SUCCESS;
}
// called by Zend for each parameter in the g_ss_warnings_to_ignore_ht and g_ss_errors_ht hash table when it is destroyed
void sqlsrv_error_const_dtor( _Inout_ zval* elem ) {
sqlsrv_error_const* error_to_ignore = static_cast<sqlsrv_error_const*>( Z_PTR_P(elem) );
pefree(error_to_ignore, 1);
}
// called by Zend for each parameter in the g_ss_encodings_ht hash table when it is destroyed
void sqlsrv_encoding_dtor( _Inout_ zval* elem ) {
sqlsrv_encoding* sql_enc = static_cast<sqlsrv_encoding*>( Z_PTR_P(elem) );
pefree(sql_enc, 1);
}
// Module shutdown function
// Free the environment handles allocated in MINIT and unregister our stream wrapper.
// Resource types and constants are automatically released since we don't flag them as
// persistent when they are registered.
PHP_MSHUTDOWN_FUNCTION(sqlsrv)
{
// SQLSRV_UNUSED( type );
UNREGISTER_INI_ENTRIES();
// clean up the list of sqlsrv errors
zend_hash_destroy( g_ss_errors_ht );
pefree( g_ss_errors_ht, 1 /*persistent*/ );
zend_hash_destroy( g_ss_warnings_to_ignore_ht );
pefree( g_ss_warnings_to_ignore_ht, 1 );
zend_hash_destroy( g_ss_encodings_ht );
pefree( g_ss_encodings_ht, 1 );
core_sqlsrv_mshutdown( *g_ss_henv_cp, *g_ss_henv_ncp );
if( php_unregister_url_stream_wrapper( SQLSRV_STREAM_WRAPPER ) == FAILURE ) {
return FAILURE;
}
return SUCCESS;
}
// Request initialization function
// This function is called once per PHP script execution
// Initialize request globals used in the request, including those that correspond to INI entries.
// Also, we allocate a list of warnings "to ignore", meaning that they are warnings that do not
// trigger errors when WarningsReturnAsErrors is true. If you have warnings that you want ignored
// (such as return values from stored procedures), add them to this collection and they won't be
// returned as errors. Or you could just set WarningsReturnAsErrors to false.
PHP_RINIT_FUNCTION(sqlsrv)
{
// SQLSRV_UNUSED( module_number );
// SQLSRV_UNUSED( type );
#if defined(ZTS)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
SQLSRV_G( warnings_return_as_errors ) = true;
ZVAL_NULL( &SQLSRV_G( errors ));
ZVAL_NULL( &SQLSRV_G( warnings ));
LOG_FUNCTION( "PHP_RINIT for php_sqlsrv" );
// read INI settings
// need to convert const char[] to char[] to avoid converting string to char* warnings
// cannot simply cast const char[] to char* since INI_INT needs the sizeof(param) and the size of char* is always 4 / 8 bytes
char warnings_as_errors[] = INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS;
char severity[] = INI_PREFIX INI_LOG_SEVERITY;
char subsystems[] = INI_PREFIX INI_LOG_SUBSYSTEMS;
char buffered_limit[] = INI_PREFIX INI_BUFFERED_QUERY_LIMIT;
SQLSRV_G( warnings_return_as_errors ) = INI_BOOL( warnings_as_errors );
SQLSRV_G( log_severity ) = INI_INT( severity );
SQLSRV_G( log_subsystems ) = INI_INT( subsystems );
SQLSRV_G( buffered_query_limit ) = INI_INT( buffered_limit );
#ifndef _WIN32
char set_locale_info[] = INI_PREFIX INI_SET_LOCALE_INFO;
SQLSRV_G(set_locale_info) = INI_INT(set_locale_info);
// if necessary, set locale from the environment for ODBC, which MUST be done before any connection
int set_locale = SQLSRV_G(set_locale_info);
if (set_locale == 2) {
setlocale(LC_ALL, "");
}
else if (set_locale == 1) {
setlocale(LC_CTYPE, "");
}
else {
// Do nothing
}
LOG(SEV_NOTICE, INI_PREFIX INI_SET_LOCALE_INFO " = %1!d!", set_locale);
#endif
LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off");
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity ));
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems ));
LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit ));
return SUCCESS;
}
// Request shutdown
// Called at the end of a script's execution
// Simply releases the variables allocated during request initialization.
PHP_RSHUTDOWN_FUNCTION(sqlsrv)
{
// SQLSRV_UNUSED( module_number );
// SQLSRV_UNUSED( type );
LOG_FUNCTION( "PHP_RSHUTDOWN for php_sqlsrv" );
reset_errors();
// destruction
zval_ptr_dtor( &SQLSRV_G( errors ));
zval_ptr_dtor( &SQLSRV_G( warnings ));
return SUCCESS;
}
// Called for php_info(); Displays the INI settings registered and their current values
PHP_MINFO_FUNCTION(sqlsrv)
{
php_info_print_table_start();
php_info_print_table_header(2, "sqlsrv support", "enabled");
php_info_print_table_row(2, "ExtensionVer", VER_FILEVERSION_STR);
php_info_print_table_end();
DISPLAY_INI_ENTRIES();
}
// DllMain for the extension.
#ifdef _WIN32
// Only needed if extension is built shared
#ifdef COMPILE_DL_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,139 +0,0 @@
#ifndef PHP_SQLSRV_H
#define PHP_SQLSRV_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: php_sqlsrv.h
//
// Contents: Declarations for the extension
//
// Comments: Also contains "internal" declarations shared across source files.
//
// Microsoft Drivers 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(sqlsrv)
// global objects for errors and warnings. These are returned by sqlsrv_errors.
zval errors;
zval warnings;
// flags for error handling and logging (set via sqlsrv_configure or php.ini)
zend_long log_severity;
zend_long log_subsystems;
zend_long current_subsystem;
zend_bool warnings_return_as_errors;
zend_long buffered_query_limit;
#ifndef _WIN32
zend_long set_locale_info;
#endif
ZEND_END_MODULE_GLOBALS(sqlsrv)
ZEND_EXTERN_MODULE_GLOBALS(sqlsrv);
// macro used to access the global variables. Use it to make global variable access agnostic to threads
#define SQLSRV_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(sqlsrv, v)
#if defined(ZTS)
ZEND_TSRMLS_CACHE_EXTERN();
#endif
//*********************************************************************************************************************************
// Initialization Functions
//*********************************************************************************************************************************
// variables set during initialization
extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP
#define phpext_sqlsrv_ptr &g_sqlsrv_module_entry
// module initialization
PHP_MINIT_FUNCTION(sqlsrv);
// module shutdown function
PHP_MSHUTDOWN_FUNCTION(sqlsrv);
// request initialization function
PHP_RINIT_FUNCTION(sqlsrv);
// request shutdown function
PHP_RSHUTDOWN_FUNCTION(sqlsrv);
// module info function (info returned by phpinfo())
PHP_MINFO_FUNCTION(sqlsrv);
//*********************************************************************************************************************************
// Functions
//*********************************************************************************************************************************
PHP_FUNCTION(sqlsrv_connect);
PHP_FUNCTION(sqlsrv_begin_transaction);
PHP_FUNCTION(sqlsrv_client_info);
PHP_FUNCTION(sqlsrv_close);
PHP_FUNCTION(sqlsrv_commit);
PHP_FUNCTION(sqlsrv_query);
PHP_FUNCTION(sqlsrv_prepare);
PHP_FUNCTION(sqlsrv_rollback);
PHP_FUNCTION(sqlsrv_server_info);
PHP_FUNCTION(sqlsrv_cancel);
PHP_FUNCTION(sqlsrv_execute);
PHP_FUNCTION(sqlsrv_fetch);
PHP_FUNCTION(sqlsrv_fetch_array);
PHP_FUNCTION(sqlsrv_fetch_object);
PHP_FUNCTION(sqlsrv_field_metadata);
PHP_FUNCTION(sqlsrv_free_stmt);
PHP_FUNCTION(sqlsrv_get_field);
PHP_FUNCTION(sqlsrv_has_rows);
PHP_FUNCTION(sqlsrv_next_result);
PHP_FUNCTION(sqlsrv_num_fields);
PHP_FUNCTION(sqlsrv_num_rows);
PHP_FUNCTION(sqlsrv_rows_affected);
PHP_FUNCTION(sqlsrv_send_stream_data);
// type functions for SQL types.
// to expose SQL Server paramterized types, we use functions that return encoded integers that contain the size/precision etc.
// for example, SQLSRV_SQLTYPE_VARCHAR(4000) matches the usage of SQLSRV_SQLTYPE_INT with the size added.
PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY);
PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR);
PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL);
PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR);
PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC);
PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR);
PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY);
PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR);
// PHP type functions
// strings and streams may have an encoding parameterized, so we use the functions
// the valid encodings are SQLSRV_ENC_BINARY and SQLSRV_ENC_CHAR.
PHP_FUNCTION(SQLSRV_PHPTYPE_STREAM);
PHP_FUNCTION(SQLSRV_PHPTYPE_STRING);
// These functions set and retrieve configuration settings. Configuration settings defined are:
// WarningsReturnAsErrors - treat all ODBC warnings as errors and return false from sqlsrv APIs.
// LogSeverity - combination of severity of messages to log (see Logging)
// LogSubsystems - subsystems within sqlsrv to log messages (see Logging)
PHP_FUNCTION(sqlsrv_configure);
PHP_FUNCTION(sqlsrv_get_config);
// *** extension error functions ***
PHP_FUNCTION(sqlsrv_errors);
#endif /* PHP_SQLSRV_H */

View file

@ -1,469 +0,0 @@
#ifndef PHP_SQLSRV_INT_H
#define PHP_SQLSRV_INT_H
//---------------------------------------------------------------------------------------------------------------------------------
// File: php_sqlsrv_int.h
//
// Contents: Internal declarations for the extension
//
// Comments: Also contains "internal" declarations shared across source files.
//
// 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"
//*********************************************************************************************************************************
// Global variables
//*********************************************************************************************************************************
// INI settings and constants
// (these are defined as macros to allow concatenation as we do below)
#define INI_WARNINGS_RETURN_AS_ERRORS "WarningsReturnAsErrors"
#define INI_LOG_SEVERITY "LogSeverity"
#define INI_LOG_SUBSYSTEMS "LogSubsystems"
#define INI_BUFFERED_QUERY_LIMIT "ClientBufferMaxKBSize"
#define INI_PREFIX "sqlsrv."
#ifndef _WIN32
#define INI_SET_LOCALE_INFO "SetLocaleInfo"
#endif
PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS , "1", PHP_INI_ALL, OnUpdateBool, warnings_return_as_errors,
zend_sqlsrv_globals, sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SEVERITY, "0", PHP_INI_ALL, OnUpdateLong, log_severity, zend_sqlsrv_globals,
sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SUBSYSTEMS, "0", PHP_INI_ALL, OnUpdateLong, log_subsystems, zend_sqlsrv_globals,
sqlsrv_globals )
STD_PHP_INI_ENTRY( INI_PREFIX INI_BUFFERED_QUERY_LIMIT, INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, buffered_query_limit,
zend_sqlsrv_globals, sqlsrv_globals )
#ifndef _WIN32
STD_PHP_INI_ENTRY(INI_PREFIX INI_SET_LOCALE_INFO, "2", PHP_INI_ALL, OnUpdateLong, set_locale_info,
zend_sqlsrv_globals, sqlsrv_globals)
#endif
PHP_INI_END()
//*********************************************************************************************************************************
// Initialization Functions
//*********************************************************************************************************************************
// module global variables (initialized in minit and freed in mshutdown)
extern HashTable* g_ss_errors_ht;
extern HashTable* g_ss_encodings_ht;
extern HashTable* g_ss_warnings_to_ignore_ht;
extern HMODULE g_sqlsrv_hmodule; // used for getting the version information
// henv context for creating connections
extern sqlsrv_context* g_ss_henv_cp;
extern sqlsrv_context* g_ss_henv_ncp;
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
struct ss_sqlsrv_conn : sqlsrv_conn
{
HashTable* stmts;
bool date_as_string;
bool format_decimals; // flag set to turn on formatting for values of decimal / numeric types
short decimal_places; // number of decimal digits to show in a result set unless format_numbers is false
bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls
// static variables used in process_params
static const char* resource_name;
static int descriptor;
// initialize with default values
ss_sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv ) :
sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM ),
stmts( NULL ),
date_as_string( false ),
format_decimals( false ),
decimal_places( NO_CHANGE_DECIMAL_PLACES ),
in_transaction( false )
{
}
};
// resource destructor
void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc );
//*********************************************************************************************************************************
// Statement
//*********************************************************************************************************************************
// holds the field names for reuse by sqlsrv_fetch_array/object as keys
struct sqlsrv_fetch_field_name {
char* name;
SQLLEN len;
};
struct stmt_option_ss_scrollable : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z );
};
// This object inherits and overrides the callbacks necessary
struct ss_sqlsrv_stmt : public sqlsrv_stmt {
ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv );
virtual ~ss_sqlsrv_stmt( void );
void new_result_set( void );
// driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants
sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream );
bool prepared; // whether the statement has been prepared yet (used for error messages)
zend_ulong conn_index; // index into the connection hash that contains this statement structure
zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute
sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys
int fetch_fields_count;
// static variables used in process_params
static const char* resource_name;
static int descriptor;
};
// holds the field names for reuse by sqlsrv_fetch_array/object as keys
struct sqlsrv_fetch_field {
char* name;
unsigned int len;
};
// holds the stream param and the encoding that it was assigned
struct sqlsrv_stream_encoding {
zval* stream_z;
unsigned int encoding;
sqlsrv_stream_encoding( _In_ zval* str_z, _In_ unsigned int enc ) :
stream_z( str_z ), encoding( enc )
{
}
};
// resource destructor
void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc );
// "internal" statement functions shared by functions in conn.cpp and stmt.cpp
void bind_params( _Inout_ ss_sqlsrv_stmt* stmt );
bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function
);
void free_odbc_resources( ss_sqlsrv_stmt* stmt );
void free_stmt_resource( _Inout_ zval* stmt_z );
//*********************************************************************************************************************************
// Errors
//*********************************************************************************************************************************
// represents the mapping between an error_code and the corresponding error message.
struct ss_error {
unsigned int error_code;
sqlsrv_error_const sqlsrv_error;
};
// List of all driver specific error codes.
enum SS_ERROR_CODES {
SS_SQLSRV_ERROR_ALREADY_IN_TXN = SQLSRV_ERROR_DRIVER_SPECIFIC,
SS_SQLSRV_ERROR_NOT_IN_TXN,
SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER,
SS_SQLSRV_ERROR_REGISTER_RESOURCE,
SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY,
SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE,
SS_SQLSRV_ERROR_INVALID_FETCH_TYPE,
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY,
SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED,
SS_SQLSRV_ERROR_ZEND_BAD_CLASS,
SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE,
SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE,
SS_SQLSRV_ERROR_INVALID_OPTION,
SS_SQLSRV_ERROR_PARAM_INVALID_INDEX,
SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION,
SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION,
SS_SQLSRV_ERROR_VAR_REQUIRED,
SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING,
SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED,
SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF,
SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED
};
extern ss_error SS_ERRORS[];
bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ int warning, _In_opt_ va_list* print_args );
// convert from the default encoding specified by the "CharacterSet"
// connection option to UTF-16. mbcs_len and utf16_len are sizes in
// bytes. The return is the number of UTF-16 characters in the string
// returned in utf16_out_string.
unsigned int convert_string_from_default_encoding( _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) wchar_t* utf16_out_string,
_In_ unsigned int utf16_len, bool use_strict_conversion = false );
// create a wide char string from the passed in mbcs string. NULL is returned if the string
// could not be created. No error is posted by this function. utf16_len is the number of
// wchar_t characters, not the number of bytes.
SQLWCHAR* utf16_string_from_mbcs_string( _In_ unsigned int 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 );
// *** internal error macros and functions ***
bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function,
sqlsrv_error const* ssphp, ... );
void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function,
sqlsrv_error const* ssphp, ... );
void __cdecl sqlsrv_error_dtor( zend_resource *rsrc );
// release current error lists and set to NULL
inline void reset_errors( void )
{
if( Z_TYPE( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE( SQLSRV_G( errors )) != IS_NULL ) {
DIE( "sqlsrv_errors contains an invalid type" );
}
if( Z_TYPE( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE( SQLSRV_G( warnings )) != IS_NULL ) {
DIE( "sqlsrv_warnings contains an invalid type" );
}
if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY ) {
zend_hash_destroy( Z_ARRVAL( SQLSRV_G( errors )));
FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( errors )));
}
if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY ) {
zend_hash_destroy( Z_ARRVAL( SQLSRV_G( warnings )));
FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( warnings )));
}
ZVAL_NULL( &SQLSRV_G( errors ));
ZVAL_NULL( &SQLSRV_G( warnings ));
}
#define THROW_SS_ERROR( ctx, error_code, ... ) \
(void)call_error_handler( ctx, error_code, 0 /*warning*/, ## __VA_ARGS__ ); \
throw ss::SSException();
class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr > {
public:
sqlsrv_context_auto_ptr( void ) :
sqlsrv_auto_ptr<sqlsrv_context, sqlsrv_context_auto_ptr >( NULL )
{
}
sqlsrv_context_auto_ptr( _Inout_opt_ const sqlsrv_context_auto_ptr& src ) :
sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >( src )
{
}
// free the original pointer and assign a new pointer. Use NULL to simply free the pointer.
void reset( _In_opt_ sqlsrv_context* ptr = NULL )
{
if( _ptr ) {
_ptr->~sqlsrv_context();
sqlsrv_free( (void*) _ptr );
}
_ptr = ptr;
}
sqlsrv_context* operator=( _In_opt_ sqlsrv_context* ptr )
{
return sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >::operator=( ptr );
}
void operator=( _Inout_opt_ sqlsrv_context_auto_ptr& src )
{
sqlsrv_context* p = src.get();
src.transferred();
this->_ptr = p;
}
};
//*********************************************************************************************************************************
// Logging
//*********************************************************************************************************************************
#define LOG_FUNCTION( function_name ) \
const char* _FN_ = function_name; \
SQLSRV_G( current_subsystem ) = current_log_subsystem; \
core_sqlsrv_register_severity_checker(ss_severity_check); \
LOG(SEV_NOTICE, "%1!s!: entering", _FN_);
// check the global variables of sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool ss_severity_check(_In_ unsigned int severity);
// subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise.
enum logging_subsystems {
LOG_INIT = 0x01,
LOG_CONN = 0x02,
LOG_STMT = 0x04,
LOG_UTIL = 0x08,
LOG_ALL = -1,
};
//*********************************************************************************************************************************
// Common function wrappers
// have to place this namespace before the utility functions
// otherwise can't compile in Linux because 'ss' not defined
//*********************************************************************************************************************************
namespace ss {
// an error which occurred in our SQLSRV driver
struct SSException : public core::CoreException {
SSException()
{
}
};
inline void zend_register_resource( _Inout_ zval& rsrc_result, _Inout_ void* rsrc_pointer, _In_ int rsrc_type, _In_opt_ const char* rsrc_name)
{
int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE);
CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast<sqlsrv_context*>( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE,
rsrc_name ) {
throw ss::SSException();
}
Z_TYPE_INFO(rsrc_result) = IS_RESOURCE_EX;
}
} // namespace ss
//*********************************************************************************************************************************
// Utility Functions
//*********************************************************************************************************************************
// generic function used to validate parameters to a PHP function.
// Register an invalid parameter error and returns NULL when parameters don't match the spec given.
template <typename H>
inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, _In_ char const* param_spec, _In_ const char* calling_func, _In_ size_t param_count, ... )
{
// SQLSRV_UNUSED( return_value );
zval* rsrc;
H* h = NULL;
// reset the errors from the previous API call
reset_errors();
if( ZEND_NUM_ARGS() > param_count + 1 ) {
DIE( "Param count and argument count don't match." );
return NULL; // for static analysis tools
}
try {
if( param_count > 6 ) {
DIE( "Param count cannot exceed 6" );
return NULL; // for static analysis tools
}
void* arr[6];
va_list vaList;
va_start(vaList, param_count); //set the pointer to first argument
for(size_t i = 0; i < param_count; ++i) {
arr[i] = va_arg(vaList, void*);
}
va_end(vaList);
int result = SUCCESS;
// dummy context to pass to the error handler
sqlsrv_context error_ctx( 0, ss_error_handler, NULL );
error_ctx.set_func( calling_func );
switch( param_count ) {
case 0:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc );
break;
case 1:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0] );
break;
case 2:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1] );
break;
case 3:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2] );
break;
case 4:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2], arr[3] );
break;
case 5:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2], arr[3], arr[4] );
break;
case 6:
result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast<char*>( param_spec ), &rsrc, arr[0],
arr[1], arr[2], arr[3], arr[4], arr[5] );
break;
default:
{
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func );
break;
}
}
CHECK_CUSTOM_ERROR(( result == FAILURE ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) {
throw ss::SSException();
}
// get the resource registered
h = static_cast<H*>( zend_fetch_resource(Z_RES_P(rsrc), H::resource_name, H::descriptor ));
CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) {
throw ss::SSException();
}
h->set_func( calling_func );
}
catch( core::CoreException& ) {
return NULL;
}
catch ( ... ) {
DIE( "%1!s!: Unknown exception caught in process_params.", calling_func );
}
return h;
}
#endif /* PHP_SQLSRV_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,2215 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: stmt.cpp
//
// Contents: Routines that use statement handles
//
// 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.
//---------------------------------------------------------------------------------------------------------------------------------
// *** header files ***
extern "C" {
#include "php_sqlsrv.h"
}
#include "php_sqlsrv_int.h"
#ifdef _WIN32
#include <sal.h>
#endif // _WIN32
//
// *** internal variables and constants ***
//
// our resource descriptor assigned in minit
int ss_sqlsrv_stmt::descriptor = 0;
const char* ss_sqlsrv_stmt::resource_name = "ss_sqlsrv_stmt";
namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_STMT;
// constants used as invalid types for type errors
const zend_uchar PHPTYPE_INVALID = SQLSRV_PHPTYPE_INVALID;
const int SQLSRV_INVALID_PRECISION = -1;
const SQLUINTEGER SQLSRV_INVALID_SIZE = (~1U);
const int SQLSRV_INVALID_SCALE = -1;
const int SQLSRV_SIZE_MAX_TYPE = -1;
// constants for maximums in SQL Server
const int SQL_SERVER_MAX_FIELD_SIZE = 8000;
const int SQL_SERVER_MAX_PRECISION = 38;
// default class used when no class is specified by sqlsrv_fetch_object
const char STDCLASS_NAME[] = "stdclass";
const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1;
// map a Zend PHP type constant to our constant type
enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = {
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_NULL,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INT,
SQLSRV_PHPTYPE_FLOAT,
SQLSRV_PHPTYPE_STRING,
SQLSRV_PHPTYPE_TABLE,
SQLSRV_PHPTYPE_DATETIME,
SQLSRV_PHPTYPE_STREAM,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INVALID,
SQLSRV_PHPTYPE_INVALID
};
// constant strings used for the field metadata results
// (char to avoid having to cast them where they are used)
namespace FieldMetaData {
const char* NAME = "Name";
const char* TYPE = "Type";
const char* SIZE = "Size";
const char* PREC = "Precision";
const char* SCALE = "Scale";
const char* NULLABLE = "Nullable";
}
/* internal functions */
void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval );
SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt* stmt);
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names );
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size,
_Out_ SQLSMALLINT* decimal_digits );
sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string );
void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt );
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype type );
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding );
zval* parse_param_array(_Inout_ ss_sqlsrv_stmt* stmt, _Inout_ HashTable* param_ht, zend_ulong index,
_Out_ SQLSMALLINT& direction, _Out_ SQLSRV_PHPTYPE& php_out_type,
_Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits);
}
// query options for cursor types
namespace SSCursorTypes {
const char QUERY_OPTION_SCROLLABLE_STATIC[] = "static";
const char QUERY_OPTION_SCROLLABLE_DYNAMIC[] = "dynamic";
const char QUERY_OPTION_SCROLLABLE_KEYSET[] = "keyset";
const char QUERY_OPTION_SCROLLABLE_FORWARD[] = "forward";
const char QUERY_OPTION_SCROLLABLE_BUFFERED[] = "buffered";
}
ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv ) :
sqlsrv_stmt( c, handle, e, drv ),
prepared( false ),
conn_index( -1 ),
params_z( NULL ),
fetch_field_names( NULL ),
fetch_fields_count ( 0 )
{
core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) );
// inherit other values based on the corresponding connection options
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
date_as_string = ss_conn->date_as_string;
format_decimals = ss_conn->format_decimals;
decimal_places = ss_conn->decimal_places;
}
ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void )
{
if( fetch_field_names != NULL ) {
for( int i=0; i < fetch_fields_count; ++i ) {
sqlsrv_free( fetch_field_names[i].name );
}
sqlsrv_free( fetch_field_names );
}
if( params_z ) {
zval_ptr_dtor( params_z );
sqlsrv_free(params_z);
}
}
// to be called whenever a new result set is created, such as after an
// execute or next_result. Resets the state variables and calls the subclass.
void ss_sqlsrv_stmt::new_result_set( void )
{
if( fetch_field_names != NULL ) {
for( int i=0; i < fetch_fields_count; ++i ) {
sqlsrv_free( fetch_field_names[i].name );
}
sqlsrv_free( fetch_field_names );
}
fetch_field_names = NULL;
fetch_fields_count = 0;
sqlsrv_stmt::new_result_set();
}
// Returns a php type for a given sql type. Also sets the encoding wherever applicable.
sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream )
{
sqlsrv_phptype ss_phptype;
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
switch( sql_type ) {
case SQL_BIGINT:
case SQL_CHAR:
case SQL_DECIMAL:
case SQL_GUID:
case SQL_NUMERIC:
case SQL_WCHAR:
case SQL_SS_VARIANT:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = this->conn->encoding();
break;
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
if( prefer_string_to_stream || size != SQL_SS_LENGTH_UNLIMITED ) {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = this->conn->encoding();
}
else {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
ss_phptype.typeinfo.encoding = this->conn->encoding();
}
break;
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
break;
case SQL_BINARY:
case SQL_LONGVARBINARY:
case SQL_VARBINARY:
case SQL_SS_UDT:
if( prefer_string_to_stream ) {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
else {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
break;
case SQL_FLOAT:
case SQL_REAL:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
break;
case SQL_SS_TABLE:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_TABLE;
break;
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
case SQL_TYPE_TIMESTAMP:
if (this->date_as_string) {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
ss_phptype.typeinfo.encoding = this->conn->encoding();
}
else {
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
}
break;
default:
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
break;
}
return ss_phptype;
}
// statement specific parameter proccessing. Uses the generic function specialised to return a statement
// resource.
#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \
rsrc = process_params<ss_sqlsrv_stmt>( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, ## __VA_ARGS__ );\
if( rsrc == NULL ) { \
RETURN_FALSE; \
}
// sqlsrv_execute( resource $stmt )
//
// Executes a previously prepared statement. See sqlsrv_prepare for information
// on preparing a statement for execution.
//
// This function is ideal for executing a prepared statement multiple times with
// different parameter values. See the MSDN documentation
//
// Parameters
// $stmt: A resource specifying the statement to be executed. For more
// information about how to create a statement resource, see sqlsrv_prepare.
//
// Return Value
// A Boolean value: true if the statement was successfully executed. Otherwise, false.
PHP_FUNCTION( sqlsrv_execute )
{
LOG_FUNCTION( "sqlsrv_execute" );
ss_sqlsrv_stmt* stmt = NULL;
try {
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
CHECK_CUSTOM_ERROR(( !stmt->prepared ), stmt, SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED ) {
throw ss::SSException();
}
// prepare for the next execution by flushing anything remaining in the result set
if( stmt->executed ) {
// to prepare to execute the next statement, we skip any remaining results (and skip parameter finalization too)
while( stmt->past_next_result_end == false ) {
core_sqlsrv_next_result( stmt, false, false );
}
}
// bind parameters before executing
bind_params( stmt );
core_sqlsrv_execute( stmt );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_execute: Unknown exception caught." );
}
}
// sqlsrv_fetch( resource $stmt )
//
// Makes the next row of a result set available for reading. Use
// sqlsrv_get_field to read fields of the row.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement. A
// statement must be executed before results can be retrieved. For information
// on executing a statement, see sqlsrv_query and sqlsrv_execute.
//
// Return Value
// If the next row of the result set was successfully retrieved, true is
// returned. If there are no more results in the result set, null is
// returned. If an error occured, false is returned
PHP_FUNCTION( sqlsrv_fetch )
{
LOG_FUNCTION( "sqlsrv_fetch" );
ss_sqlsrv_stmt* stmt = NULL;
// NOTE: zend_parse_parameter expect zend_long when the type spec is 'l',and core_sqlsrv_fetch expect short int
zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
zend_long fetch_offset = 0; // default value for parameter if one isn't supplied
// take only the statement resource
PROCESS_PARAMS( stmt, "r|ll", _FN_, 2, &fetch_style, &fetch_offset );
try {
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
throw ss::SSException();
}
bool result = core_sqlsrv_fetch( stmt, static_cast<SQLSMALLINT>(fetch_style), fetch_offset );
if( !result ) {
RETURN_NULL();
}
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_fetch: Unknown exception caught." );
}
}
// sqlsrv_fetch_array( resource $stmt [, int $fetchType] )
//
// Retrieves the next row of data as an array.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
// $fetchType [OPTIONAL]: A predefined constant. See SQLSRV_FETCH_TYPE in php_sqlsrv.h
//
// Return Value
// If a row of data is retrieved, an array is returned. If there are no more
// rows to retrieve, null is returned. If an error occurs, false is returned.
// Based on the value of the $fetchType parameter, the returned array can be a
// numerically indexed array, an associative array, or both. By default, an
// array with both numeric and associative keys is returned. The data type of a
// value in the returned array will be the default PHP data type. For
// information about default PHP data types, see Default PHP Data Types.
PHP_FUNCTION( sqlsrv_fetch_array )
{
LOG_FUNCTION( "sqlsrv_fetch_array" );
ss_sqlsrv_stmt* stmt = NULL;
zend_long fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied
zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
zend_long fetch_offset = 0; // default value for parameter if one isn't supplied
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE),
// fetch style (see SQLSRV_SCROLL_* constants) and fetch offset
PROCESS_PARAMS( stmt, "r|lll", _FN_, 3, &fetch_type, &fetch_style, &fetch_offset );
try {
CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_TYPE ) {
throw ss::SSException();
}
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
throw ss::SSException();
}
bool result = core_sqlsrv_fetch( stmt, static_cast<SQLSMALLINT>(fetch_style), fetch_offset );
if( !result ) {
RETURN_NULL();
}
zval fields;
ZVAL_UNDEF( &fields );
fetch_fields_common( stmt, fetch_type, fields, true /*allow_empty_field_names*/ );
RETURN_ARR( Z_ARRVAL( fields ));
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_fetch_array: Unknown exception caught." );
}
}
// sqlsrv_field_metadata( resource $stmt )
//
// Retrieves metadata for the fields of a prepared statement. For information
// about preparing a statement, see sqlsrv_query or sqlsrv_prepare. Note that
// sqlsrv_field_metadata can be called on any prepared statement, pre- or
// post-execution.
//
// Parameters
// $stmt: A statement resource for which field metadata is sought.
//
// Return Value
// retrieve an array of metadata for the current result set on a statement. Each element of the
// array is a sub-array containing 5 elements accessed by key:
// name - name of the field.
// type - integer of the type. Can be compared against the SQLSRV_SQLTYPE constants.
// size - length of the field. null if the field uses precision and scale instead.
// precision - number of digits in a numeric field. null if the field uses size.
// scale - number of decimal digits in a numeric field. null if the field uses sizes.
// is_nullable - if the field may contain a NULL instead of a value
// false is returned if an error occurs retrieving the metadata
PHP_FUNCTION( sqlsrv_field_metadata )
{
sqlsrv_stmt* stmt = NULL;
LOG_FUNCTION( "sqlsrv_field_metadata" );
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// get the number of fields in the resultset and its metadata if not exists
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
if (stmt->data_classification) {
core_sqlsrv_sensitivity_metadata(stmt);
}
zval result_meta_data;
ZVAL_UNDEF(&result_meta_data);
array_init(&result_meta_data);
for( SQLSMALLINT f = 0; f < num_cols; ++f ) {
field_meta_data* core_meta_data = stmt->current_meta_data[f];
// initialize the array
zval field_array;
ZVAL_UNDEF( &field_array );
array_init(&field_array );
// add the field name to the associative array but keep a copy
add_assoc_string(&field_array, FieldMetaData::NAME, reinterpret_cast<char*>(core_meta_data->field_name.get()));
//core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type );
add_assoc_long(&field_array, FieldMetaData::TYPE, core_meta_data->field_type);
switch( core_meta_data->field_type ) {
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
add_assoc_null(&field_array, FieldMetaData::SIZE);
add_assoc_long(&field_array, FieldMetaData::PREC, core_meta_data->field_precision);
add_assoc_long(&field_array, FieldMetaData::SCALE, core_meta_data->field_scale);
break;
case SQL_BIT:
case SQL_TINYINT:
case SQL_SMALLINT:
case SQL_INTEGER:
case SQL_BIGINT:
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
add_assoc_null(&field_array, FieldMetaData::SIZE);
add_assoc_long(&field_array, FieldMetaData::PREC, core_meta_data->field_precision);
add_assoc_null(&field_array, FieldMetaData::SCALE);
break;
default:
add_assoc_long(&field_array, FieldMetaData::SIZE, core_meta_data->field_size);
add_assoc_null(&field_array, FieldMetaData::PREC);
add_assoc_null(&field_array, FieldMetaData::SCALE);
break;
}
// add the nullability to the array
add_assoc_long(&field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable);
if (stmt->data_classification) {
data_classification::fill_column_sensitivity_array(stmt, f, &field_array);
}
// add this field's meta data to the result set meta data
add_next_index_zval(&result_meta_data, &field_array);
}
// return our built collection and transfer ownership
RETURN_ZVAL(&result_meta_data, 1, 1);
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_field_metadata: Unknown exception caught." );
}
}
// sqlsrv_next_result( resource $stmt )
//
// Makes the next result (result set, row count, or output parameter) of the
// specified statement active. The first (or only) result returned by a batch
// query or stored procedure is active without a call to sqlsrv_next_result.
// Any output parameters bound are only available after sqlsrv_next_result returns
// null as per ODBC Driver 11 for SQL Server specs: http://msdn.microsoft.com/en-us/library/ms403283.aspx
//
// Parameters
// $stmt: The executed statement on which the next result is made active.
//
// Return Value
// If the next result was successfully made active, the Boolean value true is
// returned. If an error occurred in making the next result active, false is
// returned. If no more results are available, null is returned.
PHP_FUNCTION( sqlsrv_next_result )
{
LOG_FUNCTION( "sqlsrv_next_result" );
ss_sqlsrv_stmt* stmt = NULL;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
core_sqlsrv_next_result( stmt, true );
// clear the current meta data since the new result will generate new meta data
stmt->clean_up_results_metadata();
if( stmt->past_next_result_end ) {
RETURN_NULL();
}
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_next_result: Unknown exception caught." );
}
}
// sqlsrv_rows_affected( resource $stmt )
//
// Returns the number of rows modified by the last statement executed. This
// function does not return the number of rows returned by a SELECT statement.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
//
// Return Value
// An integer indicating the number of rows modified by the last executed
// statement. If no rows were modified, zero (0) is returned. If no information
// about the number of modified rows is available, negative one (-1) is
// returned. If an error occurred in retrieving the number of modified rows,
// false is returned. See SQLRowCount in the MSDN ODBC documentation.
PHP_FUNCTION( sqlsrv_rows_affected )
{
LOG_FUNCTION( "sqlsrv_rows_affected" );
ss_sqlsrv_stmt* stmt = NULL;
SQLLEN rows = -1;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// make sure that the statement has already been executed.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw ss::SSException();
}
// make sure it is not scrollable. This function should only work for inserts, updates, and deletes,
// but this is the best we can do to enforce that.
CHECK_CUSTOM_ERROR( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY, stmt, SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE ) {
throw ss::SSException();
}
rows = stmt->current_results->row_count();
RETURN_LONG( rows );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_rows_affected: Unknown exception caught." );
}
}
// sqlsrv_num_rows( resource $stmt )
//
// Retrieves the number of rows in an active result set. The statement must
// have been created with the Scrollable attribute set to 'static'.
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// An integer value that represents the number of rows in the active result
// set. If an error occurs, the boolean value false is returned.
PHP_FUNCTION( sqlsrv_num_rows )
{
LOG_FUNCTION( "sqlsrv_num_rows" );
ss_sqlsrv_stmt* stmt = NULL;
SQLLEN rows = -1;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// make sure that the statement has already been executed.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw ss::SSException();
}
// make sure that the statement is scrollable and the cursor is not dynamic.
// if the cursor is dynamic, then the number of rows returned is always -1.
CHECK_CUSTOM_ERROR( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY || stmt->cursor_type == SQL_CURSOR_DYNAMIC, stmt,
SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE ) {
throw ss::SSException();
}
rows = stmt->current_results->row_count();
RETURN_LONG( rows );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_num_rows: Unknown exception caught." );
}
}
// sqlsrv_num_fields( resource $stmt )
//
// Retrieves the number of fields in an active result set. Note that
// sqlsrv_num_fields can be called on any prepared statement, before or after
// execution.
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// An integer value that represents the number of fields in the active result
// set. If an error occurs, the boolean value false is returned.
PHP_FUNCTION( sqlsrv_num_fields )
{
LOG_FUNCTION( "sqlsrv_num_fields" );
ss_sqlsrv_stmt* stmt = NULL;
SQLSMALLINT fields = -1;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// retrieve the number of columns from ODBC
fields = core::SQLNumResultCols( stmt );
RETURN_LONG( fields );
}
catch( ss::SSException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_num_fields: Unknown exception caught." );
}
}
// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams]])
//
// Retrieves the next row of data as a PHP object.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
//
// $className [OPTIONAL]: A string specifying the name of the class to
// instantiate. If a value for the $className parameter is not specified, an
// instance of the PHP stdClass is instantiated.
//
// $ctorParams [OPTIONAL]: An array that contains values passed to the
// constructor of the class specified with the $className parameter. If the
// constructor of the specified class accepts parameter values, the $ctorParams
// parameter must be used when calling sqlsrv_fetch_object.
//
// Return Value
// A PHP object with properties that correspond to result set field
// names. Property values are populated with the corresponding result set field
// values. If the class specified with the optional $className parameter does
// not exist or if there is no active result set associated with the specified
// statement, false is returned.
// The data type of a value in the returned object will be the default PHP data
// type. For information on default PHP data types, see Default PHP Data Types.
//
// Remarks
// If a class name is specified with the optional $className parameter, an
// object of this class type is instantiated. If the class has properties whose
// names match the result set field names, the corresponding result set values
// are applied to the properties. If a result set field name does not match a
// class property, a property with the result set field name is added to the
// object and the result set value is applied to the property. For more
// information about calling sqlsrv_fetch_object with the $className parameter,
// see How to: Retrieve Data as an Object (Microsoft Drivers for PHP for SQL Server).
//
// If a field with no name is returned, sqlsrv_fetch_object will discard the
// field value and issue a warning.
PHP_FUNCTION( sqlsrv_fetch_object )
{
LOG_FUNCTION( "sqlsrv_fetch_object" );
ss_sqlsrv_stmt* stmt = NULL;
zval* class_name_z = NULL;
zval* ctor_params_z = NULL;
zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
zend_long fetch_offset = 0; // default value for parameter if one isn't supplied
// stdClass is the name of the system's default base class in PHP
char* class_name = const_cast<char*>( STDCLASS_NAME );
std::size_t class_name_len = STDCLASS_NAME_LEN;
HashTable* properties_ht = NULL;
zval retval_z;
ZVAL_UNDEF( &retval_z );
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE),
// fetch style (see SQLSRV_SCROLL_* constants) and fetch offset
// we also use z! instead of s and a so that null may be passed in as valid values for
// the class name and ctor params
PROCESS_PARAMS( stmt, "r|z!z!ll", _FN_, 4, &class_name_z, &ctor_params_z, &fetch_style, &fetch_offset );
try {
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
throw ss::SSException();
}
if( class_name_z ) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
class_name = Z_STRVAL( *class_name_z );
class_name_len = Z_STRLEN( *class_name_z );
}
if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) {
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
// fetch the data
bool result = core_sqlsrv_fetch( stmt, static_cast<SQLSMALLINT>(fetch_style), fetch_offset );
if( !result ) {
RETURN_NULL();
}
fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ );
properties_ht = Z_ARRVAL( retval_z );
// find the zend_class_entry of the class the user requested (stdClass by default) for use below
zend_class_entry* class_entry = NULL;
zend_string* class_name_str_z = zend_string_init( class_name, class_name_len, 0 );
int zr = ( NULL != ( class_entry = zend_lookup_class( class_name_str_z ))) ? SUCCESS : FAILURE;
zend_string_release( class_name_str_z );
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_BAD_CLASS, class_name ) {
throw ss::SSException();
}
// create an instance of the object with its default properties
// we pass NULL for the properties so that the object will be populated by its default properties
zr = object_and_properties_init( &retval_z, class_entry, NULL /*properties*/ );
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) {
throw ss::SSException();
}
// merge in the "properties" (associative array) returned from the fetch doing this vice versa
// since putting properties_ht into object_and_properties_init and merging the default properties
// causes duplicate properties when the visibilities are different and also references the
// default parameters directly in the object, meaning the default property value is changed when
// the object's property is changed.
zend_merge_properties( &retval_z, properties_ht );
zend_hash_destroy( properties_ht );
FREE_HASHTABLE( properties_ht );
// find and call the object's constructor
// The header files (zend.h and zend_API.h) declare
// these functions and structures, so by working with those, we were able to
// develop this as a suitable snippet for calling constructors. Some observations:
// params must be an array of zval**, not a zval** to an array as we originally
// thought. Also, a constructor doesn't show up in the function table, but
// is put into the "magic methods" section of the class entry.
//
// The default values of the fci and fcic structures were determined by
// calling zend_fcall_info_init with a test callable.
// if there is a constructor (e.g., stdClass doesn't have one)
if( class_entry->constructor ) {
// take the parameters given as our last argument and put them into a sequential array
sqlsrv_malloc_auto_ptr<zval> params_m;
zval ctor_retval_z;
ZVAL_UNDEF( &ctor_retval_z );
int num_params = 0;
if ( ctor_params_z ) {
HashTable* ctor_params_ht = Z_ARRVAL( *ctor_params_z );
num_params = zend_hash_num_elements( ctor_params_ht );
params_m = reinterpret_cast<zval*>( sqlsrv_malloc( num_params * sizeof( zval ) ));
int i = 0;
zval* value_z = NULL;
ZEND_HASH_FOREACH_VAL( ctor_params_ht, value_z ) {
zr = ( value_z ) ? SUCCESS : FAILURE;
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) {
throw ss::SSException();
}
ZVAL_COPY_VALUE(&params_m[i], value_z);
i++;
} ZEND_HASH_FOREACH_END();
} //if( !Z_ISUNDEF( ctor_params_z ))
// call the constructor function itself.
zend_fcall_info fci;
zend_fcall_info_cache fcic;
memset( &fci, 0, sizeof( fci ));
fci.size = sizeof( fci );
#if PHP_VERSION_ID < 70100
fci.function_table = &( class_entry )->function_table;
#endif
ZVAL_UNDEF( &( fci.function_name ) );
fci.retval = &ctor_retval_z;
fci.param_count = num_params;
fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred.
fci.object = Z_OBJ_P( &retval_z );
memset( &fcic, 0, sizeof( fcic ));
#if PHP_VERSION_ID < 70300
fcic.initialized = 1;
#endif
fcic.function_handler = class_entry->constructor;
fcic.calling_scope = class_entry;
fcic.object = Z_OBJ_P( &retval_z );
zr = zend_call_function( &fci, &fcic );
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) {
throw ss::SSException();
}
} //if( class_entry->constructor )
RETURN_ZVAL( &retval_z, 1, 1 );
}
catch( core::CoreException& ) {
if( properties_ht != NULL ) {
zend_hash_destroy( properties_ht );
FREE_HASHTABLE( properties_ht );
}
else if ( Z_TYPE( retval_z ) == IS_ARRAY ) {
zend_hash_destroy( Z_ARRVAL( retval_z ));
FREE_HASHTABLE( Z_ARRVAL( retval_z ));
}
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_fetch_object: Unknown exception caught." );
}
}
// sqlsrv_has_rows( resource $stmt )
//
// Parameters
// $stmt: The statement on which the targeted result set is active.
//
// Return Value
// Returns whether or not there are rows waiting to be processed. There are two scenarios
// for using a function like this:
// 1) To know if there are any actual rows, not just a result set (empty or not). Use sqlsrv_has_rows to determine this.
// The guarantee is that if sqlsrv_has_rows returns true immediately after a query, that sqlsrv_fetch_* will return at least
// one row of data.
// 2) To know if there is any sort of result set, empty or not, that has to be bypassed to get to something else, such as
// output parameters being returned. Use sqlsrv_num_fields > 0 to check if there is any result set that must be bypassed
// until sqlsrv_fetch returns NULL.
// The last caveat is that this function can still return FALSE if there is an error, which is fine since an error
// most likely means that there is no result data anyways.
// If this functions returs true one time, then it will return true even after the result set is exhausted
// (sqlsrv_fetch returns null)
PHP_FUNCTION( sqlsrv_has_rows )
{
LOG_FUNCTION( "sqlsrv_has_rows" );
ss_sqlsrv_stmt* stmt = NULL;
try {
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw ss::SSException();
}
if( !stmt->has_rows && !stmt->fetch_called ) {
determine_stmt_has_rows( stmt );
}
if( stmt->has_rows ) {
RETURN_TRUE;
}
}
catch( core::CoreException& ) {
}
catch( ... ) {
DIE( "sqlsrv_has_rows: Unknown exception caught." );
}
RETURN_FALSE;
}
// sqlsrv_send_stream_data( resource $stmt )
//
// Sends data from parameter streams to the server. Up to eight kilobytes (8K)
// of data is sent with each call to sqlsrv_send_stream_data.
// By default, all stream data is sent to the server when a query is
// executed. If this default behavior is not changed, you do not have to use
// sqlsrv_send_stream_data to send stream data to the server. For information
// about changing the default behavior, see the Parameters section of
// sqlsrv_query or sqlsrv_prepare.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
//
// Return Value
// true if there is more data to be sent. null, if all the data has been sent,
// and false if an error occurred
PHP_FUNCTION( sqlsrv_send_stream_data )
{
sqlsrv_stmt* stmt = NULL;
LOG_FUNCTION( "sqlsrv_send_stream_data" );
// get the statement resource that we've bound streams to
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// if everything was sent at execute time, just return that there is nothing more to send.
if( stmt->send_streams_at_exec ) {
RETURN_NULL();
}
// send the next packet
bool more = core_sqlsrv_send_stream_packet( stmt );
// if more to send, return true
if( more ) {
RETURN_TRUE;
}
// otherwise we're done, so return null
else {
RETURN_NULL();
}
}
catch( core::CoreException& ) {
// return false if an error occurred
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_send_stream_data: Unknown exception caught." );
}
}
// sqlsrv_get_field( resource $stmt, int $fieldIndex [, int $getAsType] )
//
// Retrieves data from the specified field of the current row. Field data must
// be accessed in order. For example, data from the first field cannot be
// accessed after data from the second field has been accessed.
//
// Parameters
// $stmt: A statement resource corresponding to an executed statement.
// $fieldIndex: The index of the field to be retrieved. Indexes begin at zero.
// $getAsType [OPTIONAL]: A SQLSRV constant (SQLSRV_PHPTYPE) that determines
// the PHP data type for the returned data. For information about supported data
// types, see SQLSRV Constants (Microsoft Drivers for PHP for SQL Server). If no return
// type is specified, a default PHP type will be returned. For information about
// default PHP types, see Default PHP Data Types. For information about
// specifying PHP data types, see How to: Specify PHP Data Types.
//
// Return Value
// The field data. You can specify the PHP data type of the returned data by
// using the $getAsType parameter. If no return data type is specified, the
// default PHP data type will be returned. For information about default PHP
// types, see Default PHP Data Types. For information about specifying PHP data
// types, see How to: Specify PHP Data Types.
PHP_FUNCTION( sqlsrv_get_field )
{
LOG_FUNCTION( "sqlsrv_get_field" );
ss_sqlsrv_stmt* stmt = NULL;
sqlsrv_phptype sqlsrv_php_type;
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID;
void* field_value = NULL;
zend_long field_index = -1;
SQLLEN field_len = -1;
zval retval_z;
ZVAL_UNDEF(&retval_z);
// get the statement, the field index and the optional type
PROCESS_PARAMS( stmt, "rl|l", _FN_, 2, &field_index, &sqlsrv_php_type );
try {
// validate that the field index is within range
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
if( field_index < 0 || field_index >= num_cols ) {
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
core_sqlsrv_get_field( stmt, static_cast<SQLUSMALLINT>( field_index ), sqlsrv_php_type, false, field_value, &field_len, false/*cache_field*/,
&sqlsrv_php_type_out );
convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z );
sqlsrv_free( field_value );
RETURN_ZVAL( &retval_z, 1, 1 );
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_get_field: Unknown exception caught." );
}
}
// ** type functions. **
// When specifying PHP and SQL Server types that take parameters, such as VARCHAR(2000), we use functions
// to match that notation and return a specially encoded integer that tells us what type and size/precision
// are. For PHP types specifically we munge the type and encoding into the integer.
// As is easily seen, since they are so similar, we delegate the actual encoding to helper methods defined
// below.
// takes an encoding of the stream
PHP_FUNCTION( SQLSRV_PHPTYPE_STREAM )
{
type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STREAM );
}
// takes an encoding of the string
PHP_FUNCTION( SQLSRV_PHPTYPE_STRING )
{
type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STRING );
}
// takes the size of the binary field
PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_BINARY );
}
// takes the size of the char field
PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_CHAR );
}
// takes the precision and scale of the decimal field
PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL)
{
type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_DECIMAL );
}
// takes the size of the nchar field
PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WCHAR );
}
// takes the precision and scale of the numeric field
PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC)
{
type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_NUMERIC );
}
// takes the size (in characters, not bytes) of the nvarchar field
PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WVARCHAR );
}
// takes the size of the varbinary field
PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARBINARY );
}
// takes the size of the varchar field
PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR)
{
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARCHAR );
}
void bind_params( _Inout_ ss_sqlsrv_stmt* stmt )
{
// if there's nothing to do, just return
if( stmt->params_z == NULL ) {
return;
}
try {
stmt->executed = false;
zval* params_z = stmt->params_z;
HashTable* params_ht = Z_ARRVAL_P( params_z );
zend_ulong index = -1;
zend_string *key = NULL;
zval* param_z = NULL;
ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) {
// make sure it's an integer index
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR(type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX) {
throw ss::SSException();
}
zval* value_z = NULL;
SQLSMALLINT direction = SQL_PARAM_INPUT;
SQLSRV_ENCODING encoding = stmt->encoding();
if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) {
encoding = stmt->conn->encoding();
}
SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
SQLSMALLINT decimal_digits = 0;
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
// if it's a parameter array
if (Z_TYPE_P(param_z) == IS_ARRAY) {
try {
HashTable* param_ht = Z_ARRVAL_P(param_z);
// Check the number of elements in the array
int num_elems = zend_hash_num_elements(param_ht);
if (num_elems > 1) {
value_z = parse_param_array(stmt, param_ht, index, direction, php_out_type, encoding, sql_type, column_size, decimal_digits);
} else {
// Simply get the first variable and use the defaults
value_z = zend_hash_index_find(param_ht, 0);
if (value_z == NULL) {
THROW_SS_ERROR(stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1);
}
}
} catch (core::CoreException&) {
SQLFreeStmt(stmt->handle(), SQL_RESET_PARAMS);
throw;
}
}
else {
CHECK_CUSTOM_ERROR(!stmt->prepared && stmt->conn->ce_option.enabled, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED) {
throw ss::SSException();
}
value_z = param_z;
}
// If the user specifies a certain type for an output parameter, we have to convert the zval
// to that type so that when the buffer is filled, the type is correct. But first,
// should check if a LOB type is specified.
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && (sql_type == SQL_LONGVARCHAR
|| sql_type == SQL_WLONGVARCHAR || sql_type == SQL_LONGVARBINARY),
stmt, SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED) {
throw core::CoreException();
}
// Table-valued parameters are input-only
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && (sql_type == SQL_SS_TABLE || php_out_type == SQLSRV_PHPTYPE_TABLE), stmt, SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY) {
throw ss::SSException();
}
// bind the parameter
core_sqlsrv_bind_param( stmt, static_cast<SQLUSMALLINT>( index ), direction, value_z, php_out_type, encoding, sql_type, column_size,
decimal_digits );
} ZEND_HASH_FOREACH_END();
}
catch( core::CoreException& ) {
stmt->free_param_data();
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
zval_ptr_dtor( stmt->params_z );
sqlsrv_free( stmt->params_z );
stmt->params_z = NULL;
throw;
}
}
// sqlsrv_cancel( resource $stmt )
//
// Cancels a statement. This means that any pending results for the statement
// are discarded. After this function is called, the statement can be
// re-executed if it was prepared with sqlsrv_prepare. Calling this function is
// not necessary if all the results associated with the statement have been
// consumed.
//
// Parameters
// $stmt: The statement to be canceled.
//
// Return Value
// A Boolean value: true if the operation was successful. Otherwise, false.
PHP_FUNCTION( sqlsrv_cancel )
{
LOG_FUNCTION( "sqlsrv_cancel" );
ss_sqlsrv_stmt* stmt = NULL;
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try {
// close the stream to release the resource
close_active_stream( stmt );
SQLRETURN r = SQLCancel( stmt->handle() );
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw ss::SSException();
}
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_cancel: Unknown exception caught." );
}
}
void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc )
{
LOG_FUNCTION( "sqlsrv_stmt_dtor" );
// get the structure
ss_sqlsrv_stmt *stmt = static_cast<ss_sqlsrv_stmt*>( rsrc->ptr );
if( stmt->conn ) {
int zr = zend_hash_index_del( static_cast<ss_sqlsrv_conn*>( stmt->conn )->stmts, stmt->conn_index );
if( zr == FAILURE ) {
LOG( SEV_ERROR, "Failed to remove statement reference from the connection" );
}
}
stmt->~ss_sqlsrv_stmt();
sqlsrv_free( stmt );
rsrc->ptr = NULL;
}
// sqlsrv_free_stmt( resource $stmt )
//
// Frees all resources associated with the specified statement. The statement
// cannot be used again after this function has been called.
//
// Parameters
// $stmt: The statement to be closed.
//
// Return Value
// The Boolean value true unless the function is called with an invalid
// parameter. If the function is called with an invalid parameter, false is
// returned.
//
// Null is a valid parameter for this function. This allows the function to be
// called multiple times in a script. For example, if you free a statement in an
// error condition and free it again at the end of the script, the second call
// to sqlsrv_free_stmt will return true because the first call to
// sqlsrv_free_stmt (in the error condition) sets the statement resource to
// null.
PHP_FUNCTION( sqlsrv_free_stmt )
{
LOG_FUNCTION( "sqlsrv_free_stmt" );
zval* stmt_r = NULL;
ss_sqlsrv_stmt* stmt = NULL;
sqlsrv_context_auto_ptr error_ctx;
reset_errors();
try {
// dummy context to pass to the error handler
error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
error_ctx->set_func(_FN_);
// take only the statement resource
if( zend_parse_parameters( ZEND_NUM_ARGS(), "r", &stmt_r ) == FAILURE ) {
// Check if it was a zval
int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "z", &stmt_r );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
if( Z_TYPE_P( stmt_r ) == IS_NULL ) {
RETURN_TRUE;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
// verify the resource so we know we're deleting a statement
stmt = static_cast<ss_sqlsrv_stmt*>(zend_fetch_resource_ex(stmt_r, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor));
// if sqlsrv_free_stmt was called on an already closed statment then we just return success.
// zend_list_close sets the type of the closed statment to -1.
SQLSRV_ASSERT( stmt_r != NULL, "sqlsrv_free_stmt: stmt_r is null." );
if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) {
RETURN_TRUE;
}
if( stmt == NULL ) {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
// delete the resource from Zend's master list, which will trigger the statement's destructor
#if PHP_VERSION_ID < 80000
if (zend_list_close(Z_RES_P(stmt_r)) == FAILURE) {
LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P(stmt_r)->handle);
}
#else
zend_list_close(Z_RES_P(stmt_r));
#endif
// when stmt_r is first parsed in zend_parse_parameters, stmt_r becomes a zval that points to a zend_resource with a refcount of 2
// need to DELREF here so the refcount becomes 1 and stmt_r can be appropriate destroyed by the garbage collector when it goes out of scope
// zend_list_close only destroy the resource pointed to by Z_RES_P( stmt_r ), not the zend_resource itself
Z_TRY_DELREF_P(stmt_r);
ZVAL_NULL( stmt_r );
RETURN_TRUE;
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_free_stmt: Unknown exception caught." );
}
}
void stmt_option_ss_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
{
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_STRING ), stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) {
throw ss::SSException();
}
const char* scroll_type = Z_STRVAL_P( value_z );
unsigned long cursor_type = -1;
// find which cursor type they would like and set the ODBC statement attribute as such
if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) {
cursor_type = SQL_CURSOR_STATIC;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_DYNAMIC )) {
cursor_type = SQL_CURSOR_DYNAMIC;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_KEYSET )) {
cursor_type = SQL_CURSOR_KEYSET_DRIVEN;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_FORWARD )) {
cursor_type = SQL_CURSOR_FORWARD_ONLY;
}
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_BUFFERED )) {
cursor_type = SQLSRV_CURSOR_BUFFERED;
}
else {
THROW_SS_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE );
}
core_sqlsrv_set_scrollable( stmt, cursor_type );
}
namespace {
void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval)
{
if ( in_val == NULL ) {
ZVAL_NULL( &out_zval);
return;
}
switch (sqlsrv_php_type) {
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
{
if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) {
ZVAL_LONG( &out_zval, *(static_cast<int*>( in_val )));
}
else {
ZVAL_DOUBLE( &out_zval, *(static_cast<double*>( in_val )));
}
break;
}
case SQLSRV_PHPTYPE_STRING:
{
ZVAL_STRINGL( &out_zval, static_cast<const char*>( in_val ), field_len);
break;
}
case SQLSRV_PHPTYPE_STREAM:
{
out_zval = *( static_cast<zval*>( in_val ));
stmt->active_stream = out_zval;
//addref here because deleting out_zval later will decrement the refcount
Z_TRY_ADDREF( out_zval );
break;
}
case SQLSRV_PHPTYPE_DATETIME:
{
convert_datetime_string_to_zval(stmt, static_cast<char*>(in_val), field_len, out_zval);
break;
}
case SQLSRV_PHPTYPE_NULL:
ZVAL_NULL(&out_zval);
break;
default:
DIE("Unknown php type");
break;
}
return;
}
// put in the column size and scale/decimal digits of the sql server type
// these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx
// for SQL_VARBINARY, SQL_VARCHAR, and SQL_WLONGVARCHAR types, see https://msdn.microsoft.com/en-CA/library/ms187993.aspx
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size,
_Out_ SQLSMALLINT* decimal_digits )
{
*decimal_digits = 0;
switch( sqlsrv_type.typeinfo.type ) {
case SQL_BIGINT:
*column_size = 19;
break;
case SQL_BIT:
*column_size = 1;
break;
case SQL_INTEGER:
*column_size = 10;
break;
case SQL_SMALLINT:
*column_size = 5;
break;
case SQL_TINYINT:
*column_size = 3;
break;
case SQL_GUID:
*column_size = 36;
break;
case SQL_FLOAT:
*column_size = 53;
break;
case SQL_REAL:
*column_size = 24;
break;
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
*column_size = INT_MAX;
break;
case SQL_WLONGVARCHAR:
*column_size = INT_MAX >> 1;
break;
case SQL_SS_XML:
case SQL_SS_TABLE:
*column_size = SQL_SS_LENGTH_UNLIMITED;
break;
case SQL_BINARY:
case SQL_CHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_SS_VARIANT:
*column_size = sqlsrv_type.typeinfo.size;
if( *column_size == SQLSRV_SIZE_MAX_TYPE ) {
*column_size = SQL_SS_LENGTH_UNLIMITED;
}
else if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) {
*column_size = SQLSRV_INVALID_SIZE;
return false;
}
break;
case SQL_WCHAR:
case SQL_WVARCHAR:
*column_size = sqlsrv_type.typeinfo.size;
if( *column_size == SQLSRV_SIZE_MAX_TYPE ) {
*column_size = SQL_SS_LENGTH_UNLIMITED;
break;
}
if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) {
*column_size = SQLSRV_INVALID_SIZE;
return false;
}
break;
case SQL_DECIMAL:
case SQL_NUMERIC:
*column_size = sqlsrv_type.typeinfo.size;
*decimal_digits = sqlsrv_type.typeinfo.scale;
// if there was something wrong with the values given on type_and_precision_calc, these are set to invalid precision
if( *column_size == SQLSRV_INVALID_PRECISION || *decimal_digits == SQLSRV_INVALID_PRECISION ) {
*column_size = SQLSRV_INVALID_SIZE;
return false;
}
break;
// this can represent one of three data types: smalldatetime, datetime, and datetime2
// we present the largest for the version and let SQL Server downsize it
case SQL_TYPE_TIMESTAMP:
*column_size = sqlsrv_type.typeinfo.size;
*decimal_digits = sqlsrv_type.typeinfo.scale;
break;
case SQL_SS_TIMESTAMPOFFSET:
*column_size = 34;
*decimal_digits = 7;
break;
case SQL_TYPE_DATE:
*column_size = 10;
*decimal_digits = 0;
break;
case SQL_SS_TIME2:
*column_size = 16;
*decimal_digits = 7;
break;
default:
// an invalid sql type should have already been dealt with, so we assert here.
DIE( "Trying to determine column size for an invalid type. Type should have already been verified." );
return false;
}
return true;
}
// given a SQL Server type, return a sqlsrv php type
sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string )
{
sqlsrv_phptype sqlsrv_phptype;
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
switch( sql_type ) {
case SQL_BIGINT:
case SQL_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:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
break;
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_SS_VARIANT:
if( prefer_string || size != SQL_SS_LENGTH_UNLIMITED ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
break;
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
break;
case SQL_BINARY:
case SQL_LONGVARBINARY:
case SQL_VARBINARY:
case SQL_SS_UDT:
if( prefer_string ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
}
break;
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
if( prefer_string ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
break;
case SQL_FLOAT:
case SQL_REAL:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
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 (stmt->date_as_string) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
}
break;
}
case SQL_SS_TABLE:
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_TABLE;
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
break;
default:
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
SQLSRV_ASSERT(false, "An invalid php type was returned with (supposedly) validated sql type and column_size");
break;
}
// if an encoding hasn't been set for the statement, then use the connection's encoding
if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
sqlsrv_phptype.typeinfo.encoding = stmt->conn->encoding();
}
return sqlsrv_phptype;
}
// determine if a query returned any rows of data. It does this by actually fetching the first row
// (though not retrieving the data) and setting the has_rows flag in the stmt the fetch was successful.
// The return value simply states whether or not if an error occurred during the determination.
// (All errors are posted here before returning.)
void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt )
{
SQLRETURN r = SQL_SUCCESS;
if( stmt->fetch_called ) {
return;
}
// default condition
stmt->has_rows = false;
// if there are no columns then there are no rows
if( core::SQLNumResultCols( stmt ) == 0 ) {
return;
}
// if the statement is scrollable, our work is easier though less performant. We simply
// fetch the first row, and then roll the cursor back to be prior to the first row
if( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) {
r = stmt->current_results->fetch( SQL_FETCH_FIRST, 0 );
if( SQL_SUCCEEDED( r )) {
stmt->has_rows = true;
CHECK_SQL_WARNING( r, stmt );
// restore the cursor to its original position.
r = stmt->current_results->fetch( SQL_FETCH_ABSOLUTE, 0 );
SQLSRV_ASSERT(( r == SQL_NO_DATA ), "core_sqlsrv_has_rows: Should have scrolled the cursor to the beginning "
"of the result set." );
}
}
else {
// otherwise, we fetch the first row, but record that we did. sqlsrv_fetch checks this
// flag and simply skips the first fetch, knowing it was already done. It records its own
// flags to know if it should fetch on subsequent calls.
r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 );
if( SQL_SUCCEEDED( r )) {
stmt->has_rows = true;
CHECK_SQL_WARNING( r, stmt );
return;
}
}
}
SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt)
{
// get the numer of columns in the result set
SQLSMALLINT num_cols = -1;
num_cols = stmt->current_meta_data.size();
bool getMetaData = false;
if (num_cols == 0) {
getMetaData = true;
if (stmt->column_count == ACTIVE_NUM_COLS_INVALID) {
num_cols = core::SQLNumResultCols(stmt);
stmt->column_count = num_cols;
} else {
num_cols = stmt->column_count;
}
}
try {
if (getMetaData) {
for (int i = 0; i < num_cols; i++) {
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;
core_meta_data = core_sqlsrv_field_metadata(stmt, i);
stmt->current_meta_data.push_back(core_meta_data.get());
core_meta_data.transferred();
}
}
} catch( core::CoreException& ) {
throw;
}
SQLSRV_ASSERT(stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" );
return num_cols;
}
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names )
{
void* field_value = NULL;
sqlsrv_phptype sqlsrv_php_type;
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID;
// make sure that the fetch type is legal
CHECK_CUSTOM_ERROR((fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, stmt->func()) {
throw ss::SSException();
}
// get the numer of columns in the result set and its metadata if not exists
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
// if this is the first fetch in a new result set, then get the field names and
// store them off for successive fetches.
if ((fetch_type & SQLSRV_FETCH_ASSOC) && stmt->fetch_field_names == NULL) {
SQLLEN field_name_len = 0;
sqlsrv_malloc_auto_ptr<sqlsrv_fetch_field_name> field_names;
field_names = static_cast<sqlsrv_fetch_field_name*>(sqlsrv_malloc(num_cols * sizeof(sqlsrv_fetch_field_name)));
for (int i = 0; i < num_cols; ++i) {
// The meta data field name is already null-terminated, and the field name len is correct.
field_name_len = stmt->current_meta_data[i]->field_name_len;
field_names[i].name = static_cast<char*>(sqlsrv_malloc(field_name_len, sizeof(char), 1));
memcpy_s((void*)field_names[i].name, (field_name_len * sizeof(char)), (void*)stmt->current_meta_data[i]->field_name, field_name_len);
field_names[i].name[field_name_len] = '\0'; // null terminate the field name after the memcpy
field_names[i].len = field_name_len; // field_name_len should not need to include the null char
}
stmt->fetch_field_names = field_names;
stmt->fetch_fields_count = num_cols;
field_names.transferred();
}
int zr = SUCCESS;
array_init(&fields);
for( int i = 0; i < num_cols; ++i ) {
SQLLEN field_len = -1;
core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/,
field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out );
zval field;
ZVAL_UNDEF( &field );
convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, field );
sqlsrv_free( field_value );
if( fetch_type & SQLSRV_FETCH_NUMERIC ) {
zr = add_next_index_zval( &fields, &field );
CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) {
throw ss::SSException();
}
}
if( fetch_type & SQLSRV_FETCH_ASSOC ) {
CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 0 && !allow_empty_field_names ), stmt,
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) {
throw ss::SSException();
}
if( stmt->fetch_field_names[i].len > 0 || allow_empty_field_names ) {
add_assoc_zval(&fields, stmt->fetch_field_names[i].name, &field);
}
}
//only addref when the fetch_type is BOTH because this is the only case when fields(hashtable)
//has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because
//fields now only has 1 element pointing to field and we want the ref count to be only 1
if (fetch_type == SQLSRV_FETCH_BOTH) {
Z_TRY_ADDREF(field);
}
} //for loop
}
zval* parse_param_array(_Inout_ ss_sqlsrv_stmt* stmt, _Inout_ HashTable* param_ht, zend_ulong index, _Out_ SQLSMALLINT& direction,
_Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits)
{
zval* var_or_val = zend_hash_index_find(param_ht, 0);
bool php_type_param_is_null = true;
bool sql_type_param_is_null = true;
// Assumption: there are more than only the variable, parse the rest of the array
zval* dir = zend_hash_index_find(param_ht, 1);
if (Z_TYPE_P(dir) != IS_NULL) {
// if param direction is specified, make sure it's valid
CHECK_CUSTOM_ERROR(Z_TYPE_P(dir) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1) {
throw ss::SSException();
}
direction = static_cast<SQLSMALLINT>(Z_LVAL_P(dir));
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT,
stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1) {
throw ss::SSException();
}
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && !Z_ISREF_P(var_or_val), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1) {
throw ss::SSException();
}
}
// Check if the user provides php type or sql type or both
zval* phptype_z = zend_hash_index_find(param_ht, 2);
zval* sqltype_z = zend_hash_index_find(param_ht, 3);
php_type_param_is_null = (phptype_z == NULL || Z_TYPE_P(phptype_z) == IS_NULL);
sql_type_param_is_null = (sqltype_z == NULL || Z_TYPE_P(sqltype_z) == IS_NULL);
if (php_type_param_is_null) {
// so set default for php type based on the variable
if (Z_ISREF_P(var_or_val)) {
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(Z_REFVAL_P(var_or_val))];
} else {
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(var_or_val)];
}
} else {
CHECK_CUSTOM_ERROR(Z_TYPE_P(phptype_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1) {
throw ss::SSException();
}
sqlsrv_phptype srv_phptype;
srv_phptype.value = Z_LVAL_P(phptype_z);
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_phptype(srv_phptype), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1) {
throw ss::SSException();
}
php_out_type = static_cast<SQLSRV_PHPTYPE>(srv_phptype.typeinfo.type);
encoding = (SQLSRV_ENCODING)srv_phptype.typeinfo.encoding;
// if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established
// by the connection
if (encoding == SQLSRV_ENCODING_DEFAULT) {
encoding = stmt->conn->encoding();
}
}
if (sql_type_param_is_null) {
// the sql type is not specified, which is required for always encrypted for non-prepared statements
CHECK_CUSTOM_ERROR(stmt->conn->ce_option.enabled && !stmt->prepared, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED) {
throw ss::SSException();
}
} else {
CHECK_CUSTOM_ERROR(Z_TYPE_P(sqltype_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1) {
throw ss::SSException();
}
// since the user supplied this type, make sure it's valid
sqlsrv_sqltype sqlsrv_sql_type;
sqlsrv_sql_type.value = Z_LVAL_P(sqltype_z);
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_sqltype(sqlsrv_sql_type), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1) {
throw ss::SSException();
}
bool size_okay = determine_column_size_or_precision(stmt, sqlsrv_sql_type, &column_size, &decimal_digits);
CHECK_CUSTOM_ERROR(!size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1) {
throw ss::SSException();
}
sql_type = sqlsrv_sql_type.typeinfo.type;
if (direction != SQL_PARAM_INPUT && php_type_param_is_null) {
sqlsrv_phptype srv_phptype;
srv_phptype = determine_sqlsrv_php_type(stmt, sql_type, (SQLUINTEGER)column_size, true);
php_out_type = static_cast<SQLSRV_PHPTYPE>(srv_phptype.typeinfo.type);
encoding = static_cast<SQLSRV_ENCODING>(srv_phptype.typeinfo.encoding);
}
}
if (direction == SQL_PARAM_OUTPUT) {
if (php_out_type == SQLSRV_PHPTYPE_NULL || php_out_type == SQLSRV_PHPTYPE_DATETIME || php_out_type == SQLSRV_PHPTYPE_STREAM) {
THROW_CORE_ERROR(stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
}
}
return var_or_val;
}
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;
}
// return if the type is a valid sql server type not including
// size, precision or scale. Use determine_precision_and_scale for that.
bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype sql_type )
{
switch( sql_type.typeinfo.type ) {
case SQL_BIGINT:
case SQL_BIT:
case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT:
case SQL_GUID:
case SQL_FLOAT:
case SQL_REAL:
case SQL_LONGVARBINARY:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:
case SQL_BINARY:
case SQL_CHAR:
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_VARBINARY:
case SQL_VARCHAR:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_TYPE_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_SS_TIME2:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TABLE:
break;
default:
return false;
}
return true;
}
// verify an encoding given to type_and_encoding by looking through the list
// of standard encodings created at module initialization time
bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding )
{
void* encoding_temp = NULL;
zend_ulong index = -1;
zend_string* key = NULL;
ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, encoding_temp ) {
if (encoding_temp) {
sqlsrv_encoding* encoding = reinterpret_cast<sqlsrv_encoding*>(encoding_temp);
encoding_temp = NULL;
if (!stricmp(encoding_string, encoding->iana)) {
phptype_encoding.typeinfo.encoding = encoding->code_page;
return true;
}
}
else {
DIE("Fatal: Error retrieving encoding from encoding hash table.");
}
} ZEND_HASH_FOREACH_END();
return false;
}
// called when one of the SQLSRV_SQLTYPE type functions is called. Encodes the type and size
// into a sqlsrv_sqltype bit fields (see php_sqlsrv.h).
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type )
{
char* size_p = NULL;
size_t size_len = 0;
int size = 0;
if( zend_parse_parameters( ZEND_NUM_ARGS(), "s", &size_p, &size_len ) == FAILURE ) {
return;
}
if (size_p) {
if (!strnicmp("max", size_p, sizeof("max") / sizeof(char))) {
size = SQLSRV_SIZE_MAX_TYPE;
}
else {
#ifndef _WIN32
errno = 0;
#else
_set_errno(0); // reset errno for atol
#endif // !_WIN32
size = atol(size_p);
if (errno != 0) {
size = SQLSRV_INVALID_SIZE;
}
}
}
else {
DIE("type_and_size_calc: size_p is null.");
}
int max_size = SQL_SERVER_MAX_FIELD_SIZE;
// size is actually the number of characters, not the number of bytes, so if they ask for a
// 2 byte per character type, then we half the maximum size allowed.
if( type == SQL_WVARCHAR || type == SQL_WCHAR ) {
max_size >>= 1;
}
if( size > max_size || size < SQLSRV_SIZE_MAX_TYPE || size == 0 ) {
LOG( SEV_ERROR, "invalid size. size must be > 0 and <= %1!d! characters or 'max'", max_size );
size = SQLSRV_INVALID_SIZE;
}
sqlsrv_sqltype sql_type;
sql_type.typeinfo.type = type;
sql_type.typeinfo.size = size;
sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE;
ZVAL_LONG( return_value, sql_type.value );
}
// called when the user gives SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC sql types as the type of the
// field. encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h)
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type )
{
zend_long prec = SQLSRV_INVALID_PRECISION;
zend_long scale = SQLSRV_INVALID_SCALE;
if( zend_parse_parameters( ZEND_NUM_ARGS(), "|ll", &prec, &scale ) == FAILURE ) {
return;
}
if( prec > SQL_SERVER_MAX_PRECISION ) {
LOG( SEV_ERROR, "Invalid precision. Precision can't be > 38" );
prec = SQLSRV_INVALID_PRECISION;
}
if( prec < 0 ) {
LOG( SEV_ERROR, "Invalid precision. Precision can't be negative" );
prec = SQLSRV_INVALID_PRECISION;
}
if( scale > prec ) {
LOG( SEV_ERROR, "Invalid scale. Scale can't be > precision" );
scale = SQLSRV_INVALID_SCALE;
}
sqlsrv_sqltype sql_type;
sql_type.typeinfo.type = type;
sql_type.typeinfo.size = prec;
sql_type.typeinfo.scale = scale;
ZVAL_LONG( return_value, sql_type.value );
}
// common code for SQLSRV_PHPTYPE_STREAM and SQLSRV_PHPTYPE_STRING php types given as parameters.
// encodes the type and encoding into a sqlsrv_phptype structure (see php_sqlsrv.h)
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type )
{
SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." );
char* encoding_param;
size_t encoding_param_len = 0;
// set the default encoding values to invalid so that
// if the encoding isn't validated, it will return the invalid setting.
sqlsrv_phptype sqlsrv_php_type;
sqlsrv_php_type.typeinfo.type = type;
sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
if( zend_parse_parameters( ZEND_NUM_ARGS(), "s", &encoding_param, &encoding_param_len ) == FAILURE ) {
ZVAL_LONG( return_value, sqlsrv_php_type.value );
}
if( !verify_and_set_encoding( encoding_param, sqlsrv_php_type )) {
LOG( SEV_ERROR, "Invalid encoding for php type." );
}
ZVAL_LONG( return_value, sqlsrv_php_type.value );
}
}

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\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

View file

@ -1,993 +0,0 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: util.cpp
//
// Contents: Utility functions used by both connection or statement functions
//
// 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.
//---------------------------------------------------------------------------------------------------------------------------------
extern "C" {
#include "php_sqlsrv.h"
}
#include "php_sqlsrv_int.h"
namespace {
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
unsigned int current_log_subsystem = LOG_UTIL;
// *** internal functions ***
sqlsrv_error_const* get_error_message( _In_ unsigned int sqlsrv_error_code );
void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain,
_In_ bool warning );
bool ignore_warning( _In_ char* sql_state, _In_ int native_code );
bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, _In_ logging_severity log_severity,
_In_ unsigned int sqlsrv_error_code, _In_ int warning, _In_opt_ va_list* print_args );
int sqlsrv_merge_zend_hash_dtor( _Inout_ zval* dest );
bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z );
}
// List of all error messages
ss_error SS_ERRORS[] = {
{
SS_SQLSRV_ERROR_INVALID_OPTION,
{ IMSSP, (SQLCHAR*)"Invalid option %1!s! was passed to sqlsrv_connect.", -1, true }
},
// no equivalent to error 2 in 2.0
// error 3 is superceded by -16
// these two share the same code since they are basically the same error.
{
SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED,
{ IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be"
" escaped with another right brace (}}).", -4, false }
},
{
SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED,
{ IMSSP, (SQLCHAR*)"An unescaped right brace (}) was found in option %1!s!.", -4, true }
},
{
SQLSRV_ERROR_NO_DATA,
{ IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -5, true }
},
{
SQLSRV_ERROR_STREAMABLE_TYPES_ONLY,
{ IMSSP, (SQLCHAR*)"Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using "
"streams.", -6, false}
},
{
SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
{ IMSSP, (SQLCHAR*)"An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be "
"specified as output parameters.", -7, false }
},
{
SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY,
{ IMSSP, (SQLCHAR*)"An invalid connection option key type was received. Option key types must be strings.", -8, false }
},
{
SS_SQLSRV_ERROR_VAR_REQUIRED,
{ IMSSP, (SQLCHAR*)"Parameter array %1!d! must have at least one value or variable.", -9, true }
},
{
SS_SQLSRV_ERROR_INVALID_FETCH_TYPE,
{ IMSSP, (SQLCHAR*)"An invalid fetch type was specified. SQLSRV_FETCH_NUMERIC, SQLSRV_FETCH_ARRAY and SQLSRV_FETCH_BOTH are acceptable values.", -10, false }
},
{
SQLSRV_ERROR_STATEMENT_NOT_EXECUTED,
{ IMSSP, (SQLCHAR*)"The statement must be executed before results can be retrieved.", -11, false }
},
{
SS_SQLSRV_ERROR_ALREADY_IN_TXN,
{ IMSSP, (SQLCHAR*)"Cannot begin a transaction until the current transaction has been completed by calling either "
"sqlsrv_commit or sqlsrv_rollback.", -12, false }
},
{
SS_SQLSRV_ERROR_NOT_IN_TXN,
{ IMSSP, (SQLCHAR*)"A transaction must be started by calling sqlsrv_begin_transaction before calling sqlsrv_commit or "
"sqlsrv_rollback.", -13, false }
},
{
SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER,
{ IMSSP, (SQLCHAR*)"An invalid parameter was passed to %1!s!.", -14, true }
},
{
SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION,
{ IMSSP, (SQLCHAR*)"An invalid direction for parameter %1!d! was specified. SQLSRV_PARAM_IN, SQLSRV_PARAM_OUT, and "
"SQLSRV_PARAM_INOUT are valid values.", -15, true }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
{ IMSSP, (SQLCHAR*)"An invalid PHP type for parameter %1!d! was specified.", -16, true }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
{ IMSSP, (SQLCHAR*)"An invalid SQL Server type for parameter %1!d! was specified.", -17, true }
},
{
SQLSRV_ERROR_FETCH_NOT_CALLED,
{ IMSSP, (SQLCHAR*)"A row must be retrieved with sqlsrv_fetch before retrieving data with sqlsrv_get_field.", -18, false }
},
{
SQLSRV_ERROR_FIELD_INDEX_ERROR,
{ IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. "
"The sqlsrv_get_field function cannot retrieve field %1!d! because its index is less "
"than the index of a field that has already been retrieved (%2!d!).", -19, true }
},
{
SQLSRV_ERROR_DATETIME_CONVERSION_FAILED,
{ IMSSP, (SQLCHAR*)"The retrieval of the DateTime object failed.", -20, false }
},
// no equivalent to SQLSRV_ERROR_SERVER_INFO in 2.0 so -21 is skipped
{
SQLSRV_ERROR_FETCH_PAST_END,
{ IMSSP, (SQLCHAR*)"There are no more rows in the active result set. Since this result set is not scrollable, no more "
"data may be retrieved.", -22, false }
},
{
SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED,
{ IMSSP, (SQLCHAR*)"A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute.", -23, false }
},
{
SQLSRV_ERROR_ZEND_HASH,
{ IMSSP, (SQLCHAR*)"An error occurred while creating or accessing a Zend hash table.", -24, false }
},
{
SQLSRV_ERROR_ZEND_STREAM,
{ IMSSP, (SQLCHAR*)"An error occurred while reading from a PHP stream.", -25, false }
},
{
SQLSRV_ERROR_NEXT_RESULT_PAST_END,
{ IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -26, false }
},
{
SQLSRV_ERROR_STREAM_CREATE,
{ IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -27, false }
},
{
SQLSRV_ERROR_NO_FIELDS,
{ IMSSP, (SQLCHAR*)"The active result for the query contains no fields.", -28, false }
},
{
SS_SQLSRV_ERROR_ZEND_BAD_CLASS,
{ IMSSP, (SQLCHAR*)"Failed to find class %1!s!.", -29, true }
},
{
SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED,
{ IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true }
},
{
SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION,
{ IMSSP, (SQLCHAR*)"An invalid size or precision for parameter %1!d! was specified.", -31, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_KEY,
{ IMSSP, (SQLCHAR*)"Option %1!s! is invalid.", -32, true }
},
// these three errors are returned for invalid options, so they are given the same number for compatibility with 1.1
{
SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option SQLSRV_QUERY_TIMEOUT.", -33, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
{ IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -33, true }
},
{
SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
{ IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -33, true }
},
{
SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH,
{ IMSSP, (SQLCHAR*)"The type of output parameter %1!d! does not match the type specified by the SQLSRV_PHPTYPE_* constant."
" For output parameters, the type of the variable's current value must match the SQLSRV_PHPTYPE_* constant, or be NULL. "
"If the type is NULL, the PHP type of the output parameter is inferred from the SQLSRV_SQLTYPE_* constant.", -34, true }
},
{
SQLSRV_ERROR_INVALID_TYPE,
{ IMSSP, (SQLCHAR*)"Invalid type", -35, false }
},
// 36-38 have no equivalent 2.0 errors
{
SS_SQLSRV_ERROR_REGISTER_RESOURCE,
{ IMSSP, (SQLCHAR*)"Registering the %1!s! resource failed.", -39, true }
},
{
SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -40, true }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for an output param to UTF-8: %1!s!", -41, true }
},
{
SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -42, true }
},
{
SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*)"An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -43, true }
},
{
SQLSRV_ERROR_MARS_OFF,
{ IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. "
"To make the connection available for other queries, either fetch all results or cancel or free the statement. "
"For more information, see the product documentation about the MultipleActiveResultSets connection option.", -44, false }
},
{
SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE,
{ IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of "
"key/value pairs.", -45, false }
},
{
SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -46, true }
},
{
SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -47, true }
},
{
SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING,
{ IMSSP, (SQLCHAR*)"The encoding '%1!s!' is not a supported encoding for the CharacterSet connection option.", -48, true }
},
{
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver for SQL Server. "
"Access the following URL to download the ODBC Driver for SQL Server for %1!s!: "
"https://go.microsoft.com/fwlink/?LinkId=163712", -49, true }
},
{
SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE,
{ IMSSP, (SQLCHAR*)"This function only works with statements that have static or keyset scrollable cursors.", -50, false }
},
{
SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE,
{ IMSSP, (SQLCHAR*)"This function only works with statements that are not scrollable.", -51, false }
},
// new error for 2.0, used here since 1.1 didn't have a -52
{
SQLSRV_ERROR_MAX_PARAMS_EXCEEDED,
{ IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -52, true }
},
{
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE,
{ IMSSP, (SQLCHAR*)"The scroll type passed to sqlsrv_fetch, sqlsrv_fetch_array, or sqlsrv_fetch_object was not valid. "
"Please use one of the SQLSRV_SCROLL constants.", -53, false }
},
{
SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE,
{ IMSSP, (SQLCHAR*)"The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', "
"'keyset', 'forward', or 'buffered'.", -54, false }
},
{
SQLSRV_ERROR_UNKNOWN_SERVER_VERSION,
{ IMSSP, (SQLCHAR*)"Failed to retrieve the server version. Unable to continue.", -55, false }
},
{
SQLSRV_ERROR_INVALID_PARAMETER_ENCODING,
{ IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -56, true }
},
{
SS_SQLSRV_ERROR_PARAM_INVALID_INDEX,
{ IMSSP, (SQLCHAR*)"String keys are not allowed in parameters arrays.", -57, false }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED,
{ IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -58, true }
},
{
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
{ IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -59, true }
},
{
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
{ IMSSP, (SQLCHAR*) "Setting for " INI_BUFFERED_QUERY_LIMIT " was non-int or non-positive.", -60, false }
},
{
SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF,
{ IMSSP, (SQLCHAR*)"Variable parameter %1!d! not passed by reference (prefaced with an &). "
"Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value."
, -61, true }
},
{
SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED,
{ IMSSP, (SQLCHAR*)"Must specify the SQL type for each parameter in a parameterized query when using sqlsrv_query in a column encryption enabled connection.", -63, false }
},
// internal warning definitions
{
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY,
{ SSPWARN, (SQLCHAR*)"An empty field name was skipped by sqlsrv_fetch_object.", -100, 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!.", -105, true }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! was specified for Driver option.", -106, true }
},
{
SQLSRV_ERROR_SPECIFIED_DRIVER_NOT_FOUND,
{ IMSSP, (SQLCHAR*) "The specified ODBC Driver is not found.", -107, false }
},
{
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -108, false }
},
{
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
{ IMSSP, (SQLCHAR*)"Error converting a double (value out of range) to an integer.", -109, false }
},
{
SQLSRV_ERROR_INVALID_AKV_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*) "Invalid option for the KeyStoreAuthentication keyword. Only KeyVaultPassword or KeyVaultClientSecret is allowed.", -110, 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.", -111, false }
},
{
SQLSRV_ERROR_AKV_NAME_MISSING,
{ IMSSP, (SQLCHAR*) "The username or client Id for Azure Key Vault is missing.", -112, false }
},
{
SQLSRV_ERROR_AKV_SECRET_MISSING,
{ IMSSP, (SQLCHAR*) "The password or client secret for Azure Key Vault is missing.", -113, false }
},
{
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -114, 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.", -115, false}
},
{
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -116, false}
},
{
SQLSRV_ERROR_INVALID_DECIMAL_PLACES,
{ IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -117, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION,
{ IMSSP, (SQLCHAR*) "The statement must be executed to retrieve Data Classification Sensitivity Metadata.", -119, 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.", -120, false}
},
{
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -121, true}
},
{
SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE,
{ IMSSP, (SQLCHAR*) "An error occurred translating a string for Table-Valued Param %1!d! Column %2!d! to UTF-16: %3!s!", -122, true }
},
{
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
{ IMSSP, (SQLCHAR*) "An invalid type for Table-Valued Param %1!d! Column %2!d! was specified", -123, true }
},
{
SQLSRV_ERROR_TVP_FETCH_METADATA,
{ IMSSP, (SQLCHAR*) "Failed to get metadata for Table-Valued Param %1!d!", -124, true }
},
{
SQLSRV_ERROR_TVP_INVALID_INPUTS,
{ IMSSP, (SQLCHAR*) "Invalid inputs for Table-Valued Param %1!d!", -125, true }
},
{
SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME,
{ IMSSP, (SQLCHAR*) "Expect a non-empty string for a Type Name for Table-Valued Param %1!d!", -126, true }
},
{
SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE,
{ IMSSP, (SQLCHAR*) "For Table-Valued Param %1!d! the number of values in a row is expected to be %2!d!", -127, true }
},
{
SQLSRV_ERROR_TVP_STRING_KEYS,
{ IMSSP, (SQLCHAR*) "Associative arrays not allowed for Table-Valued Param %1!d!", -128, true }
},
{
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
{ IMSSP, (SQLCHAR*) "Expect an array for each row for Table-Valued Param %1!d!", -129, true }
},
{
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
{ IMSSP, (SQLCHAR*) "You cannot return data in a table-valued parameter. Table-valued parameters are input-only.", -130, false }
},
// terminate the list of errors/warnings
{ UINT_MAX, {} }
};
// check the global variables of sqlsrv severity whether the message qualifies to be logged with the LOG macro
bool ss_severity_check(_In_ unsigned int severity)
{
return ((severity & SQLSRV_G(log_severity)) && (SQLSRV_G(current_subsystem) & SQLSRV_G(log_subsystems)));
}
bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ int warning, _In_opt_ va_list* print_args )
{
logging_severity severity = SEV_ERROR;
if( warning && !SQLSRV_G( warnings_return_as_errors )) {
severity = SEV_WARNING;
}
return handle_errors_and_warnings( ctx, &SQLSRV_G( errors ), &SQLSRV_G( warnings ), severity, sqlsrv_error_code, warning,
print_args );
}
// sqlsrv_errors( [int $errorsAndOrWarnings] )
//
// Returns extended error and/or warning information about the last sqlsrv
// operation performed.
//
// The sqlsrv_errors function can return error and/or warning information by
// calling it with one of the following parameter values below.
//
// Parameters
//
// $errorsAndOrWarnings[OPTIONAL]: A predefined constant. This parameter can
// take one of the values listed:
//
// SQLSRV_ERR_ALL
// Errors and warnings generated on the last sqlsrv function call are returned.
// SQLSRV_ERR_ERRORS
// Errors generated on the last sqlsrv function call are returned.
// SQLSRV_ERR_WARNINGS
// Warnings generated on the last sqlsrv function call are returned.
//
// If no parameter value is supplied, SQLSRV_ERR_ALL is the default
//
// Return Value
// An array of arrays, or null. An example of an error returned:
// Array
// (
// [0] => Array
// (
// [0] => HYT00
// [SQLSTATE] => HYT00
// [1] => 0
// [code] => 0
// [2] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired
// [message] => [Microsoft][ODBC Driver 11 for SQL Server]Query timeout expired
// )
// )
PHP_FUNCTION( sqlsrv_errors )
{
// SQLSRV_UNUSED( execute_data );
zend_long flags = SQLSRV_ERR_ALL;
LOG_FUNCTION( "sqlsrv_errors" );
if(( zend_parse_parameters( ZEND_NUM_ARGS(), "|l", &flags ) == FAILURE ) ||
( flags != SQLSRV_ERR_ALL && flags != SQLSRV_ERR_ERRORS && flags != SQLSRV_ERR_WARNINGS )) {
LOG( SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ );
RETURN_FALSE;
}
zval err_z;
ZVAL_UNDEF(&err_z);
array_init(&err_z);
if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_ERRORS ) {
if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( errors ) )) {
zval_ptr_dtor(&err_z);
RETURN_FALSE;
}
}
if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_WARNINGS ) {
if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( warnings ) )) {
zval_ptr_dtor(&err_z);
RETURN_FALSE;
}
}
if( zend_hash_num_elements( Z_ARRVAL_P( &err_z )) == 0 ) {
zval_ptr_dtor(&err_z);
RETURN_NULL();
}
RETURN_ZVAL( &err_z, 1, 1 );
}
// sqlsrv_configure( string $setting, mixed $value )
//
// Changes the settings for error handling and logging options.
//
// Parameters
// $setting: The name of the setting to be configured. The possible implemented values are
// "WarningsReturnAsErrors", "LogSubsystems", and "LogSeverity".
//
// $value: The value to be applied to the setting specified in the $setting
// parameter. See MSDN or the MINIT function for possible values.
//
// Return Value
// If sqlsrv_configure is called with an unsupported setting or value, the
// function returns false. Otherwise, the function returns true.
PHP_FUNCTION( sqlsrv_configure )
{
// SQLSRV_UNUSED( execute_data );
LOG_FUNCTION( "sqlsrv_configure" );
char* option;
size_t option_len;
zval* value_z;
sqlsrv_context_auto_ptr error_ctx;
RETVAL_FALSE;
reset_errors();
try {
// dummy context to pass onto the error handler
error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
error_ctx->set_func(_FN_);
int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "sz", &option, &option_len, &value_z );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
// WarningsReturnAsErrors
SQLSRV_ASSERT( option[option_len] == '\0', "sqlsrv_configure: option was not null terminated." );
if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) {
SQLSRV_G( warnings_return_as_errors ) = zend_is_true( value_z ) ? true : false;
LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off");
RETURN_TRUE;
}
// LogSeverity
else if( !stricmp( option, INI_LOG_SEVERITY )) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
zend_long severity_mask = Z_LVAL_P( value_z );
// make sure they can't use 0 to shut off the masking in the severity
if( severity_mask < SEV_ALL || severity_mask == 0 || severity_mask > (SEV_NOTICE + SEV_ERROR + SEV_WARNING) ) {
RETURN_FALSE;
}
SQLSRV_G( log_severity ) = static_cast<logging_severity>( severity_mask );
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity ));
RETURN_TRUE;
}
// LogSubsystems
else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
zend_long subsystem_mask = Z_LVAL_P( value_z );
if( subsystem_mask < LOG_ALL || subsystem_mask > (LOG_INIT + LOG_CONN + LOG_STMT + LOG_UTIL) ) {
RETURN_FALSE;
}
SQLSRV_G( log_subsystems ) = static_cast<logging_subsystems>( subsystem_mask );
LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems ));
RETURN_TRUE;
}
else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) {
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_LONG ), error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) {
throw ss::SSException();
}
zend_long buffered_query_limit = Z_LVAL_P( value_z );
CHECK_CUSTOM_ERROR( buffered_query_limit <= 0, error_ctx, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, _FN_ ) {
throw ss::SSException();
}
SQLSRV_G( buffered_query_limit ) = buffered_query_limit;
LOG( SEV_NOTICE, INI_PREFIX INI_BUFFERED_QUERY_LIMIT " = %1!d!", SQLSRV_G( buffered_query_limit ));
RETURN_TRUE;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_configure: Unknown exception caught." );
}
}
// sqlsrv_get_config( string $setting )
//
// Returns the current value of the specified configuration setting.
//
// Parameters
// $setting: The configuration setting for which the value is returned. For a
// list of configurable settings, see sqlsrv_configure.
//
// Return Value
// The value of the setting specified by the $setting parameter. If an invalid
// setting is specified, false is returned and an error is added to the error
// collection. Because false is a valid value for WarningsReturnAsErrors, to
// really determine if an error occurred, call sqlsrv_errors.
PHP_FUNCTION( sqlsrv_get_config )
{
// SQLSRV_UNUSED( execute_data );
char* option = NULL;
size_t option_len;
sqlsrv_context_auto_ptr error_ctx;
LOG_FUNCTION( "sqlsrv_get_config" );
reset_errors();
try {
// dummy context to pass onto the error handler
error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
error_ctx->set_func(_FN_);
int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "s", &option, &option_len );
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
throw ss::SSException();
}
SQLSRV_ASSERT( option != NULL, "sqlsrv_get_config: option was null." );
if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) {
ZVAL_BOOL( return_value, SQLSRV_G( warnings_return_as_errors ));
return;
}
else if( !stricmp( option, INI_LOG_SEVERITY )) {
ZVAL_LONG( return_value, SQLSRV_G( log_severity ));
return;
}
else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) {
ZVAL_LONG( return_value, SQLSRV_G( log_subsystems ));
return;
}
else if( !stricmp( option, INI_BUFFERED_QUERY_LIMIT )) {
ZVAL_LONG( return_value, SQLSRV_G( buffered_query_limit ));
return;
}
else {
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
}
}
catch( core::CoreException& ) {
RETURN_FALSE;
}
catch( ... ) {
DIE( "sqlsrv_get_config: Unknown exception caught." );
}
}
namespace {
sqlsrv_error_const* get_error_message( _In_ 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_ss_errors_ht, sqlsrv_error_code ))) != NULL ? SUCCESS : FAILURE;
if ( zr == FAILURE ) {
DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code );
}
SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null" );
return error_message;
}
void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain,
_In_ bool warning )
{
array_init(error_z);
// sqlstate
zval temp;
ZVAL_UNDEF(&temp);
core::sqlsrv_zval_stringl( &temp, reinterpret_cast<char*>( error->sqlstate ), SQL_SQLSTATE_SIZE );
Z_TRY_ADDREF_P( &temp );
if( add_next_index_zval( error_z, &temp ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
add_assoc_zval(error_z, "SQLSTATE", &temp);
// native_code
if( add_next_index_long( error_z, error->native_code ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
add_assoc_long(error_z, "code", error->native_code);
// native_message
ZVAL_UNDEF(&temp);
ZVAL_STRING( &temp, reinterpret_cast<char*>( error->native_message ) );
Z_TRY_ADDREF_P(&temp);
if( add_next_index_zval( error_z, &temp ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
add_assoc_zval(error_z, "message", &temp);
// If it is an error or if warning_return_as_errors is true than
// add the error or warning to the reported_chain.
if( !warning || SQLSRV_G( warnings_return_as_errors ) )
{
// if the warning is part of the ignored warning list than
// add to the ignored chain if the ignored chain is not null.
if( warning && ignore_warning( reinterpret_cast<char*>(error->sqlstate), error->native_code ) &&
ignored_chain != NULL ) {
if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
}
else {
// It is either an error or a warning which should not be ignored.
if( add_next_index_zval( reported_chain, error_z ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
}
}
else
{
// It is a warning with warning_return_as_errors as false, so simply add it to the ignored_chain list
if( ignored_chain != NULL ) {
if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) {
DIE( "Fatal error during error processing" );
}
}
}
}
bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, _In_ logging_severity log_severity,
_In_ unsigned int sqlsrv_error_code, _In_ int warning, _In_opt_ va_list* print_args )
{
bool result = true;
bool errors_ignored = false;
size_t prev_reported_cnt = 0;
bool reported_chain_was_null = false;
bool ignored_chain_was_null = false;
zval error_z;
ZVAL_UNDEF(&error_z);
sqlsrv_error_auto_ptr error;
// array of reported errors
if( Z_TYPE_P( reported_chain ) == IS_NULL ) {
reported_chain_was_null = true;
array_init(reported_chain);
}
else {
prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_P( reported_chain ));
}
// array of ignored errors
if( ignored_chain != NULL ) {
if( Z_TYPE_P( ignored_chain ) == IS_NULL ) {
ignored_chain_was_null = true;
array_init( ignored_chain );
}
}
if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {
core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, log_severity, print_args );
copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning );
}
SQLSMALLINT record_number = 0;
do {
result = core_sqlsrv_get_odbc_error( ctx, ++record_number, error, log_severity );
if( result ) {
copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning );
}
} while( result );
// If it were a warning, we report that warnings where ignored except if warnings_return_as_errors
// was true and we added some warnings to the reported_chain.
if( warning ) {
errors_ignored = true;
if( SQLSRV_G( warnings_return_as_errors ) ) {
if( zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) > prev_reported_cnt ) {
// We actually added some errors
errors_ignored = false;
}
}
}
// if the error array came in as NULL and didn't have anything added to it, return it as NULL
if( reported_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( reported_chain )) == 0 ) {
zend_hash_destroy( Z_ARRVAL_P( reported_chain ));
FREE_HASHTABLE( Z_ARRVAL_P( reported_chain ));
ZVAL_NULL( reported_chain );
}
if( ignored_chain != NULL && ignored_chain_was_null && zend_hash_num_elements( Z_ARRVAL_P( ignored_chain )) == 0 ) {
zend_hash_destroy( Z_ARRVAL_P( ignored_chain ));
FREE_HASHTABLE( Z_ARRVAL_P( ignored_chain ));
ZVAL_NULL( ignored_chain );
}
// If it was an error instead of a warning than we always return errors_ignored = false.
return errors_ignored;
}
// return whether or not a warning should be ignored or returned as an error if WarningsReturnAsErrors is true
// see RINIT in init.cpp for information about which errors are ignored.
bool ignore_warning( _In_ char* sql_state, _In_ int native_code )
{
zend_ulong index = -1;
zend_string* key = NULL;
void* error_temp = NULL;
ZEND_HASH_FOREACH_KEY_PTR( g_ss_warnings_to_ignore_ht, index, key, error_temp ) {
sqlsrv_error* error = static_cast<sqlsrv_error*>( error_temp );
if (NULL == error) {
return false;
}
if( !strncmp( reinterpret_cast<char*>( error->sqlstate ), sql_state, SQL_SQLSTATE_SIZE ) &&
( error->native_code == native_code || error->native_code == -1 )) {
return true;
}
} ZEND_HASH_FOREACH_END();
return false;
}
int sqlsrv_merge_zend_hash_dtor( _Inout_ zval* dest )
{
zval_ptr_dtor( dest );
return ZEND_HASH_APPLY_REMOVE;
}
// sqlsrv_merge_zend_hash
// merge a source hash into a dest hash table and return any errors.
bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z )
{
if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" );
if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" );
if( Z_TYPE_P( src_z ) == IS_NULL ) {
return true;
}
HashTable* src_ht = Z_ARRVAL_P( src_z );
zend_ulong index = -1;
zend_string* key = NULL;
zval* value_z = NULL;
ZEND_HASH_FOREACH_KEY_VAL( src_ht, index, key, value_z ) {
if ( !value_z ) {
zend_hash_apply( Z_ARRVAL_P(dest_z), sqlsrv_merge_zend_hash_dtor );
return false;
}
int result = add_next_index_zval( dest_z, value_z );
if( result == FAILURE ) {
zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor );
return false;
}
Z_TRY_ADDREF_P( value_z );
} ZEND_HASH_FOREACH_END();
return true;
}
} // namespace