Merge remote-tracking branch 'upstream/dev' into nextrowset-fix

This commit is contained in:
David Puglielli 2017-09-26 15:04:40 -07:00
commit be2be4bb27
142 changed files with 7011 additions and 488 deletions

View file

@ -15,7 +15,9 @@ env:
- PHPSQLDIR=/REPO/msphpsql-dev
- TEST_PHP_SQL_SERVER=sql
- SQLSRV_DBNAME=msphpsql_sqlsrv
- PDOSQLSRV_DBNAME=msphpsql_pdosqlsrv
- PDOSQLSRV_DBNAME=msphpsql_pdosqlsrv
- TEST_PHP_SQL_UID=sa
- TEST_PHP_SQL_PWD=Password123
before_install:
- docker pull microsoft/mssql-server-linux
@ -33,6 +35,12 @@ script:
- docker logs client
- travis_retry docker exec client python ./test/functional/setup/setup_dbs.py -dbname $SQLSRV_DBNAME
- travis_retry docker exec client python ./test/functional/setup/setup_dbs.py -dbname $PDOSQLSRV_DBNAME
- docker exec client cp ./source/shared/msodbcsql.h ./test/functional/setup/
- travis_retry docker exec client python ./test/functional/setup/build_ksp.py
- docker exec client cp ./test/functional/setup/myKSP.so ./test/functional/sqlsrv/
- docker exec client cp ./test/functional/setup/myKSP.so ./test/functional/pdo_sqlsrv/
- travis_retry docker exec client python ./test/functional/setup/run_ksp.py -server $TEST_PHP_SQL_SERVER -dbname $SQLSRV_DBNAME -uid $TEST_PHP_SQL_UID -pwd $TEST_PHP_SQL_PWD
- travis_retry docker exec client python ./test/functional/setup/run_ksp.py -server $TEST_PHP_SQL_SERVER -dbname $PDOSQLSRV_DBNAME -uid $TEST_PHP_SQL_UID -pwd $TEST_PHP_SQL_PWD
- travis_retry docker exec client php ./source/pdo_sqlsrv/run-tests.php ./test/functional/pdo_sqlsrv/*.phpt
- travis_retry docker exec client php ./source/sqlsrv/run-tests.php ./test/functional/sqlsrv/*.phpt
- docker exec client bash -c 'for f in ./test/functional/sqlsrv/*.diff; do ls $f 2>/dev/null; cat $f 2>/dev/null; done || true'

View file

@ -3,6 +3,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
## Windows/Linux 5.1.0-preview - 2017-09-15
Updated PECL release packages. Here is the list of updates:
### Added
- Added support for SUSE 12
- Added support for Always Encrypted with basic CRUD functionalities (see [here](https://github.com/Microsoft/msphpsql/wiki/Features#aebindparam))
- Support for Windows Certificate Store (use connection keyword ColumnEncryption)
- Support for custom key store provider (use connection keywords ColumnEncryption, CEKeystoreProvider, CEKeystoreName, CEKeystoreEncryptKey)
- Support for inserting into an encrypted column
- Support for fetching from an encrypted column
- Added support for MSODBC 17 preview
- Added Driver option to set the ODBC driver, Added"Driver" option, valid values are ODBC Driver 13 for SQL Server,ODBC Driver 11 for SQL Server, and ODBC Driver 17 for SQL Server
- If the user intends to use the new Always Encrypted features, we recommend you to specify explicitly the 'Driver' option to 'ODBC Driver 17 for SQL Server' in the connection string
### Limitations
- Always Encrypted functionalities are only supported using [MSODBC 17 preview](https://github.com/Microsoft/msphpsql/tree/dev/ODBC%2017%20binaries%20preview)
- ODBC binaries for macOS available upon request
- MSODBC 17 preview msodbcsql.msi only works for Windows10
- [Always Encrypted limitations](https://github.com/Microsoft/msphpsql/wiki/Features#aelimitation)
- when using sqlsrv_query with Always Encrypted feature, SQL type has to be specified for each input (see [here](https://github.com/Microsoft/msphpsql/wiki/Features#aebindparam))
- No support for inout / output params when using sql_variant type
### Known Issues
- Connection pooling on Linux doesn't work properly if the user uses the MSODBC17 preview
- PDO::quote returns truncated string with garbage characters appended if the string contains a ASCII NUL ('/0') character
- Binding decimal type when using Always Encrypted in the SQLSRV x64 driver returns an error during insertion when the input does not have any decimal places
- When pooling is enabled in Linux or MAC
- unixODBC <= 2.3.4 (Linux and MAC) might not return proper diagnostics 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/Connection-Pooling-on-Linux-and-Mac)
## Windows/Linux/MAC 5.0.0-preview - 2017-07-31
Updated PECL release packages. Here is the list of updates:

View file

@ -0,0 +1,3 @@
The ODBC driver 17 preview binaries in this directory are required in order to use Always Encrypted (AE) functionality. Please note that these drivers should be considered to be preview versions -- they should not be used in production and are not supported by Microsoft. They will be replaced upon the official release of ODBC driver 17.
To install on Windows, simply run the msi. For instructions on installing the binaries on Linux platforms, please see [this page](https://github.com/Microsoft/msphpsql/wiki/Install-and-configuration#odbc-17-linux-installation).

Binary file not shown.

Binary file not shown.

View file

@ -166,6 +166,12 @@ test_script:
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\setup_dbs.py -dbname %SQLSRV_DBNAME%
- Echo setup test database for PDO_SQLSRV tests - %PDOSQLSRV_DBNAME%
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\setup_dbs.py -dbname %PDOSQLSRV_DBNAME%
- copy %APPVEYOR_BUILD_FOLDER%\source\shared\msodbcsql.h %APPVEYOR_BUILD_FOLDER%\test\functional\setup\
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\build_ksp.py
- copy %APPVEYOR_BUILD_FOLDER%\test\functional\setup\*.dll %APPVEYOR_BUILD_FOLDER%\test\functional\sqlsrv\
- copy %APPVEYOR_BUILD_FOLDER%\test\functional\setup\*.dll %APPVEYOR_BUILD_FOLDER%\test\functional\pdo_sqlsrv\
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\run_ksp.py -server %TEST_PHP_SQL_SERVER% -dbname %SQLSRV_DBNAME% -uid %TEST_PHP_SQL_UID% -pwd %TEST_PHP_SQL_PWD%
- python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\run_ksp.py -server %TEST_PHP_SQL_SERVER% -dbname %PDOSQLSRV_DBNAME% -uid %TEST_PHP_SQL_UID% -pwd %TEST_PHP_SQL_PWD%
- php run-tests.php -p php.exe %APPVEYOR_BUILD_FOLDER%\test\functional\sqlsrv\*.phpt > %APPVEYOR_BUILD_FOLDER%\test\functional\sqlsrv.log 2>&1
- type %APPVEYOR_BUILD_FOLDER%\test\functional\sqlsrv.log
- php run-tests.php -p php.exe %APPVEYOR_BUILD_FOLDER%\test\functional\pdo_sqlsrv\*.phpt > %APPVEYOR_BUILD_FOLDER%\test\functional\pdo_sqlsrv.log 2>&1

View file

@ -3,7 +3,7 @@
//
// Contents: JScript build configuration used by buildconf.bat
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Implements the PDO object for PDO_SQLSRV
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -42,6 +42,12 @@ const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char ConnectionPooling[] = "ConnectionPooling";
const char Authentication[] = "Authentication";
const char ColumnEncryption[] = "ColumnEncryption";
const char Driver[] = "Driver";
const char CEKeystoreProvider[] = "CEKeystoreProvider";
const char CEKeystoreName[] = "CEKeystoreName";
const char CEKeystoreEncryptKey[] = "CEKeystoreEncryptKey";
#ifdef _WIN32
const char ConnectRetryCount[] = "ConnectRetryCount";
const char ConnectRetryInterval[] = "ConnectRetryInterval";
@ -220,6 +226,51 @@ const connection_option PDO_CONN_OPTS[] = {
CONN_ATTR_BOOL,
conn_null_func::func
},
{
PDOConnOptionNames::ColumnEncryption,
sizeof(PDOConnOptionNames::ColumnEncryption),
SQLSRV_CONN_OPTION_COLUMNENCRYPTION,
ODBCConnOptions::ColumnEncryption,
sizeof(ODBCConnOptions::ColumnEncryption),
CONN_ATTR_STRING,
column_encryption_set_func::func
},
{
PDOConnOptionNames::Driver,
sizeof(PDOConnOptionNames::Driver),
SQLSRV_CONN_OPTION_DRIVER,
ODBCConnOptions::Driver,
sizeof(ODBCConnOptions::Driver),
CONN_ATTR_STRING,
driver_set_func::func
},
{
PDOConnOptionNames::CEKeystoreProvider,
sizeof(PDOConnOptionNames::CEKeystoreProvider),
SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER,
ODBCConnOptions::CEKeystoreProvider,
sizeof(ODBCConnOptions::CEKeystoreProvider),
CONN_ATTR_STRING,
ce_ksp_provider_set_func::func
},
{
PDOConnOptionNames::CEKeystoreName,
sizeof(PDOConnOptionNames::CEKeystoreName),
SQLSRV_CONN_OPTION_CEKEYSTORE_NAME,
ODBCConnOptions::CEKeystoreName,
sizeof(ODBCConnOptions::CEKeystoreName),
CONN_ATTR_STRING,
ce_ksp_provider_set_func::func
},
{
PDOConnOptionNames::CEKeystoreEncryptKey,
sizeof(PDOConnOptionNames::CEKeystoreEncryptKey),
SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY,
ODBCConnOptions::CEKeystoreEncryptKey,
sizeof(ODBCConnOptions::CEKeystoreEncryptKey),
CONN_ATTR_STRING,
ce_ksp_provider_set_func::func
},
#ifdef _WIN32
{
PDOConnOptionNames::ConnectRetryCount,

View file

@ -3,7 +3,7 @@
//
// Contents: initialization routines for PDO_SQLSRV
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -5,7 +5,7 @@
//
// Copyright Microsoft Corporation
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Implements the PDOStatement object for the PDO_SQLSRV
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Utility functions used by both connection or statement functions
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -56,8 +56,8 @@ pdo_error PDO_ERRORS[] = {
{
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 13 for SQL Server to "
"communicate with SQL Server. Access the following URL to download the ODBC Driver 13 for SQL Server "
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver for SQL Server to "
"communicate with SQL Server. Access the following URL to download the ODBC Driver for SQL Server "
"for %1!s!: "
"http://go.microsoft.com/fwlink/?LinkId=163712", -1, true }
},
@ -380,7 +380,31 @@ pdo_error PDO_ERRORS[] = {
{
PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -73, false }
},
},
{
SQLSRV_ERROR_KEYSTORE_NAME_MISSING,
{ IMSSP, (SQLCHAR*) "The name of the custom keystore provider is missing.", -74, false}
},
{
SQLSRV_ERROR_KEYSTORE_PATH_MISSING,
{ IMSSP, (SQLCHAR*) "The path to the custom keystore provider is missing.", -75, false}
},
{
SQLSRV_ERROR_KEYSTORE_KEY_MISSING,
{ IMSSP, (SQLCHAR*) "The encryption key for the custom keystore provider is missing.", -76, false}
},
{
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading a custom keystore provider.", -77, false}
},
{
SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.", -78, false }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! was specified for Driver option.", -79, true }
},
{ UINT_MAX, {} }
};

View file

@ -6,7 +6,7 @@
//
// Contents: Declarations for the extension
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Version resource
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -6,7 +6,7 @@
// Contents: Contains functions for handling Windows format strings
// and UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// Contents: Contains functions for handling Windows format strings
// and UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Contains functions for handling UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Contains functions for handling UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -26,9 +26,8 @@
#include <windows.h>
#include <winver.h>
#endif // _WIN32
#include <string>
#include <sstream>
#include <vector>
#ifndef _WIN32
#include <sys/utsname.h>
@ -49,11 +48,12 @@ const int INFO_BUFFER_LEN = 256;
// processor architectures
const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" };
// ODBC driver name.
const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"};
// ODBC driver names.
// the order of this list should match the order of DRIVER_VERSION enum
std::vector<std::string> CONNECTION_STRING_DRIVER_NAME{ "Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};", "Driver={ODBC Driver 17 for SQL Server};" };
// default options if only the server is specified
const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}";
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};";
@ -64,14 +64,14 @@ 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 TSRMLS_DC );
_Inout_opt_ HashTable* options_ht, _In_ const connection_option valid_conn_opts[],
void* driver,_Inout_ std::string& connection_string TSRMLS_DC );
void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC );
const char* get_processor_arch( void );
void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len TSRMLS_DC );
connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ const char* key, _In_ SQLULEN key_len TSRMLS_DC );
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 TSRMLS_DC );
void load_configure_ksp( _Inout_ sqlsrv_conn* conn TSRMLS_DC );
}
// core_sqlsrv_connect
@ -99,13 +99,14 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
std::string conn_str;
conn_str.reserve( DEFAULT_CONN_STR_LEN );
sqlsrv_malloc_auto_ptr<sqlsrv_conn> conn;
sqlsrv_malloc_auto_ptr<SQLWCHAR> wconn_string;
unsigned int wconn_len = 0;
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 {
@ -121,6 +122,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
( 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
@ -130,11 +132,12 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
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);
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;
henv = &henv_ncp;
is_pooled = false;
}
}
}
@ -142,73 +145,51 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
SQLHANDLE temp_conn_h;
core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC );
conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC );
conn->set_func( driver_func );
build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str TSRMLS_CC );
bool is_missing_driver = false;
for( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) {
conn_str = CONNECTION_STRING_DRIVER_NAME[i];
build_connection_string_and_set_conn_attr(conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str TSRMLS_CC);
if ( conn->is_driver_set ) {
r = core_odbc_connect( conn, conn_str, is_missing_driver, is_pooled );
}
else if ( conn->ce_option.enabled ) {
conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION::ODBC_DRIVER_17 ];
r = core_odbc_connect( conn, conn_str, is_missing_driver, is_pooled );
// We only support UTF-8 encoding for connection string.
// Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW
wconn_len = static_cast<unsigned int>( conn_str.length() + 1 ) * sizeof( SQLWCHAR );
CHECK_CUSTOM_ERROR( is_missing_driver, conn, SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED, get_processor_arch()) {
throw core::CoreException();
}
}
else {
wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast<unsigned int>(conn_str.length()), &wconn_len );
CHECK_CUSTOM_ERROR( wconn_string == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message())
{
for ( std::size_t i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST; ++i ) {
is_missing_driver = false;
std::string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION(i) ];
r = core_odbc_connect( conn, conn_str_driver, is_missing_driver, is_pooled );
CHECK_CUSTOM_ERROR( is_missing_driver && ( i == DRIVER_VERSION::LAST ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) {
throw core::CoreException();
}
SQLSMALLINT output_conn_size;
#ifndef _WIN32
// unixODBC 2.3.1 requires a non-wide SQLDriverConnect call while pooling enabled.
// connection handle has been allocated using henv_cp, means pooling enabled in a PHP script
if ( henv == &henv_cp )
{
r = SQLDriverConnect( conn->handle(), NULL, (SQLCHAR*)conn_str.c_str(), SQL_NTS, NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
}
else
{
r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast<SQLWCHAR*>( wconn_string.get() ), static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
}
#else
r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast<SQLWCHAR*>( wconn_string.get() ), static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
#endif // !_WIN32
// clear the connection string from memory to remove sensitive data (such as a password).
memset( const_cast<char*>(conn_str.c_str()), 0, conn_str.size());
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
if ( !SQL_SUCCEEDED( r )) {
SQLCHAR state[SQL_SQLSTATE_BUFSIZE];
SQLSMALLINT len;
SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
bool missing_driver_error = ( SQL_SUCCEEDED( sr ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && state[4] == '2' );
// if it's a IM002, meaning that the correct ODBC driver is not installed
CHECK_CUSTOM_ERROR(missing_driver_error && ( i == DRIVER_VERSION::MAX ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) {
throw core::CoreException();
}
if ( !missing_driver_error ) {
break;
}
}
else {
conn->driver_version = static_cast<DRIVER_VERSION>( i );
if ( !is_missing_driver) {
break;
}
}
CHECK_SQL_ERROR( r, conn ) {
throw core::CoreException();
}
} // for
} // else ce_option enabled
CHECK_SQL_ERROR( r, conn ) {
throw core::CoreException();
}
CHECK_SQL_WARNING_AS_ERROR( r, conn ) {
throw core::CoreException();
}
CHECK_SQL_WARNING_AS_ERROR( r, conn ) {
throw core::CoreException();
}
// determine the version of the server we're connected to. The server version is left in the
// connection upon return.
load_configure_ksp( conn );
// 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)
@ -223,32 +204,29 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
#endif // !_WIN32
}
catch( std::bad_alloc& ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
conn->invalidate();
DIE( "C++ memory allocation failure building the connection string." );
}
catch( std::out_of_range const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
conn->invalidate();
throw;
}
catch( std::length_error const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
conn->invalidate();
throw;
}
catch( core::CoreException& ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
conn->invalidate();
throw;
}
conn_str.clear();
sqlsrv_conn* return_conn = conn;
conn.transferred();
@ -256,6 +234,60 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
}
// 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
// missing_driver_error - indicates whether odbc driver is installed on client machine
// 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, _Inout_ bool& is_missing_driver, _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 );
// 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 );
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 to remove sensitive data (such as a password).
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
if (!SQL_SUCCEEDED(r)) {
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
// sql state IM002/IM003 in ODBC 17, means that the correct ODBC driver is not installed
is_missing_driver = ( SQL_SUCCEEDED(sr) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && (state[4] == '2' || state[4] == '3'));
}
return r;
}
// core_sqlsrv_begin_transaction
// Begins a transaction on a specified connection. The current transaction
@ -776,6 +808,66 @@ void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC )
conn->server_version = version_major;
}
// Column Encryption feature: if a custom keystore provider is specified,
// load and configure it when column encryption is enabled, but this step has
// to be executed after the connection has been established
void load_configure_ksp( _Inout_ sqlsrv_conn* conn TSRMLS_DC )
{
// If column encryption is not enabled simply do nothing. Otherwise, check if a custom keystore provider
// is required for encryption or decryption. Note, in order to load and configure a custom keystore provider,
// all KSP fields in conn->ce_option must be defined.
if ( ! conn->ce_option.enabled || ! conn->ce_option.ksp_required )
return;
// Do something like the following sample
// use the KSP related fields in conn->ce_option
// CEKEYSTOREDATA is defined in msodbcsql.h
// https://docs.microsoft.com/en-us/sql/connect/odbc/custom-keystore-providers
CHECK_CUSTOM_ERROR( conn->ce_option.ksp_name == NULL, conn, SQLSRV_ERROR_KEYSTORE_NAME_MISSING) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( conn->ce_option.ksp_path == NULL, conn, SQLSRV_ERROR_KEYSTORE_PATH_MISSING) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR( conn->ce_option.key_size == 0, conn, SQLSRV_ERROR_KEYSTORE_KEY_MISSING) {
throw core::CoreException();
}
char* ksp_name = Z_STRVAL_P( conn->ce_option.ksp_name );
char* ksp_path = Z_STRVAL_P( conn->ce_option.ksp_path );
unsigned int name_len = Z_STRLEN_P( conn->ce_option.ksp_name );
unsigned int key_size = conn->ce_option.key_size;
sqlsrv_malloc_auto_ptr<unsigned char> ksp_data;
ksp_data = reinterpret_cast<unsigned char*>( sqlsrv_malloc( sizeof( CEKEYSTOREDATA ) + key_size ) );
CEKEYSTOREDATA *pKsd = reinterpret_cast<CEKEYSTOREDATA*>( ksp_data.get() );
pKsd->dataSize = key_size;
// First, convert conn->ce_option.ksp_name to a WCHAR version
unsigned int wname_len = 0;
sqlsrv_malloc_auto_ptr<SQLWCHAR> wksp_name;
wksp_name = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, ksp_name, name_len, &wname_len );
CHECK_CUSTOM_ERROR( wksp_name == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE ) {
throw core::CoreException();
}
pKsd->name = (wchar_t *) wksp_name.get();
// Next, extract the character string from conn->ce_option.ksp_encrypt_key into encrypt_key
char* encrypt_key = Z_STRVAL_P( conn->ce_option.ksp_encrypt_key );
memcpy_s( pKsd->data, key_size * sizeof( char ) , encrypt_key, key_size );
core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREPROVIDER, ksp_path, SQL_NTS );
core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast<SQLPOINTER>( pKsd ), 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 TSRMLS_DC )
{
// wrap a connection option in a quote. It is presumed that any character that need to be escaped will
@ -795,8 +887,7 @@ void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_l
} // 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
TSRMLS_DC )
void conn_str_append_func::func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str TSRMLS_DC )
{
const char* val_str = Z_STRVAL_P( value );
size_t val_len = Z_STRLEN_P( value );
@ -805,12 +896,72 @@ void conn_str_append_func::func( _In_ connection_option const* option, _In_ zval
// do nothing for connection pooling since we handled it earlier when
// deciding which environment handle to use.
void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/
TSRMLS_DC )
void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ TSRMLS_DC )
{
TSRMLS_C;
}
void driver_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC )
{
const char* val_str = Z_STRVAL_P( value );
size_t val_len = Z_STRLEN_P( value );
std::string driver_option( "" );
common_conn_str_append_func( option->odbc_name, val_str, val_len, driver_option TSRMLS_CC );
CHECK_CUSTOM_ERROR( std::find( CONNECTION_STRING_DRIVER_NAME.begin(), CONNECTION_STRING_DRIVER_NAME.end(), driver_option) == CONNECTION_STRING_DRIVER_NAME.end(), conn, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, val_str){
throw core::CoreException();
}
conn->is_driver_set = true;
conn_str += driver_option;
}
void column_encryption_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC )
{
convert_to_string( value );
const char* value_str = Z_STRVAL_P( value );
// Column Encryption is disabled by default unless it is explicitly 'Enabled'
conn->ce_option.enabled = false;
if ( !stricmp(value_str, "enabled" )) {
conn->ce_option.enabled = true;
}
conn_str += option->odbc_name;
conn_str += "=";
conn_str += value_str;
conn_str += ";";
}
void ce_ksp_provider_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC )
{
SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "Wrong zval type for this keyword" )
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_CEKEYSTORE_PROVIDER:
conn->ce_option.ksp_path = value;
conn->ce_option.ksp_required = true;
break;
case SQLSRV_CONN_OPTION_CEKEYSTORE_NAME:
conn->ce_option.ksp_name = value;
conn->ce_option.ksp_required = true;
break;
case SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY:
conn->ce_option.ksp_encrypt_key = value;
conn->ce_option.key_size = value_len;
conn->ce_option.ksp_required = true;
break;
default:
SQLSRV_ASSERT(false, "ce_ksp_provider_set_func: Invalid KSP 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.

View file

@ -3,7 +3,7 @@
//
// Contents: common initialization routines shared by PDO and sqlsrv
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Result sets
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -83,7 +83,7 @@ bool get_bit( _In_ void* ptr, _In_ unsigned int bit )
// 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 TSRMLS_DC );
_In_ zend_long mem_used, _In_ size_t row_count TSRMLS_DC );
// dtor for each row in the cache
void cache_row_dtor( _In_ zval* data );
@ -342,12 +342,12 @@ struct row_dtor_closure {
sqlsrv_error* odbc_get_diag_rec( _In_ sqlsrv_stmt* odbc, _In_ SQLSMALLINT record_number )
{
SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ];
SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ];
SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ];
SQLINTEGER native_code;
SQLSMALLINT wnative_message_len = 0;
SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message,
SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len );
SQL_MAX_ERROR_MESSAGE_LENGTH + 1, &wnative_message_len );
if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
return NULL;
}
@ -687,7 +687,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm
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 TSRMLS_CC );
*lob_addr = read_lob_field( stmt, i, meta[i], mem_used, row_count TSRMLS_CC );
// a NULL pointer means NULL field
if( *lob_addr == NULL ) {
*out_buffer_length = SQL_NULL_DATA;
@ -734,12 +734,12 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm
break;
}
row_count++;
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
@ -1498,7 +1498,7 @@ void cache_row_dtor( _In_ zval* data )
}
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 TSRMLS_DC )
_In_ zend_long mem_used, _In_ size_t row_count TSRMLS_DC )
{
SQLSMALLINT extra = 0;
SQLULEN* output_buffer_len = NULL;
@ -1563,6 +1563,19 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in
SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" );
if ( stmt->conn->ce_option.enabled == true ) {
// cursor type SQLSRV_CURSOR_BUFFERED has to be FORWARD_ONLY
// thus has to close and reopen cursor to reset the cursor buffer
core::SQLCloseCursor(stmt);
core::SQLExecute(stmt);
// FETCH_NEXT until the cursor reaches the row that it was at
for (int i = 0; i <= row_count; i++) {
core::SQLFetchScroll(stmt, SQL_FETCH_NEXT, 0);
}
}
else {
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 ) {
@ -1571,8 +1584,6 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in
throw core::CoreException();
}
already_read += to_read - already_read;
to_read = last_field_len;
buffer.resize( to_read + extra + sizeof( SQLULEN ));
output_buffer_len = reinterpret_cast<SQLULEN*>( buffer.get() );
@ -1582,7 +1593,6 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in
}
// otherwise allocate another chunk of memory to read in
else {
already_read += to_read - already_read;
to_read *= 2;
CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {

View file

@ -6,7 +6,7 @@
//
// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -151,6 +151,7 @@ OACR_WARNING_POP
#include <deque>
#include <map>
#include <string>
#include <algorithm>
#include <limits>
#include <cassert>
@ -172,6 +173,8 @@ const int SQL_SERVER_MAX_FIELD_SIZE = 8000;
const int SQL_SERVER_MAX_PRECISION = 38;
const int SQL_SERVER_MAX_TYPE_SIZE = 0;
const int SQL_SERVER_MAX_PARAMS = 2100;
// increase the maximum message length to accommodate for the long error returned for operand type clash
const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 4;
// max size of a date time string when converting from a DateTime object to a string
const int MAX_DATETIME_STRING_LEN = 256;
@ -187,6 +190,9 @@ namespace AzureADOptions {
const char AZURE_AUTH_AD_PASSWORD[] = "ActiveDirectoryPassword";
}
// 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
@ -772,9 +778,9 @@ struct sqlsrv_error : public sqlsrv_error_const {
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_MESSAGE_LENGTH + 1 ));
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_MESSAGE_LENGTH + 1, reinterpret_cast<const char*>( message ));
strcpy_s( reinterpret_cast<char*>( native_message ), SQL_MAX_ERROR_MESSAGE_LENGTH + 1, reinterpret_cast<const char*>( message ));
native_code = code;
format = printf_format;
}
@ -1019,7 +1025,6 @@ struct sqlsrv_henv {
}
};
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
@ -1033,17 +1038,33 @@ enum SERVER_VERSION {
};
// supported driver versions.
enum DRIVER_VERSION : size_t {
MIN = 0,
ODBC_DRIVER_13 = MIN,
ODBC_DRIVER_11 = 1,
MAX = ODBC_DRIVER_11,
// the latest RTWed ODBC is the first one
enum DRIVER_VERSION : std::size_t{
FIRST = 0,
ODBC_DRIVER_13 = FIRST,
ODBC_DRIVER_11 = 1,
ODBC_DRIVER_17 = 2,
LAST = ODBC_DRIVER_17
};
// 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
zval_auto_ptr ksp_name; // keystore provider name
zval_auto_ptr ksp_path; // keystore provider path to the dynamically linked libary (either a *.dll or *.so)
zval_auto_ptr ksp_encrypt_key; // the encryption key used to configure the keystore provider
size_t key_size; // the length of ksp_encrypt_key without the NULL terminator
bool ksp_required; // a keystore provider is required to enable column encryption, false by default
col_encryption_option() : enabled( false ), key_size ( 0 ), ksp_required( false )
{
}
};
// *** connection resource structure ***
// this is the resource structure returned when a connection is made.
struct sqlsrv_conn : public sqlsrv_context {
@ -1051,14 +1072,15 @@ struct sqlsrv_conn : public sqlsrv_context {
// instance variables
SERVER_VERSION server_version; // version of the server that we're connected to
DRIVER_VERSION driver_version;
col_encryption_option ce_option; // holds the details of what are required to enable column encryption
bool is_driver_set;
// initialize with default values
sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) :
sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding )
{
server_version = SERVER_VERSION_UNKNOWN;
driver_version = ODBC_DRIVER_13;
is_driver_set = false;
}
// sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be
@ -1085,6 +1107,11 @@ const char APP[] = "APP";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char Authentication[] = "Authentication";
const char ColumnEncryption[] = "ColumnEncryption";
const char Driver[] = "Driver";
const char CEKeystoreProvider[] = "CEKeystoreProvider";
const char CEKeystoreName[] = "CEKeystoreName";
const char CEKeystoreEncryptKey[] = "CEKeystoreEncryptKey";
const char CharacterSet[] = "CharacterSet";
const char ConnectionPooling[] = "ConnectionPooling";
#ifdef _WIN32
@ -1131,6 +1158,11 @@ enum SQLSRV_CONN_OPTIONS {
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_TRANSPARANT_NETWORK_IP_RESOLUTION,
#ifdef _WIN32
SQLSRV_CONN_OPTION_CONN_RETRY_COUNT,
@ -1171,18 +1203,30 @@ struct connection_option {
void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC );
};
// 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 TSRMLS_DC );
};
struct conn_null_func {
static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/
TSRMLS_DC );
static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ TSRMLS_DC );
};
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 TSRMLS_DC );
};
struct driver_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
struct ce_ksp_provider_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
// 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 TSRMLS_DC );
@ -1191,6 +1235,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
_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 TSRMLS_DC );
SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _Inout_ bool& is_missing_driver, _In_ bool is_pooled );
void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len TSRMLS_DC );
void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC );
@ -1307,6 +1352,7 @@ struct sqlsrv_stmt : public sqlsrv_context {
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
int fwd_row_index; // fwd_row_index is the current row index, SQL_CURSOR_FORWARD_ONLY
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
@ -1593,6 +1639,8 @@ enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_ODBC,
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
SQLSRV_ERROR_ZEND_HASH,
SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
@ -1631,16 +1679,16 @@ enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_FIELD_INDEX_ERROR,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
SQLSRV_ERROR_KEYSTORE_NAME_MISSING,
SQLSRV_ERROR_KEYSTORE_PATH_MISSING,
SQLSRV_ERROR_KEYSTORE_KEY_MISSING,
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
// Driver specific error codes starts from here.
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
};
// the message returned by ODBC Driver 11 for SQL Server
static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command",
"[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" };
// SQLSTATE for all internal errors
extern SQLCHAR IMSSP[];
@ -1806,18 +1854,22 @@ namespace core {
// and return a more helpful message prepended to the ODBC errors if that error occurs
if( !SQL_SUCCEEDED( r )) {
SQLCHAR err_msg[ SQL_MAX_MESSAGE_LENGTH + 1 ] = { '\0' };
SQLCHAR err_msg[ SQL_MAX_ERROR_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 );
err_msg, SQL_MAX_ERROR_MESSAGE_LENGTH, &len );
CHECK_SQL_ERROR_OR_WARNING( rtemp, stmt ) {
throw CoreException();
}
std::size_t driver_version = stmt->conn->driver_version;
if( !strcmp( reinterpret_cast<const char*>( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) {
// 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 );
}
@ -1877,6 +1929,14 @@ namespace core {
}
}
inline void SQLCloseCursor( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
{
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,
@ -1928,6 +1988,17 @@ namespace core {
}
}
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 TSRMLS_DC )
{
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 SQLEndTran( _In_ SQLSMALLINT handleType, _Inout_ sqlsrv_conn* conn, _In_ SQLSMALLINT completionType TSRMLS_DC )
{
SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType );
@ -1998,6 +2069,15 @@ namespace core {
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 TSRMLS_DC)
{
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 TSRMLS_DC )
@ -2138,6 +2218,17 @@ namespace core {
}
}
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 TSRMLS_DC )
{
SQLRETURN r;
SQLHDESC hIpd = NULL;
core::SQLGetStmtAttr( stmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0 );
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 TSRMLS_DC )
{
@ -2348,17 +2439,15 @@ sqlsrv_conn* allocate_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void*
} // namespace core
// connection attribute functions
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*/ TSRMLS_DC )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_STRVAL_P( value )),
static_cast<SQLINTEGER>(Z_STRLEN_P( value )) TSRMLS_CC );
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_STRVAL_P( value )), static_cast<SQLINTEGER>( Z_STRLEN_P( value )) TSRMLS_CC );
}
catch( core::CoreException& ) {
catch ( core::CoreException& ) {
throw;
}
}

View file

@ -3,7 +3,7 @@
//
// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -101,6 +101,8 @@ void default_sql_size_and_scale( _Inout_ sqlsrv_stmt* stmt, _In_opt_ unsigned in
// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate)
void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding,
_Out_ SQLSMALLINT& sql_type TSRMLS_DC );
void ae_get_sql_type_info( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ SQLSMALLINT direction, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding,
_Out_ SQLSMALLINT& sql_type, _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC );
void col_cache_dtor( _Inout_ zval* data_z );
void field_cache_dtor( _Inout_ zval* data_z );
void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
@ -110,8 +112,9 @@ stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
// assure there is enough space for the output parameter string
void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z, _In_ SQLULEN paramno, SQLSRV_ENCODING encoding,
_In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _Out_writes_(buffer_len) SQLPOINTER& buffer,
_Out_ SQLLEN& buffer_len TSRMLS_DC );
_In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _In_ SQLSMALLINT decimal_digits,
_Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len TSRMLS_DC );
bool reset_ae_stream_cursor( _Inout_ sqlsrv_stmt* stmt );
void save_output_param_for_later( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_output_param& param TSRMLS_DC );
// send all the stream data
void send_param_streams( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
@ -131,6 +134,7 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error
past_fetch_end( false ),
current_results( NULL ),
cursor_type( SQL_CURSOR_FORWARD_ONLY ),
fwd_row_index( -1 ),
has_rows( false ),
fetch_called( false ),
last_field_index( -1 ),
@ -213,6 +217,7 @@ void sqlsrv_stmt::free_param_data( TSRMLS_D )
void sqlsrv_stmt::new_result_set( TSRMLS_D )
{
this->fetch_called = false;
this->fwd_row_index = -1;
this->has_rows = false;
this->past_next_result_end = false;
this->past_fetch_end = false;
@ -433,16 +438,23 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 ||
encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" );
// if the sql type is unknown, then set the default based on the PHP type passed in
if( sql_type == SQL_UNKNOWN_TYPE ) {
default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC );
if ( stmt->conn->ce_option.enabled && ( sql_type == SQL_UNKNOWN_TYPE || column_size == SQLSRV_UNKNOWN_SIZE )) {
ae_get_sql_type_info( stmt, param_num, direction, param_z, encoding, sql_type, column_size, decimal_digits TSRMLS_CC );
// change long to double if the sql type is decimal
if ( sql_type == SQL_DECIMAL && Z_TYPE_P( param_z ) == IS_LONG )
convert_to_double( param_z );
}
else {
// if the sql type is unknown, then set the default based on the PHP type passed in
if ( sql_type == SQL_UNKNOWN_TYPE ) {
default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC );
}
// if the size is unknown, then set the default based on the PHP type passed in
if( column_size == SQLSRV_UNKNOWN_SIZE ) {
default_sql_size_and_scale( stmt, static_cast<unsigned int>( param_num ), param_z, encoding, column_size, decimal_digits TSRMLS_CC );
// if the size is unknown, then set the default based on the PHP type passed in
if ( column_size == SQLSRV_UNKNOWN_SIZE ) {
default_sql_size_and_scale( stmt, static_cast<unsigned int>(param_num), param_z, encoding, column_size, decimal_digits TSRMLS_CC );
}
}
// determine the ODBC C type
c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC );
@ -535,7 +547,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
// since this is an output string, assure there is enough space to hold the requested size and
// set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr)
resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size,
resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, decimal_digits,
buffer, buffer_len TSRMLS_CC );
// save the parameter to be adjusted and/or converted after the results are processed
@ -549,7 +561,8 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
// avoid this silent truncation, we set the column_size to be "MAX" size for
// string types. This will guarantee that there is no silent truncation for
// output parameters.
if( direction == SQL_PARAM_OUTPUT ) {
// if column encryption is enabled, at this point the correct column size has been set by SQLDescribeParam
if( direction == SQL_PARAM_OUTPUT && !stmt->conn->ce_option.enabled ) {
switch( sql_type ) {
@ -660,6 +673,13 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
core::SQLBindParameter( stmt, param_num + 1, direction,
c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC );
if ( stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP )
{
if ( decimal_digits == 3 )
core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, 0 );
else if (decimal_digits == 0)
core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, 0 );
}
}
catch( core::CoreException& e ) {
stmt->free_param_data( TSRMLS_C );
@ -796,6 +816,11 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient
// move to the record requested. For absolute records, we use a 0 based offset, so +1 since
// SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided.
SQLRETURN r = stmt->current_results->fetch( fetch_orientation, ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 TSRMLS_CC );
// when AE is enabled, will keep track of the number of rows being fetched so far such that the cursor can be reset back to its original position when getting stream data
if ( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->conn->ce_option.enabled == true ) {
stmt->fwd_row_index++;
}
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 ) {
@ -1996,6 +2021,13 @@ void default_sql_size_and_scale( _Inout_ sqlsrv_stmt* stmt, _In_opt_ unsigned in
}
}
void ae_get_sql_type_info( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ SQLSMALLINT direction, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding,
_Out_ SQLSMALLINT& sql_type, _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC )
{
SQLSMALLINT Nullable;
core::SQLDescribeParam( stmt, paramno + 1, &sql_type, &column_size, &decimal_digits, &Nullable );
}
void col_cache_dtor( _Inout_ zval* data_z )
{
col_cache* cache = static_cast<col_cache*>( Z_PTR_P( data_z ));
@ -2130,85 +2162,84 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type,
_Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC )
{
SQLRETURN r;
SQLSMALLINT c_type;
SQLLEN sql_field_type = 0;
SQLSMALLINT extra = 0;
SQLLEN field_len_temp = 0;
SQLLEN sql_display_size = 0;
char* field_value_temp = NULL;
SQLRETURN r;
SQLSMALLINT c_type;
SQLLEN sql_field_type = 0;
SQLSMALLINT extra = 0;
SQLLEN field_len_temp = 0;
SQLLEN sql_display_size = 0;
char* field_value_temp = NULL;
try {
try {
DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING,
"Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" );
DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING,
"Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" );
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding();
}
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding();
}
// Set the C type and account for null characters at the end of the data.
switch( sqlsrv_php_type.typeinfo.encoding ) {
case CP_UTF8:
c_type = SQL_C_WCHAR;
extra = sizeof( SQLWCHAR );
break;
case SQLSRV_ENCODING_BINARY:
c_type = SQL_C_BINARY;
extra = 0;
break;
default:
c_type = SQL_C_CHAR;
extra = sizeof( SQLCHAR );
break;
}
// Set the C type and account for null characters at the end of the data.
switch( sqlsrv_php_type.typeinfo.encoding ) {
case CP_UTF8:
c_type = SQL_C_WCHAR;
extra = sizeof( SQLWCHAR );
break;
case SQLSRV_ENCODING_BINARY:
c_type = SQL_C_BINARY;
extra = 0;
break;
default:
c_type = SQL_C_CHAR;
extra = sizeof( SQLCHAR );
break;
}
col_cache* cached = NULL;
if ( NULL != ( cached = static_cast< col_cache* >( zend_hash_index_find_ptr( Z_ARRVAL( stmt->col_cache ), static_cast< zend_ulong >( field_index ))))) {
sql_field_type = cached->sql_type;
sql_display_size = cached->display_size;
}
else {
// Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling
core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC );
col_cache* cached = NULL;
if ( NULL != ( cached = static_cast< col_cache* >( zend_hash_index_find_ptr( Z_ARRVAL( stmt->col_cache ), static_cast< zend_ulong >( field_index ))))) {
sql_field_type = cached->sql_type;
sql_display_size = cached->display_size;
}
else {
// Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling
core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC );
// Calculate the field size.
calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC );
// Calculate the field size.
calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC );
col_cache cache( sql_field_type, sql_display_size );
core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC );
}
col_cache cache( sql_field_type, sql_display_size );
core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC );
}
// if this is a large type, then read the first few bytes to get the actual length from SQLGetData
if( sql_display_size == 0 || sql_display_size == INT_MAX ||
sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) {
field_len_temp = INITIAL_FIELD_STRING_LEN;
// if this is a large type, then read the first few bytes to get the actual length from SQLGetData
if( sql_display_size == 0 || sql_display_size == INT_MAX ||
sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) {
field_len_temp = INITIAL_FIELD_STRING_LEN;
SQLLEN initiallen = field_len_temp + extra;
field_value_temp = static_cast<char*>( sqlsrv_malloc( field_len_temp + extra + 1 ));
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*/ TSRMLS_CC );
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ),
&field_len_temp, false /*handle_warning*/ TSRMLS_CC );
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException();
}
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( field_len_temp == SQL_NULL_DATA ) {
field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
if( r == SQL_SUCCESS_WITH_INFO ) {
if( r == SQL_SUCCESS_WITH_INFO ) {
SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { 0 };
SQLSMALLINT len = 0;
SQLSMALLINT len = 0;
stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC );
stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC );
// with Linux connection pooling may not get a truncated warning back but the actual field_len_temp
// can be greater than the initallen value.
@ -2218,145 +2249,165 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
if( is_truncated_warning( state ) ) {
#endif // !_WIN32
SQLLEN dummy_field_len = 0;
SQLLEN dummy_field_len = 0;
// for XML (and possibly other conditions) the field length returned is not the real field length, so
// in every pass, we double the allocation size to retrieve all the contents.
if( field_len_temp == SQL_NO_TOTAL ) {
// for XML (and possibly other conditions) the field length returned is not the real field length, so
// in every pass, we double the allocation size to retrieve all the contents.
if( field_len_temp == SQL_NO_TOTAL ) {
// reset the field_len_temp
field_len_temp = INITIAL_FIELD_STRING_LEN;
// reset the field_len_temp
field_len_temp = INITIAL_FIELD_STRING_LEN;
do {
SQLLEN initial_field_len = field_len_temp;
// Double the size.
field_len_temp *= 2;
do {
SQLLEN initial_field_len = field_len_temp;
// Double the size.
field_len_temp *= 2;
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
field_len_temp -= initial_field_len;
// reset AE stream fetch buffer
if ( reset_ae_stream_cursor( stmt )){
// fetch the original column again with a bigger buffer length
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, field_len_temp + extra,
&dummy_field_len, false /*handle_warning*/ TSRMLS_CC );
// if field_len_temp was bit enough to hold all data, dummy_field_len contain the actual amount retrieved,
// not SQL_NO_TOTAL
if ( dummy_field_len != SQL_NO_TOTAL )
field_len_temp = dummy_field_len;
else
field_len_temp += initial_field_len;
}
else {
field_len_temp -= initial_field_len;
// Get the rest of the data.
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len,
field_len_temp + extra, &dummy_field_len,
false /*handle_warning*/ TSRMLS_CC );
// Get the rest of the data.
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len,
field_len_temp + extra, &dummy_field_len,
false /*handle_warning*/ TSRMLS_CC );
// the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL
// so we calculate the actual length of the string with that.
if ( dummy_field_len != SQL_NO_TOTAL )
field_len_temp += dummy_field_len;
else
field_len_temp += initial_field_len;
}
// the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL
// so we calculate the actual length of the string with that.
if( dummy_field_len != SQL_NO_TOTAL )
field_len_temp += dummy_field_len;
else
field_len_temp += initial_field_len;
if( r == SQL_SUCCESS_WITH_INFO ) {
core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len
TSRMLS_CC );
}
if( r == SQL_SUCCESS_WITH_INFO ) {
core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len
TSRMLS_CC );
}
} while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state ));
}
else {
// the real field length is returned here, thus no need to double the allocation size here, just have to
// allocate field_len_temp (which is the field length retrieved from the first SQLGetData
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
} while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state ));
}
else {
// reset AE stream fetch buffer
if ( reset_ae_stream_cursor( stmt ) ) {
// fetch the original column again with a bigger buffer length
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, field_len_temp + extra,
&dummy_field_len, false /*handle_warning*/ TSRMLS_CC );
}
else {
// We have already recieved INITIAL_FIELD_STRING_LEN size data.
field_len_temp -= INITIAL_FIELD_STRING_LEN;
// We got the field_len_temp from SQLGetData call.
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
// Get the rest of the data.
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN,
field_len_temp + extra, &dummy_field_len,
true /*handle_warning*/ TSRMLS_CC );
field_len_temp += INITIAL_FIELD_STRING_LEN;
}
// We have already recieved INITIAL_FIELD_STRING_LEN size data.
field_len_temp -= INITIAL_FIELD_STRING_LEN;
if( dummy_field_len == SQL_NULL_DATA ) {
field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
// Get the rest of the data.
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN,
field_len_temp + extra, &dummy_field_len,
true /*handle_warning*/ TSRMLS_CC );
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException();
}
}
if( dummy_field_len == SQL_NULL_DATA ) {
field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
} // if( is_truncation_warning ( state ) )
else {
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
}
} // if( r == SQL_SUCCESS_WITH_INFO )
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException();
}
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) {
field_len_temp += INITIAL_FIELD_STRING_LEN;
}
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
&field_value_temp, field_len_temp );
} // if( is_truncation_warning ( state ) )
else {
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
throw core::CoreException();
}
}
} // if( r == SQL_SUCCESS_WITH_INFO )
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
}
} // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. )
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) {
else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) {
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
&field_value_temp, field_len_temp );
// only allow binary retrievals for char and binary types. All others get a string converted
// to the encoding type they asked for.
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
}
} // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. )
// null terminator
if( c_type == SQL_C_CHAR ) {
sql_display_size += sizeof( SQLCHAR );
}
else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) {
// For WCHAR multiply by sizeof(WCHAR) and include the null terminator
else if( c_type == SQL_C_WCHAR ) {
sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR);
}
// only allow binary retrievals for char and binary types. All others get a string converted
// to the encoding type they asked for.
field_value_temp = static_cast<char*>( sqlsrv_malloc( sql_display_size + extra + 1 ));
// null terminator
if( c_type == SQL_C_CHAR ) {
sql_display_size += sizeof( SQLCHAR );
}
// get the data
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size,
&field_len_temp, true /*handle_warning*/ TSRMLS_CC );
CHECK_SQL_ERROR( r, stmt ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException();
}
// For WCHAR multiply by sizeof(WCHAR) and include the null terminator
else if( c_type == SQL_C_WCHAR ) {
sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR);
}
if( field_len_temp == SQL_NULL_DATA ) {
field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
field_value_temp = static_cast<char*>( sqlsrv_malloc( sql_display_size + extra + 1 ));
if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) {
// get the data
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size,
&field_len_temp, true /*handle_warning*/ TSRMLS_CC );
CHECK_SQL_ERROR( r, stmt ) {
throw core::CoreException();
}
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
throw core::CoreException();
}
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
&field_value_temp, field_len_temp );
if( field_len_temp == SQL_NULL_DATA ) {
field_value = NULL;
sqlsrv_free( field_value_temp );
return;
}
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
}
} // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE )
if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) {
else {
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
&field_value_temp, field_len_temp );
DIE( "Invalid sql_display_size" );
return; // to eliminate a warning
}
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
}
} // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE )
field_value = field_value_temp;
*field_len = field_len_temp;
else {
DIE( "Invalid sql_display_size" );
return; // to eliminate a warning
}
field_value = field_value_temp;
*field_len = field_len_temp;
// prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP
// runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode.
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary
// operator to set add 1 to fill the null terminator
// prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP
// runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode.
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary
// operator to set add 1 to fill the null terminator
// with unixODBC connection pooling sometimes field_len_temp can be SQL_NO_DATA.
// In that cause do not set null terminator and set length to 0.
@ -2368,22 +2419,22 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
{
*field_len = 0;
}
}
}
catch( core::CoreException& ) {
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;
}
catch ( ... ) {
field_value = NULL;
*field_len = 0;
sqlsrv_free( field_value_temp );
throw;
}
field_value = NULL;
*field_len = 0;
sqlsrv_free( field_value_temp );
throw;
}
}
@ -2458,8 +2509,8 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type )
// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter
void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z, _In_ SQLULEN paramno, SQLSRV_ENCODING encoding,
_In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _Out_writes_(buffer_len) SQLPOINTER& buffer,
_Out_ SQLLEN& buffer_len TSRMLS_DC )
_In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _In_ SQLSMALLINT decimal_digits,
_Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len TSRMLS_DC )
{
SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." );
buffer_len = Z_STRLEN_P( param_z );
@ -2474,6 +2525,12 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval*
// account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning
SQLULEN field_size = column_size;
// with AE on, when column_size is retrieved from SQLDescribeParam, column_size does not include the decimal place
// include the decimal for output params by adding elem_size
if ( stmt->conn->ce_option.enabled && decimal_digits > 0 )
{
field_size += elem_size;
}
if (column_size == SQL_SS_LENGTH_UNLIMITED) {
field_size = SQL_SERVER_MAX_FIELD_SIZE / elem_size;
}
@ -2524,6 +2581,22 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval*
}
}
bool reset_ae_stream_cursor( _Inout_ sqlsrv_stmt* stmt ) {
// only handled differently when AE is on because AE does not support streaming
// AE only works with SQL_CURSOR_FORWARD_ONLY for max types
if (stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type == SQL_CURSOR_FORWARD_ONLY) {
// close and reopen the cursor
core::SQLCloseCursor(stmt->current_results->odbc);
core::SQLExecute(stmt);
// FETCH_NEXT until the cursor reaches the row that it was at
for (int i = 0; i <= stmt->fwd_row_index; i++) {
core::SQLFetchScroll(stmt->current_results->odbc, SQL_FETCH_NEXT, 0);
}
return true;
}
return false;
}
// output parameters have their reference count incremented so that they do not disappear
// while the query is executed and processed. They are saved in the statement so that
// their reference count may be decremented later (after results are processed)

View file

@ -3,7 +3,7 @@
//
// Contents: Implementation of PHP streams for reading SQL Server data
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -5,7 +5,7 @@
//
// Comments: Mostly error handling and some type handling
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -220,7 +220,7 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu
SQLRETURN r = SQL_SUCCESS;
SQLSMALLINT wmessage_len = 0;
SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ] = { L'\0' };
SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ] = { L'\0' };
SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = { L'\0' };
SQLSRV_ENCODING enc = ctx.encoding();
switch( h_type ) {
@ -245,7 +245,7 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu
default:
error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error();
r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message,
SQL_MAX_MESSAGE_LENGTH + 1, &wmessage_len );
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
// Workaround for a bug in unixODBC 2.3.4 when connection pooling is enabled (PDO SQLSRV).
// Instead of returning false, we return an empty error message to prevent the driver from throwing an exception.
@ -290,12 +290,12 @@ void core_sqlsrv_format_driver_error( _In_ sqlsrv_context& ctx, _In_ sqlsrv_erro
// 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_MESSAGE_LENGTH + 1 ));
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_MESSAGE_LENGTH, args );
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_MESSAGE_LENGTH,
strcpy_s( reinterpret_cast<char*>( formatted_error->native_message ), SQL_MAX_ERROR_MESSAGE_LENGTH,
reinterpret_cast<char*>( INTERNAL_FORMAT_ERROR ));
}

View file

@ -4,7 +4,7 @@
// Contents: Contains functions for handling Windows format strings
// and UTF-16 on non-Windows platforms
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// Contents: Contains a portable abstraction for interlocked, atomic
// operations on int32_t and pointer types.
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// Contents: Contains a portable abstraction for interlocked, atomic
// operations on int32_t and pointer types.
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// Contents: Contains a portable abstraction for interlocked, singly
// linked list.
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Contains portable classes for localization
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -5,7 +5,7 @@
// Must be included in one c/cpp file per binary
// A build error will occur if this inclusion policy is not followed
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -20,7 +20,7 @@
// pecuniary loss) arising out of the use of or inability to use
// this SDK, even if Microsoft has been advised of the possibility
// of such damages.
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -370,6 +370,45 @@
#pragma warning(disable:4200)
#endif
// 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
{

View file

@ -3,7 +3,7 @@
//
// Contents: Contains the minimal definitions to build on non-Windows platforms
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -1,7 +1,7 @@
//---------------------------------------------------------------------------------------------------------------------------------
// File: typedefs_for_linux.h
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// File: version.h
// Contents: Version number constants
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -27,7 +27,7 @@
// Increase Minor with backward compatible new functionalities and API changes.
// Increase Patch for backward compatible fixes.
#define SQLVERSION_MAJOR 5
#define SQLVERSION_MINOR 0
#define SQLVERSION_MINOR 1
#define SQLVERSION_PATCH 0
#define SQLVERSION_BUILD 0

View file

@ -3,7 +3,7 @@
//
// Contents: include for definition of Windows types for non-Windows platforms
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -4,7 +4,7 @@
// Contents: This module defines helper functions to prevent
// integer overflow bugs.
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Contains the minimal definitions to build on non-Windows platforms
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Contains the minimal definitions to build on non-Windows platforms
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: JScript build configuration used by buildconf.bat
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -3,7 +3,7 @@
//
// Contents: Routines that use connection handles
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -134,8 +134,7 @@ struct bool_conn_attr_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
try {
core::SQLSetConnectAttr(conn, Attr, reinterpret_cast<SQLPOINTER>((zend_long)zend_is_true(value)),
SQL_IS_UINTEGER TSRMLS_CC);
core::SQLSetConnectAttr(conn, Attr, reinterpret_cast<SQLPOINTER>((zend_long)zend_is_true(value)), SQL_IS_UINTEGER TSRMLS_CC);
}
catch( core::CoreException& ) {
@ -188,6 +187,12 @@ const char AttachDBFileName[] = "AttachDbFileName";
const char CharacterSet[] = "CharacterSet";
const char Authentication[] = "Authentication";
const char ConnectionPooling[] = "ConnectionPooling";
const char ColumnEncryption[] = "ColumnEncryption";
const char Driver[] = "Driver";
const char CEKeystoreProvider[] = "CEKeystoreProvider";
const char CEKeystoreName[] = "CEKeystoreName";
const char CEKeystoreEncryptKey[] = "CEKeystoreEncryptKey";
#ifdef _WIN32
const char ConnectRetryCount[] = "ConnectRetryCount";
const char ConnectRetryInterval[] = "ConnectRetryInterval";
@ -295,13 +300,58 @@ const connection_option SS_CONN_OPTS[] = {
},
{
SSConnOptionNames::ConnectionPooling,
sizeof( SSConnOptionNames::ConnectionPooling ),
sizeof(SSConnOptionNames::ConnectionPooling),
SQLSRV_CONN_OPTION_CONN_POOLING,
ODBCConnOptions::ConnectionPooling,
sizeof( ODBCConnOptions::ConnectionPooling ),
sizeof(ODBCConnOptions::ConnectionPooling),
CONN_ATTR_BOOL,
conn_null_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::Driver,
sizeof(SSConnOptionNames::Driver),
SQLSRV_CONN_OPTION_DRIVER,
ODBCConnOptions::Driver,
sizeof(ODBCConnOptions::Driver),
CONN_ATTR_STRING,
driver_set_func::func
},
{
SSConnOptionNames::CEKeystoreProvider,
sizeof(SSConnOptionNames::CEKeystoreProvider),
SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER,
ODBCConnOptions::CEKeystoreProvider,
sizeof(ODBCConnOptions::CEKeystoreProvider),
CONN_ATTR_STRING,
ce_ksp_provider_set_func::func
},
{
SSConnOptionNames::CEKeystoreName,
sizeof(SSConnOptionNames::CEKeystoreName),
SQLSRV_CONN_OPTION_CEKEYSTORE_NAME,
ODBCConnOptions::CEKeystoreName,
sizeof(ODBCConnOptions::CEKeystoreName),
CONN_ATTR_STRING,
ce_ksp_provider_set_func::func
},
{
SSConnOptionNames::CEKeystoreEncryptKey,
sizeof(SSConnOptionNames::CEKeystoreEncryptKey),
SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY,
ODBCConnOptions::CEKeystoreEncryptKey,
sizeof(ODBCConnOptions::CEKeystoreEncryptKey),
CONN_ATTR_STRING,
ce_ksp_provider_set_func::func
},
#ifdef _WIN32
{
SSConnOptionNames::ConnectRetryCount,

View file

@ -2,7 +2,7 @@
// File: init.cpp
// Contents: initialization routines for the extension
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -8,7 +8,7 @@
//
// Comments: Also contains "internal" declarations shared across source files.
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -353,7 +353,8 @@ enum SS_ERROR_CODES {
SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED,
SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF,
SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION
SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED
};
extern ss_error SS_ERRORS[];

View file

@ -3,7 +3,7 @@
//
// Contents: Routines that use statement handles
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -1231,6 +1231,9 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC )
value_z = var;
}
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;
}
// bind the parameter
@ -1983,9 +1986,11 @@ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array,
sql_type = sqlsrv_sql_type.typeinfo.type;
}
// else the sql type and size are uknown, so tell the core layer to use its defaults
// else the sql type and size are unknown, so tell the core layer to use its defaults
else {
CHECK_CUSTOM_ERROR( !stmt->prepared && stmt->conn->ce_option.enabled, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED ) {
throw ss::SSException();
}
sql_type_param_was_null = true;
sql_type = SQL_UNKNOWN_TYPE;

View file

@ -3,7 +3,7 @@
//
// Contents: Version resource
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License

View file

@ -5,7 +5,7 @@
//
// Comments: Mostly error handling and some type handling
//
// Microsoft Drivers 5.0 for PHP for SQL Server
// Microsoft Drivers 5.1 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
@ -300,8 +300,8 @@ ss_error SS_ERRORS[] = {
{
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 or 13 for SQL Server. "
"Access the following URL to download the ODBC Driver 11 or 13 for SQL Server for %1!s!: "
{ 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!: "
"http://go.microsoft.com/fwlink/?LinkId=163712", -49, true }
},
@ -370,13 +370,40 @@ ss_error SS_ERRORS[] = {
SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*)"Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -62, false }
},
{
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_KEYSTORE_NAME_MISSING,
{ IMSSP, (SQLCHAR*) "The name of the custom keystore provider is missing.", -101, false}
},
{
SQLSRV_ERROR_KEYSTORE_PATH_MISSING,
{ IMSSP, (SQLCHAR*) "The path to the custom keystore provider is missing.", -102, false}
},
{
SQLSRV_ERROR_KEYSTORE_KEY_MISSING,
{ IMSSP, (SQLCHAR*) "The encryption key for the custom keystore provider is missing.", -103, false}
},
{
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading a custom keystore provider.", -104, false}
},
{
SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.", -105, false }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! was specified for Driver option.", -106, true }
},
// terminate the list of errors/warnings
{ UINT_MAX, {} }
};

View file

@ -0,0 +1,139 @@
# Performance Test Results for the Microsoft Drivers for PHP for SQL Server
This page lists benchmarking results for both the SQLSRV and PDO_SQLSRV drivers for various operations and various environments. Test are performed on the following client environment:
* PHP 7.1.7
* Driver version 5.0.0
* Platform: x64
* Non-thread safe
* ODBC Driver version 13.1
The client OS includes:
* Windows Server 2016
* MacOS Sierra
* Red Hat 7.2
* Ubuntu 16.04
* SUSE 12
The server testing environment is either
* Windows Server 2016 with SQL Server 2016, or
* Ubuntu 16.04 with SQL Server Linux 2016
The following table lists benchmarking results for each scenario tested. Listed are the times taken for all iterations, and the maximum memory usage for all iterations in each scenario.
| | | | SQLSRV | | | PDO_SQLSRV | | |
| :----- | :----- | :----- | :----- | -----: | -----: | :-----| -----: | -----: |
|Scenario|Server|Client|Iterations|Memory(MB)|Time(s)|Iterations|Memory(MB)|Time(s)|
|CRUD |SQL Server Windows|Windows Server 2016|1000x100 rows|0.88667|1074|1000x100 rows|0.88562|1195|
| | |OS X Sierra |1000x100 rows|0.88781|941 |1000x100 rows|0.88678|1019|
| | |Red Hat 7.2 |1000x100 rows|0.88223|1053|1000x100 rows|0.88120|1164|
| | |Ubuntu 16.04 |1000x100 rows|0.88056|1059|1000x100 rows|0.87954|1172|
| | |Suse 12 |1000x100 rows|0.88945|978 |1000x100 rows|0.88849|1084|
| |SQL Server Linux |Windows Server 2016| | | | | | |
| | |OS X Sierra |1000x100 rows|0.88781|1112|1000x100 rows|0.88678|1210|
| | |Red Hat 7.2 |1000x100 rows|0.88223|1189|1000x100 rows|0.88120|1315|
| | |Ubuntu 16.04 |1000x100 rows|0.88056|1199| | | |
| | |Suse 12 | | | | | | |
|CRUD Insert|SQL Server Windows|Windows Server 2016|1000x100 rows|0.88380|311|1000x100 rows|0.88264|319|
| | |OS X Sierra |1000x100 rows|0.88770|280|1000x100 rows|0.88660|282|
| | |Red Hat 7.2 |1000x100 rows|0.88342|304|1000x100 rows|0.88229|309|
| | |Ubuntu 16.04 |1000x100 rows|0.88171|309|1000x100 rows|0.88065|357|
| | |Suse 12 |1000x100 rows|0.88655|285|1000x100 rows|0.88548|290|
| |SQL Server Linux |Windows Server 2016|1000x100 rows|0.88681|368|1000x100 rows|0.88568|387|
| | |OS X Sierra |1000x100 rows|0.88770|348|1000x100 rows|0.88660|359|
| | |Red Hat 7.2 |1000x100 rows|0.88342|349|1000x100 rows|0.88230|358|
| | |Ubuntu 16.04 |1000x100 rows|0.88179|357|1000x100 rows|0.88067|363|
| | |Suse 12 |1000x100 rows|0.88655|342|1000x100 rows|0.88548|344|
|CRUD Fetch |SQL Server Windows|Windows Server 2016|1000x100 rows|0.88431|255|1000x100 rows|0.88318|250|
| | |OS X Sierra |1000x100 rows|0.88828|188|1000x100 rows|0.88718|187|
| | |Red Hat 7.2 |1000x100 rows|0.88396|248|1000x100 rows|0.88284|245|
| | |Ubuntu 16.04 |1000x100 rows|0.88226|250|1000x100 rows|0.87514|247|
| | |Suse 12 |1000x100 rows|0.88709|228|1000x100 rows|0.88602|227|
| |SQL Server Linux |Windows Server 2016|1000x100 rows|0.88735|287|1000x100 rows|0.88622|282|
| | |OS X Sierra |1000x100 rows|0.88828|229|1000x100 rows|0.88718|228|
| | |Red Hat 7.2 |1000x100 rows|0.88400|272|1000x100 rows|0.88287|270|
| | |Ubuntu 16.04 |1000x100 rows|0.88236|276|1000x100 rows|0.88124|274|
| | |Suse 12 |1000x100 rows|0.88709|257|1000x100 rows|0.88602|254|
|CRUD Update|SQL Server Windows|Windows Server 2016|1000x100 rows|0.88594|314|1000x100 rows|0.88478|323|
| | |OS X Sierra |1000x100 rows|0.88988|284|1000x100 rows|0.88879|285|
| | |Red Hat 7.2 |1000x100 rows|0.88560|307|1000x100 rows|0.88448|313|
| | |Ubuntu 16.04 |1000x100 rows|0.87787|310|1000x100 rows|0.88235|327|
| | |Suse 12 |1000x100 rows|0.88869|288|1000x100 rows|0.88762|294|
| |SQL Server Linux |Windows Server 2016|1000x100 rows|0.88899|369|1000x100 rows|0.88786|376|
| | |OS X Sierra |1000x100 rows|0.88988|349|1000x100 rows|0.88879|362|
| | |Red Hat 7.2 |1000x100 rows|0.88560|357|1000x100 rows|0.88448|362|
| | |Ubuntu 16.04 |1000x100 rows|0.88397|361|1000x100 rows|0.88285|367|
| | |Suse 12 |1000x100 rows|0.88873|343|1000x100 rows|0.88766|340|
|CRUD Delete|SQL Server Windows|Windows Server 2016|1000x100 rows|0.88452|197|1000x100 rows|0.88341|303|
| | |OS X Sierra |1000x100 rows|0.88843|188|1000x100 rows|0.88737|274|
| | |Red Hat 7.2 |1000x100 rows|0.88411|192|1000x100 rows|0.88303|295|
| | |Ubuntu 16.04 |1000x100 rows|0.87638|193|1000x100 rows|0.88094|298|
| | |Suse 12 |1000x100 rows|0.88728|181|1000x100 rows|0.88625|277|
| |SQL Server Linux |Windows Server 2016|1000x100 rows|0.88754|233|1000x100 rows|0.88645|358|
| | |OS X Sierra |1000x100 rows|0.88843|243|1000x100 rows|0.88737|340|
| | |Red Hat 7.2 |1000x100 rows|0.88415|229|1000x100 rows|0.88307|342|
| | |Ubuntu 16.04 |1000x100 rows|0.88251|229|1000x100 rows|0.88144|347|
| | |Suse 12 |1000x100 rows|0.88728|220|1000x100 rows|0.88625|328|
|Large Read - 10,000,000 rows |SQL Server Windows|Windows Server 2016|1|0.88117|606|1|0.88003|382|
| | |OS X Sierra |1|0.88572|420|1|0.88463|344|
| | |Red Hat 7.2 |1|0.88145|495|1|0.88035|694|
| | |Ubuntu 16.04 |1|0.87981|413|1|0.87259|585|
| | |Suse 12 |1|0.88397|453|1|0.88291|326|
| |SQL Server Linux |Windows Server 2016|1|0.88423|685| | | |
| | |OS X Sierra |1|0.88571|308| | | |
| | |Red Hat 7.2 |1|0.88140|512| | | |
| | |Ubuntu 16.04 |1|0.87977|749| | | |
| | |Suse 12 |1|0.88397|828| | | |
|SELECT @@Version|SQL Server Windows|Windows Server 2016|10,000|0.88074|16|10,000|0.87955|28|
| | |OS X Sierra |10,000|0.88522|19|10,000|0.88412|33|
| | |Red Hat 7.2 |10,000|0.88118|11|10,000|0.88006|22|
| | |Ubuntu 16.04 | | | | | | |
| | |Suse 12 |10,000|0.88351|13|10,000|0.88243|24|
| |SQL Server Linux |Windows Server 2016|10,000|0.88377|16|10,000|0.88264|30|
| | |OS X Sierra |10,000|0.88522|19|10,000|0.88412|33|
| | |Red Hat 7.2 |10,000|0.88094|17|10,000|0.87981|28|
| | |Ubuntu 16.04 |10,000|0.87930|16|10,000|0.87818|26|
| | |Suse 12 |10,000|0.88351|15|10,000|0.88243|26|
|CREATE DATABASE/TABLE/STORED PROCS|SQL Server Windows|Windows Server 2016|1000|0.88237| 515|1000|0.88123| 529|
| | |OS X Sierra |1000|0.88638| 420|1000|0.88530| 457|
| | |Red Hat 7.2 |1000|0.88213| 434|1000|0.88102| 447|
| | |Ubuntu 16.04 |1000|0.87437| 395| | | |
| | |Suse 12 |1000|0.88512| 494|1000|0.88406| 507|
| |SQL Server Linux |Windows Server 2016|1000|0.88544|2285|1000|0.88433|2324|
| | |OS X Sierra |1000|0.88638|2338|1000|0.88530|2352|
| | |Red Hat 7.2 |1000|0.88216|2292|1000|0.88106|2312|
| | |Ubuntu 16.04 |1000|0.80530|2340|1000|0.87943|1228|
| | |Suse 12 |1000|0.88512|2341|1000|0.88406|2337|
|Open and close 1000 connections|SQL Server Windows|Windows Server 2016|1000|0.87956|39|1000|0.87843|38|
| | |OS X Sierra |1000|0.88363|62|1000|0.88255|62|
| | |Red Hat 7.2 |1000|0.87935|30|1000|0.87825|30|
| | |Ubuntu 16.04 | | | |1000|0.87608|29|
| | |Suse 12 |1000|0.88232|24|1000|0.88126|25|
| |SQL Server Linux |Windows Server 2016|1000|0.88267|45|1000|0.88156|45|
| | |OS X Sierra |1000|0.88363|65|1000|0.88255|65|
| | |Red Hat 7.2 |1000|0.87932|35|1000|0.87822|35|
| | |Ubuntu 16.04 |1000|0.87771|35|1000|0.87659|34|
| | |Suse 12 |1000|0.88232|29|1000|0.88126|30|
|Open and close 1000 connections with connection pooling|SQL Server Windows|Windows Server 2016|||||||
| | |OS X Sierra |||||||
| | |Red Hat 7.2 |||||||
| | |Ubuntu 16.04 |||||||
| | |Suse 12 |||||||
| |SQL Server Linux |Windows Server 2016|||||||
| | |OS X Sierra |||||||
| | |Red Hat 7.2 |||||||
| | |Ubuntu 16.04 |||||||
| | |Suse 12 |||||||
The following table lists details of each scenario tested, including the SQL statements and data types used.
|Scenario|Description|Iterations|Operations/Iteration|t-SQL|Column Datatypes|
|---|---|---|---|---|---|
|CRUD|Contains a loop for inserting a row into, fetching from, updating a row in, and deleting a row from a table. Measurement starts immediately before preparing the first INSERT INTO statement and ends immediately after the 100th DELETE statement executes.|1000|100|<ul><li>INSERT INTO &lt;tableName&gt; VALUES (&lt;params&gt;)</li><li>SELECT \* FROM &lt;tableName&gt;</li><li>UPDATE &lt;tableName&gt; SET &lt;params&gt;</li><li>DELETE TOP(1) FROM &lt;tableName&gt;</li></ul>|<ul><li>VARCHAR(64)</li><li>NVARCHAR(64)</li><li>INT</li><li>DATETIME2</li><li>CHAR(64)</li><li>NCHAR(64)</li><li>NUMERIC</li><li>BINARY(64)</li><li>VARBINARY</li><li>DATETIMEOFFSET</li></ul>|
|CRUD Insert|Contains a loop for inserting into a table. Each iteration prepares a statement, binds params, and executes. Measurement starts immediately before preparing the first INSERT INTO statement and ends immediately after the 100th statement executes.|1000|100|INSERT INTO &lt;tableName&gt; VALUES (&lt;params&gt;)||
|CRUD Fetch|Contains a loop for fetching from a table with data inserted using the t-SQL in CRUD Insert. Each iteration prepares and executes a statement, and fetches a row from the result set. Measurement starts immediately before preparing the first SELECT statmeent and ends immediately after the 100th fetch.|1000|100|SELECT \* FROM &lt;tableName&gt;|
|CRUD Update|Contains a loop for updating a table with one row populated using the t-SQL in CRUD Insert. Each iteration prepares a statement, binds params, and executes. Measurement starts immediately before preparing the 1st UPDATE statement and ends immediately after the 100th statement executes.|1000|100|UPDATE &lt;tableName&gt; SET &lt;params&gt;|
|CRUD Delete|Contains a loop for deleting one row from a table containing 100 rows which were populated uing the t-SQL in CRUD Insert. Each iteration prepares and executes a statement. Measurement starts immediately before preparing the 1st DELETE statement and ends immediately after the 100th statement executes.|1000|100|DELETE TOP(1) FROM &lt;tableName&gt;|
|Large Read|Fetches one row at a time from a large prepopulated database until the whole result set is fetched. Measurement starts immediately before preparing the SELECT statement and ends immediately after the last fetch.|10,000,000|1|SELECT \* FROM &lt;tableName&gt;|
|Select @Version|Fetches the SQL Server version. Measurement starts immediately before executing the SELECT statement and ends immediately after fetch.|10,000|1|SELECT @@Version|N/A|
|Create database, table, procedure|Executes t-SQL statements to create a database, table, and procedure. Measurement starts immediately before executing the CREATE DATABASE statement and ends immediately after the DROP DATABASE statement.|1000|1|<ul><li>CREATE DATABASE &lt;dbName&gt;</li><li>USE &lt;dbName&gt;</li><li>CREATE TABLE &lt;tableName&gt; (&lt;params&gt;)</li><li>CREATE PROCEDURE &lt;procName&gt; @id INTEGER, @name VARCHAR(32) AS SET NOCOUNT ON; SELECT id, name, value FROM $databaseName.$tableName WHERE id = @id AND name = @name</li><li>USE MASTER; DROP DATABASE &lt;dbName&gt;</li></ul>|<ul><li>INT</li><li>VARCHAR(32)</li><li>INT</li><li>DATE</li><li>TIMESTAMP</li><li>TIME(7)</li></ul>|
|Connection|Connects and disconnects from the database, with and without connection pooling. Measurement starts immediately before connecting and ends immediately after disconnecting.|1000|1||N/A|

View file

@ -45,5 +45,5 @@ echo "$count sales accounted for the first $$salesTotal in revenue.\n";
/* Cancel the pending results. The statement can be reused. */
sqlsrv_cancel( $stmt);
?>
--EXPECT--
57 sales accounted for the first $104171.7607 in revenue.
--EXPECTREGEX--
[3-5][0-9] sales accounted for the first \$10[0-9]{4}\.[0-9]{2,4} in revenue.

View file

@ -0,0 +1,72 @@
<?php
// exact numerics
$bigint_params = array( 2147483648, -922337203685479936, 922337203685479936, -2147583649, 461168601735364608, -461168601735364608 );
$int_params = array( 32768, -2147483647, 2147483647, -32769, 1073725440, -1073725440 );
$smallint_params = array( 256, -32767, 32767, -1, 16256, -16256 );
$tinyint_params = array( 128, 0, 255, 96, 64, 162 );
$decimal_params = array( 21474.83648, -9223372036854.80000, 9223372036854.80000, -21475.83649, -4611686017353.64608, -4611686017353.64608 );
$numeric_params = array( 0.32768, -21474.83647, 21474.83647, -0.32769, 10737.25440, -10737.25440 );
$money_params = array( 214748.3648, -92233720368548.0000, 92233720368548.0000, -214758.3649, 46116860173536.608, -46116860173536.608 );
$smallmoney_params = array( 0, -214748.3647, 214748.3647, 161061.2736, 107374.1824, -107374.1824 );
$bit_params = array( 0, 1, 0, 1, 0, 1 );
// approximate numerics
$float_params = array( 21474.83648, -9223372036.8548, 9223372036.8548, -21475, 4611686017, -4611686017 );
$real_params = array( 0, -2147.483, 2147.483, 1610, 1073, -1073 );
// date and time
$date_params = array( '1900-01-01', '0001-01-01', '9999-12-31', '5000-07-15', '2500-04-08', '7500-10-23' );
$datetime2_params = array( '1900-01-01 00:00:00', '0001-01-01 00:00:00', '9999-12-31 23:59:59.9999999', '5000-07-15 12:30:30.5555', '2500-04-08 06:15:15.33', '7500-10-23 18:45:45.888888' );
$datetime_params = array( '1900-01-01 00:00:00', '1753-01-01 00:00:00', '9999-12-31 23:59:59.997', '5000-07-15 12:30:30.5', '2500-04-08 06:15:15.33', '7500-10-23 18:45:45.888' );
$datetimeoffset_params = array( '1900-01-01 00:00:00 +01:00', '0001-01-01 00:00:00 -14:00', '9999-12-31 23:59:59.9999999 +14:00', '5000-07-15 12:30:30.55 -03:00', '2500-04-08 06:15:15.3333 -07:00', '7500-10-23 18:45:45.888888 +07:00' );
$smalldatetime_params = array( '1900-01-01 00:00:00', '1900-01-01 00:00:00', '2079-06-05 23:59:00', '1990-07-15 12:30:00', '1945-04-08 06:15:00', '2000-10-23 18:45:00' );
$time_params = array( '00:00:00', '00:00:00.0000000', '23:59:59.9999999', '12:30:30.5555', '06:15:15.33', '18:45:45.888888' );
// character strings
$char_params = array( 'Fixed', '-leng', 'th, n', 'on-Un', 'icode', 'strin' );
$varchar_params = array( 'This large row size can cause errors (such as error 512) during some normal operations, such as a clustered index key update, or sorts of the full column set, which users cannot anticipate until performing an operation.',
'Use varchar(max) when the sizes of the column data entries vary considerably, and the size might exceed 8,000 bytes.',
'Each non-null varchar(max) or nvarchar(max) column requires 24 bytes of additional fixed allocation which counts against the 8,060 byte row limit during a sort operation.',
'This can create an implicit limit to the number of non-null varchar(max) or nvarchar(max) columns that can be created in a table.',
'No special error is provided when the table is created (beyond the usual warning that the maximum row size exceeds the allowed maximum of 8060 bytes) or at the time of data insertion.',
'This large row size can cause errors (such as error 512) during some normal operations, such as a clustered index key update, or sorts of the full column set, which users cannot anticipate until performing an operation.' );
// unicode character strings
$nchar_params = array( 'Fixed', '-leng', 'th Un', 'icode', 'strin', 'g dat' );
$nvarchar_params = array( 'max indicates that the maximum storage size is 2^31-1 bytes (2 GB).',
'When prefixing a string constant with the letter N, the implicit conversion will result in a Unicode string if the constant to convert does not exceed the max length for a Unicode string data type (4,000).',
'Otherwise, the implicit conversion will result in a Unicode large-value (max).',
'Each non-null varchar(max) or nvarchar(max) column requires 24 bytes of additional fixed allocation which counts against the 8,060 byte row limit during a sort operation.',
'This can create an implicit limit to the number of non-null varchar(max) or nvarchar(max) columns that can be created in a table.',
'No special error is provided when the table is created (beyond the usual warning that the maximum row size exceeds the allowed maximum of 8060 bytes) or at the time of data insertion.' );
// binary strings
$binary_params = array( 'Fixed', '-leng', 'th, n', 'on-Un', 'icode', 'strin' );
$varbinary_params = array( 'Variable-length, non-', 'Unicode string data. n', 'defines the string length', 'and can be a value from 1', 'through 8,000.', 'The storage size is the' );
$varbinarymax_params = array( 'max indicates that the maximum storage size is 2^31-1 bytes (2 GB)',
'Use varchar(max) when the sizes of the column data entries vary considerably, and the size might exceed 8,000 bytes.',
'Each non-null varchar(max) or nvarchar(max) column requires 24 bytes of additional fixed allocation which counts against the 8,060 byte row limit during a sort operation.',
'This can create an implicit limit to the number of non-null varchar(max) or nvarchar(max) columns that can be created in a table.',
'No special error is provided when the table is created (beyond the usual warning that the maximum row size exceeds the allowed maximum of 8060 bytes) or at the time of data insertion.',
'This large row size can cause errors (such as error 512) during some normal operations, such as a clustered index key update, or sorts of the full column set, which users cannot anticipate until performing an operation.' );
// Array containing all PDO::PARAM_ types to pass into a PDOstatement::bindParam
$pdoParamTypes = array(
'PDO::PARAM_BOOL',
'PDO::PARAM_NULL',
'PDO::PARAM_INT',
'PDO::PARAM_STR',
'PDO::PARAM_LOB'
);
// Checks if the current error is the incompatible types error
// if so, state which sql type is incompatible with which data type
function is_incompatible_types_error( $stmt, $dataType, $pdoParamType ) {
if ( $stmt->errorInfo()[0] == "22018" )
echo "PDO param type $pdoParamType is incompatible with encrypted $dataType\n";
else
var_dump( $stmt->errorInfo() );
}
?>

View file

@ -0,0 +1,25 @@
<?php
function getKSPpath()
{
$name = 'myKSP';
$dir_name = realpath(dirname(__FILE__));
$ksp = $dir_name . DIRECTORY_SEPARATOR . $name;
if ( strtoupper( substr( php_uname( 's' ), 0, 3 ) ) == 'WIN' ) {
$arch = 'x64';
if ( PHP_INT_SIZE == 4 ) // running 32 bit
$arch = '';
$ksp .= $arch . '.dll';
}
else
$ksp .= '.so';
return $ksp;
}
$ksp_name = 'MyCustomKSPName';
$encrypt_key = 'LPKCWVD07N3RG98J0MBLG4H2';
$ksp_test_table = 'CustomKSPTestTable';
?>

View file

@ -14,6 +14,18 @@
//
require 'MsData.inc';
function IsAEQualified($conn)
{
$msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"];
$server_ver = $conn->getAttribute(PDO::ATTR_SERVER_VERSION);
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
$msodbcsql_min = explode(".", $msodbcsql_ver)[1];
if ($msodbcsql_maj < 17 || explode('.', $server_ver)[0] < 13)
return false;
return true;
}
// TO BE DELETED
function connect($options=array())
{
try
@ -41,6 +53,401 @@ function connect($options=array())
}
}
/**
* Connect to the database specified in MsSetup.inc; Column Encryption keywords automatically added when $keystore is not none
* @param string $keywords : string to append to the dsn string in PDO::_construct
* @param array $options : attributes to pass to PDO::_construct
* @param bool $disableCE : flag for disabling column encryption even when keystore is NOT none
* for testing fetching encrypted data when connection column encryption is off
* @return PDO connection object
*/
function ae_connect( $keywords='', $options=array(), $disableCE = false )
{
try
{
// simply use $databaseName from MsSetup.inc to facilitate testing in Azure,
// which does not support switching databases
require 'MsSetup.inc';
$dsn = "sqlsrv:Server=$server;database=$databaseName;ConnectionPooling=false;";
if ( $keystore != "none" && !$disableCE )
{
$dsn .= "ColumnEncryption=Enabled;";
}
if ( $keystore == "ksp" && !$disableCE )
{
require( 'AE_Ksp.inc' );
$ksp_path = getKSPPath();
$dsn .= "CEKeystoreProvider=$ksp_path;CEKeystoreName=$ksp_name;CEKeystoreEncryptKey=$encrypt_key;";
}
if ( $keywords )
{
$dsn .= $keywords;
}
$conn = new PDO( $dsn, $uid, $pwd, $options );
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
return $conn;
}
catch( PDOException $e )
{
var_dump( $e );
exit;
}
catch(Exception $e)
{
var_dump( $e );
exit;
}
}
/**
* @return string CEK name depending on the connection keywords
*/
function getCekName()
{
require 'MsSetup.inc';
$cekName = '';
switch ( $keystore ) {
case "none":
$cekName = '';
break;
case "win":
$cekName = 'AEColumnKey';
break;
case "ksp":
$cekName = 'CustomCEK';
break;
case "akv":
$cekName = 'AKVColumnKey';
break;
default:
echo "getCekName: Invalid keystore name.\n";
}
return $cekName;
}
/**
* class for encapsulating column metadata needed for creating a table
*/
class columnMeta {
public $colName;
public $dataType; //a string that includes the size of the type if necessary (e.g., decimal(10,5))
public $encType; //randomized or deterministic; default is deterministic
public $options; //a string that is null by default (e.g. NOT NULL Identity (1,1) )
function __construct( $dataType, $colName = null, $options = null, $encType = "deterministic" )
{
if ( is_null( $colName ))
{
$this->colName = get_default_colname( $dataType );
}
else
{
$this->colName = $colName;
}
$this->dataType = $dataType;
$this->encType = $encType;
$this->options = $options;
}
/**
* @return string column definition for creating a table
*/
function getColDef()
{
require 'MsSetup.inc';
$append = " ";
// an identity column is not encrypted because a select query with identity column as the where clause is often run and the user want to have to bind parameter every time
if ( $keystore != "none" && stripos( $this->options, "identity" ) === false )
{
$cekName = getCekName();
if ( stripos( $this->dataType, "char" ) !== false )
$append .= "COLLATE Latin1_General_BIN2 ";
$append .= sprintf( "ENCRYPTED WITH (ENCRYPTION_TYPE = %s, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = $cekName) ", $this->encType );
}
$append .= $this->options;
$colDef = "[" . $this->colName . "] " . $this->dataType . $append;
return $colDef;
}
}
/**
* @return string default column name when a name is not provided in the columnMeta class
*/
function get_default_colname( $dataType )
{
$colName = "c_" . str_replace( ",", "_", str_replace( "(", "_", $dataType ));
$colName = rtrim( $colName, ")" );
return $colName;
}
/**
* Create a table
* @param object $conn : PDO connection object
* @param string $tbname : name of the table to be created
* @param array $columnMetaArr : array of columnMeta objects, which contain metadata for one column
*/
function create_table( $conn, $tbname, $columnMetaArr )
{
try
{
DropTable( $conn, $tbname );
$colDef = "";
foreach ( $columnMetaArr as $meta )
{
$colDef = $colDef . $meta->getColDef() . ", ";
}
$colDef = rtrim( $colDef, ", " );
$createSql = "CREATE TABLE $tbname ( $colDef )";
$conn->exec( $createSql );
}
catch ( Exception $e )
{
var_dump( $e );
exit;
}
}
/**
* class for encapsulating optional parameters for PDOStatement::bindParam
*/
class bindParamOption {
public $index; //1-based index of the parameter
public $pdoType; //PDO_PARAM_ type specified for PDOStatement::bindParam
public $length; //length specified for PDOStatement::bindParam
public $options; //options specified for PDOStatement::bindParam
function __construct( $index, $pdoType = null, $length = null, $options = null )
{
$this->index = $index;
$this->pdoType = $pdoType;
$this->length = $length;
$this->options = $options;
}
/**
* @param object $stmt : PDO Statement object
* @param mix $var : variable to bind to the SQL statement parameter
*/
function bindWithOp( $stmt, $var )
{
$stmt->bindParam( $this->index, $var, constant( $this->pdoType ), $this->length, $this->options );
}
}
/**
* Insert a row into a table
* @param object $conn : PDO connection object
* @param string $tbname : name of the table for the row to be inserted
* @param array $inputs : an associative array column name and its value
* @param bool $r : true if the row was successfully inserted, otherwise false. Default value is null to make this parameter optional.
* $param string $api : PDO_SQLSRV API used for executing the insert query
* accepted values: "exec", "query", "prepare", "prepareBindParam"
* @param array $paramOption : an array of bindParamOptions. Should only be provided if $api is "perpareWithParamOp", if provided, has to be in the same order as $inputs
* @return object PDOStatement object of the insert statement
*/
function insert_row( $conn, $tbname, $inputs, &$r = null, $api = "query", $paramOption = array() )
{
try
{
require 'MsSetup.inc';
$stmt = null;
if ( $keystore == "none" && $api != "prepareBindParam" )
{
$insertSql = get_insertSql_complete( $tbname, $inputs );
switch ( $api ) {
case "exec":
$conn->exec( $insertSql );
break;
case "query":
$stmt = $conn->query( $insertSql );
break;
case "prepare":
$stmt = $conn->prepare( $insertSql );
$r = $stmt->execute();
break;
}
}
else
{
// if AE is on, must bind param
$insertSql = get_insertSql_placeholders( $tbname, $inputs );
$stmt = $conn->prepare( $insertSql );
if ( empty( $paramOption ))
{
$i = 1;
foreach( $inputs as $key => $value )
{
$stmt->bindParam( $i, $inputs[$key] );
$i++;
}
}
else
{
$i = 1;
foreach( $inputs as $key => $value )
{
$noparam = true;
foreach( $paramOption as $op )
{
if ( $op->index == $i )
{
$op->bindWithOp( $stmt, $inputs[$key] );
$noparam = false;
$i++;
break;
}
}
if ( $noparam )
{
$stmt->bindParam( $i, $inputs[$key] );
$i++;
}
}
}
$r = $stmt->execute();
}
return $stmt;
}
catch ( Exception $e )
{
var_dump( $e );
exit;
}
}
/**
* @param string $tbname : name of the table for an insert sql
* @param array $inputs : associative array containing a key value pair of column name and data to put into an insert sql string
* @return string a complete insert sql string
*/
function get_insertSql_complete( $tbname, $inputs )
{
$colStr = "INSERT INTO $tbname (";
$valStr = "VALUES (";
if ( empty( $inputs ))
{
echo "get_insertSql_complete: inputs for inserting a row cannot be empty.\n";
return;
}
foreach( $inputs as $key => $value )
{
$colStr .= $key . ", ";
if ( is_null( $value ))
echo "get_insertSql_complete: value provided for input $value is null.\n";
elseif ( is_string( $value ))
$valStr .= "'" . $value . "', ";
else
$valStr .= $value . ", ";
}
$colStr = rtrim( $colStr, ", " ) . ") ";
$valStr = rtrim( $valStr, ", " ) . ") ";
$insertSql = $colStr . $valStr;
return $insertSql;
}
/**
* @param string $tbname : name of the table for an insert sql
* @param array $input : associative array containing a key value pair of column name and data to put into an insert sql string
* @return string an insert sql string with "?" placeholders for all values
*/
function get_insertSql_placeholders( $tbname, $inputs )
{
$colStr = "INSERT INTO $tbname (";
$valStr = "VALUES (";
if ( empty( $inputs ))
{
echo "get_insertSql_placeholders: inputs for inserting a row cannot be empty.\n";
return;
}
foreach( $inputs as $key => $value )
{
$colStr .= $key . ", ";
}
$colStr = rtrim( $colStr, ", " ) . ") ";
$valStr .= get_seq_placeholders( count( $inputs )) . ") ";
$insertSql = $colStr . $valStr;
return $insertSql;
}
/**
* @param string $spname : name of the stored procedure
* @param int $num : number of parameters needed for the stored procedure
* @return string a call stored procedure sql string with "?" placeholders for all parameters
*/
function get_callProcSql_placeholders( $spname, $num )
{
$callStr = "{CALL $spname (";
$callStr .= get_seq_placeholders( $num ) . ")} ";
return $callStr;
}
/**
* @param int $num : number of placeholders needed
* @return string a string containing $num number of repeated "?" placeholders delimited by ", "
*/
function get_seq_placeholders( $num )
{
if ( $num < 0 )
{
echo "get_seq_placeholders: num provided for creating a sequence of placeholders cannot be less than 0.\n";
return;
}
$placeholderStr = str_repeat( "?, ", $num );
$placeholderStr = rtrim( $placeholderStr, ", " );
return $placeholderStr;
}
/**
* Fetch all rows and all columns given a table name, and print them
* @param object $conn : PDO connection object
* @param string $tbname : name of the table to fetch from
*/
function fetch_all( $conn, $tbname ) {
try
{
$sql = "SELECT * FROM $tbname";
$stmt = $conn->query( $sql );
while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ))
{
foreach ( $row as $key => $value )
{
print ( "$key: $value\n" );
}
}
}
catch ( Exception $e )
{
var_dump( $e );
exit;
}
}
/**
* @return bool false if $keystore specified in MsSetup.inc is none, otherwise return true
*/
function is_col_enc()
{
require 'MsSetup.inc';
if ( $keystore == "none" )
return false;
else
return true;
}
// Create and insert
function create_and_insert_table1( $conn )
{
@ -1082,7 +1489,7 @@ function CreateUniqueIndexEx($conn, $tableName, $tableIndex, $colIndex)
function DropTable($conn, $tableName)
{
$tsql = "DROP TABLE [$tableName]";
$tsql = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" . $tableName . "') AND type in (N'U')) DROP TABLE $tableName";
if (IsPdoMode())
{
$mode = $conn->getAttribute(PDO::ATTR_ERRMODE);
@ -1536,4 +1943,6 @@ function PhpVersionComponents( &$major, &$minor, &$sub )
$minor = strtok( "." );
$sub = strtok( "." );
}
?>

View file

@ -40,4 +40,8 @@ $marsMode = true;
$dsnMode = true;
$traceEnabled = false;
// column encryption variables
$keystore = "none"; // key store provider, acceptable values are none, win, ksp, akv
$dataEncrypted = false; // whether data is to be encrypted
?>

View file

@ -108,7 +108,7 @@ Repro();
?>
--EXPECTF--
__construct('%s', '%s', '%s')
exec('DROP TABLE %s')
exec('IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(%s) AND type in (N\'U\')) DROP TABLE %s')
exec('CREATE TABLE %s (id INT)')
exec('INSERT INTO %s (id) VALUES (1), (2)')
query('SELECT * FROM %s ORDER BY id ASC')
@ -127,5 +127,5 @@ array(2) {
bool(false)
__call('intercept_call', array (
))
exec('DROP TABLE %s')
exec('IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(%s) AND type in (N\'U\')) DROP TABLE %s')
Test "PDO - Extension" completed successfully.

View file

@ -31,11 +31,11 @@ $conn = null;
print "Done";
?>
--EXPECT--
--EXPECTREGEX--
Array
(
[0] => 21S01
[1] => 213
[2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Column name or number of supplied values does not match table definition.
)
\(
\[0\] => 21S01
\[1\] => 213
\[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Column name or number of supplied values does not match table definition\.
\)
Done

View file

@ -0,0 +1,77 @@
--TEST--
Test for inserting and retrieving encrypted data of datetime types
--DESCRIPTION--
No PDO::PARAM_ type specified when binding parameters
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
include 'AEData.inc';
$dataTypes = array( "date", "datetime", "datetime2", "smalldatetime", "time", "datetimeoffset" );
try
{
$conn = ae_connect();
foreach ( $dataTypes as $dataType ) {
echo "\nTesting $dataType:\n";
// create table
$tbname = GetTempTableName( "", false );
$colMetaArr = array( new columnMeta( $dataType, "c_det" ), new columnMeta( $dataType, "c_rand", null, "randomized" ));
create_table( $conn, $tbname, $colMetaArr );
// insert a row
$inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 );
$r;
$stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r );
if ( $r === false ) {
is_incompatible_types_error( $stmt, $dataType, "default type" );
}
else {
echo "****Encrypted default type is compatible with encrypted $dataType****\n";
fetch_all( $conn, $tbname );
}
DropTable( $conn, $tbname );
}
unset( $stmt );
unset( $conn );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
?>
--EXPECT--
Testing date:
****Encrypted default type is compatible with encrypted date****
c_det: 0001-01-01
c_rand: 9999-12-31
Testing datetime:
****Encrypted default type is compatible with encrypted datetime****
c_det: 1753-01-01 00:00:00.000
c_rand: 9999-12-31 23:59:59.997
Testing datetime2:
****Encrypted default type is compatible with encrypted datetime2****
c_det: 0001-01-01 00:00:00.0000000
c_rand: 9999-12-31 23:59:59.9999999
Testing smalldatetime:
****Encrypted default type is compatible with encrypted smalldatetime****
c_det: 1900-01-01 00:00:00
c_rand: 2079-06-05 23:59:00
Testing time:
****Encrypted default type is compatible with encrypted time****
c_det: 00:00:00.0000000
c_rand: 23:59:59.9999999
Testing datetimeoffset:
****Encrypted default type is compatible with encrypted datetimeoffset****
c_det: 0001-01-01 00:00:00.0000000 -14:00
c_rand: 9999-12-31 23:59:59.9999999 +14:00

View file

@ -0,0 +1,86 @@
--TEST--
Test for inserting and retrieving encrypted data of money types
--DESCRIPTION--
No PDO::PARAM_ tpe specified when binding parameters
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
include 'AEData.inc';
$dataTypes = array( "smallmoney", "money" );
try
{
$conn = ae_connect();
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
foreach ( $dataTypes as $dataType ) {
echo "\nTesting $dataType:\n";
$success = true;
// create table
$tbname = GetTempTableName( "", false );
$colMetaArr = array( new columnMeta( $dataType, "c_det" ), new columnMeta( $dataType, "c_rand", null, "randomized" ));
create_table( $conn, $tbname, $colMetaArr );
// insert a row
$inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 );
$r;
$stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r );
if ( !is_col_enc() )
{
if ( $r === false )
{
echo "Default type should be compatible with $dataType.\n";
$success = false;
}
else
{
$sql = "SELECT * FROM $tbname";
$stmt = $conn->query( $sql );
$row = $stmt->fetch( PDO::FETCH_ASSOC );
if ( $row["c_det"] != $inputValues[0] || $row["c_rand"] != $inputValues[1] )
{
echo "Incorrect output retrieved for datatype $dataType.\n";
$success = false;
}
}
}
else
{
if ( $r === false )
{
if ( $stmt->errorInfo()[0] != "22018" )
{
echo "Incorrect error returned.\n";
$success = false;
}
}
else
{
echo "$dataType is not compatible with any type.\n";
$success = false;
}
}
if ( $success )
echo "Test successfully done.\n";
DropTable( $conn, $tbname );
}
unset( $stmt );
unset( $conn );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
?>
--EXPECT--
Testing smallmoney:
Test successfully done.
Testing money:
Test successfully done.

View file

@ -0,0 +1,92 @@
--TEST--
Test for inserting and retrieving encrypted data of numeric types
--DESCRIPTION--
No PDO::PARAM_ tpe specified when binding parameters
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
include 'AEData.inc';
$dataTypes = array( "bit", "tinyint", "smallint", "int", "bigint", "decimal(18,5)", "numeric(10,5)", "float", "real" );
try
{
$conn = ae_connect();
foreach ( $dataTypes as $dataType ) {
echo "\nTesting $dataType:\n";
// create table
$tbname = GetTempTableName( "", false );
$colMetaArr = array( new columnMeta( $dataType, "c_det" ), new columnMeta( $dataType, "c_rand", null, "randomized" ));
create_table( $conn, $tbname, $colMetaArr );
// insert a row
$inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 );
$r;
$stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r );
if ( $r === false ) {
is_incompatible_types_error( $stmt, $dataType, "default type" );
}
else {
echo "****Encrypted default type is compatible with encrypted $dataType****\n";
fetch_all( $conn, $tbname );
}
DropTable( $conn, $tbname );
}
unset( $stmt );
unset( $conn );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
?>
--EXPECT--
Testing bit:
****Encrypted default type is compatible with encrypted bit****
c_det: 1
c_rand: 0
Testing tinyint:
****Encrypted default type is compatible with encrypted tinyint****
c_det: 0
c_rand: 255
Testing smallint:
****Encrypted default type is compatible with encrypted smallint****
c_det: -32767
c_rand: 32767
Testing int:
****Encrypted default type is compatible with encrypted int****
c_det: -2147483647
c_rand: 2147483647
Testing bigint:
****Encrypted default type is compatible with encrypted bigint****
c_det: -922337203685479936
c_rand: 922337203685479936
Testing decimal(18,5):
****Encrypted default type is compatible with encrypted decimal(18,5)****
c_det: -9223372036854.80000
c_rand: 9223372036854.80000
Testing numeric(10,5):
****Encrypted default type is compatible with encrypted numeric(10,5)****
c_det: -21474.83647
c_rand: 21474.83647
Testing float:
****Encrypted default type is compatible with encrypted float****
c_det: -9223372036.8547993
c_rand: 9223372036.8547993
Testing real:
****Encrypted default type is compatible with encrypted real****
c_det: -2147.4829
c_rand: 2147.4829

View file

@ -0,0 +1,154 @@
--TEST--
Test for inserting and retrieving encrypted data of datetime types
--DESCRIPTION--
Use PDOstatement::bindParam with all PDO::PARAM_ types
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
include 'AEData.inc';
$dataTypes = array( "date", "datetime", "datetime2", "smalldatetime", "time", "datetimeoffset" );
try
{
$conn = ae_connect();
foreach ( $dataTypes as $dataType ) {
echo "\nTesting $dataType:\n";
// create table
$tbname = GetTempTableName( "", false );
$colMetaArr = array( new columnMeta( $dataType, "c_det" ), new columnMeta( $dataType, "c_rand", null, "randomized" ));
create_table( $conn, $tbname, $colMetaArr );
// prepare statement for inserting into table
foreach ( $pdoParamTypes as $pdoParamType ) {
// insert a row
$inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 );
$r;
$stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r, "prepareBindParam", array( new bindParamOption( 1, $pdoParamType ), new bindParamOption( 2, $pdoParamType )));
if ( $r === false )
{
is_incompatible_types_error( $stmt, $dataType, $pdoParamType );
}
else {
echo "****PDO param type $pdoParamType is compatible with encrypted $dataType****\n";
fetch_all( $conn, $tbname );
}
$conn->query( "TRUNCATE TABLE $tbname" );
}
DropTable( $conn, $tbname );
}
unset( $stmt );
unset( $conn );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
?>
--EXPECT--
Testing date:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted date****
c_det: 0001-01-01
c_rand: 9999-12-31
****PDO param type PDO::PARAM_NULL is compatible with encrypted date****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted date****
c_det: 0001-01-01
c_rand: 9999-12-31
****PDO param type PDO::PARAM_STR is compatible with encrypted date****
c_det: 0001-01-01
c_rand: 9999-12-31
****PDO param type PDO::PARAM_LOB is compatible with encrypted date****
c_det: 0001-01-01
c_rand: 9999-12-31
Testing datetime:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted datetime****
c_det: 1753-01-01 00:00:00.000
c_rand: 9999-12-31 23:59:59.997
****PDO param type PDO::PARAM_NULL is compatible with encrypted datetime****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted datetime****
c_det: 1753-01-01 00:00:00.000
c_rand: 9999-12-31 23:59:59.997
****PDO param type PDO::PARAM_STR is compatible with encrypted datetime****
c_det: 1753-01-01 00:00:00.000
c_rand: 9999-12-31 23:59:59.997
****PDO param type PDO::PARAM_LOB is compatible with encrypted datetime****
c_det: 1753-01-01 00:00:00.000
c_rand: 9999-12-31 23:59:59.997
Testing datetime2:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted datetime2****
c_det: 0001-01-01 00:00:00.0000000
c_rand: 9999-12-31 23:59:59.9999999
****PDO param type PDO::PARAM_NULL is compatible with encrypted datetime2****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted datetime2****
c_det: 0001-01-01 00:00:00.0000000
c_rand: 9999-12-31 23:59:59.9999999
****PDO param type PDO::PARAM_STR is compatible with encrypted datetime2****
c_det: 0001-01-01 00:00:00.0000000
c_rand: 9999-12-31 23:59:59.9999999
****PDO param type PDO::PARAM_LOB is compatible with encrypted datetime2****
c_det: 0001-01-01 00:00:00.0000000
c_rand: 9999-12-31 23:59:59.9999999
Testing smalldatetime:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted smalldatetime****
c_det: 1900-01-01 00:00:00
c_rand: 2079-06-05 23:59:00
****PDO param type PDO::PARAM_NULL is compatible with encrypted smalldatetime****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted smalldatetime****
c_det: 1900-01-01 00:00:00
c_rand: 2079-06-05 23:59:00
****PDO param type PDO::PARAM_STR is compatible with encrypted smalldatetime****
c_det: 1900-01-01 00:00:00
c_rand: 2079-06-05 23:59:00
****PDO param type PDO::PARAM_LOB is compatible with encrypted smalldatetime****
c_det: 1900-01-01 00:00:00
c_rand: 2079-06-05 23:59:00
Testing time:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted time****
c_det: 00:00:00.0000000
c_rand: 23:59:59.9999999
****PDO param type PDO::PARAM_NULL is compatible with encrypted time****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted time****
c_det: 00:00:00.0000000
c_rand: 23:59:59.9999999
****PDO param type PDO::PARAM_STR is compatible with encrypted time****
c_det: 00:00:00.0000000
c_rand: 23:59:59.9999999
****PDO param type PDO::PARAM_LOB is compatible with encrypted time****
c_det: 00:00:00.0000000
c_rand: 23:59:59.9999999
Testing datetimeoffset:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted datetimeoffset****
c_det: 0001-01-01 00:00:00.0000000 -14:00
c_rand: 9999-12-31 23:59:59.9999999 +14:00
****PDO param type PDO::PARAM_NULL is compatible with encrypted datetimeoffset****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted datetimeoffset****
c_det: 0001-01-01 00:00:00.0000000 -14:00
c_rand: 9999-12-31 23:59:59.9999999 +14:00
****PDO param type PDO::PARAM_STR is compatible with encrypted datetimeoffset****
c_det: 0001-01-01 00:00:00.0000000 -14:00
c_rand: 9999-12-31 23:59:59.9999999 +14:00
****PDO param type PDO::PARAM_LOB is compatible with encrypted datetimeoffset****
c_det: 0001-01-01 00:00:00.0000000 -14:00
c_rand: 9999-12-31 23:59:59.9999999 +14:00

View file

@ -0,0 +1,93 @@
--TEST--
Test for inserting and retrieving encrypted data of money types
--DESCRIPTION--
Use PDOstatement::bindParam with all PDO::PARAM_ types
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
include 'AEData.inc';
$dataTypes = array( "smallmoney", "money" );
try
{
$conn = ae_connect();
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
foreach ( $dataTypes as $dataType ) {
echo "\nTesting $dataType:\n";
$success = true;
// create table
$tbname = GetTempTableName( "", false );
$colMetaArr = array( new columnMeta( $dataType, "c_det" ), new columnMeta( $dataType, "c_rand", null, "randomized" ));
create_table( $conn, $tbname, $colMetaArr );
// test each PDO::PARAM_ type
foreach ( $pdoParamTypes as $pdoParamType ) {
// insert a row
$inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 );
$r;
$stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r, "prepareBindParam", array( new bindParamOption( 1, $pdoParamType ), new bindParamOption( 2, $pdoParamType )));
if ( !is_col_enc() )
{
if ( $r === false )
{
echo "$pdoParamType should be compatible with $dataType.\n";
$success = false;
}
else
{
$sql = "SELECT * FROM $tbname";
$stmt = $conn->query( $sql );
$row = $stmt->fetch( PDO::FETCH_ASSOC );
if ( ( $row["c_det"] != $inputValues[0] || $row["c_rand"] != $inputValues[1] ) && $pdoParamType != "PDO::PARAM_NULL" )
{
echo "Incorrect output retrieved for datatype $dataType.\n";
var_dump( $inputValues );
var_dump( $row );
$success = false;
}
}
}
else
{
if ( $r === false )
{
if ( $stmt->errorInfo()[0] != "22018" )
{
echo "Incorrect error returned.\n";
$success = false;
}
}
else
{
echo "$dataType is not compatible with any type.\n";
$success = false;
}
}
$conn->query( "TRUNCATE TABLE $tbname" );
}
if ( $success )
echo "Test successfully done.\n";
DropTable( $conn, $tbname );
}
unset( $stmt );
unset( $conn );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
?>
--EXPECT--
Testing smallmoney:
Test successfully done.
Testing money:
Test successfully done.

View file

@ -0,0 +1,188 @@
--TEST--
Test for inserting and retrieving encrypted data of numeric types
--DESCRIPTION--
Use PDOstatement::bindParam with all PDO::PARAM_ types
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
include 'AEData.inc';
$dataTypes = array( "bit", "tinyint", "smallint", "int", "decimal(18,5)", "numeric(10,5)", "float", "real" );
try
{
$conn = ae_connect();
foreach ( $dataTypes as $dataType ) {
echo "\nTesting $dataType:\n";
// create table
$tbname = GetTempTableName( "", false );
$colMetaArr = array( new columnMeta( $dataType, "c_det" ), new columnMeta( $dataType, "c_rand", null, "randomized" ));
create_table( $conn, $tbname, $colMetaArr );
// test each PDO::PARAM_ type
foreach ( $pdoParamTypes as $pdoParamType ) {
// insert a row
$inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 );
$r;
$stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r, "prepareBindParam", array( new bindParamOption( 1, $pdoParamType ), new bindParamOption( 2, $pdoParamType )));
if ( $r === false )
{
is_incompatible_types_error( $stmt, $dataType, $pdoParamType );
}
else {
echo "****PDO param type $pdoParamType is compatible with encrypted $dataType****\n";
fetch_all( $conn, $tbname );
}
$conn->query( "TRUNCATE TABLE $tbname" );
}
DropTable( $conn, $tbname );
}
unset( $stmt );
unset( $conn );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
?>
--EXPECT--
Testing bit:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted bit****
c_det: 1
c_rand: 0
****PDO param type PDO::PARAM_NULL is compatible with encrypted bit****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted bit****
c_det: 1
c_rand: 0
****PDO param type PDO::PARAM_STR is compatible with encrypted bit****
c_det: 1
c_rand: 0
****PDO param type PDO::PARAM_LOB is compatible with encrypted bit****
c_det: 1
c_rand: 0
Testing tinyint:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted tinyint****
c_det: 0
c_rand: 1
****PDO param type PDO::PARAM_NULL is compatible with encrypted tinyint****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted tinyint****
c_det: 0
c_rand: 255
****PDO param type PDO::PARAM_STR is compatible with encrypted tinyint****
c_det: 0
c_rand: 255
****PDO param type PDO::PARAM_LOB is compatible with encrypted tinyint****
c_det: 0
c_rand: 255
Testing smallint:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted smallint****
c_det: 1
c_rand: 1
****PDO param type PDO::PARAM_NULL is compatible with encrypted smallint****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted smallint****
c_det: -32767
c_rand: 32767
****PDO param type PDO::PARAM_STR is compatible with encrypted smallint****
c_det: -32767
c_rand: 32767
****PDO param type PDO::PARAM_LOB is compatible with encrypted smallint****
c_det: -32767
c_rand: 32767
Testing int:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted int****
c_det: 1
c_rand: 1
****PDO param type PDO::PARAM_NULL is compatible with encrypted int****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted int****
c_det: -2147483647
c_rand: 2147483647
****PDO param type PDO::PARAM_STR is compatible with encrypted int****
c_det: -2147483647
c_rand: 2147483647
****PDO param type PDO::PARAM_LOB is compatible with encrypted int****
c_det: -2147483647
c_rand: 2147483647
Testing decimal(18,5):
****PDO param type PDO::PARAM_BOOL is compatible with encrypted decimal(18,5)****
c_det: -9223372036854.80078
c_rand: 9223372036854.80078
****PDO param type PDO::PARAM_NULL is compatible with encrypted decimal(18,5)****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted decimal(18,5)****
c_det: -9223372036854.80078
c_rand: 9223372036854.80078
****PDO param type PDO::PARAM_STR is compatible with encrypted decimal(18,5)****
c_det: -9223372036854.80000
c_rand: 9223372036854.80000
****PDO param type PDO::PARAM_LOB is compatible with encrypted decimal(18,5)****
c_det: -9223372036854.80078
c_rand: 9223372036854.80078
Testing numeric(10,5):
****PDO param type PDO::PARAM_BOOL is compatible with encrypted numeric(10,5)****
c_det: -21474.83647
c_rand: 21474.83647
****PDO param type PDO::PARAM_NULL is compatible with encrypted numeric(10,5)****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted numeric(10,5)****
c_det: -21474.83647
c_rand: 21474.83647
****PDO param type PDO::PARAM_STR is compatible with encrypted numeric(10,5)****
c_det: -21474.83647
c_rand: 21474.83647
****PDO param type PDO::PARAM_LOB is compatible with encrypted numeric(10,5)****
c_det: -21474.83647
c_rand: 21474.83647
Testing float:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted float****
c_det: -9223372036.8547993
c_rand: 9223372036.8547993
****PDO param type PDO::PARAM_NULL is compatible with encrypted float****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted float****
c_det: -9223372036.8547993
c_rand: 9223372036.8547993
****PDO param type PDO::PARAM_STR is compatible with encrypted float****
c_det: -9223372036.8547993
c_rand: 9223372036.8547993
****PDO param type PDO::PARAM_LOB is compatible with encrypted float****
c_det: -9223372036.8547993
c_rand: 9223372036.8547993
Testing real:
****PDO param type PDO::PARAM_BOOL is compatible with encrypted real****
c_det: -2147.4829
c_rand: 2147.4829
****PDO param type PDO::PARAM_NULL is compatible with encrypted real****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted real****
c_det: -2147.4829
c_rand: 2147.4829
****PDO param type PDO::PARAM_STR is compatible with encrypted real****
c_det: -2147.4829
c_rand: 2147.4829
****PDO param type PDO::PARAM_LOB is compatible with encrypted real****
c_det: -2147.4829
c_rand: 2147.4829

View file

@ -0,0 +1,121 @@
--TEST--
Test for inserting and retrieving encrypted data of string types
--DESCRIPTION--
Use PDOstatement::bindParam with all PDO::PARAM_ types
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
include 'AEData.inc';
$dataTypes = array( "char(5)", "varchar(max)", "nchar(5)", "nvarchar(max)" );
try
{
$conn = ae_connect();
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
foreach ( $dataTypes as $dataType ) {
echo "\nTesting $dataType:\n";
// create table
$tbname = GetTempTableName( "", false );
$colMetaArr = array( new columnMeta( $dataType, "c_det" ), new columnMeta( $dataType, "c_rand", null, "randomized" ));
create_table( $conn, $tbname, $colMetaArr );
// prepare statement for inserting into table
foreach ( $pdoParamTypes as $pdoParamType ) {
// insert a row
$inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 );
$r;
$stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r, "prepareBindParam", array( new bindParamOption( 1, $pdoParamType ), new bindParamOption( 2, $pdoParamType )));
if ( $r === false )
{
is_incompatible_types_error( $stmt, $dataType, $pdoParamType );
}
else {
echo "****PDO param type $pdoParamType is compatible with encrypted $dataType****\n";
fetch_all( $conn, $tbname );
}
$conn->query( "TRUNCATE TABLE $tbname" );
}
DropTable( $conn, $tbname );
}
unset( $stmt );
unset( $conn );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
?>
--EXPECT--
Testing char(5):
****PDO param type PDO::PARAM_BOOL is compatible with encrypted char(5)****
c_det: -leng
c_rand: th, n
****PDO param type PDO::PARAM_NULL is compatible with encrypted char(5)****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted char(5)****
c_det: -leng
c_rand: th, n
****PDO param type PDO::PARAM_STR is compatible with encrypted char(5)****
c_det: -leng
c_rand: th, n
****PDO param type PDO::PARAM_LOB is compatible with encrypted char(5)****
c_det: -leng
c_rand: th, n
Testing varchar(max):
****PDO param type PDO::PARAM_BOOL is compatible with encrypted varchar(max)****
c_det: Use varchar(max) when the sizes of the column data entries vary considerably, and the size might exceed 8,000 bytes.
c_rand: Each non-null varchar(max) or nvarchar(max) column requires 24 bytes of additional fixed allocation which counts against the 8,060 byte row limit during a sort operation.
****PDO param type PDO::PARAM_NULL is compatible with encrypted varchar(max)****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted varchar(max)****
c_det: Use varchar(max) when the sizes of the column data entries vary considerably, and the size might exceed 8,000 bytes.
c_rand: Each non-null varchar(max) or nvarchar(max) column requires 24 bytes of additional fixed allocation which counts against the 8,060 byte row limit during a sort operation.
****PDO param type PDO::PARAM_STR is compatible with encrypted varchar(max)****
c_det: Use varchar(max) when the sizes of the column data entries vary considerably, and the size might exceed 8,000 bytes.
c_rand: Each non-null varchar(max) or nvarchar(max) column requires 24 bytes of additional fixed allocation which counts against the 8,060 byte row limit during a sort operation.
****PDO param type PDO::PARAM_LOB is compatible with encrypted varchar(max)****
c_det: Use varchar(max) when the sizes of the column data entries vary considerably, and the size might exceed 8,000 bytes.
c_rand: Each non-null varchar(max) or nvarchar(max) column requires 24 bytes of additional fixed allocation which counts against the 8,060 byte row limit during a sort operation.
Testing nchar(5):
****PDO param type PDO::PARAM_BOOL is compatible with encrypted nchar(5)****
c_det: -leng
c_rand: th Un
****PDO param type PDO::PARAM_NULL is compatible with encrypted nchar(5)****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted nchar(5)****
c_det: -leng
c_rand: th Un
****PDO param type PDO::PARAM_STR is compatible with encrypted nchar(5)****
c_det: -leng
c_rand: th Un
****PDO param type PDO::PARAM_LOB is compatible with encrypted nchar(5)****
c_det: -leng
c_rand: th Un
Testing nvarchar(max):
****PDO param type PDO::PARAM_BOOL is compatible with encrypted nvarchar(max)****
c_det: When prefixing a string constant with the letter N, the implicit conversion will result in a Unicode string if the constant to convert does not exceed the max length for a Unicode string data type (4,000).
c_rand: Otherwise, the implicit conversion will result in a Unicode large-value (max).
****PDO param type PDO::PARAM_NULL is compatible with encrypted nvarchar(max)****
c_det:
c_rand:
****PDO param type PDO::PARAM_INT is compatible with encrypted nvarchar(max)****
c_det: When prefixing a string constant with the letter N, the implicit conversion will result in a Unicode string if the constant to convert does not exceed the max length for a Unicode string data type (4,000).
c_rand: Otherwise, the implicit conversion will result in a Unicode large-value (max).
****PDO param type PDO::PARAM_STR is compatible with encrypted nvarchar(max)****
c_det: When prefixing a string constant with the letter N, the implicit conversion will result in a Unicode string if the constant to convert does not exceed the max length for a Unicode string data type (4,000).
c_rand: Otherwise, the implicit conversion will result in a Unicode large-value (max).
****PDO param type PDO::PARAM_LOB is compatible with encrypted nvarchar(max)****
c_det: When prefixing a string constant with the letter N, the implicit conversion will result in a Unicode string if the constant to convert does not exceed the max length for a Unicode string data type (4,000).
c_rand: Otherwise, the implicit conversion will result in a Unicode large-value (max).

View file

@ -0,0 +1,83 @@
--TEST--
Test for inserting encrypted data and retrieving both encrypted and decrypted data
--DESCRIPTION--
Retrieving SQL query contains encrypted filter
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
try
{
$conn = ae_connect();
// Create the table
$tbname = 'Patients';
$colMetaArr = array( new columnMeta( "int", "PatientId", "IDENTITY(1,1)" ),
new columnMeta( "char(11)", "SSN"),
new columnMeta( "nvarchar(50)", "FirstName", "NULL" ),
new columnMeta( "nvarchar(50)", "LastName", "NULL"),
new columnMeta( "date", "BirthDate", null, "randomized" ));
create_table( $conn, $tbname, $colMetaArr );
// insert a row
$SSN = "795-73-9838";
$inputs = array( "SSN" => $SSN, "FirstName" => "Catherine", "LastName" => "Abel", "BirthDate" => "1996-10-19" );
$stmt = insert_row( $conn, $tbname, $inputs );
echo "Retrieving plaintext data:\n";
$selectSql = "SELECT SSN, FirstName, LastName, BirthDate FROM $tbname WHERE SSN = ?";
$stmt = $conn->prepare( $selectSql );
$stmt->bindParam( 1, $SSN );
$stmt->execute();
$decrypted_row = $stmt->fetch( PDO::FETCH_ASSOC );
foreach ( $decrypted_row as $key => $value )
{
print "$key: $value\n";
}
unset( $stmt );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
// for AE only
echo "\nChecking ciphertext data:\n";
if ( is_col_enc() )
{
try
{
$conn1 = ae_connect( null, null, true );
$selectSql = "SELECT SSN, FirstName, LastName, BirthDate FROM $tbname";
$stmt = $conn1->query( $selectSql );
$encrypted_row = $stmt->fetch( PDO::FETCH_ASSOC );
foreach ( $encrypted_row as $key => $value )
{
if ( ctype_print( $value ))
print "Error: expected a binary array for $key\n";
}
unset( $stmt );
unset( $conn1 );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
}
DropTable( $conn, $tbname );
unset( $conn );
echo "Done\n";
?>
--EXPECT--
Retrieving plaintext data:
SSN: 795-73-9838
FirstName: Catherine
LastName: Abel
BirthDate: 1996-10-19
Checking ciphertext data:
Done

View file

@ -0,0 +1,90 @@
--TEST--
Test for inserting encrypted fixed size types data and retrieve both encrypted and decrypted data
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
try
{
$conn = ae_connect();
// Create the table
$tbname = 'FixedSizeAnalysis';
$colMetaArr = array( new columnMeta( "tinyint", "TinyIntData" ),
new columnMeta( "smallint", "SmallIntData" ),
new columnMeta( "int", "IntData" ),
new columnMeta( "bigint", "BigIntData" ),
new columnMeta( "decimal(38,0)", "DecimalData" ),
new columnMeta( "bit", "BitData" ),
new columnMeta( "datetime", "DateTimeData" ),
new columnMeta( "datetime2", "DateTime2Data" ));
create_table( $conn, $tbname, $colMetaArr );
// insert a row
$inputs = array( "TinyIntData" => 255,
"SmallIntData" => 32767,
"IntData" => 2147483647,
"BigIntData" => 92233720368547,
"DecimalData" => 79228162514264,
"BitData" => true,
"DateTimeData" => '9999-12-31 23:59:59.997',
"DateTime2Data" => '9999-12-31 23:59:59.9999999');
$paramOptions = array( new bindParamOption( 4, "PDO::PARAM_INT" ) );
$r;
$stmt = insert_row( $conn, $tbname, $inputs, $r, "prepareBindParam", $paramOptions );
print "Decrypted values:\n";
fetch_all( $conn, $tbname );
unset( $stmt );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
// for AE only
if ( is_col_enc() )
{
try
{
$conn1 = ae_connect( null, null, true );
$selectSql = "SELECT * FROM $tbname";
$stmt = $conn1->query( $selectSql );
$encrypted_row = $stmt->fetch( PDO::FETCH_ASSOC );
foreach ( $encrypted_row as $key => $value )
{
if ( ctype_print( $value ))
{
print "Error: expected a binary array for $key\n";
}
}
unset( $stmt );
unset( $conn1 );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
}
DropTable( $conn, $tbname );
unset( $conn );
echo "Done\n";
?>
--EXPECT--
Decrypted values:
TinyIntData: 255
SmallIntData: 32767
IntData: 2147483647
BigIntData: 92233720368547
DecimalData: 79228162514264
BitData: 1
DateTimeData: 9999-12-31 23:59:59.997
DateTime2Data: 9999-12-31 23:59:59.9999999
Done

View file

@ -0,0 +1,79 @@
--TEST--
Test for inserting encrypted nvarchar data of variable lengths and retrieving encrypted and decrypted data
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
$testPass = true;
try
{
$conn = ae_connect();
// Create the table
$tbname = 'NVarcharAnalysis';
$colMetaArr = array( new columnMeta( "int", "CharCount", "IDENTITY(0,1)" ), new columnMeta( "nvarchar(1000)" ));
create_table( $conn, $tbname, $colMetaArr );
// insert 1000 rows
for ( $i = 0; $i < 1000; $i++ )
{
$data = str_repeat( "*", $i );
$stmt = insert_row( $conn, $tbname, array( get_default_colname( "nvarchar(1000)" ) => $data ) );
}
$selectSql = "SELECT * FROM $tbname";
$stmt = $conn->query( $selectSql );
while ( $decrypted_row = $stmt->fetch( PDO::FETCH_ASSOC ))
{
if ( $decrypted_row[ 'CharCount' ] != strlen( $decrypted_row[ get_default_colname( "nvarchar(1000)" ) ] ))
{
$rowInd = $decrypted_row[ 'CharCount' ] + 1;
echo "Failed to decrypted at row $rowInd\n";
$testPass = false;
}
}
unset( $stmt );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
// for AE only
if ( is_col_enc() )
{
try
{
$conn1 = ae_connect( null, null, true );
$stmt = $conn1->query( $selectSql );
while ( $decrypted_row = $stmt->fetch( PDO::FETCH_ASSOC ))
{
if ( $decrypted_row[ 'CharCount' ] == strlen( $decrypted_row[ get_default_colname( "nvarchar(1000)" ) ] ))
{
$rowInd = $decrypted_row[ 'CharCount' ] + 1;
echo "Failed to encrypted at row $rowInd\n";
$testPass = false;
}
}
unset( $stmt );
unset( $conn1 );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
}
DropTable( $conn, $tbname );
unset( $conn );
if ( $testPass ) {
echo "Test successfully done.\n";
}
?>
--EXPECT--
Test successfully done.

View file

@ -0,0 +1,78 @@
--TEST--
Test for inserting encrypted varchar data of variable lengths and retrieving encrypted and decrypted data
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
$testPass = true;
try
{
$conn = ae_connect();
// Create the table
$tbname = 'VarcharAnalysis';
$colMetaArr = array( new columnMeta( "int", "CharCount", "IDENTITY(0,1)" ), new columnMeta( "varchar(1000)" ));
create_table( $conn, $tbname, $colMetaArr );
// insert 1000 rows
for( $i = 0; $i < 1000; $i++ )
{
$data = str_repeat( "*", $i );
$stmt = insert_row( $conn, $tbname, array( get_default_colname( "varchar(1000)" ) => $data ) );
}
$selectSql = "SELECT * FROM $tbname";
$stmt = $conn->query( $selectSql );
while ( $decrypted_row = $stmt->fetch( PDO::FETCH_ASSOC ))
{
if ( $decrypted_row[ 'CharCount' ] != strlen( $decrypted_row[ get_default_colname( "varchar(1000)" ) ] ))
{
$rowInd = $decrypted_row[ 'CharCount' ] + 1;
echo "Failed to decrypted at row $rowInd\n";
$testPass = false;
}
}
unset( $stmt );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
// for AE only
if ( is_col_enc() )
{
try
{
$conn1 = ae_connect( null, null, true );
$stmt = $conn1->query( $selectSql );
while ( $decrypted_row = $stmt->fetch( PDO::FETCH_ASSOC ))
{
if ( $decrypted_row[ 'CharCount' ] == strlen( $decrypted_row[ get_default_colname( "varchar(1000)" ) ] ))
{
$rowInd = $decrypted_row[ 'CharCount' ] + 1;
echo "Failed to encrypted at row $rowInd\n";
$testPass = false;
}
}
unset( $stmt );
unset( $conn1 );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
}
DropTable( $conn, $tbname );
unset( $conn );
if ( $testPass ) {
echo "Test successfully done.\n";
}
?>
--EXPECT--
Test successfully done.

View file

@ -0,0 +1,67 @@
--TEST--
Test for inserting and retrieving encrypted data of string types
--DESCRIPTION--
No PDO::PARAM_ type specified when binding parameters
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
include 'AEData.inc';
$dataTypes = array( "char(5)", "varchar(max)", "nchar(5)", "nvarchar(max)" );
try
{
$conn = ae_connect();
foreach ( $dataTypes as $dataType ) {
echo "\nTesting $dataType:\n";
// create table
$tbname = GetTempTableName( "", false );
$colMetaArr = array( new columnMeta( $dataType, "c_det" ), new columnMeta( $dataType, "c_rand", null, "randomized" ));
create_table( $conn, $tbname, $colMetaArr );
// insert a row
$inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 );
$r;
$stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r );
if ( $r === false ) {
is_incompatible_types_error( $stmt, $dataType, "default type" );
}
else {
echo "****Encrypted default type is compatible with encrypted $dataType****\n";
fetch_all( $conn, $tbname );
}
DropTable( $conn, $tbname );
}
unset( $stmt );
unset( $conn );
}
catch( PDOException $e )
{
echo $e->getMessage();
}
?>
--EXPECT--
Testing char(5):
****Encrypted default type is compatible with encrypted char(5)****
c_det: -leng
c_rand: th, n
Testing varchar(max):
****Encrypted default type is compatible with encrypted varchar(max)****
c_det: Use varchar(max) when the sizes of the column data entries vary considerably, and the size might exceed 8,000 bytes.
c_rand: Each non-null varchar(max) or nvarchar(max) column requires 24 bytes of additional fixed allocation which counts against the 8,060 byte row limit during a sort operation.
Testing nchar(5):
****Encrypted default type is compatible with encrypted nchar(5)****
c_det: -leng
c_rand: th Un
Testing nvarchar(max):
****Encrypted default type is compatible with encrypted nvarchar(max)****
c_det: When prefixing a string constant with the letter N, the implicit conversion will result in a Unicode string if the constant to convert does not exceed the max length for a Unicode string data type (4,000).
c_rand: Otherwise, the implicit conversion will result in a Unicode large-value (max).

View file

@ -0,0 +1,164 @@
--TEST--
Test for binding output params for encrypted data for all types
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
include 'MsCommon.inc';
$conn = ae_connect();
// Create the table
$tbname = GetTempTableName( "", false );
$colMetaArr = array( new columnMeta( "int", "c1_int" ),
new columnMeta( "smallint", "c2_smallint" ),
new columnMeta( "tinyint", "c3_tinyint" ),
new columnMeta( "bit", "c4_bit" ),
new columnMeta( "bigint", "c5_bigint" ),
new columnMeta( "decimal(18,5)", "c6_decimal" ),
new columnMeta( "numeric(10,5)", "c7_numeric" ),
new columnMeta( "float", "c8_float" ),
new columnMeta( "real", "c9_real" ),
new columnMeta( "date", "c10_date" ),
new columnMeta( "datetime", "c11_datetime" ),
new columnMeta( "datetime2", "c12_datetime2" ),
new columnMeta( "datetimeoffset", "c13_datetimeoffset" ),
new columnMeta( "time", "c14_time" ),
new columnMeta( "char(5)", "c15_char" ),
new columnMeta( "varchar(max)", "c16_varchar" ),
new columnMeta( "nchar(5)", "c17_nchar" ),
new columnMeta( "nvarchar(max)", "c18_nvarchar" ));
create_table( $conn, $tbname, $colMetaArr );
// Create a Store Procedure
$spname = 'selectAllColumns';
$spSql = "CREATE PROCEDURE $spname (
@c1_int int OUTPUT, @c2_smallint smallint OUTPUT,
@c3_tinyint tinyint OUTPUT, @c4_bit bit OUTPUT,
@c5_bigint bigint OUTPUT, @c6_decimal decimal(18,5) OUTPUT,
@c7_numeric numeric(10,5) OUTPUT, @c8_float float OUTPUT,
@c9_real real OUTPUT, @c10_date date OUTPUT,
@c11_datetime datetime OUTPUT, @c12_datetime2 datetime2 OUTPUT,
@c13_datetimeoffset datetimeoffset OUTPUT, @c14_time time OUTPUT,
@c15_char char(5) OUTPUT, @c16_varchar varchar(max) OUTPUT,
@c17_nchar nchar(5) OUTPUT, @c18_nvarchar nvarchar(max) OUTPUT) AS
SELECT @c1_int = c1_int, @c2_smallint = c2_smallint,
@c3_tinyint = c3_tinyint, @c4_bit = c4_bit,
@c5_bigint = c5_bigint, @c6_decimal = c6_decimal,
@c7_numeric = c7_numeric, @c8_float = c8_float,
@c9_real = c9_real, @c10_date = c10_date,
@c11_datetime = c11_datetime, @c12_datetime2 = c12_datetime2,
@c13_datetimeoffset = c13_datetimeoffset, @c14_time = c14_time,
@c15_char = c15_char, @c16_varchar = c16_varchar,
@c17_nchar = c17_nchar, @c18_nvarchar = c18_nvarchar
FROM $tbname";
$conn->query( $spSql );
// Insert data
$inputs = array( "c1_int" => 2147483647,
"c2_smallint" => 32767,
"c3_tinyint" => 255,
"c4_bit" => 1,
"c5_bigint" => 922337203685479936,
"c6_decimal" => 9223372036854.80000,
"c7_numeric" => 21474.83647,
"c8_float" => 9223372036.8548,
"c9_real" => 2147.483,
"c10_date" => '9999-12-31',
"c11_datetime" => '9999-12-31 23:59:59.997',
"c12_datetime2" => '9999-12-31 23:59:59.9999999',
"c13_datetimeoffset" => '9999-12-31 23:59:59.9999999 +14:00',
"c14_time" => '23:59:59.9999999',
"c15_char" => 'th, n',
"c16_varchar" => 'This large row size can cause errors (such as error 512) during some normal operations, such as a clustered index key update, or sorts of the full column set, which users cannot anticipate until performing an operation.',
"c17_nchar" => 'th Un',
"c18_nvarchar" => 'When prefixing a string constant with the letter N, the implicit conversion will result in a Unicode string if the constant to convert does not exceed the max length for a Unicode string data type (4,000).' );
$paramOptions = array( new bindParamOption( 5, "PDO::PARAM_INT" ) );
$r;
$stmt = insert_row( $conn, $tbname, $inputs, $r, "prepareBindParam", $paramOptions );
// Call store procedure
$outSql = get_callProcSql_placeholders( $spname, count( $inputs ));
$intOut = 0;
$smallintOut = 0;
$tinyintOut = 0;
$bitOut = 0;
$bigintOut = 0.0;
$decimalOut = 0.0;
$numericOut = 0.0;
$floatOut = 0.0;
$realOut = 0.0;
$dateOut = '0001-01-01';
$datetimeOut = '1753-01-01 00:00:00';
$datetime2Out = '0001-01-01 00:00:00';
$datetimeoffsetOut = '1900-01-01 00:00:00 +01:00';
$timeOut = '00:00:00';
$charOut = '';
$varcharOut = '';
$ncharOut = '';
$nvarcharOut = '';
$stmt = $conn->prepare( $outSql );
$stmt->bindParam(1, $intOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
$stmt->bindParam(2, $smallintOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
$stmt->bindParam(3, $tinyintOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
$stmt->bindParam(4, $bitOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
$stmt->bindParam(5, $bigintOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(6, $decimalOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(7, $numericOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(8, $floatOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(9, $realOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(10, $dateOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(11, $datetimeOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(12, $datetime2Out, PDO::PARAM_STR, 2048);
$stmt->bindParam(13, $datetimeoffsetOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(14, $timeOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(15, $charOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(16, $varcharOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(17, $ncharOut, PDO::PARAM_STR, 2048);
$stmt->bindParam(18, $nvarcharOut, PDO::PARAM_STR, 2048);
$stmt->execute();
print ( "intOut: " . $intOut . "\n" );
print ( "smallintOut: " . $smallintOut . "\n" );
print ( "tinyintOut: " . $tinyintOut . "\n" );
print ( "bitOut: " . $bitOut . "\n" );
print ( "bigintOut: " . $bigintOut . "\n" );
print ( "decimalOut: " . $decimalOut . "\n" );
print ( "numericOut: " . $numericOut . "\n" );
print ( "floatOut: " . $floatOut . "\n" );
print ( "realOut: " . $realOut . "\n" );
print ( "dateOut: " . $dateOut . "\n" );
print ( "datetimeOut: " . $datetimeOut . "\n" );
print ( "datetime2Out: " . $datetime2Out . "\n" );
print ( "datetimeoffsetOut: " . $datetimeoffsetOut . "\n" );
print ( "timeOut: " . $timeOut . "\n" );
print ( "charOut: " . $charOut . "\n" );
print ( "varcharOut: " . $varcharOut . "\n" );
print ( "ncharOut: " . $ncharOut . "\n" );
print ( "nvarcharOut: " . $nvarcharOut . "\n" );
$conn->query( "DROP PROCEDURE $spname" );
$conn->query( "DROP TABLE $tbname" );
unset( $stmt );
unset( $conn );
?>
--EXPECTREGEX--
intOut: 2147483647
smallintOut: 32767
tinyintOut: 255
bitOut: 1
bigintOut: 922337203685479936
decimalOut: 9223372036854\.80000
numericOut: 21474\.83647
floatOut: (9223372036\.8547993|9\.22337e\+009)
realOut: (2147\.4829|2147\.48)
dateOut: 9999-12-31
datetimeOut: (9999-12-31 23:59:59\.997|Dec 31 9999 11:59PM)
datetime2Out: 9999-12-31 23:59:59\.9999999
datetimeoffsetOut: 9999-12-31 23:59:59\.9999999 \+14:00
timeOut: 23:59:59\.9999999
charOut: th\, n
varcharOut: This large row size can cause errors \(such as error 512\) during some normal operations\, such as a clustered index key update\, or sorts of the full column set\, which users cannot anticipate until performing an operation\.
ncharOut: th Un
nvarcharOut: When prefixing a string constant with the letter N\, the implicit conversion will result in a Unicode string if the constant to convert does not exceed the max length for a Unicode string data type \(4,000\).

View file

@ -0,0 +1,108 @@
--TEST--
Test new connection keyword ColumnEncryption
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
$msodbcsql_maj = "";
try
{
$conn = new PDO( "sqlsrv:server = $server", $uid, $pwd );
$msodbcsql_ver = $conn->getAttribute( PDO::ATTR_CLIENT_VERSION )['DriverVer'];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
}
catch( PDOException $e )
{
echo "Failed to connect\n";
print_r( $e->getMessage() );
echo "\n";
}
test_ColumnEncryption( $server, $uid, $pwd, $msodbcsql_maj );
echo "Done";
function verify_output( $PDOerror, $expected )
{
if( strpos( $PDOerror->getMessage(), $expected ) === false )
{
print_r( $PDOerror->getMessage() );
echo "\n";
}
}
function test_ColumnEncryption( $server, $uid, $pwd, $msodbcsql_maj )
{
// Only works for ODBC 17
////////////////////////////////////////
$connectionInfo = "ColumnEncryption = Enabled;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
}
catch( PDOException $e )
{
if($msodbcsql_maj < 17)
{
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
verify_output( $e, $expected );
}
else
{
print_r( $e->getMessage() );
echo "\n";
}
}
// Works for ODBC 17, ODBC 13
////////////////////////////////////////
$connectionInfo = "ColumnEncryption = Disabled;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
}
catch( PDOException $e )
{
if($msodbcsql_maj < 13)
{
$expected = "Invalid connection string attribute";
verify_output( $e, $expected );
}
else
{
print_r( $e->getMessage() );
echo "\n";
}
}
// should fail for all ODBC drivers
////////////////////////////////////////
$connectionInfo = "ColumnEncryption = false;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
}
catch( PDOException $e )
{
$expected = "Invalid value specified for connection string attribute 'ColumnEncryption'";
verify_output( $e, $expected );
}
// should fail for all ODBC drivers
////////////////////////////////////////
$connectionInfo = "ColumnEncryption = 1;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
}
catch( PDOException $e )
{
$expected = "Invalid value specified for connection string attribute 'ColumnEncryption'";
verify_output( $e, $expected );
}
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,54 @@
--TEST--
Fetch data from a prepopulated test table given a custom keystore provider
--SKIPIF--
<?php require('skipif_not_ksp.inc'); ?>
--FILE--
<?php
require( 'MsSetup.inc' );
require( 'AE_Ksp.inc' );
$ksp_path = getKSPpath();
$connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; ";
$connectionInfo .= "CEKeystoreProvider = $ksp_path; ";
$connectionInfo .= "CEKeystoreName = $ksp_name; ";
$connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; ";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
echo "Connected successfully with ColumnEncryption enabled and KSP specified.\n";
}
catch( PDOException $e )
{
echo "Failed to connect.\n";
print_r( $e->getMessage() );
echo "\n";
}
$tsql = "SELECT * FROM CustomKSPTestTable";
$stmt = $conn->query($tsql);
while ($row = $stmt->fetch(PDO::FETCH_NUM))
{
echo "c1=" . $row[0] . "\tc2=" . $row[1] . "\tc3=" . $row[2] . "\tc4=" . $row[3] . "\n";
}
$stmt = null;
$conn = null;
echo "Done\n";
?>
--EXPECT--
Connected successfully with ColumnEncryption enabled and KSP specified.
c1=1 c2=Sample data 0 for column 2 c3=abc c4=2017-08-10
c1=12 c2=Sample data 1 for column 2 c3=bcd c4=2017-08-11
c1=23 c2=Sample data 2 for column 2 c3=cde c4=2017-08-12
c1=34 c2=Sample data 3 for column 2 c3=def c4=2017-08-13
c1=45 c2=Sample data 4 for column 2 c3=efg c4=2017-08-14
c1=56 c2=Sample data 5 for column 2 c3=fgh c4=2017-08-15
c1=67 c2=Sample data 6 for column 2 c3=ghi c4=2017-08-16
c1=78 c2=Sample data 7 for column 2 c3=hij c4=2017-08-17
c1=89 c2=Sample data 8 for column 2 c3=ijk c4=2017-08-18
c1=100 c2=Sample data 9 for column 2 c3=jkl c4=2017-08-19
Done

View file

@ -0,0 +1,58 @@
--TEST--
Fetch encrypted data from a prepopulated test table given a custom keystore provider
--SKIPIF--
<?php require('skipif_not_ksp.inc'); ?>
--FILE--
<?php
require( 'MsSetup.inc' );
require( 'AE_Ksp.inc' );
$ksp_path = getKSPpath();
$connectionInfo = "Database = $databaseName; ";
$connectionInfo .= "CEKeystoreProvider = $ksp_path; ";
$connectionInfo .= "CEKeystoreName = $ksp_name; ";
$connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; ";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
echo "Connected successfully with ColumnEncryption disabled and KSP specified.\n";
}
catch( PDOException $e )
{
echo "Failed to connect.\n";
print_r( $e->getMessage() );
echo "\n";
}
$tsql = "SELECT * FROM CustomKSPTestTable";
$stmt = $conn->query($tsql);
while ($row = $stmt->fetch(PDO::FETCH_NUM))
{
echo "c1=" . $row[0];
echo "\tc2=" . bin2hex($row[1]);
echo "\tc3=" . bin2hex($row[2]);
echo "\tc4=" . bin2hex($row[3]);
echo "\n" ;
}
$stmt = null;
$conn = null;
echo "Done\n";
?>
--EXPECTREGEX--
Connected successfully with ColumnEncryption disabled and KSP specified.
c1=1 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+
c1=12 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+
c1=23 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+
c1=34 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+
c1=45 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+
c1=56 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+
c1=67 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+
c1=78 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+
c1=89 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+
c1=100 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+
Done

View file

@ -0,0 +1,100 @@
--TEST--
Connect using a custom keystore provider with some required inputs missing
--SKIPIF--
<?php require('skipif_not_ksp.inc'); ?>
--FILE--
<?php
require( 'MsSetup.inc' );
require( 'AE_Ksp.inc' );
function connect( $connectionInfo )
{
global $server, $uid, $pwd;
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
echo "Connected successfully with ColumnEncryption enabled and KSP specified.\n";
}
catch( PDOException $e )
{
echo "Failed to connect.\n";
print_r( $e->getMessage() );
echo "\n";
}
}
$ksp_path = getKSPpath();
echo("Connecting... with column encryption\n");
$connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; ";
connect( $connectionInfo );
echo("\nConnecting... with an invalid input to CEKeystoreProvider\n");
$connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; ";
$connectionInfo .= "CEKeystoreName = 1; ";
$connectionInfo .= "CEKeystoreProvider = $ksp_path; ";
$connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; ";
connect( $connectionInfo );
echo("\nConnecting... with an empty path\n");
$connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; ";
$connectionInfo .= "CEKeystoreName = $ksp_name; ";
$connectionInfo .= "CEKeystoreProvider = ; ";
$connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; ";
connect( $connectionInfo );
echo("\nConnecting... without a path\n");
$connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; ";
$connectionInfo .= "CEKeystoreName = $ksp_name; ";
$connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key;";
connect( $connectionInfo );
echo("\nConnecting... without a name\n");
$connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; ";
$connectionInfo .= "CEKeystoreProvider = $ksp_path; ";
$connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; ";
connect( $connectionInfo );
echo("\nConnecting... without a key\n");
$connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; ";
$connectionInfo .= "CEKeystoreProvider = $ksp_path; ";
$connectionInfo .= "CEKeystoreName = $ksp_name; ";
connect( $connectionInfo );
echo("\nConnecting... with all required inputs\n");
$connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; ";
$connectionInfo .= "CEKeystoreProvider = $ksp_path; ";
$connectionInfo .= "CEKeystoreName = $ksp_name; ";
$connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; ";
connect( $connectionInfo );
echo "Done\n";
?>
--EXPECTREGEX--
Connecting\.\.\. with column encryption
Connected successfully with ColumnEncryption enabled and KSP specified\.
Connecting\.\.\. with an invalid input to CEKeystoreProvider
Failed to connect.
SQLSTATE\[HY024\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid attribute value
Connecting\.\.\. with an empty path
Failed to connect.
SQLSTATE\[IMSSP\]: Invalid value for loading a custom keystore provider\.
Connecting\.\.\. without a path
Failed to connect.
SQLSTATE\[IMSSP\]: The path to the custom keystore provider is missing\.
Connecting\.\.\. without a name
Failed to connect.
SQLSTATE\[IMSSP\]: The name of the custom keystore provider is missing\.
Connecting\.\.\. without a key
Failed to connect.
SQLSTATE\[IMSSP\]: The encryption key for the custom keystore provider is missing\.
Connecting\.\.\. with all required inputs
Connected successfully with ColumnEncryption enabled and KSP specified\.
Done

View file

@ -29,5 +29,5 @@ Array
\(
\[0\] => 42S22
\[1\] => 207
\[2\] => \[Microsoft\]\[ODBC Driver (09|10|11|12|13) for SQL Server\]\[SQL Server\]Invalid column name 'IntColX'\.
\[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid column name 'IntColX'\.
\)

View file

@ -91,13 +91,13 @@ conn native code and native message are NULL\.
Warning: PDOStatement::bindParam\(\): SQLSTATE\[HY093\]: Invalid parameter number: parameter was not defined in .+(\/|\\)pdo_errorinfo_emulateprepare\.php on line [0-9]+
Warning: PDOStatement::execute\(\): SQLSTATE\[07002\]: COUNT field incorrect: 0 \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]COUNT field incorrect or syntax error in .+(\/|\\)pdo_errorinfo_emulateprepare\.php on line [0-9]+
Warning: PDOStatement::execute\(\): SQLSTATE\[07002\]: COUNT field incorrect: 0 \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]COUNT field incorrect or syntax error in .+(\/|\\)pdo_errorinfo_emulateprepare\.php on line [0-9]+
Statement error info:
Array
\(
\[0\] => 07002
\[1\] => 0
\[2\] => \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]COUNT field incorrect or syntax error
\[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]COUNT field incorrect or syntax error
\)
Connection error info:
Array

View file

@ -77,9 +77,9 @@ function RunTest()
RunTest();
?>
--EXPECT--
--EXPECTREGEX--

Number of rows: 1
SQLSTATE[42000]: [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Implicit conversion from data type sql_variant to nvarchar(max) is not allowed. Use the CONVERT function to run this query.
SQLSTATE\[42000\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Implicit conversion from data type sql_variant to nvarchar\(max\) is not allowed. Use the CONVERT function to run this query.
Done
Test "pdo_param_output_select_variant" completed successfully.
Test \"pdo_param_output_select_variant\" completed successfully\.

View file

@ -131,10 +131,10 @@ function RunTest()
RunTest();
?>
--EXPECT--
--EXPECTREGEX--

SQLSTATE[22018]: [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Operand type clash: nvarchar(max) is incompatible with sql_variant
SQLSTATE[22018]: [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Operand type clash: nvarchar(max) is incompatible with sql_variant
SQLSTATE\[22018\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Operand type clash: nvarchar\(max\) is incompatible with sql_variant
SQLSTATE\[22018\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Operand type clash: nvarchar\(max\) is incompatible with sql_variant
Done
Test "pdo_param_output_variants" completed successfully.
Test \"pdo_param_output_variants\" completed successfully\.

View file

@ -79,7 +79,7 @@ Array
\)
Prepare with emulate prepare and no bindparam options:
Fatal error: Uncaught PDOException: SQLSTATE\[42000\]: \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]\[SQL Server\]Implicit conversion from data type varchar to varbinary\(max\) is not allowed\. Use the CONVERT function to run this query\. in .+(\/|\\)pdo_prepare_emulatePrepare_binary\.php:[0-9]+
Fatal error: Uncaught PDOException: SQLSTATE\[42000\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Implicit conversion from data type varchar to varbinary\(max\) is not allowed\. Use the CONVERT function to run this query\. in .+(\/|\\)pdo_prepare_emulatePrepare_binary\.php:[0-9]+
Stack trace:
#0 .+(\/|\\)pdo_prepare_emulatePrepare_binary\.php\([0-9]+\): PDOStatement->execute\(\)
#1 {main}

View file

@ -66,27 +66,27 @@ function RunTest()
RunTest();
?>
--EXPECT--
--EXPECTREGEX--
Starting test...
Starting test\.\.\.
Setting query timeout as an attribute in connection
array(3) {
[0]=>
string(5) "HYT00"
[1]=>
int(0)
[2]=>
string(63) "[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired"
}
array\(3\) \{
\[0\]=>
string\(5\) \"HYT00\"
\[1\]=>
int\(0\)
\[2\]=>
string\(63\) \"\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Query timeout expired\"
\}
Setting query timeout in the statement
array(3) {
[0]=>
string(5) "HYT00"
[1]=>
int(0)
[2]=>
string(63) "[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired"
}
array\(3\) \{
\[0\]=>
string\(5\) \"HYT00\"
\[1\]=>
int\(0\)
\[2\]=>
string\(63\) \"\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Query timeout expired\"
\}
Done
Test "pdo_query_timeout" completed successfully.
Test \"pdo_query_timeout\" completed successfully\.

View file

@ -20,7 +20,7 @@ UTF-8 connection strings
?>
--EXPECTREGEX--
Fatal error: Uncaught PDOException: SQLSTATE\[(28000|08001|HYT00)\]: .*\[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\](\[SQL Server\])?(Named Pipes Provider: Could not open a connection to SQL Server \[2\]\. |Login timeout expired|Login failed for user 'sa'\.) in .+(\/|\\)pdo_utf8_conn\.php:[0-9]+
Fatal error: Uncaught PDOException: SQLSTATE\[(28000|08001|HYT00)\]: .*\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](\[SQL Server\])?(Named Pipes Provider: Could not open a connection to SQL Server \[2\]\. |Login timeout expired|Login failed for user 'sa'\.) in .+(\/|\\)pdo_utf8_conn\.php:[0-9]+
Stack trace:
#0 .+(\/|\\)pdo_utf8_conn\.php\([0-9]+\): PDO->__construct\('sqlsrv:Server=l\.\.\.', 'sa', 'Sunshine4u'\)
#1 {main}

View file

@ -35,4 +35,4 @@ Error Message: An unsupported attribute was designated on the PDO object\.
Warning: PDO::getAttribute\(\): SQLSTATE\[IM001\]: Driver does not support this function: driver does not support that attribute in .+(\/|\\)pdo_warnings\.php on line [0-9]+
Warning: PDOStatement::execute\(\): SQLSTATE\[42000\]: Syntax error or access violation: 156 \[Microsoft\]\[ODBC Driver [0-9]+ for SQL Server\]\[SQL Server\]Incorrect syntax near the keyword 'TABLE'\. in .+(\/|\\)pdo_warnings\.php on line [0-9]+
Warning: PDOStatement::execute\(\): SQLSTATE\[42000\]: Syntax error or access violation: 156 \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Incorrect syntax near the keyword 'TABLE'\. in .+(\/|\\)pdo_warnings\.php on line [0-9]+

View file

@ -0,0 +1,22 @@
<?php
if (! extension_loaded( 'pdo' ) || ! extension_loaded( 'pdo_sqlsrv' ))
die( "PDO driver cannot be loaded; skipping test.\n" );
require_once( "MsSetup.inc" );
if ($keystore != 'ksp')
die ( 'skip - this test requires a custom keystore provider.' );
require_once( "MsCommon.inc" );
$conn = new PDO( "sqlsrv:server = $server;", $uid, $pwd );
if( ! $conn )
{
echo( "Error: could not connect during SKIPIF!" );
}
else if(! IsAEQualified($conn))
{
die( "skip - AE feature not supported in the current environment." );
}
?>

View file

@ -0,0 +1,18 @@
<?php
if (! extension_loaded( 'pdo' ) || ! extension_loaded( 'pdo_sqlsrv' ))
die( "PDO driver cannot be loaded; skipping test.\n" );
require_once( "MsSetup.inc" );
require_once( "MsCommon.inc" );
$conn = ae_connect();
if( ! $conn )
{
echo( "Error: could not connect during SKIPIF!" );
}
else if(! IsAEQualified($conn))
{
die( "skip - AE feature not supported in the current environment." );
}
?>

View file

@ -0,0 +1,36 @@
--TEST--
retrieval of names of column master key and column encryption key generated in the database setup
--SKIPIF--
<?php require('skipif_unix.inc'); ?>
--FILE--
<?php
sqlsrv_configure( 'WarningsReturnAsErrors', 0 );
sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL );
require( 'MsCommon.inc' );
$conn = Connect();
if (IsAEQualified($conn)){
$query = "SELECT name FROM sys.column_master_keys";
$stmt = $conn->query($query);
$master_key_row = $stmt->fetch();
$query = "SELECT name FROM sys.column_encryption_keys";
$stmt = $conn->query($query);
$encryption_key_row = $stmt->fetch();
if ($master_key_row[0] == 'AEMasterKey' && $encryption_key_row[0] == 'AEColumnKey'){
echo "Test Successfully done.\n";
}
else {
die("Column Master Key and Column Encryption Key not created.\n");
}
unset($stmt);
}
else {
echo "Test Successfully done.\n";
}
unset($conn);
?>
--EXPECT--
Test Successfully done.

Binary file not shown.

View file

@ -0,0 +1,38 @@
USE $(dbname)
GO
/* DROP Column Encryption Key first, Column Master Key cannot be dropped until no encryption depends on it */
IF EXISTS (SELECT * FROM sys.column_encryption_keys WHERE [name] LIKE '%AEColumnKey%')
BEGIN
DROP COLUMN ENCRYPTION KEY [AEColumnKey]
END
GO
/* Can finally drop Column Master Key after the Encryption Key is dropped */
IF EXISTS (SELECT * FROM sys.column_master_keys WHERE [name] LIKE '%AEMasterKey%')
BEGIN
DROP COLUMN MASTER KEY [AEMasterKey]
END
GO
/* Recreate the Column Master Key */
CREATE COLUMN MASTER KEY [AEMasterKey]
WITH
(
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/my/237F94738E7F5214D8588006C2269DBC6B370816'
)
GO
/* Create Column Encryption Key using the Column Master Key */
/* ENCRYPTED_VALUE is generated by SSMS and it is always the same if the same Certificate is imported */
CREATE COLUMN ENCRYPTION KEY [AEColumnKey]
WITH VALUES
(
COLUMN_MASTER_KEY = [AEMasterKey],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00320033003700660039003400370033003800650037006600350032003100340064003800350038003800300030003600630032003200360039006400620063003600620033003700300038003100360039DE2397A08F6313E7820D75382D8469BE1C8F3CD47E3240A5A6D6F82D322F6EB1B103C9C47999A69FFB164D37E7891F60FFDB04ADEADEB990BE88AE488CAFB8774442DF909D2EF8BB5961A5C11B85BA7903E0E453B27B49CE0A30D14FF4F412B5737850A4C564B44C744E690E78FAECF007F9005E3E0FB4F8D6C13B016A6393B84BB3F83FEED397C4E003FF8C5BBDDC1F6156349A8B40EDC26398C9A03920DD81B9197BC83A7378F79ECB430A04B4CFDF3878B0219BB629F5B5BF3C2359A7498AD9A6F5D63EF15E060CDB10A65E6BF059C7A32237F0D9E00C8AC632CCDD68230774477D4F2E411A0E4D9B351E8BAA87793E64456370D91D4420B5FD9A252F6D9178AE3DD02E1ED57B7F7008114272419F505CBCEB109715A6C4331DEEB73653990A7140D7F83089B445C59E4858809D139658DC8B2781CB27A749F1CE349DC43238E1FBEAE0155BF2DBFEF6AFD9FD2BD1D14CEF9AC125523FD1120488F24416679A6041184A2719B0FC32B6C393FF64D353A3FA9BC4FA23DFDD999B0771A547B561D72B92A0B2BB8B266BC25191F2A0E2F8D93648F8750308DCD79BE55A2F8D5FBE9285265BEA66173CD5F5F21C22CC933AE2147F46D22BFF329F6A712B3D19A6488DDEB6FDAA5B136B29ADB0BA6B6D1FD6FBA5D6A14F76491CB000FEE4769D5B268A3BF50EA3FBA713040944558EDE99D38A5828E07B05236A4475DA27915E
)
GO

View file

@ -0,0 +1,122 @@
#!/usr/bin/python3
#########################################################################################
#
# Description: This script builds a custom keystore provider and compiles the app that
# uses this KSP. Their names can be passed as arguments, but the outputs
# are always
# - myKSP.dll (myKSPx64.dll) / myKSP.so
# - ksp_app.exe / ksp_app
#
# Requirement:
# python 3.x
# myKSP.c (or any equivalent)
# ksp_app.c (or any equivalent)
# msodbcsql.h (odbc header file)
#
# Execution: Run with command line with optional options
# py build_ksp.py --KSP myKSP --APP ksp_app
#
#############################################################################################
import sys
import os
import platform
import argparse
# This creates a batch *filename*, which compiles a C program according to
# *command* and *arch* (either x86 or x64)
def create_batch_file(arch, filename, command):
root_dir = 'C:' + os.sep
vcvarsall = os.path.join(root_dir, "Program Files (x86)", "Microsoft Visual Studio 14.0", "VC", "vcvarsall.bat")
try:
file = open(filename, 'w')
file.write('@ECHO OFF' + os.linesep)
if arch == 'x64':
file.write('@CALL "' + vcvarsall + '" amd64' + os.linesep)
else:
file.write('@CALL "' + vcvarsall + '" x86' + os.linesep)
# compile the code
file.write('@CALL ' + command + os.linesep)
file.close()
except:
print('Cannot create ', filename)
# This invokes the newly created batch file to compile the code,
# according to *arch* (either x86 or x64). The batch file will be
# removed afterwards
def compile_KSP_windows(arch, ksp_src):
output = 'myKSP'
if arch == 'x64':
output = output + arch + '.dll'
else:
output = output + '.dll'
command = 'cl {0} /LD /MD /link /out:'.format(ksp_src) + output
batchfile = 'build_KSP.bat'
create_batch_file(arch, batchfile, command)
os.system(batchfile)
os.remove(batchfile)
# This compiles myKSP.c
#
# In Windows, this will create batch files to compile two dll(s).
# Otherwise, this will compile the code and generate a .so file.
#
# Output: A custom keystore provider created
def compile_KSP(ksp_src):
print('Compiling ', ksp_src)
if platform.system() == 'Windows':
compile_KSP_windows('x64', ksp_src)
compile_KSP_windows('x86', ksp_src)
else:
os.system('gcc -fshort-wchar -fPIC -o myKSP.so -shared {0}'.format(ksp_src))
# This compiles ksp app, which assumes the existence of the .dll or the .so file.
#
# In Windows, a batch file is created in order to compile the code.
def configure_KSP(app_src):
print('Compiling ', app_src)
if platform.system() == 'Windows':
command = 'cl /MD {0} /link odbc32.lib /out:ksp_app.exe'.format(app_src)
batchfile = 'build_app.bat'
create_batch_file('x86', batchfile, command)
os.system(batchfile)
os.remove(batchfile)
else:
os.system('gcc -o ksp_app -fshort-wchar {0} -lodbc -ldl'.format(app_src))
################################### Main Function ###################################
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-ksp', '--KSPSRC', default='myKSP.c', help='The source file of KSP (keystore provider)')
parser.add_argument('-app', '--APPSRC', default='ksp_app.c', help='The source file for the app that uses the KSP')
args = parser.parse_args()
ksp_src = args.KSPSRC
app_src = args.APPSRC
header = 'msodbcsql.h'
cwd = os.getcwd()
# make sure all required source and header files are present
work_dir = os.path.dirname(os.path.realpath(__file__))
os.chdir(work_dir)
if not os.path.exists(os.path.join(work_dir, header)):
print('Error: {0} not found!'.format(header))
exit(1)
if not os.path.exists(os.path.join(work_dir, ksp_src)):
print('Error: {0}.c not found!'.format(ksp_src))
exit(1)
if not os.path.exists(os.path.join(work_dir, app_src)):
print('Error: {0}.c not found!'.format(app_src))
exit(1)
compile_KSP(ksp_src)
configure_KSP(app_src)
os.chdir(cwd)

View file

@ -27,3 +27,9 @@ if __name__ == '__main__':
conn_options = ' -S ' + server + ' -U ' + uid + ' -P ' + pwd + ' '
executeSQLscript( os.path.join( os.path.dirname(os.path.realpath(__file__)), 'drop_db.sql'), conn_options, args.DBNAME)
# if Windows, remove self signed certificate using ps command
if platform.system() == 'Windows':
remove_cert_ps = "Get-ChildItem Cert:CurrentUser\My | Where-Object { $_.Subject -match 'PHPAlwaysEncryptedCert' } | Remove-Item"
inst_command = 'powershell -executionPolicy Unrestricted -command ' + remove_cert_ps
executeCommmand(inst_command)

View file

@ -0,0 +1,18 @@
--for this script to work in Azure, use sqlcmd to connect to master database
IF NOT EXISTS (SELECT name FROM sys.sql_logins WHERE name = 'test_password')
BEGIN
CREATE LOGIN test_password WITH PASSWORD='! ;4triou';
END
GO
IF NOT EXISTS (SELECT name FROM sys.sql_logins WHERE name = 'test_password2')
BEGIN
CREATE LOGIN test_password2 WITH PASSWORD='!} ;4triou';
END
GO
IF NOT EXISTS (SELECT name FROM sys.sql_logins WHERE name = 'test_password3')
BEGIN
CREATE LOGIN test_password3 WITH PASSWORD='! ;4triou}';
END
GO

View file

@ -0,0 +1,21 @@
--for this script to work in Azure, create_logins_azure.sql must have been invoked beforehand
--assuming these logins exist, use sqlcmd to connect to a test database
--these users will be granted access to that database
IF NOT EXISTS (SELECT name FROM sysusers WHERE name = 'test_password')
BEGIN
CREATE USER test_password FROM LOGIN test_password;
END
GO
IF NOT EXISTS (SELECT name FROM sysusers WHERE name = 'test_password2')
BEGIN
CREATE USER test_password2 FROM LOGIN test_password2;
END
GO
IF NOT EXISTS (SELECT name FROM sysusers WHERE name = 'test_password3')
BEGIN
CREATE USER test_password3 FROM LOGIN test_password3;
END
GO

View file

@ -0,0 +1,305 @@
/******************************************************************************
Example application for demonstration of custom keystore provider usage
Windows: compile with cl /MD ksp_app.c /link odbc32.lib /out:ksp_app.exe
Linux/mac: compile with gcc -o ksp_app -fshort-wchar ksp_app.c -lodbc -ldl
usage: kspapp connstr
******************************************************************************/
#define KSPNAME L"MyCustomKSPName"
#define PROV_ENCRYPT_KEY "LPKCWVD07N3RG98J0MBLG4H2" /* this can be any character string */
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#else
#define __stdcall
#include <dlfcn.h>
#endif
#include <sql.h>
#include <sqlext.h>
#include "msodbcsql.h"
enum job {
set_up = 0,
clean_up = 1
};
/* Convenience functions */
int checkRC(SQLRETURN rc, char *msg, int ret, SQLHANDLE h, SQLSMALLINT ht) {
if (rc == SQL_ERROR) {
fprintf(stderr, "Error occurred upon %s\n", msg);
if (h) {
SQLSMALLINT i = 0;
SQLSMALLINT outlen = 0;
char errmsg[1024];
while ((rc = SQLGetDiagField(
ht, h, ++i, SQL_DIAG_MESSAGE_TEXT, errmsg, sizeof(errmsg), &outlen)) == SQL_SUCCESS
|| rc == SQL_SUCCESS_WITH_INFO) {
fprintf(stderr, "Err#%d: %s\n", i, errmsg);
}
}
if (ret)
exit(ret);
return 0;
}
else if (rc == SQL_SUCCESS_WITH_INFO && h) {
SQLSMALLINT i = 0;
SQLSMALLINT outlen = 0;
char errmsg[1024];
printf("Success with info for %s:\n", msg);
while ((rc = SQLGetDiagField(
ht, h, ++i, SQL_DIAG_MESSAGE_TEXT, errmsg, sizeof(errmsg), &outlen)) == SQL_SUCCESS
|| rc == SQL_SUCCESS_WITH_INFO) {
fprintf(stderr, "Msg#%d: %s\n", i, errmsg);
}
}
return 1;
}
void postKspError(CEKEYSTORECONTEXT *ctx, const wchar_t *msg, ...) {
if (msg > (wchar_t*)65535)
wprintf(L"Provider emitted message: %s\n", msg);
else
wprintf(L"Provider emitted message ID %d\n", msg);
}
int setKSPLibrary(SQLHSTMT stmt) {
unsigned char CEK[32];
unsigned char *ECEK;
unsigned short ECEKlen;
unsigned char foundProv = 0;
int i;
#ifdef _WIN32
HMODULE hProvLib;
#else
void *hProvLib;
#endif
CEKEYSTORECONTEXT ctx = {0};
CEKEYSTOREPROVIDER **ppKsp, *pKsp;
int(__stdcall *pEncryptCEK)(CEKEYSTORECONTEXT *, errFunc *, unsigned char *, unsigned short, unsigned char **, unsigned short *);
/* Load the provider library */
#ifdef _WIN32
if (!(hProvLib = LoadLibrary("myKSP.dll"))) {
#else
if (!(hProvLib = dlopen("./myKSP.so", RTLD_NOW))) {
#endif
fprintf(stderr, "Error loading KSP library\n");
return 2;
}
#ifdef _WIN32
if (!(ppKsp = (CEKEYSTOREPROVIDER**)GetProcAddress(hProvLib, "CEKeystoreProvider"))) {
#else
if (!(ppKsp = (CEKEYSTOREPROVIDER**)dlsym(hProvLib, "CEKeystoreProvider"))) {
#endif
fprintf(stderr, "The export CEKeystoreProvider was not found in the KSP library\n");
return 3;
}
while (pKsp = *ppKsp++) {
if (!memcmp(KSPNAME, pKsp->Name, sizeof(KSPNAME))) {
foundProv = 1;
break;
}
}
if (! foundProv) {
fprintf(stderr, "Could not find provider in the library\n");
return 4;
}
if (pKsp->Init && !pKsp->Init(&ctx, postKspError)) {
fprintf(stderr, "Could not initialize provider\n");
return 5;
}
#ifdef _WIN32
if (!(pEncryptCEK = (LPVOID)GetProcAddress(hProvLib, "KeystoreEncrypt"))) {
#else
if (!(pEncryptCEK = dlsym(hProvLib, "KeystoreEncrypt"))) {
#endif
fprintf(stderr, "The export KeystoreEncrypt was not found in the KSP library\n");
return 6;
}
if (!pKsp->Write) {
fprintf(stderr, "Provider does not support configuration\n");
return 7;
}
/* Configure the provider with the key */
if (!pKsp->Write(&ctx, postKspError, PROV_ENCRYPT_KEY, strlen(PROV_ENCRYPT_KEY))) {
fprintf(stderr, "Error writing to KSP\n");
return 8;
}
/* Generate a CEK and encrypt it with the provider */
srand(time(0) ^ getpid());
for (i = 0; i < sizeof(CEK); i++)
CEK[i] = rand();
if (!pEncryptCEK(&ctx, postKspError, CEK, sizeof(CEK), &ECEK, &ECEKlen)) {
fprintf(stderr, "Error encrypting CEK\n");
return 9;
}
/* Create a CMK definition on the server */
{
static char cmkSql[] = "CREATE COLUMN MASTER KEY CustomCMK WITH ("
"KEY_STORE_PROVIDER_NAME = 'MyCustomKSPName',"
"KEY_PATH = 'TheOneAndOnlyKey')";
printf("Create CMK: %s\n", cmkSql);
SQLExecDirect(stmt, cmkSql, SQL_NTS);
}
/* Create a CEK definition on the server */
{
const char cekSqlBefore[] = "CREATE COLUMN ENCRYPTION KEY CustomCEK WITH VALUES ("
"COLUMN_MASTER_KEY = CustomCMK,"
"ALGORITHM = 'none',"
"ENCRYPTED_VALUE = 0x";
char *cekSql = malloc(sizeof(cekSqlBefore) + 2 * ECEKlen + 2); /* 1 for ')', 1 for null terminator */
strcpy(cekSql, cekSqlBefore);
for (i = 0; i < ECEKlen; i++)
sprintf(cekSql + sizeof(cekSqlBefore) - 1 + 2 * i, "%02x", ECEK[i]);
strcat(cekSql, ")");
printf("Create CEK: %s\n", cekSql);
SQLExecDirect(stmt, cekSql, SQL_NTS);
free(cekSql);
#ifdef _WIN32
LocalFree(ECEK);
#else
free(ECEK);
#endif
}
#ifdef _WIN32
FreeLibrary(hProvLib);
#else
dlclose(hProvLib);
#endif
return 0;
}
void populateTestTable(SQLHDBC dbc, SQLHSTMT stmt)
{
SQLRETURN rc;
int i, j;
/* Create a table with encrypted columns */
{
static char *tableSql = "CREATE TABLE CustomKSPTestTable ("
"c1 int,"
"c2 varchar(255) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'),"
"c3 char(5) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'),"
"c4 date ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'))";
printf("Create table: %s\n", tableSql);
SQLExecDirect(stmt, tableSql, SQL_NTS);
}
/* Load provider into the ODBC Driver and configure it */
{
unsigned char ksd[sizeof(CEKEYSTOREDATA) + sizeof(PROV_ENCRYPT_KEY) - 1];
CEKEYSTOREDATA *pKsd = (CEKEYSTOREDATA*)ksd;
pKsd->name = L"MyCustomKSPName";
pKsd->dataSize = sizeof(PROV_ENCRYPT_KEY) - 1;
memcpy(pKsd->data, PROV_ENCRYPT_KEY, sizeof(PROV_ENCRYPT_KEY) - 1);
#ifdef _WIN32
rc = SQLSetConnectAttr(dbc, SQL_COPT_SS_CEKEYSTOREPROVIDER, "myKSP.dll", SQL_NTS);
#else
rc = SQLSetConnectAttr(dbc, SQL_COPT_SS_CEKEYSTOREPROVIDER, "./myKSP.so", SQL_NTS);
#endif
checkRC(rc, "Loading KSP into ODBC Driver", 7, dbc, SQL_HANDLE_DBC);
rc = SQLSetConnectAttr(dbc, SQL_COPT_SS_CEKEYSTOREDATA, (SQLPOINTER)pKsd, SQL_IS_POINTER);
checkRC(rc, "Configuring the KSP", 7, dbc, SQL_HANDLE_DBC);
}
/* Insert some data */
{
int c1;
char c2[256];
char c3[6];
SQL_DATE_STRUCT date;
SQLLEN cbdate;
rc = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &c1, 0, 0);
checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT);
rc = SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 255, 0, c2, 255, 0);
checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT);
rc = SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 5, 0, c3, 5, 0);
checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT);
checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT);
cbdate = sizeof(SQL_DATE_STRUCT);
rc = SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_TYPE_DATE, SQL_TYPE_DATE, 10, 0, &date, 0, &cbdate);
checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT);
date.year = 2017;
date.month = 8;
for (i = 0; i < 10; i++) {
date.day = i + 10;
c1 = i * 10 + i + 1;
sprintf(c2, "Sample data %d for column 2", i);
for (j = 0; j < 3; j++) {
c3[j] = 'a' + i + j;
}
c3[3] = '\0';
rc = SQLExecDirect(stmt, "INSERT INTO CustomKSPTestTable (c1, c2, c3, c4) values (?, ?, ?, ?)", SQL_NTS);
checkRC(rc, "Inserting rows query", 10, stmt, SQL_HANDLE_STMT);
}
printf("(Encrypted) data has been inserted into CustomKSPTestTable. You may inspect the data now.\n");
}
}
int main(int argc, char **argv) {
char sqlbuf[1024];
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
SQLRETURN rc;
int i;
char connStr[1024];
enum job task;
if (argc < 6) {
fprintf(stderr, "usage: kspapp job server database uid pwd\n");
return 1;
}
task = atoi(argv[1]);
sprintf(connStr, "DRIVER={ODBC Driver 17 for SQL Server};SERVER=%s;ColumnEncryption=Enabled;DATABASE=%s;UID=%s;PWD=%s", argv[2], argv[3], argv[4], argv[5]);
/* Connect to Server */
rc = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &env);
checkRC(rc, "allocating environment handle", 2, 0, 0);
rc = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
checkRC(rc, "setting ODBC version to 3.0", 3, env, SQL_HANDLE_ENV);
rc = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
checkRC(rc, "allocating connection handle", 4, env, SQL_HANDLE_ENV);
rc = SQLDriverConnect(dbc, 0, connStr, strlen(connStr), NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
checkRC(rc, "connecting to data source", 5, dbc, SQL_HANDLE_DBC);
rc = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
checkRC(rc, "allocating statement handle", 6, dbc, SQL_HANDLE_DBC);
if (task == set_up) {
printf("Setting up KSP...\n");
setKSPLibrary(stmt);
populateTestTable(dbc, stmt);
}
else if (task == clean_up) {
printf("Cleaning up KSP...\n");
SQLExecDirect(stmt, "DROP TABLE CustomKSPTestTable", SQL_NTS);
SQLExecDirect(stmt, "DROP COLUMN ENCRYPTION KEY CustomCEK", SQL_NTS);
SQLExecDirect(stmt, "DROP COLUMN MASTER KEY CustomCMK", SQL_NTS);
printf("Removed table, CEK, and CMK\n");
}
SQLDisconnect(dbc);
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
return 0;
}

View file

@ -0,0 +1,132 @@
/******************************************************************************
Custom Keystore Provider Example
Windows: compile with cl myKSP.c /LD /MD /link /out:myKSP.dll
Linux/mac: compile with gcc -fshort-wchar -fPIC -o myKSP.so -shared myKSP.c
******************************************************************************/
#ifdef _WIN32
#include <windows.h>
#else
#define __stdcall
#endif
#define DEBUG 0
#include <stdio.h>
#include <stdlib.h>
#include <sqltypes.h>
#include <sql.h>
#include <sqlext.h>
#include "msodbcsql.h"
int wcscmp_short(wchar_t *s1, wchar_t *s2) {
while(*s1 && *s2 && *s1 == *s2)
s1++, s2++;
return *s1 - *s2;
}
int __stdcall KeystoreInit(CEKEYSTORECONTEXT *ctx, errFunc *onError) {
if (DEBUG)
printf("KSP Init() function called\n");
return 1;
}
static unsigned char *g_encryptKey;
static unsigned int g_encryptKeyLen;
int __stdcall KeystoreWrite(CEKEYSTORECONTEXT *ctx, errFunc *onError, void *data, unsigned int len) {
if (DEBUG)
printf("KSP Write() function called (%d bytes)\n", len);
if (len) {
if (g_encryptKey)
free(g_encryptKey);
g_encryptKey = malloc(len);
if (!g_encryptKey) {
onError(ctx, L"Memory Allocation Error");
return 0;
}
memcpy(g_encryptKey, data, len);
g_encryptKeyLen = len;
}
return 1;
}
// Very simple "encryption" scheme - rotating XOR with the key
int __stdcall KeystoreDecrypt(CEKEYSTORECONTEXT *ctx, errFunc *onError, const wchar_t *keyPath, const wchar_t *alg, unsigned char *ecek, unsigned short ecekLen, unsigned char **cekOut, unsigned short *cekLen) {
unsigned int i;
if (DEBUG)
printf("KSP Decrypt() function called (keypath=%S alg=%S ecekLen=%u)\n", keyPath, alg, ecekLen);
if (wcscmp_short(keyPath, L"TheOneAndOnlyKey")) {
onError(ctx, L"Invalid key path");
return 0;
}
if (wcscmp_short(alg, L"none")) {
onError(ctx, L"Invalid algorithm");
return 0;
}
if (!g_encryptKey) {
onError(ctx, L"Keystore provider not initialized with key");
return 0;
}
#ifndef _WIN32
*cekOut = malloc(ecekLen);
#else
*cekOut = LocalAlloc(LMEM_FIXED, ecekLen);
#endif
if (!*cekOut) {
onError(ctx, L"Memory Allocation Error");
return 0;
}
*cekLen = ecekLen;
for (i = 0; i < ecekLen; i++)
(*cekOut)[i] = ecek[i] ^ g_encryptKey[i % g_encryptKeyLen];
return 1;
}
// Note that in the provider interface, this function would be referenced via the CEKEYSTOREPROVIDER
// structure. However, that does not preclude keystore providers from exporting their own functions,
// as illustrated by this example where the encryption is performed via a separate function (with a
// different prototype than the one in the KSP interface.)
#ifdef _WIN32
__declspec(dllexport)
#endif
int KeystoreEncrypt(CEKEYSTORECONTEXT *ctx, errFunc *onError,
unsigned char *cek, unsigned short cekLen,
unsigned char **ecekOut, unsigned short *ecekLen) {
unsigned int i;
if (DEBUG)
printf("KSP Encrypt() function called (cekLen=%u)\n", cekLen);
if (!g_encryptKey) {
onError(ctx, L"Keystore provider not initialized with key");
return 0;
}
*ecekOut = malloc(cekLen);
if (!*ecekOut) {
onError(ctx, L"Memory Allocation Error");
return 0;
}
*ecekLen = cekLen;
for (i = 0; i < cekLen; i++)
(*ecekOut)[i] = cek[i] ^ g_encryptKey[i % g_encryptKeyLen];
return 1;
}
CEKEYSTOREPROVIDER MyCustomKSPName_desc = {
L"MyCustomKSPName",
KeystoreInit,
0,
KeystoreWrite,
KeystoreDecrypt,
0
};
#ifdef _WIN32
__declspec(dllexport)
#endif
CEKEYSTOREPROVIDER *CEKeystoreProvider[] = {
&MyCustomKSPName_desc,
0
};

View file

@ -0,0 +1,57 @@
#!/usr/bin/python3
#########################################################################################
#
# Description: This script assumes the existence of the ksp_app executable and will
# invoke it to create / remove the Column Master Key, the Column Encryption key,
# and the table [CustomKSPTestTable] in the test database.
#
# Requirement:
# python 3.x
# ksp_app executable
#
# Execution: Run with command line with required options
# py run_ksp.py --SERVER=server --DBNAME=database --UID=uid --PWD=pwd
# py run_ksp.py --SERVER=server --DBNAME=database --UID=uid --PWD=pwd --REMOVE
#
#############################################################################################
import sys
import os
import platform
import argparse
################################### Main Function ###################################
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-server', '--SERVER', required=True, help='SQL Server')
parser.add_argument('-dbname', '--DBNAME', required=True, help='Name of an existing database')
parser.add_argument('-uid', '--UID', required=True, help='User name')
parser.add_argument('-pwd', '--PWD', required=True, help='User password')
parser.add_argument('-remove', '--REMOVE', action='store_true', help='Clean up KSP related data, false by default')
args = parser.parse_args()
app_name = 'ksp_app'
cwd = os.getcwd()
# first check if the ksp app is present
work_dir = os.path.dirname(os.path.realpath(__file__))
os.chdir(work_dir)
if platform.system() == 'Windows':
path = os.path.join(work_dir, app_name + '.exe')
executable = app_name
else:
path = os.path.join(work_dir, app_name)
executable = './' + app_name
if not os.path.exists(path):
print('Error: {0} not found!'.format(path))
exit(1)
if args.REMOVE:
os.system('{0} 1 {1} {2} {3} {4}'.format(executable, args.SERVER, args.DBNAME, args.UID, args.PWD))
else:
os.system('{0} 0 {1} {2} {3} {4}'.format(executable, args.SERVER, args.DBNAME, args.UID, args.PWD))
os.chdir(cwd)

View file

@ -45,6 +45,14 @@ def executeBulkCopy(conn_options, dbname, tblname, datafile):
inst_command = redirect_string.format(dbname, tblname, datafile) + conn_options
executeCommmand(inst_command)
def setupAE( conn_options, dbname, azure ):
if (platform.system() == 'Windows' and azure.lower() == 'no'):
# import self signed certificate
inst_command = "certutil -user -p '' -importPFX My PHPcert.pfx NoRoot"
executeCommmand(inst_command)
# create Column Master Key and Column Encryption Key
executeSQLscript('ae_keys.sql', conn_options, dbname)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-dbname', '--DBNAME', required=True)
@ -75,5 +83,8 @@ if __name__ == '__main__':
setupTestDatabase(conn_options, args.DBNAME, args.AZURE)
# populate these tables
populateTables(conn_options, args.DBNAME)
# setup AE (certificate, column master key and column encryption key)
setupAE(conn_options, args.DBNAME, args.AZURE)
os.chdir(current_working_dir)

Some files were not shown because too many files have changed in this diff Show more