diff --git a/.travis.yml b/.travis.yml index c290dccf..dfe1b0f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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' diff --git a/CHANGELOG.md b/CHANGELOG.md index be5eb651..ac9f3c4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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: diff --git a/ODBC 17 binaries preview/Debian 8/msodbcsql_17.0.0.1-1_amd64.deb b/ODBC 17 binaries preview/Debian 8/msodbcsql_17.0.0.1-1_amd64.deb new file mode 100644 index 00000000..1032b040 Binary files /dev/null and b/ODBC 17 binaries preview/Debian 8/msodbcsql_17.0.0.1-1_amd64.deb differ diff --git a/ODBC 17 binaries preview/Debian 8/mssql-tools_17.0.0.1-1_amd64.deb b/ODBC 17 binaries preview/Debian 8/mssql-tools_17.0.0.1-1_amd64.deb new file mode 100644 index 00000000..ea2ea5ef Binary files /dev/null and b/ODBC 17 binaries preview/Debian 8/mssql-tools_17.0.0.1-1_amd64.deb differ diff --git a/ODBC 17 binaries preview/README.md b/ODBC 17 binaries preview/README.md new file mode 100644 index 00000000..6b79f14d --- /dev/null +++ b/ODBC 17 binaries preview/README.md @@ -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). diff --git a/ODBC 17 binaries preview/Red Hat 7/msodbcsql-17.0.0.1-1.x86_64.rpm b/ODBC 17 binaries preview/Red Hat 7/msodbcsql-17.0.0.1-1.x86_64.rpm new file mode 100644 index 00000000..5eef69a5 Binary files /dev/null and b/ODBC 17 binaries preview/Red Hat 7/msodbcsql-17.0.0.1-1.x86_64.rpm differ diff --git a/ODBC 17 binaries preview/Red Hat 7/mssql-tools-17.0.0.1-1.x86_64.rpm b/ODBC 17 binaries preview/Red Hat 7/mssql-tools-17.0.0.1-1.x86_64.rpm new file mode 100644 index 00000000..e234bf02 Binary files /dev/null and b/ODBC 17 binaries preview/Red Hat 7/mssql-tools-17.0.0.1-1.x86_64.rpm differ diff --git a/ODBC 17 binaries preview/SUSE 12/msodbcsql-17.0.0.1-1.x86_64.rpm b/ODBC 17 binaries preview/SUSE 12/msodbcsql-17.0.0.1-1.x86_64.rpm new file mode 100644 index 00000000..46e68023 Binary files /dev/null and b/ODBC 17 binaries preview/SUSE 12/msodbcsql-17.0.0.1-1.x86_64.rpm differ diff --git a/ODBC 17 binaries preview/SUSE 12/mssql-tools-17.0.0.1-1.x86_64.rpm b/ODBC 17 binaries preview/SUSE 12/mssql-tools-17.0.0.1-1.x86_64.rpm new file mode 100644 index 00000000..dbfb96fb Binary files /dev/null and b/ODBC 17 binaries preview/SUSE 12/mssql-tools-17.0.0.1-1.x86_64.rpm differ diff --git a/ODBC 17 binaries preview/Ubuntu 16/msodbcsql_17.0.0.1-1_amd64.deb b/ODBC 17 binaries preview/Ubuntu 16/msodbcsql_17.0.0.1-1_amd64.deb new file mode 100644 index 00000000..01c28622 Binary files /dev/null and b/ODBC 17 binaries preview/Ubuntu 16/msodbcsql_17.0.0.1-1_amd64.deb differ diff --git a/ODBC 17 binaries preview/Ubuntu 16/mssql-tools_17.0.0.1-1_amd64.deb b/ODBC 17 binaries preview/Ubuntu 16/mssql-tools_17.0.0.1-1_amd64.deb new file mode 100644 index 00000000..3e4bdf36 Binary files /dev/null and b/ODBC 17 binaries preview/Ubuntu 16/mssql-tools_17.0.0.1-1_amd64.deb differ diff --git a/ODBC 17 binaries preview/Windows/x64/msodbcsql.msi b/ODBC 17 binaries preview/Windows/x64/msodbcsql.msi new file mode 100644 index 00000000..7604b35d Binary files /dev/null and b/ODBC 17 binaries preview/Windows/x64/msodbcsql.msi differ diff --git a/ODBC 17 binaries preview/Windows/x86/msodbcsql.msi b/ODBC 17 binaries preview/Windows/x86/msodbcsql.msi new file mode 100644 index 00000000..c31fc209 Binary files /dev/null and b/ODBC 17 binaries preview/Windows/x86/msodbcsql.msi differ diff --git a/appveyor.yml b/appveyor.yml index 58d7d91c..66ba81da 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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 diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index 14eb7205..9b8dbd5f 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -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 diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 81352cfe..80a99b7f 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -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, diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index afa47b3c..7d46b5d8 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -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 diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index e7194160..b724bea2 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -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 diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 7ce2e0f2..549de06f 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -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 diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 3f08c0b3..a9bf253a 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -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, {} } }; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 73ca9ac2..da9024e9 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -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 diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index 9ccfc835..35ae7fcb 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -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 diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index 5ddfdfa6..ecffe8f7 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -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 diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h index c5ee154e..64719df7 100644 --- a/source/shared/FormattedPrint.h +++ b/source/shared/FormattedPrint.h @@ -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 diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp index d8e1caf2..72d9859a 100644 --- a/source/shared/StringFunctions.cpp +++ b/source/shared/StringFunctions.cpp @@ -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 diff --git a/source/shared/StringFunctions.h b/source/shared/StringFunctions.h index 1cd765cf..2199d655 100644 --- a/source/shared/StringFunctions.h +++ b/source/shared/StringFunctions.h @@ -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 diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 0cc17a64..29a68ff9 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -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 #include #endif // _WIN32 - -#include #include +#include #ifndef _WIN32 #include @@ -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 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 conn; - sqlsrv_malloc_auto_ptr 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( 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(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( wconn_string.get() ), static_cast( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); - } -#else - r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ), static_cast( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); -#endif // !_WIN32 - - // clear the connection string from memory to remove sensitive data (such as a password). - memset( const_cast(conn_str.c_str()), 0, conn_str.size()); - memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes - conn_str.clear(); - if ( !SQL_SUCCEEDED( r )) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE]; - SQLSMALLINT len; - SQLRETURN 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( 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( 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( 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( 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( 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 wconn_string; + unsigned int wconn_len = static_cast( 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( 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( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); + } +#else + r = SQLDriverConnectW( conn->handle(), NULL, wconn_string, static_cast( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT ); +#endif // !_WIN32 + + // clear the connection string from memory to remove sensitive data (such as a password). + memset( 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 ksp_data; + + ksp_data = reinterpret_cast( sqlsrv_malloc( sizeof( CEKEYSTOREDATA ) + key_size ) ); + + CEKEYSTOREDATA *pKsd = reinterpret_cast( 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 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( 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. diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index 29bb9ec7..e7f85514 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -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 diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 2130875c..34e5b708 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -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( &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( 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 ) { diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 8bc799c6..0175042b 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -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 #include +#include #include #include #include @@ -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( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); + native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_ERROR_MESSAGE_LENGTH + 1 )); strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); - strcpy_s( reinterpret_cast( native_message ), SQL_MAX_MESSAGE_LENGTH + 1, reinterpret_cast( message )); + strcpy_s( reinterpret_cast( native_message ), SQL_MAX_ERROR_MESSAGE_LENGTH + 1, reinterpret_cast( 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( 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( 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 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( Z_STRVAL_P( value )), - static_cast(Z_STRLEN_P( value )) TSRMLS_CC ); + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), static_cast( Z_STRLEN_P( value )) TSRMLS_CC ); } - catch( core::CoreException& ) { + catch ( core::CoreException& ) { throw; } } diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index f4df065f..29255029 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -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( 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(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( 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( sqlsrv_malloc( field_len_temp + extra + 1 )); - field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), + &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); - 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( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); + field_value_temp = static_cast( 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( 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( 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_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_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( 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( 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_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_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) diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index c19b14f9..72461df3 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -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 diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index da64d42a..8453a6b8 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -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( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - formatted_error->native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH + 1 )); + formatted_error->native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_ERROR_MESSAGE_LENGTH + 1 )); DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, reinterpret_cast( custom_error->native_message ), 0, 0, - reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, args ); + reinterpret_cast( formatted_error->native_message ), SQL_MAX_ERROR_MESSAGE_LENGTH, args ); if( rc == 0 ) { - strcpy_s( reinterpret_cast( formatted_error->native_message ), SQL_MAX_MESSAGE_LENGTH, + strcpy_s( reinterpret_cast( formatted_error->native_message ), SQL_MAX_ERROR_MESSAGE_LENGTH, reinterpret_cast( INTERNAL_FORMAT_ERROR )); } diff --git a/source/shared/globalization.h b/source/shared/globalization.h index 0781d607..d5fb5723 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -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 diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h index 2642e5a5..da8e5701 100644 --- a/source/shared/interlockedatomic.h +++ b/source/shared/interlockedatomic.h @@ -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 diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h index 8f54ad46..cefa45cb 100644 --- a/source/shared/interlockedatomic_gcc.h +++ b/source/shared/interlockedatomic_gcc.h @@ -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 diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h index 1af301b0..9f8d0740 100644 --- a/source/shared/interlockedslist.h +++ b/source/shared/interlockedslist.h @@ -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 diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index c2709a46..ecb4f936 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -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 diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 59a58fa9..94c8e676 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -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 diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index 9c6e0d41..f4efbd29 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -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 { diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h index 13fd73ab..fbfb0629 100644 --- a/source/shared/sal_def.h +++ b/source/shared/sal_def.h @@ -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 diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h index 531d637d..461cfcef 100644 --- a/source/shared/typedefs_for_linux.h +++ b/source/shared/typedefs_for_linux.h @@ -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 diff --git a/source/shared/version.h b/source/shared/version.h index 98eebfae..565b2dc0 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -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 diff --git a/source/shared/xplat.h b/source/shared/xplat.h index 2032a164..8cfcdd4d 100644 --- a/source/shared/xplat.h +++ b/source/shared/xplat.h @@ -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 diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h index 56f2eed8..a720492b 100644 --- a/source/shared/xplat_intsafe.h +++ b/source/shared/xplat_intsafe.h @@ -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 diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h index a6078c6e..2ba22237 100644 --- a/source/shared/xplat_winerror.h +++ b/source/shared/xplat_winerror.h @@ -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 diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h index d80367f9..8e7efc63 100644 --- a/source/shared/xplat_winnls.h +++ b/source/shared/xplat_winnls.h @@ -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 diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 81b3d019..4a17c934 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -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 diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 2e2f6f99..5a2870ca 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -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((zend_long)zend_is_true(value)), - SQL_IS_UINTEGER TSRMLS_CC); + core::SQLSetConnectAttr(conn, Attr, reinterpret_cast((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, diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 12a02f0e..8055934d 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -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 diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 1147f539..baa0b2c1 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -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[]; diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 5251834e..57be959b 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -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; diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index 2dd474e3..15fc2e66 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -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 diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index cd04101c..47e45b93 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -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, {} } }; diff --git a/test/Performance/performance_test_results.md b/test/Performance/performance_test_results.md new file mode 100644 index 00000000..76f989ce --- /dev/null +++ b/test/Performance/performance_test_results.md @@ -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|
  • INSERT INTO <tableName> VALUES (<params>)
  • SELECT \* FROM <tableName>
  • UPDATE <tableName> SET <params>
  • DELETE TOP(1) FROM <tableName>
|
  • VARCHAR(64)
  • NVARCHAR(64)
  • INT
  • DATETIME2
  • CHAR(64)
  • NCHAR(64)
  • NUMERIC
  • BINARY(64)
  • VARBINARY
  • DATETIMEOFFSET
| +|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 <tableName> VALUES (<params>)|| +|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 <tableName>| +|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 <tableName> SET <params>| +|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 <tableName>| +|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 <tableName>| +|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|
  • CREATE DATABASE <dbName>
  • USE <dbName>
  • CREATE TABLE <tableName> (<params>)
  • CREATE PROCEDURE <procName> @id INTEGER, @name VARCHAR(32) AS SET NOCOUNT ON; SELECT id, name, value FROM $databaseName.$tableName WHERE id = @id AND name = @name
  • USE MASTER; DROP DATABASE <dbName>
|
  • INT
  • VARCHAR(32)
  • INT
  • DATE
  • TIMESTAMP
  • TIME(7)
| +|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| diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_cancel.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_cancel.phpt index 382c58d6..9e60a486 100644 --- a/test/bvt/sqlsrv/msdn_sqlsrv_cancel.phpt +++ b/test/bvt/sqlsrv/msdn_sqlsrv_cancel.phpt @@ -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. diff --git a/test/functional/pdo_sqlsrv/AEData.inc b/test/functional/pdo_sqlsrv/AEData.inc new file mode 100644 index 00000000..f5247c33 --- /dev/null +++ b/test/functional/pdo_sqlsrv/AEData.inc @@ -0,0 +1,72 @@ +errorInfo()[0] == "22018" ) + echo "PDO param type $pdoParamType is incompatible with encrypted $dataType\n"; + else + var_dump( $stmt->errorInfo() ); +} + +?> \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/AE_Ksp.inc b/test/functional/pdo_sqlsrv/AE_Ksp.inc new file mode 100644 index 00000000..8b845f7c --- /dev/null +++ b/test/functional/pdo_sqlsrv/AE_Ksp.inc @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/MsCommon.inc b/test/functional/pdo_sqlsrv/MsCommon.inc index d1ec0c43..cef996f1 100644 --- a/test/functional/pdo_sqlsrv/MsCommon.inc +++ b/test/functional/pdo_sqlsrv/MsCommon.inc @@ -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( "." ); } + + ?> diff --git a/test/functional/pdo_sqlsrv/MsSetup.inc b/test/functional/pdo_sqlsrv/MsSetup.inc index 2aa68b27..db02e6d2 100644 --- a/test/functional/pdo_sqlsrv/MsSetup.inc +++ b/test/functional/pdo_sqlsrv/MsSetup.inc @@ -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 + ?> \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt b/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt index cada819e..5175f6a2 100644 --- a/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt +++ b/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt @@ -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. \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt b/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt index 33f4ace2..c2e10320 100644 --- a/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt +++ b/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt @@ -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 diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime.phpt new file mode 100644 index 00000000..f81cf8a8 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime.phpt @@ -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-- + +--FILE-- +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 diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_money.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_money.phpt new file mode 100644 index 00000000..e778290c --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_money.phpt @@ -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-- + +--FILE-- +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. diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt new file mode 100644 index 00000000..753d59b0 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt @@ -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-- + +--FILE-- +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 diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime.phpt new file mode 100644 index 00000000..c6b3523c --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime.phpt @@ -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-- + +--FILE-- +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 diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_money.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_money.phpt new file mode 100644 index 00000000..9a8b2412 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_money.phpt @@ -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-- + +--FILE-- +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. diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_numeric.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_numeric.phpt new file mode 100644 index 00000000..c40e2aeb --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_numeric.phpt @@ -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-- + +--FILE-- +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 diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_string.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_string.phpt new file mode 100644 index 00000000..4b2b9c59 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_string.phpt @@ -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-- + +--FILE-- +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). diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve.phpt new file mode 100644 index 00000000..7bba2212 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve.phpt @@ -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-- + +--FILE-- + $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 diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve_fixed_size.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve_fixed_size.phpt new file mode 100644 index 00000000..ca76ba2f --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve_fixed_size.phpt @@ -0,0 +1,90 @@ +--TEST-- +Test for inserting encrypted fixed size types data and retrieve both encrypted and decrypted data +--SKIPIF-- + +--FILE-- + 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 \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve_nvarchar.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve_nvarchar.phpt new file mode 100644 index 00000000..bd2dab8f --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve_nvarchar.phpt @@ -0,0 +1,79 @@ +--TEST-- +Test for inserting encrypted nvarchar data of variable lengths and retrieving encrypted and decrypted data +--SKIPIF-- + +--FILE-- + $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. \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve_varchar.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve_varchar.phpt new file mode 100644 index 00000000..3b5218c1 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_retrieve_varchar.phpt @@ -0,0 +1,78 @@ +--TEST-- +Test for inserting encrypted varchar data of variable lengths and retrieving encrypted and decrypted data +--SKIPIF-- + +--FILE-- + $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. \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_string.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_string.phpt new file mode 100644 index 00000000..4ef6b026 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_string.phpt @@ -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-- + +--FILE-- +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). diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt new file mode 100644 index 00000000..b870d957 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt @@ -0,0 +1,164 @@ +--TEST-- +Test for binding output params for encrypted data for all types +--SKIPIF-- + +--FILE-- +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\). \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt new file mode 100644 index 00000000..cb073668 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt @@ -0,0 +1,108 @@ +--TEST-- +Test new connection keyword ColumnEncryption +--SKIPIF-- + +--FILE-- +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 \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt new file mode 100644 index 00000000..26f1eee4 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt @@ -0,0 +1,54 @@ +--TEST-- +Fetch data from a prepopulated test table given a custom keystore provider +--SKIPIF-- + +--FILE-- +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 \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt new file mode 100644 index 00000000..8a0d7272 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt @@ -0,0 +1,58 @@ +--TEST-- +Fetch encrypted data from a prepopulated test table given a custom keystore provider +--SKIPIF-- + +--FILE-- +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 \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt new file mode 100644 index 00000000..64c0eb25 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt @@ -0,0 +1,100 @@ +--TEST-- +Connect using a custom keystore provider with some required inputs missing +--SKIPIF-- + +--FILE-- +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 \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_error.phpt b/test/functional/pdo_sqlsrv/pdo_error.phpt index 1c804e5a..efbb6ca5 100644 --- a/test/functional/pdo_sqlsrv/pdo_error.phpt +++ b/test/functional/pdo_sqlsrv/pdo_error.phpt @@ -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'\. \) \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_errorinfo_emulateprepare.phpt b/test/functional/pdo_sqlsrv/pdo_errorinfo_emulateprepare.phpt index 9d040ebf..19e7f8f4 100644 --- a/test/functional/pdo_sqlsrv/pdo_errorinfo_emulateprepare.phpt +++ b/test/functional/pdo_sqlsrv/pdo_errorinfo_emulateprepare.phpt @@ -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 diff --git a/test/functional/pdo_sqlsrv/pdo_param_output_select_variant.phpt b/test/functional/pdo_sqlsrv/pdo_param_output_select_variant.phpt index 9f45cf8a..9c3dfff3 100644 --- a/test/functional/pdo_sqlsrv/pdo_param_output_select_variant.phpt +++ b/test/functional/pdo_sqlsrv/pdo_param_output_select_variant.phpt @@ -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\. diff --git a/test/functional/pdo_sqlsrv/pdo_param_output_variants.phpt b/test/functional/pdo_sqlsrv/pdo_param_output_variants.phpt index b81ba6cb..0749c1e1 100644 --- a/test/functional/pdo_sqlsrv/pdo_param_output_variants.phpt +++ b/test/functional/pdo_sqlsrv/pdo_param_output_variants.phpt @@ -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\. diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_binary.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_binary.phpt index 4cd28510..ebd52447 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_binary.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_binary.phpt @@ -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} diff --git a/test/functional/pdo_sqlsrv/pdo_query_timeout.phpt b/test/functional/pdo_sqlsrv/pdo_query_timeout.phpt index 45da1f9d..ade9e96c 100644 --- a/test/functional/pdo_sqlsrv/pdo_query_timeout.phpt +++ b/test/functional/pdo_sqlsrv/pdo_query_timeout.phpt @@ -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\. diff --git a/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt b/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt index 8ec2a3ae..e616f401 100644 --- a/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt +++ b/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt @@ -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} diff --git a/test/functional/pdo_sqlsrv/pdo_warnings.phpt b/test/functional/pdo_sqlsrv/pdo_warnings.phpt index 9d345007..18891d62 100644 --- a/test/functional/pdo_sqlsrv/pdo_warnings.phpt +++ b/test/functional/pdo_sqlsrv/pdo_warnings.phpt @@ -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]+ \ No newline at end of file +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]+ \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/skipif_not_ksp.inc b/test/functional/pdo_sqlsrv/skipif_not_ksp.inc new file mode 100644 index 00000000..71f2c877 --- /dev/null +++ b/test/functional/pdo_sqlsrv/skipif_not_ksp.inc @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/skipif_versions_old.inc b/test/functional/pdo_sqlsrv/skipif_versions_old.inc new file mode 100644 index 00000000..b6f0a5a4 --- /dev/null +++ b/test/functional/pdo_sqlsrv/skipif_versions_old.inc @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt b/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt new file mode 100644 index 00000000..ed42f74c --- /dev/null +++ b/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt @@ -0,0 +1,36 @@ +--TEST-- +retrieval of names of column master key and column encryption key generated in the database setup +--SKIPIF-- + +--FILE-- +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. \ No newline at end of file diff --git a/test/functional/setup/PHPcert.pfx b/test/functional/setup/PHPcert.pfx new file mode 100644 index 00000000..742a217c Binary files /dev/null and b/test/functional/setup/PHPcert.pfx differ diff --git a/test/functional/setup/ae_keys.sql b/test/functional/setup/ae_keys.sql new file mode 100644 index 00000000..aa4b9d78 --- /dev/null +++ b/test/functional/setup/ae_keys.sql @@ -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 \ No newline at end of file diff --git a/test/functional/setup/build_ksp.py b/test/functional/setup/build_ksp.py new file mode 100644 index 00000000..fb524719 --- /dev/null +++ b/test/functional/setup/build_ksp.py @@ -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) + + \ No newline at end of file diff --git a/test/functional/setup/cleanup_dbs.py b/test/functional/setup/cleanup_dbs.py index 36eac6ac..86406303 100644 --- a/test/functional/setup/cleanup_dbs.py +++ b/test/functional/setup/cleanup_dbs.py @@ -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) \ No newline at end of file diff --git a/test/functional/setup/create_logins_azure.sql b/test/functional/setup/create_logins_azure.sql new file mode 100644 index 00000000..ecc5530a --- /dev/null +++ b/test/functional/setup/create_logins_azure.sql @@ -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 diff --git a/test/functional/setup/create_users_azure.sql b/test/functional/setup/create_users_azure.sql new file mode 100644 index 00000000..af577063 --- /dev/null +++ b/test/functional/setup/create_users_azure.sql @@ -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 + diff --git a/test/functional/setup/ksp_app.c b/test/functional/setup/ksp_app.c new file mode 100644 index 00000000..86107802 --- /dev/null +++ b/test/functional/setup/ksp_app.c @@ -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 +#include +#ifdef _WIN32 +#include +#else +#define __stdcall +#include +#endif +#include +#include +#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; +} diff --git a/test/functional/setup/myKSP.c b/test/functional/setup/myKSP.c new file mode 100644 index 00000000..d6188842 --- /dev/null +++ b/test/functional/setup/myKSP.c @@ -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 +#else +#define __stdcall +#endif + +#define DEBUG 0 + +#include +#include +#include +#include +#include +#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 +}; \ No newline at end of file diff --git a/test/functional/setup/run_ksp.py b/test/functional/setup/run_ksp.py new file mode 100644 index 00000000..0d0ff897 --- /dev/null +++ b/test/functional/setup/run_ksp.py @@ -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) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index fb779d43..3c8fb404 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -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) diff --git a/test/functional/sqlsrv/0068.phpt b/test/functional/sqlsrv/0068.phpt index 6563db9d..805fe4df 100644 --- a/test/functional/sqlsrv/0068.phpt +++ b/test/functional/sqlsrv/0068.phpt @@ -50,8 +50,8 @@ Array \[SQLSTATE\] => 42000 \[1\] => 257 \[code\] => 257 - \[2\] => \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]\[SQL Server\]Implicit conversion from data type varchar\(max\) to varbinary\(max\) is not allowed\. Use the CONVERT function to run this query\. - \[message\] => \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]\[SQL Server\]Implicit conversion from data type varchar\(max\) to varbinary\(max\) is not allowed\. Use the CONVERT function to run this query\. + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Implicit conversion from data type varchar\(max\) to varbinary\(max\) is not allowed\. Use the CONVERT function to run this query\. + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Implicit conversion from data type varchar\(max\) to varbinary\(max\) is not allowed\. Use the CONVERT function to run this query\. \) \[1\] => Array @@ -60,8 +60,8 @@ Array \[SQLSTATE\] => 42000 \[1\] => 8180 \[code\] => 8180 - \[2\] => \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]\[SQL Server\]Statement\(s\) could not be prepared\. - \[message\] => \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]\[SQL Server\]Statement\(s\) could not be prepared\. + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Statement\(s\) could not be prepared\. + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Statement\(s\) could not be prepared\. \) \) \ No newline at end of file diff --git a/test/functional/sqlsrv/AEData.inc b/test/functional/sqlsrv/AEData.inc new file mode 100644 index 00000000..bea885c3 --- /dev/null +++ b/test/functional/sqlsrv/AEData.inc @@ -0,0 +1,113 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/AE_Ksp.inc b/test/functional/sqlsrv/AE_Ksp.inc new file mode 100644 index 00000000..8b845f7c --- /dev/null +++ b/test/functional/sqlsrv/AE_Ksp.inc @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index ae32bee6..3a779086 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -80,6 +80,17 @@ function IsDaasMode() return ($daasMode ? true : false); } +function IsAEQualified($conn) +{ + $msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer']; + $server_ver = sqlsrv_server_info($conn)['SQLServerVersion']; + $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; +} + function StartTest($testName) { include 'MsSetup.inc'; @@ -159,6 +170,409 @@ function Connect($options = array()) return ($conn); } + +/** + * Connect to the database specified in MsSetup.inc; Column Encryption options automatically added when $keystore is not none + * @param array $options : connection attributes to pass to sqlsrv_connect + * @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 connection resource + */ +function ae_connect( $options = array(), $disableCE = false ) +{ + require 'MsSetup.inc'; + if ( sizeof( $options ) > 0 ) + { + $connectionOptions = array_merge( $connectionOptions, $options ); + } + if ( $keystore != "none" && !$disableCE ) + { + $connectionOptions = array_merge( $connectionOptions, array( "ColumnEncryption" => "Enabled" )); + } + if ( $keystore == "ksp" && !$disableCE ) + { + require( 'AE_Ksp.inc' ); + $ksp_path = getKSPPath(); + $ksp_options = array( "CEKeystoreProvider"=>$ksp_path, "CEKeystoreName"=>$ksp_name, "CEKeystoreEncryptKey"=>$encrypt_key ); + $connectionOptions = array_merge( $connectionOptions, $ksp_options ); + } + $conn = sqlsrv_connect( $server, $connectionOptions ); + if ( $conn === false ) + FatalError( "Failed to connect to $server." ); + return $conn; +} + + +/** + * @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 ) +{ + DropTable( $conn, $tbname ); + $colDef = ""; + foreach ( $columnMetaArr as $meta ) + { + $colDef = $colDef . $meta->getColDef() . ", "; + } + $colDef = rtrim( $colDef, ", " ); + $createSql = "CREATE TABLE $tbname ( $colDef )"; + sqlsrv_query( $conn, $createSql ); +} + + +/** + * class for encapsulating optional parameters for binding parameters in sqlsrv_prepare + */ +class bindParamOption { + public $index; //1-based index of the parameter + public $direction; //SQLSRV_PARAM_ constant indicating the parameter direction + public $phpType; //SQLSRV_PHPTYPE_ constant specifying the php type of the return values + public $sqlType; //SQLSRV_SQLTYPE_ constant specifying the SQL type of the input + + function __construct( $index, $direction = null, $phpType = null, $sqlType = null ) + { + $this->index = $index; + $this->direction = $direction; + $this->phpType = $phpType; + $this->sqlType = $sqlType; + } + /** + * @param resource $conn : connection resource + * @param mix $var : variable to bind to the SQL statement parameter + * @return array needed to bind parameter in sqlsrv_prepare + */ + function bindParamArr( $var ) + { + // get the constant values of direction, phpType, and sqlType + $direction = null; + $phpType = null; + $sqlType = null; + if ( $this->direction ) + $direction = constant( $this->direction ); + if ( $this->phpType ) + $phpType = constant( $this->phpType ); + if ( $this->sqlType ) + { + // parse out the datatype name, size, precision, and/or scale from a SQLSRV_SQLTYPE_ constant + $size = null; + $prec = null; + $scal = null; + $type_size = explode( "(", $this->sqlType ); + $type = $type_size[0]; + if ( count( $type_size ) > 1 ) + { + $size = $type_size[1]; + $prec_scal = explode( ",", $size ); + if ( count( $prec_scal ) > 1 ) + { + $prec = $prec_scal[0]; + $scal = rtrim( $prec_scal[1], ")" ); + $size = null; + } + } + // get the sqlType constant + if ( $prec && $scal ) + $sqlType = call_user_func( $type, $prec, $scal ); + elseif ( $size ) + $sqlType = call_user_func( $type, $size ); + else + $sqlType = constant( $type ); + } + return array( $var, $direction, $phpType, $sqlType ); + } +} + + +/** + * 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 : SQLSRV API used for executing the insert query + * accepted values: "query", "queryParamsOp", "prepare", "prepareParamsOp" + * @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() ) +{ + require 'MsSetup.inc'; + + $stmt = null; + if ( $keystore == "none" && $api != "queryParamsOp" && $api != "prepareParamsOp" ) + { + $insertSql = get_insertSql_complete( $tbname, $inputs ); + switch ( $api ) { + case "query": + $stmt = sqlsrv_query( $conn, $insertSql ); + break; + case "prepare": + $stmt = sqlsrv_prepare( $conn, $insertSql ); + $r = sqlsrv_execute( $stmt ); + break; + } + } + else + { + // if AE is on, must bind param + $insertSql = get_insertSql_placeholders( $tbname, $inputs ); + $params = array(); + if ( empty( $paramOption )) + { + foreach ( $inputs as $key => $value ) + { + array_push($params, $inputs[$key] ); + } + } + else + { + $i = 1; + foreach( $inputs as $key => $value ) + { + $nooption = true; + foreach( $paramOption as $op ) + { + if ( $op->index == $i ) + { + array_push( $params, $op->bindParamArr( $inputs[$key] )); + $nooption = false; + $i++; + break; + } + } + if ( $nooption ) + { + array_push( $params, $inputs[$key] ); + $i++; + } + } + } + if ( $keystore != "none" || $api == "prepareParamsOp" ) + { + $stmt = sqlsrv_prepare( $conn, $insertSql, $params ); + $r = sqlsrv_execute( $stmt ); + } + else + $stmt = sqlsrv_query( $conn, $insertSql, $params ); + } + return $stmt; +} + + +/** + * @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 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 $inputs : 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 resource $conn : connection resource + * @param string $tbname : name of the table to fetch from + */ +function fetch_all( $conn, $tbname ) +{ + $sql = "SELECT * FROM $tbname"; + $stmt = sqlsrv_query( $conn, $sql ); + while ( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC )) + { + foreach ( $row as $key => $value ) + { + if ( is_object( $value ) ) + { + print "$key:\n"; + foreach ( $value as $k =>$v ) + { + print ( " $k: $v\n" ); + } + } + else + print ( "$key: $value\n" ); + } + } +} + + +/** + * @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; +} + + function GetTempTableName($table = '', $temporary = true) { // A temporary table name with the '#' prefix will be automatically @@ -727,7 +1141,7 @@ function CreateUniqueIndexEx($conn, $tableName, $tableIndex, $colIndex) function DropTable($conn, $tableName) { - $stmt = sqlsrv_query($conn, "DROP TABLE [$tableName]"); + $stmt = sqlsrv_query($conn, "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" . $tableName . "') AND type in (N'U')) DROP TABLE $tableName"); if ($stmt === false) { } diff --git a/test/functional/sqlsrv/MsSetup.inc b/test/functional/sqlsrv/MsSetup.inc index 2b0f02a8..38569ac9 100644 --- a/test/functional/sqlsrv/MsSetup.inc +++ b/test/functional/sqlsrv/MsSetup.inc @@ -40,4 +40,9 @@ if (isset($_ENV['MSSQL_SERVER']) || isset($_ENV['MSSQL_USER']) || isset($_ENV['M $pwd = $userPassword; $databaseName = $database; } + +// column encryption variables +$keystore = "none"; // key store provider, acceptable values are none, win, ksp, akv +$dataEncrypted = false; // whether data is to be encrypted + ?> diff --git a/test/functional/sqlsrv/skipif_not_ksp.inc b/test/functional/sqlsrv/skipif_not_ksp.inc new file mode 100644 index 00000000..a2c8468d --- /dev/null +++ b/test/functional/sqlsrv/skipif_not_ksp.inc @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_versions_old.inc b/test/functional/sqlsrv/skipif_versions_old.inc new file mode 100644 index 00000000..8ae712c7 --- /dev/null +++ b/test/functional/sqlsrv/skipif_versions_old.inc @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime.phpt new file mode 100644 index 00000000..5ad69ca6 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime.phpt @@ -0,0 +1,113 @@ +--TEST-- +Test for inserting and retrieving encrypted data of datetime types +--DESCRIPTION-- +Bind params using sqlsrv_prepare without any sql_type specified +--SKIPIF-- + +--FILE-- +colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r ); + if ( $r === false ) { + is_incompatible_types_error( $dataType, "default type" ); + } + else { + echo "****Encrypted default type is compatible with encrypted $dataType****\n"; + if ( $dataType != "time" ) + fetch_all( $conn, $tbname ); + else + { + $sql = "SELECT * FROM $tbname"; + $stmt = sqlsrv_query( $conn, $sql ); + $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC ); + foreach ( $row as $key => $value ) + { + //var_dump( $row ); + $t = $value->format( 'H:i:s' ); + print "$key: $t\n"; + } + } + } + DropTable( $conn, $tbname ); +} +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--EXPECT-- + +Testing date: +****Encrypted default type is compatible with encrypted date**** +c_det: + date: 0001-01-01 00:00:00.000000 + timezone_type: 3 + timezone: Canada/Pacific +c_rand: + date: 9999-12-31 00:00:00.000000 + timezone_type: 3 + timezone: Canada/Pacific + +Testing datetime: +****Encrypted default type is compatible with encrypted datetime**** +c_det: + date: 1753-01-01 00:00:00.000000 + timezone_type: 3 + timezone: Canada/Pacific +c_rand: + date: 9999-12-31 23:59:59.997000 + timezone_type: 3 + timezone: Canada/Pacific + +Testing datetime2: +****Encrypted default type is compatible with encrypted datetime2**** +c_det: + date: 0001-01-01 00:00:00.000000 + timezone_type: 3 + timezone: Canada/Pacific +c_rand: + date: 9999-12-31 23:59:59.1000000 + timezone_type: 3 + timezone: Canada/Pacific + +Testing smalldatetime: +****Encrypted default type is compatible with encrypted smalldatetime**** +c_det: + date: 1900-01-01 00:00:00.000000 + timezone_type: 3 + timezone: Canada/Pacific +c_rand: + date: 2079-06-05 23:59:00.000000 + timezone_type: 3 + timezone: Canada/Pacific + +Testing time: +****Encrypted default type is compatible with encrypted time**** +c_det: 00:00:00 +c_rand: 23:59:59 + +Testing datetimeoffset: +****Encrypted default type is compatible with encrypted datetimeoffset**** +c_det: + date: 0001-01-01 00:00:00.000000 + timezone_type: 1 + timezone: -14:00 +c_rand: + date: 9999-12-31 23:59:59.1000000 + timezone_type: 1 + timezone: +14:00 diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_money.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_money.phpt new file mode 100644 index 00000000..0f896635 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_money.phpt @@ -0,0 +1,77 @@ +--TEST-- +Test for inserting and retrieving encrypted data of money types +--DESCRIPTION-- +Bind params using sqlsrv_prepare without any sql_type specified +--SKIPIF-- + +--FILE-- +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 = sqlsrv_query( $conn, $sql ); + $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC ); + if ( $row["c_det"] != $inputValues[0] || $row["c_rand"] != $inputValues[1] ) + { + echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType.\n"; + $success = false; + } + } + } + else + { + if ( $r === false ) + { + if ( sqlsrv_errors()[0]['SQLSTATE'] != 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 ); +} +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--EXPECT-- + +Testing smallmoney: +Test successfully done. + +Testing money: +Test successfully done. diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_numeric.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_numeric.phpt new file mode 100644 index 00000000..07c7b71c --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_numeric.phpt @@ -0,0 +1,84 @@ +--TEST-- +Test for inserting and retrieving encrypted data of numeric types +--DESCRIPTION-- +Bind params using sqlsrv_prepare without any sql_type specified +--SKIPIF-- + +--FILE-- +colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r ); + if ( $r === false ) { + is_incompatible_types_error( $dataType, "default type" ); + } + else { + echo "****Encrypted default type is compatible with encrypted $dataType****\n"; + fetch_all( $conn, $tbname ); + } + DropTable( $conn, $tbname ); +} +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--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.8548 +c_rand: 9223372036.8548 + +Testing real: +****Encrypted default type is compatible with encrypted real**** +c_det: -2147.4829101562 +c_rand: 2147.4829101562 diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve.phpt new file mode 100644 index 00000000..77411787 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve.phpt @@ -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-- + +--FILE-- + $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 = sqlsrv_prepare( $conn, $selectSql, array( $SSN )); +sqlsrv_execute( $stmt ); +$decrypted_row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC ); +foreach ( $decrypted_row as $key => $value ) +{ + if ( !is_object( $value )) + print "$key: $value\n"; + else + { + print "$key:\n"; + foreach ( $value as $dateKey => $dateValue ) + { + print " $dateKey: $dateValue\n"; + } + } +} +sqlsrv_free_stmt( $stmt ); + +//for AE only +echo "\nChecking ciphertext data:\n"; +if ( is_col_enc() ) +{ + $conn1 = ae_connect( null, true ); + $selectSql = "SELECT SSN, FirstName, LastName, BirthDate FROM $tbname"; + $stmt = sqlsrv_query( $conn1, $selectSql ); + $encrypted_row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC ); + foreach( $encrypted_row as $key => $value ) + { + if ( ctype_print( $value )) + print "Error: expected a binary array for $key!\n"; + } + + sqlsrv_free_stmt( $stmt ); + sqlsrv_close( $conn1 ); +} + +DropTable( $conn, $tbname ); +sqlsrv_close( $conn ); + +echo "Done"; + +?> +--EXPECT-- +Retrieving plaintext data: +SSN: 795-73-9838 +FirstName: Catherine +LastName: Abel +BirthDate: + date: 1996-10-19 00:00:00.000000 + timezone_type: 3 + timezone: Canada/Pacific + +Checking ciphertext data: +Done diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_fixed_size.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_fixed_size.phpt new file mode 100644 index 00000000..e0cae788 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_fixed_size.phpt @@ -0,0 +1,86 @@ +--TEST-- +Test for inserting encrypted fixed size types data and retrieve both encrypted and decrypted data +--SKIPIF-- + +--FILE-- + 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'); +$r; +$stmt = insert_row( $conn, $tbname, $inputs, $r ); +if ( $r === false ) { + var_dump( sqlsrv_errors() ); +} + +print "Decrypted values:\n"; +fetch_all( $conn, $tbname ); + +sqlsrv_free_stmt( $stmt ); + +// for AE only +if ( is_col_enc() ) +{ + $conn1 = ae_connect( null, true ); + + $selectSql = "SELECT * FROM $tbname"; + $stmt = sqlsrv_query( $conn1, $selectSql ); + if ( $stmt === false ) + var_dump( sqlsrv_errors() ); + $encrypted_row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC ); + foreach( $encrypted_row as $key => $value ) + { + if ( ctype_print( $value )) + print "Error: expected a binary array for $key!\n"; + } + sqlsrv_free_stmt( $stmt ); + sqlsrv_close( $conn1 ); +} + +DropTable( $conn, $tbname ); +sqlsrv_close( $conn ); + +echo "Done\n"; + +?> +--EXPECT-- +Decrypted values: +TinyIntData: 255 +SmallIntData: 32767 +IntData: 2147483647 +BigIntData: 92233720368547 +DecimalData: 79228162514264 +BitData: 1 +DateTimeData: + date: 9999-12-31 23:59:59.997000 + timezone_type: 3 + timezone: Canada/Pacific +DateTime2Data: + date: 9999-12-31 23:59:59.1000000 + timezone_type: 3 + timezone: Canada/Pacific +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_nvarchar.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_nvarchar.phpt new file mode 100644 index 00000000..59f2b0a9 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_nvarchar.phpt @@ -0,0 +1,66 @@ +--TEST-- +Test for inserting encrypted nvarchar data of variable lengths and retrieving encrypted and decrypted data +--SKIPIF-- + +--FILE-- + $data )); +} + +$selectSql = "SELECT * FROM $tbname"; +$stmt = sqlsrv_query( $conn, $selectSql ); +while ( $decrypted_row = sqlsrv_fetch_array( $stmt, SQLSRV_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; + } +} +sqlsrv_free_stmt( $stmt ); + +// for AE only +if ( is_col_enc() ) +{ + $conn1 = ae_connect( null, true ); + $stmt = sqlsrv_query( $conn1, $selectSql ); + while ( $encrypted_row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC )) + { + if ( $encrypted_row[ 'CharCount' ] == strlen( $encrypted_row[ get_default_colname( "nvarchar(1000)" ) ] )) + { + $rowInd = $encrypted_row[ 'CharCount' ] + 1; + echo "Failed to encrypted at row $rowInd\n"; + $testPass = false; + } + } + + sqlsrv_free_stmt( $stmt ); + sqlsrv_close( $conn1 ); +} + +DropTable( $conn, $tbname ); +sqlsrv_close( $conn ); + +if ( $testPass ) { + echo "Test successfully done.\n"; +} + +?> +--EXPECT-- +Test successfully done. \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_varchar.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_varchar.phpt new file mode 100644 index 00000000..1873a5ba --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_varchar.phpt @@ -0,0 +1,65 @@ +--TEST-- +Test for inserting encrypted varchar data of variable lengths and retrieving encrypted and decrypted data +--SKIPIF-- + +--FILE-- + $data )); +} + +$selectSql = "SELECT * FROM $tbname"; +$stmt = sqlsrv_query( $conn, $selectSql ); +while ( $decrypted_row = sqlsrv_fetch_array( $stmt, SQLSRV_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; + } +} +sqlsrv_free_stmt( $stmt ); + +// for AE only +if ( is_col_enc() ) +{ + $conn1 = ae_connect( null, true ); + $stmt = sqlsrv_query( $conn1, $selectSql ); + while ( $encrypted_row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC )) + { + if ( $encrypted_row[ 'CharCount' ] == strlen( $encrypted_row[ get_default_colname( "varchar(1000)" ) ] )) + { + $rowInd = $encrypted_row[ 'CharCount' ] + 1; + echo "Failed to encrypted at row $rowInd\n"; + $testPass = false; + } + } + sqlsrv_free_stmt( $stmt ); + sqlsrv_close( $conn1 ); +} + +DropTable( $conn, $tbname ); +sqlsrv_close( $conn ); + +if ( $testPass ) { + echo "Test successfully done.\n"; +} + +?> +--EXPECT-- +Test successfully done. \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_datetime.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_datetime.phpt new file mode 100644 index 00000000..ec17ca5e --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_datetime.phpt @@ -0,0 +1,115 @@ +--TEST-- +Test for inserting and retrieving encrypted data of datetime types +--DESCRIPTION-- +Bind params using sqlsrv_prepare with all sql_type +--SKIPIF-- + +--FILE-- + array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2" ), + "datetime" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2" ), + "datetime2" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2" ), + "smalldatetime" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2" ), + "time" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2" ), + "datetimeoffset" => array("SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2") ); + +$conn = ae_connect(); + +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 SQLSRV_SQLTYPE_ constants + foreach ( $sqlTypes as $sqlType ) + { + // insert a row + $inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 ); + $sqlType = get_default_size_prec( $sqlType ); + $paramOp = array( new bindParamOption( 1, null, null, $sqlType ), new bindParamOption( 2, null, null, $sqlType )); + $r; + $stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r, "prepareParamsOp", $paramOp ); + + if ( !is_col_enc() ) + { + if ( $r === false ) + { + $isCompatible = false; + foreach ( $compatList[$dataType] as $compatType ) + { + if ( $compatType == $sqlType ) + $isCompatible = true; + } + // 22018 is the SQLSTATE for any incompatible conversion errors + if ( $isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018 ) + { + echo "$sqlType should be compatible with $dataType\n"; + $success = false; + } + } + } + else + { + if ( $r === false ) + { + // always encrypted only allow sqlType that is identical to the encrypted column datatype + if ( "SQLSRV_SQLTYPE_" . strtoupper( $dataType ) == $sqlType ) + { + echo "$sqlType should be compatible with $dataType\n"; + $success = false; + } + } + else + { + $sql = "SELECT * FROM $tbname"; + $stmt = sqlsrv_query( $conn, $sql ); + $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC ); + + if ( $row["c_det"] != new DateTime( $inputValues[0] ) || $row["c_rand"] != new DateTime( $inputValues[1] )) + { + echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType.\n"; + var_dump( $inputValues ); + var_dump( $row ); + $success = false; + } + } + } + sqlsrv_query( $conn, "TRUNCATE TABLE $tbname" ); + } + if ( $success ) + echo "Test successfully done.\n"; + DropTable( $conn, $tbname ); +} +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--EXPECT-- + +Testing date: +Test successfully done. + +Testing datetime: +Test successfully done. + +Testing datetime2: +Test successfully done. + +Testing smalldatetime: +Test successfully done. + +Testing time: +Test successfully done. + +Testing datetimeoffset: +Test successfully done. diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_money.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_money.phpt new file mode 100644 index 00000000..1654f199 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_money.phpt @@ -0,0 +1,80 @@ +--TEST-- +Test for inserting and retrieving encrypted data of money types +--DESCRIPTION-- +Bind params using sqlsrv_prepare with all sql_type +--SKIPIF-- + +--FILE-- + array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ), + "money" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" )); + +$conn = ae_connect(); + +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 SQLSRV_SQLTYPE_ constants + foreach ( $sqlTypes as $sqlType ) + { + // insert a row + $inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 ); + $sqlType = get_default_size_prec( $sqlType ); + $paramOp = array( new bindParamOption( 1, null, null, $sqlType ), new bindParamOption( 2, null, null, $sqlType )); + $r; + $stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r, "prepareParamsOp", $paramOp ); + + if ( !is_col_enc() ) + { + if ( $r === false ) + { + $isCompatible = false; + foreach ( $compatList[$dataType] as $compatType ) + { + if ( stripos( $compatType, $sqlType ) !== false ) + $isCompatible = true; + } + // 22018 is the SQLSTATE for any incompatible conversion errors + if ( $isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018 ) + { + echo "$sqlType should be compatible with $dataType\n"; + $success = false; + } + } + } + else + { + if ( $r !== false ) + { + echo "$dataType should not be compatible with any type.\n"; + $success = false; + } + } + sqlsrv_query( $conn, "TRUNCATE TABLE $tbname" ); + } + if ( $success ) + echo "Test successfully done.\n"; + DropTable( $conn, $tbname ); +} +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--EXPECT-- + +Testing smallmoney: +Test successfully done. + +Testing money: +Test successfully done. diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_numeric.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_numeric.phpt new file mode 100644 index 00000000..81105e05 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_numeric.phpt @@ -0,0 +1,136 @@ +--TEST-- +Test for inserting and retrieving encrypted data of numeric types +--DESCRIPTION-- +Bind params using sqlsrv_prepare with all sql_type +--SKIPIF-- + +--FILE-- + array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ), + "tinyint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ), + "smallint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ), + "int" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ), + "bigint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ), + "decimal(18,5)" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ), + "numeric(10,5)" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ), + "float" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT" ), + "real" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT" )); +$epsilon = 0.0001; + +$conn = ae_connect(); + +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 SQLSRV_SQLTYPE_ constants + foreach ( $sqlTypes as $sqlType ) + { + // insert a row + $inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 ); + $sqlType = get_default_size_prec( $sqlType ); + $paramOp = array( new bindParamOption( 1, null, null, $sqlType ), new bindParamOption( 2, null, null, $sqlType )); + $r; + $stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r, "prepareParamsOp", $paramOp ); + + if ( !is_col_enc() ) + { + if ( $r === false ) + { + $isCompatibleible = false; + foreach ( $compatList[$dataType] as $compatType ) + { + if ( stripos( $compatType, $sqlType ) !== false ) + $isCompatibleible = true; + } + // 22018 is the SQLSTATE for any incompatible conversion errors + if ( $isCompatibleible && sqlsrv_errors()[0]['SQLSTATE'] == 22018 ) + { + echo "$sqlType should be compatible with $dataType\n"; + $success = false; + } + } + } + else + { + if ( $r === false ) + { + // always encrypted only allow sqlType that is identical to the encrypted column datatype + if ( stripos( "SQLSRV_SQLTYPE_" . $dataType, $sqlType ) !== false ) + { + echo "$sqlType should be compatible with $dataType\n"; + $success = false; + } + } + else + { + $sql = "SELECT * FROM $tbname"; + $stmt = sqlsrv_query( $conn, $sql ); + $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC ); + + if ( $dataType == "float" || $dataType == "real" ) + { + if ( abs( $row["c_det"] - $inputValues[0] ) > $epsilon || abs( $row["c_rand"] - $inputValues[1] ) > $epsilon ) + { + echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType.\n"; + $success = false; + } + } + else + { + if ( $row["c_det"] != $inputValues[0] || $row["c_rand"] != $inputValues[1] ) + { + echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType.\n"; + $success = false; + } + } + } + } + sqlsrv_query( $conn, "TRUNCATE TABLE $tbname" ); + } + if ( $success ) + echo "Test successfully done.\n"; + DropTable( $conn, $tbname ); +} +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--EXPECT-- + +Testing bit: +Test successfully done. + +Testing tinyint: +Test successfully done. + +Testing smallint: +Test successfully done. + +Testing int: +Test successfully done. + +Testing bigint: +Test successfully done. + +Testing decimal(18,5): +Test successfully done. + +Testing numeric(10,5): +Test successfully done. + +Testing float: +Test successfully done. + +Testing real: +Test successfully done. diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_string.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_string.phpt new file mode 100644 index 00000000..a5f7fb5e --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_string.phpt @@ -0,0 +1,101 @@ +--TEST-- +Test for inserting and retrieving encrypted data of string types +--DESCRIPTION-- +Bind params using sqlsrv_prepare with all sql_type +--SKIPIF-- + +--FILE-- + array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML" ), + "varchar(max)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML" ), + "nchar(5)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML" ), + "nvarchar(max)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML" )); + +$conn = ae_connect(); + +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 SQLSRV_SQLTYPE_ constants + foreach ( $sqlTypes as $sqlType ) + { + // insert a row + $inputValues = array_slice( ${explode( "(", $dataType )[0] . "_params"}, 1, 2 ); + $sqlType = get_default_size_prec( $sqlType ); + $paramOp = array( new bindParamOption( 1, null, null, $sqlType ), new bindParamOption( 2, null, null, $sqlType )); + $r; + $stmt = insert_row( $conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r, "prepareParamsOp", $paramOp ); + + if ( $r === false ) + { + if ( !is_col_enc() ) + { + $isCompatible = false; + foreach ( $compatList[$dataType] as $compatType ) + { + if ( stripos( $compatType, $sqlType ) !== false ) + $isCompatible = true; + } + // 22018 is the SQLSTATE for any incompatible conversion errors + if ( $isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018 ) + { + echo "$sqlType should be compatible with $dataType\n"; + $success = false; + } + } + else + { + // always encrypted only allow sqlType that is identical to the encrypted column datatype + if ( stripos( "SQLSRV_SQLTYPE_" . $dataType, $sqlType ) !== false ) + { + echo "$sqlType should be compatible with $dataType\n"; + $success = false; + } + } + } + else + { + $sql = "SELECT * FROM $tbname"; + $stmt = sqlsrv_query( $conn, $sql ); + $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC ); + + if ( $row["c_det"] != $inputValues[0] || $row["c_rand"] != $inputValues[1] ) + { + echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType.\n"; + $success = false; + } + } + sqlsrv_query( $conn, "TRUNCATE TABLE $tbname" ); + } + if ( $success ) + echo "Test successfully done.\n"; + DropTable( $conn, $tbname ); +} +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--EXPECT-- + +Testing char(5): +Test successfully done. + +Testing varchar(max): +Test successfully done. + +Testing nchar(5): +Test successfully done. + +Testing nvarchar(max): +Test successfully done. diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_string.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_string.phpt new file mode 100644 index 00000000..98700074 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_string.phpt @@ -0,0 +1,59 @@ +--TEST-- +Test for inserting and retrieving encrypted data of string types +--DESCRIPTION-- +Bind params using sqlsrv_prepare without any sql_type specified +--SKIPIF-- + +--FILE-- +colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r ); + if ( $r === false ) { + is_incompatible_types_error( $dataType, "default type" ); + } + else { + echo "****Encrypted default type is compatible with encrypted $dataType****\n"; + fetch_all( $conn, $tbname ); + } + DropTable( $conn, $tbname ); +} +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); +?> +--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). diff --git a/test/functional/sqlsrv/sqlsrv_ae_output_param.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param.phpt new file mode 100644 index 00000000..39afc373 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param.phpt @@ -0,0 +1,104 @@ +--TEST-- +Test for binding output parameter of encrypted values for a sample emplolyee table +--SKIPIF-- + +--FILE-- + $firstNameParams[$i], + "LastName" => $lastNameParams[$i], + "Address" => $addressParams[$i], + "City" => $cityParams[$i] ); + $stmt = insert_row( $conn, $tbname, $inputs ); +} + +// call Store Procedure +$callSpSql = "{? = CALL $spname (?, ?)}"; +$retParam = 0; +$pPersonID = 1; +$cbOutParam = 0; +$stmt = sqlsrv_prepare( $conn, $callSpSql, array( array( &$retParam, SQLSRV_PARAM_OUT ), array( $pPersonID, SQLSRV_PARAM_IN ), array( &$cbOutParam, SQLSRV_PARAM_OUT ))); +sqlsrv_execute( $stmt ); +sqlsrv_next_result( $stmt ); +if ( sqlsrv_errors() ) +{ + var_dump( sqlsrv_errors() ); +} +print ( "retParam: " . $retParam . "\n" ); +print ( "pPersonID: " . $pPersonID . "\n" ); +print ( "cbOutParam: " . $cbOutParam . "\n" ); + +// Retrieve all data through output params +$outSql = get_callProcSql_placeholders( $sspname, 5 ); +$firstNameOut = ''; +$lastNameOut = ''; +$addressOut = ''; +$cityOut = ''; +$pPersonID = 2; +$stmt = sqlsrv_prepare( $conn, $outSql, array( array( &$firstNameOut, SQLSRV_PARAM_OUT ), array( &$lastNameOut, SQLSRV_PARAM_OUT ), array( &$addressOut, SQLSRV_PARAM_OUT ), array( &$cityOut, SQLSRV_PARAM_OUT ), array( $pPersonID, SQLSRV_PARAM_IN ))); +sqlsrv_execute( $stmt ); +if ( sqlsrv_errors() ) +{ + var_dump( sqlsrv_errors() ); +} + +print ( "firstNameOut: " . $firstNameOut . "\n" ); +print ( "lastNameOut: " . $lastNameOut . "\n" ); +print ( "addressOut: " . $addressOut . "\n" ); +print ( "cityOut: " . $cityOut . "\n" ); +print ( "pPersonID: " . $pPersonID . "\n" ); + +sqlsrv_query( $conn, "DROP PROCEDURE $spname" ); +sqlsrv_query( $conn, "DROP PROCEDURE $sspname" ); +sqlsrv_query( $conn, "DROP TABLE $tbname" ); +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); + +?> +--EXPECT-- +retParam: 100 +pPersonID: 1 +cbOutParam: 1 +firstNameOut: Tahir +lastNameOut: Chaudry +addressOut: 83 First Street +cityOut: Brooklyn +pPersonID: 2 \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_ae_output_param_all.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_all.phpt new file mode 100644 index 00000000..4c0cd5d8 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_all.phpt @@ -0,0 +1,163 @@ +--TEST-- +Test for binding output parameter of encrypted values for all types +--SKIPIF-- + +--FILE-- + 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).' ); +$stmt = insert_row( $conn, $tbname, $inputs ); + +// 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 = ''; +$datetimeOut = ''; +$datetime2Out = ''; +$datetimeoffsetOut = ''; +$timeOut = ''; +$charOut = ''; +$varcharOut = ''; +$ncharOut = ''; +$nvarcharOut = ''; +$stmt = sqlsrv_prepare( $conn, $outSql, array( array( &$intOut, SQLSRV_PARAM_OUT ), + array( &$smallintOut, SQLSRV_PARAM_OUT ), + array( &$tinyintOut, SQLSRV_PARAM_OUT ), + array( &$bitOut, SQLSRV_PARAM_OUT ), + array( &$bigintOut, SQLSRV_PARAM_OUT ), + array( &$decimalOut, SQLSRV_PARAM_OUT ), + array( &$numericOut, SQLSRV_PARAM_OUT ), + array( &$floatOut, SQLSRV_PARAM_OUT ), + array( &$realOut, SQLSRV_PARAM_OUT ), + array( &$dateOut, SQLSRV_PARAM_OUT ), + array( &$datetimeOut, SQLSRV_PARAM_OUT ), + array( &$datetime2Out, SQLSRV_PARAM_OUT ), + array( &$datetimeoffsetOut, SQLSRV_PARAM_OUT ), + array( &$timeOut, SQLSRV_PARAM_OUT ), + array( &$charOut, SQLSRV_PARAM_OUT ), + array( &$varcharOut, SQLSRV_PARAM_OUT ), + array( &$ncharOut, SQLSRV_PARAM_OUT ), + array( &$nvarcharOut, SQLSRV_PARAM_OUT ))); +sqlsrv_execute( $stmt ); + +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" ); + +sqlsrv_query( $conn, "DROP PROCEDURE $spname" ); +sqlsrv_query( $conn, "DROP TABLE $tbname" ); +sqlsrv_free_stmt( $stmt ); +sqlsrv_close( $conn ); + +?> +--EXPECTREGEX-- +intOut: 2147483647 +smallintOut: 32767 +tinyintOut: 255 +bitOut: 1 +bigintOut: 9.2233720368548E\+17 +decimalOut: 9223372036854\.8 +numericOut: 21474\.83647 +floatOut: 9223372036\.8548 +realOut: 2147\.4829101562 +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\)\. \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_complex_query.phpt b/test/functional/sqlsrv/sqlsrv_complex_query.phpt index 136fc287..8f0c90b4 100644 --- a/test/functional/sqlsrv/sqlsrv_complex_query.phpt +++ b/test/functional/sqlsrv/sqlsrv_complex_query.phpt @@ -90,12 +90,12 @@ Repro(); ?> --EXPECTREGEX--  -Test begins... -\[Microsoft\]\[ODBC Driver 13 for SQL Server\]\[SQL Server\]Cannot insert explicit value for identity column in table '.+' when IDENTITY_INSERT is set to OFF. +Test begins\.\.\. +\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Cannot insert explicit value for identity column in table '.+' when IDENTITY_INSERT is set to OFF\. 544 23000 Number of rows inserted: 2 Number of rows fetched: 2 Done -Test "sqlsrv_statement_complex_query" completed successfully. +Test \"sqlsrv_statement_complex_query\" completed successfully\. diff --git a/test/functional/sqlsrv/sqlsrv_connect_driver.phpt b/test/functional/sqlsrv/sqlsrv_connect_driver.phpt new file mode 100644 index 00000000..006508b2 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_driver.phpt @@ -0,0 +1,111 @@ +--TEST-- +Test new connection keyword Driver with valid and invalid values +--SKIPIF-- + +--FILE-- +$database,"UID"=>$userName, "PWD"=>$userPassword); +$conn = sqlsrv_connect($server, $connectionOptions); +if ($conn === false) +{ + print_r(sqlsrv_errors()); +} +$msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer']; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; +sqlsrv_close($conn); + +// start test +test_valid_values($msodbcsql_maj,$server ,$connectionOptions); +test_invalid_values($msodbcsql_maj,$server ,$connectionOptions); +echo "Done"; +// end test + +/////////////////////////// +function connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected = '' ) +{ + $conn = sqlsrv_connect($server, $connectionOptions); + if ($conn === false) + { + if( strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false ) + { + print_r(sqlsrv_errors()); + } + } +} + +function test_valid_values( $msodbcsql_maj ,$server ,$connectionOptions){ + $value = ""; + // Test with {} + switch ( $msodbcsql_maj ) + { + case 17: + $value = "{ODBC Driver 17 for SQL Server}"; + break; + case 13: + $value = "{ODBC Driver 13 for SQL Server}"; + break; + case 12: + case 11: + $value = "{ODBC Driver 11 for SQL Server}"; + break; + default: + $value = "invalid value"; + } + $connectionOptions['Driver']=$value; + connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ); + + // Test without {} + switch ( $msodbcsql_maj ) + { + case 17: + $value = "ODBC Driver 17 for SQL Server"; + break; + case 13: + $value = "ODBC Driver 13 for SQL Server"; + break; + case 12: + case 11: + $value = "ODBC Driver 11 for SQL Server"; + break; + default: + $value = "invalid value"; + } + + $connectionOptions['Driver']=$value; + connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ); +} + +function test_invalid_values($msodbcsql_maj ,$server ,$connectionOptions){ + // test invalid value + $value = "{SQL Server Native Client 11.0}"; + $connectionOptions['Driver']=$value; + $expected = "Invalid value $value was specified for Driver option."; + connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected ); + + $value = "SQL Server Native Client 11.0"; + $connectionOptions['Driver']=$value; + $expected = "Invalid value $value was specified for Driver option."; + connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected ); + + $value = "ODBC Driver 00 for SQL Server"; + $connectionOptions['Driver']=$value; + $expected = "Invalid value $value was specified for Driver option."; + connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected ); + + $value = 123; + $connectionOptions['Driver']=$value; + $expected = "Invalid value type for option Driver was specified. String type was expected."; + connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected ); + + $value = false; + $connectionOptions['Driver']=$value; + $expected = "Invalid value type for option Driver was specified. String type was expected."; + connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected ); +} + +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt new file mode 100644 index 00000000..e6a5bbb6 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt @@ -0,0 +1,102 @@ +--TEST-- +Test new connection keyword ColumnEncryption +--SKIPIF-- + +--FILE-- +$database,"UID"=>$userName, "PWD"=>$userPassword); +test_ColumnEncryption($server, $connectionOptions); +echo "Done"; + +function test_ColumnEncryption($server ,$connectionOptions){ + $conn = sqlsrv_connect($server, $connectionOptions); + if ($conn === false) + { + print_r(sqlsrv_errors()); + } + $msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer']; + $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; + + // Only works for ODBC 17 + $connectionOptions['ColumnEncryption']='Enabled'; + $conn = sqlsrv_connect( $server, $connectionOptions ); + if( $conn === false ) + { + if($msodbcsql_maj < 17){ + $expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server."; + if( strcasecmp(sqlsrv_errors($conn)[0]['message'], $expected ) != 0 ) + { + print_r(sqlsrv_errors()); + } + } + else + { + print_r(sqlsrv_errors()); + } + } + + // Works for ODBC 17, ODBC 13 + $connectionOptions['ColumnEncryption']='Disabled'; + $conn = sqlsrv_connect( $server, $connectionOptions ); + if( $conn === false ) + { + if($msodbcsql_maj < 13) + { + $expected_substr = "Invalid connection string attribute"; + if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false ) + { + print_r(sqlsrv_errors()); + } + } + else + { + print_r(sqlsrv_errors()); + } + } + else + { + sqlsrv_close($conn); + } + + // should fail for all ODBC drivers + $connectionOptions['ColumnEncryption']='false'; + $conn = sqlsrv_connect( $server, $connectionOptions ); + if( $conn === false ) + { + $expected_substr = "Invalid value specified for connection string attribute 'ColumnEncryption'"; + if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false ) + { + print_r(sqlsrv_errors()); + } + } + + // should fail for all ODBC drivers + $connectionOptions['ColumnEncryption']=true; + $conn = sqlsrv_connect( $server, $connectionOptions ); + if( $conn === false ) + { + $expected_substr = "Invalid value type for option ColumnEncryption was specified. String type was expected."; + if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false ) + { + print_r(sqlsrv_errors()); + } + } + + // should fail for all ODBC drivers + $connectionOptions['ColumnEncryption']=false; + $conn = sqlsrv_connect( $server, $connectionOptions ); + if( $conn === false ) + { + $expected_substr = "Invalid value type for option ColumnEncryption was specified. String type was expected."; + if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false ) + { + print_r(sqlsrv_errors()); + } + } +} +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt new file mode 100644 index 00000000..86dcb105 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt @@ -0,0 +1,64 @@ +--TEST-- +Fetch data from a prepopulated test table given a custom keystore provider +--SKIPIF-- + +--FILE-- +$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key, + 'ReturnDatesAsStrings'=>true); + + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + print_r( sqlsrv_errors() ); + } + else + { + echo "Connected successfully with ColumnEncryption enabled.\n"; + } + + $tsql = "SELECT * FROM $ksp_test_table"; + $stmt = sqlsrv_prepare($conn, $tsql); + if (! sqlsrv_execute($stmt) ) + { + echo "Failed to fetch data.\n"; + print_r( sqlsrv_errors() ); + } + + // fetch data + while ($row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC )) + { + echo "c1=" . $row[0] . "\tc2=" . $row[1] . "\tc3=" . $row[2] . "\tc4=" . $row[3] . "\n" ; + } + + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); + + echo "Done\n"; +?> +--EXPECT-- +Connected successfully with ColumnEncryption enabled. +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 \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_encrypted.phpt new file mode 100644 index 00000000..24a6644d --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_encrypted.phpt @@ -0,0 +1,68 @@ +--TEST-- +Fetch encrypted data from a prepopulated test table given a custom keystore provider +--SKIPIF-- + +--FILE-- +$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key, + 'ReturnDatesAsStrings'=>true); + + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + print_r( sqlsrv_errors() ); + } + else + { + echo "Connected successfully with ColumnEncryption disabled.\n"; + } + + $tsql = "SELECT * FROM $ksp_test_table"; + $stmt = sqlsrv_prepare($conn, $tsql); + if (! sqlsrv_execute($stmt) ) + { + echo "Failed to fetch data.\n"; + print_r( sqlsrv_errors() ); + } + + // fetch data + while ($row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC )) + { + // all columns should return binary data except the first column + echo "c1=" . $row[0]; + echo "\tc2=" . bin2hex($row[1]); + echo "\tc3=" . bin2hex($row[2]); + echo "\tc4=" . bin2hex($row[3]); + echo "\n" ; + } + + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); + + echo "Done\n"; +?> +--EXPECTREGEX-- +Connected successfully with ColumnEncryption disabled. +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 \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt new file mode 100644 index 00000000..06a5de79 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt @@ -0,0 +1,130 @@ +--TEST-- +Connect using a custom keystore provider with some required inputs missing +--SKIPIF-- + +--FILE-- + $error ) + { + if( is_string( $key ) ) + echo "[$key] => $error\n"; + } + echo "\n"; + } + else + { + echo "Connected successfully with ColumnEncryption enabled.\n"; + } + + return $conn; + } + + sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL ); + + require( 'MsSetup.inc' ); + require( 'AE_Ksp.inc' ); + + $ksp_path = getKSPpath(); + + echo("Connecting... with column encryption\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled"); + + connect( $server, $connectionInfo ); + + echo("Connecting... with an invalid input to CEKeystoreProvider\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>1); + + connect( $server, $connectionInfo ); + + echo("Connecting... with an empty path\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>"", + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key); + + connect( $server, $connectionInfo ); + + echo("Connecting... without a name\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreEncryptKey"=>$encrypt_key); + + connect( $server, $connectionInfo ); + + echo("Connecting... with an empty name\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>"", + "CEKeystoreEncryptKey"=>$encrypt_key); + + connect( $server, $connectionInfo ); + + echo("Connecting... without a key\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name); + + connect( $server, $connectionInfo ); + + echo("Connecting... with all required inputs\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key); + + connect( $server, $connectionInfo ); + + echo "Done\n"; +?> +--EXPECT-- +Connecting... with column encryption +Connected successfully with ColumnEncryption enabled. +Connecting... with an invalid input to CEKeystoreProvider +Failed to connect. +[SQLSTATE] => IMSSP +[code] => -33 +[message] => Invalid value type for option CEKeystoreProvider was specified. String type was expected. + +Connecting... with an empty path +Failed to connect. +[SQLSTATE] => IMSSP +[code] => -104 +[message] => Invalid value for loading a custom keystore provider. + +Connecting... without a name +Failed to connect. +[SQLSTATE] => IMSSP +[code] => -101 +[message] => The name of the custom keystore provider is missing. + +Connecting... with an empty name +Failed to connect. +[SQLSTATE] => IMSSP +[code] => -104 +[message] => Invalid value for loading a custom keystore provider. + +Connecting... without a key +Failed to connect. +[SQLSTATE] => IMSSP +[code] => -103 +[message] => The encryption key for the custom keystore provider is missing. + +Connecting... with all required inputs +Connected successfully with ColumnEncryption enabled. +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_encrypted_patients_ksp.phpt b/test/functional/sqlsrv/sqlsrv_encrypted_patients_ksp.phpt new file mode 100644 index 00000000..7bc0b621 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_encrypted_patients_ksp.phpt @@ -0,0 +1,250 @@ +--TEST-- +Test simple insert, fetch and update with ColumnEncryption enabled and a custome keystore provider +--SKIPIF-- + +--FILE-- +PatientId . "\n"; + echo $obj->SSN . "\n"; + echo $obj->FirstName . "\n"; + echo $obj->LastName . "\n"; + echo $obj->BirthDate . "\n\n"; + } + } + + function SelectDataBuffered() + { + global $conn, $tablename; + + $stmt = sqlsrv_query($conn, "SELECT * FROM $tablename", array(), array("Scrollable"=>"buffered")); + + $row_count = sqlsrv_num_rows($stmt); + echo "\nRow count for result set is $row_count\n"; + + echo "First record=>\t"; + $row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_FIRST); + $SSN = sqlsrv_get_field( $stmt, 1); + echo "SSN = $SSN \n"; + + echo "Next record=>\t"; + $row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_NEXT); + $BirthDate = sqlsrv_get_field( $stmt, 4); + echo "BirthDate = $BirthDate \n"; + + echo "Last record=>\t"; + $row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_LAST); + $LastName = sqlsrv_get_field( $stmt, 3); + echo "LastName = $LastName \n"; + } + + sqlsrv_configure( 'WarningsReturnAsErrors', 1 ); + sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL ); + + require_once( 'MsSetup.inc' ); + require_once( 'AE_Ksp.inc' ); + + $ksp_path = getKSPpath(); + + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ReturnDatesAsStrings"=>true, "ColumnEncryption"=>'Enabled', + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key); + + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + print_r( sqlsrv_errors() ); + } + else + { + echo "Connected successfully with ColumnEncryption enabled.\n"; + } + + $tablename = CreatePatientsTable(); + + InsertData('748-68-0245', 'Jeannette', 'McDonald', '2002-11-28'); + InsertData('795-73-9838', 'John', 'Doe', '2001-05-29'); + InsertData('456-12-5486', 'Jonathan', 'Wong', '1999-12-20'); + InsertData('156-45-5486', 'Marianne', 'Smith', '1997-03-04'); + + SelectData(); + + /////////////////////////////////////////// + echo "Update Patient Jonathan Wong...\n"; + $params = array(array('1999-12-31', null, null, SQLSRV_SQLTYPE_DATE), array('Chang', null, null, SQLSRV_SQLTYPE_NVARCHAR(50)), array('456-12-5486', null, null, SQLSRV_SQLTYPE_CHAR(11))); + + $tsql = "UPDATE Patients SET BirthDate = ?, LastName = ? WHERE SSN = ?"; + $stmt = sqlsrv_query($conn, $tsql, $params); + + if (! $stmt) + { + echo "Failed to update record\n"; + print_r( sqlsrv_errors() ); + } + + echo "Update his birthdate too...\n"; + $params = array(array('456-12-5486', null, null, SQLSRV_SQLTYPE_CHAR(11))); + $tsql = "SELECT SSN, FirstName, LastName, BirthDate FROM Patients WHERE SSN = ?"; + $stmt = sqlsrv_query($conn, $tsql, $params); + if (! $stmt) + { + echo "Failed to select with a WHERE clause\n"; + print_r( sqlsrv_errors() ); + } + else + { + $obj = sqlsrv_fetch_object( $stmt ); + + echo "BirthDate updated for $obj->FirstName:\n"; + echo $obj->SSN . "\n"; + echo $obj->FirstName . "\n"; + echo $obj->LastName . "\n"; + echo $obj->BirthDate . "\n\n"; + } + + /////////////////////////////////////////// + $procName = '#phpAEProc1'; + $spArgs = "@p1 INT, @p2 DATE OUTPUT"; + $spCode = "SET @p2 = ( SELECT [BirthDate] FROM Patients WHERE [PatientId] = @p1 )"; + $stmt = sqlsrv_query($conn, "CREATE PROC [$procName] ($spArgs) AS BEGIN $spCode END"); + sqlsrv_free_stmt($stmt); + + $callResult = '1900-01-01'; + //when binding parameter using sqlsrv_query in a column encryption enabled connection, need to provide the sql_type in all parameters + $params = array( array( 1, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_INT ), array( &$callResult, SQLSRV_PARAM_OUT, null, SQLSRV_SQLTYPE_DATE)); + $callArgs = "?, ?"; + $stmt = sqlsrv_query($conn, "{ CALL [$procName] ($callArgs)}", $params); + if (! $stmt ) + { + print_r( sqlsrv_errors() ); + } + else + { + echo "BirthDate for the first record is: $callResult\n"; + } + + /////////////////////////////////////////// + $procName = '#phpAEProc2'; + $spArgs = "@p1 INT, @p2 CHAR(11) OUTPUT"; + $spCode = "SET @p2 = ( SELECT [SSN] FROM Patients WHERE [PatientId] = @p1 )"; + $stmt = sqlsrv_query($conn, "CREATE PROC [$procName] ($spArgs) AS BEGIN $spCode END"); + sqlsrv_free_stmt($stmt); + + $callResult = '000-00-0000'; + //when binding parameter using sqlsrv_query in a column encryption enabled connection, need to provide the sql_type in all parameters + $params = array( array( 1, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_INT ), array( &$callResult, SQLSRV_PARAM_OUT, null, SQLSRV_SQLTYPE_CHAR(11))); + $callArgs = "?, ?"; + $stmt = sqlsrv_query($conn, "{ CALL [$procName] ($callArgs)}", $params); + if (! $stmt ) + { + print_r( sqlsrv_errors() ); + } + else + { + echo "SSN for the first record is: $callResult\n"; + } + + SelectDataBuffered(); + + echo "\nDone\n"; +?> +--EXPECT-- +Connected successfully with ColumnEncryption enabled. +1 +748-68-0245 +Jeannette +McDonald +2002-11-28 + +2 +795-73-9838 +John +Doe +2001-05-29 + +3 +456-12-5486 +Jonathan +Wong +1999-12-20 + +4 +156-45-5486 +Marianne +Smith +1997-03-04 + +Update Patient Jonathan Wong... +Update his birthdate too... +BirthDate updated for Jonathan: +456-12-5486 +Jonathan +Chang +1999-12-31 + +BirthDate for the first record is: 2002-11-28 +SSN for the first record is: 748-68-0245 + +Row count for result set is 4 +First record=> SSN = 748-68-0245 +Next record=> BirthDate = 2001-05-29 +Last record=> LastName = Smith + +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_encrypted_query_nosqltype.phpt b/test/functional/sqlsrv/sqlsrv_encrypted_query_nosqltype.phpt new file mode 100644 index 00000000..d68c3731 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_encrypted_query_nosqltype.phpt @@ -0,0 +1,138 @@ +--TEST-- +Test using sqlserv_query for binding parameters with ColumnEncryption enabled and a custome keystore provider +--SKIPIF-- + +--FILE-- +PatientId . "\n"; + echo $obj->SSN . "\n"; + echo $obj->FirstName . "\n"; + echo $obj->LastName . "\n"; + echo $obj->BirthDate . "\n\n"; + } + } + + function PrintError() + { + $errors = sqlsrv_errors(); + foreach ( $errors as $error ) + { + echo " SQLSTATE: " . $error['SQLSTATE'] . "\n"; + echo " code: " . $error['code'] . "\n"; + echo " message: " . $error['message'] . "\n\n"; + } + } + + sqlsrv_configure( 'WarningsReturnAsErrors', 1 ); + sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL ); + + require_once( 'MsSetup.inc' ); + require_once( 'AE_Ksp.inc' ); + + $ksp_path = getKSPpath(); + + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ReturnDatesAsStrings"=>true, "ColumnEncryption"=>'Enabled', + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key); + + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + PrintError(); + } + else + { + echo "Connected successfully with ColumnEncryption enabled.\n\n"; + } + + $tablename = CreatePatientsTable(); + + $tsql = "INSERT INTO $tablename (SSN, FirstName, LastName, BirthDate) VALUES (?, ?, ?, ?)"; + $inputs = array( '748-68-0245', 'Jeannette', 'McDonald', '2002-11-28' ); + + //expects an error in Column Encryption enabled connection + print_r( "Using sqlsrv_query and binding parameters with literal values:\n" ); + $stmt = sqlsrv_query( $conn, $tsql, $inputs ); + if ( !$stmt) + PrintError(); + + //expects an error in Column Encryption enabled connection + print_r( "Using sqlsrv_query and binding parameters with parameter arrays and no sqltypes provided:\n" ); + $stmt = sqlsrv_query( $conn, $tsql, array( array( $inputs[0], SQLSRV_PARAM_IN ), + array( $inputs[1], SQLSRV_PARAM_IN ), + array( $inputs[2], SQLSRV_PARAM_IN ), + array( $inputs[3], SQLSRV_PARAM_IN ))); + if ( !$stmt) + PrintError(); + + //no error is expected + print_r( "Using sqlsrv_query and binding parameters with parameter arrays and sqltypes provided:\n" ); + $stmt = sqlsrv_query( $conn, $tsql, array( array( $inputs[0], null, null, SQLSRV_SQLTYPE_CHAR(11) ), + array( $inputs[1], null, null, SQLSRV_SQLTYPE_NVARCHAR(50) ), + array( $inputs[2], null, null, SQLSRV_SQLTYPE_NVARCHAR(50) ), + array( $inputs[3], null, null, SQLSRV_SQLTYPE_DATE ) )); + if ( !$stmt) + PrintError(); + + SelectData(); + + + echo "Done\n"; +?> +--EXPECT-- +Connected successfully with ColumnEncryption enabled. + +Using sqlsrv_query and binding parameters with literal values: + SQLSTATE: IMSSP + code: -63 + message: Must specify the SQL type for each parameter in a parameterized query when using sqlsrv_query in a column encryption enabled connection. + +Using sqlsrv_query and binding parameters with parameter arrays and no sqltypes provided: + SQLSTATE: IMSSP + code: -63 + message: Must specify the SQL type for each parameter in a parameterized query when using sqlsrv_query in a column encryption enabled connection. + +Using sqlsrv_query and binding parameters with parameter arrays and sqltypes provided: +1 +748-68-0245 +Jeannette +McDonald +2002-11-28 + +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_get_field.phpt b/test/functional/sqlsrv/sqlsrv_get_field.phpt index 5b389940..df68d4be 100644 --- a/test/functional/sqlsrv/sqlsrv_get_field.phpt +++ b/test/functional/sqlsrv/sqlsrv_get_field.phpt @@ -254,9 +254,9 @@ array\(1\) \{ \["code"\]=> int\(0\) \[2\]=> - string\(68\) "\[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]Numeric value out of range" + string\(68\) "\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Numeric value out of range" \["message"\]=> - string\(68\) "\[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]Numeric value out of range" + string\(68\) "\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Numeric value out of range" \} \} NULL @@ -292,9 +292,9 @@ array\(1\) \{ \["code"\]=> int\(0\) \[2\]=> - string\(68\) "\[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]Numeric value out of range" + string\(68\) "\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Numeric value out of range" \["message"\]=> - string\(68\) "\[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]Numeric value out of range" + string\(68\) "\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Numeric value out of range" \} \} NULL diff --git a/test/functional/sqlsrv/sqlsrv_param_output_variants.phpt b/test/functional/sqlsrv/sqlsrv_param_output_variants.phpt index 930c35ce..162dfa24 100644 --- a/test/functional/sqlsrv/sqlsrv_param_output_variants.phpt +++ b/test/functional/sqlsrv/sqlsrv_param_output_variants.phpt @@ -129,11 +129,11 @@ function RunTest() RunTest(); ?> ---EXPECT-- +--EXPECTREGEX--  -[Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Operand type clash: varchar(max) is incompatible with sql_variant +\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Operand type clash: varchar\(max\) is incompatible with sql_variant -[Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Operand type clash: varchar(max) is incompatible with sql_variant +\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Operand type clash: varchar\(max\) is incompatible with sql_variant Done -Test "sqlsrv_param_output_variants" completed successfully. \ No newline at end of file +Test \"sqlsrv_param_output_variants\" completed successfully\. \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_statement_cancel.phpt b/test/functional/sqlsrv/sqlsrv_statement_cancel.phpt index 45a325d0..5e820660 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_cancel.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_cancel.phpt @@ -94,9 +94,9 @@ Repro(); --EXPECTREGEX--  Test begins... -(.*\[Driver Manager\]|\[Microsoft\](\[ODBC Driver 13 for SQL Server\]|\[ODBC Driver Manager\]))([ ]{0,1}Function sequence error) +(.*\[Driver Manager\]|\[Microsoft\](\[ODBC Driver 1[1-9] for SQL Server\]|\[ODBC Driver Manager\]))([ ]{0,1}Function sequence error) 0 (HY010) Done -Test "sqlsrv_statement_cancel" completed successfully. +Test \"sqlsrv_statement_cancel\" completed successfully\. diff --git a/test/functional/sqlsrv/sqlsrv_statement_query_timeout.phpt b/test/functional/sqlsrv/sqlsrv_statement_query_timeout.phpt index 31a21880..66f30f02 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_query_timeout.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_query_timeout.phpt @@ -60,15 +60,15 @@ function Repro() Repro(); ?> ---EXPECT-- +--EXPECTREGEX--  Test begins... -[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired +\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Query timeout expired 0 HYT00 -[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired +\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Query timeout expired 0 HYT00 Done -Test "sqlsrv_statement_query_timeout" completed successfully. +Test \"sqlsrv_statement_query_timeout\" completed successfully\. diff --git a/test/functional/sqlsrv/sqlsrv_statement_query_timeout_transaction.phpt b/test/functional/sqlsrv/sqlsrv_statement_query_timeout_transaction.phpt index 4d103ffe..81fdf2fa 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_query_timeout_transaction.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_query_timeout_transaction.phpt @@ -74,15 +74,15 @@ function Repro() Repro(); ?> ---EXPECT-- +--EXPECTREGEX--  Test begins... -[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired +\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Query timeout expired 0 HYT00 -[Microsoft][ODBC Driver 13 for SQL Server]Query timeout expired +\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Query timeout expired 0 HYT00 Done -Test "sqlsrv_statement_query_timeout_transaction" completed successfully. +Test \"sqlsrv_statement_query_timeout_transaction\" completed successfully\. diff --git a/test/functional/sqlsrv/sqlsrv_testConnection.phpt b/test/functional/sqlsrv/sqlsrv_testConnection.phpt index 9d6da98c..5ed6f770 100644 --- a/test/functional/sqlsrv/sqlsrv_testConnection.phpt +++ b/test/functional/sqlsrv/sqlsrv_testConnection.phpt @@ -187,13 +187,13 @@ array\(1\) \{ \["SQLSTATE"\]=> string\(5\) "IMSSP" \[1\]=> - int\(-1\) + int\(-106\) \["code"\]=> - int\(-1\) + int\(-106\) \[2\]=> - string\([0-9]+\) "Invalid option .* was passed to sqlsrv_connect." + string\([0-9]+\) "Invalid value SQL Server Native Client 11.0 was specified for Driver option." \["message"\]=> - string\([0-9]+\) "Invalid option .* was passed to sqlsrv_connect." + string\([0-9]+\) "Invalid value SQL Server Native Client 11.0 was specified for Driver option." \} \} Test sqlsrv_connect with driver injection diff --git a/test/functional/sqlsrv/srv_050_error_conversion_varchar_int.phpt b/test/functional/sqlsrv/srv_050_error_conversion_varchar_int.phpt index bf657961..66661b9f 100644 --- a/test/functional/sqlsrv/srv_050_error_conversion_varchar_int.phpt +++ b/test/functional/sqlsrv/srv_050_error_conversion_varchar_int.phpt @@ -32,14 +32,14 @@ sqlsrv_close($conn); print "Done" ?> ---EXPECT-- +--EXPECTREGEX-- Array -( - [0] => 22018 - [SQLSTATE] => 22018 - [1] => 245 - [code] => 245 - [2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the varchar value 'null' to data type int. - [message] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the varchar value 'null' to data type int. -) +\( + \[0\] => 22018 + \[SQLSTATE\] => 22018 + \[1\] => 245 + \[code\] => 245 + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Conversion failed when converting the varchar value 'null' to data type int\. + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Conversion failed when converting the varchar value 'null' to data type int\. +\) Done \ No newline at end of file diff --git a/test/functional/sqlsrv/srv_051_error_conversion_nchar.phpt b/test/functional/sqlsrv/srv_051_error_conversion_nchar.phpt index 09957a47..2295180f 100644 --- a/test/functional/sqlsrv/srv_051_error_conversion_nchar.phpt +++ b/test/functional/sqlsrv/srv_051_error_conversion_nchar.phpt @@ -32,14 +32,14 @@ sqlsrv_close($conn); print "Done" ?> ---EXPECT-- +--EXPECTREGEX-- Array -( - [0] => 22018 - [SQLSTATE] => 22018 - [1] => 245 - [code] => 245 - [2] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the nvarchar value '银河' to data type int. - [message] => [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Conversion failed when converting the nvarchar value '银河' to data type int. -) +\( + \[0\] => 22018 + \[SQLSTATE\] => 22018 + \[1\] => 245 + \[code\] => 245 + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Conversion failed when converting the nvarchar value '银河' to data type int\. + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Conversion failed when converting the nvarchar value '银河' to data type int\. +\) Done diff --git a/test/functional/sqlsrv/srv_053_mars_disabled_error_checks.phpt b/test/functional/sqlsrv/srv_053_mars_disabled_error_checks.phpt index 0121f075..34a8d59b 100644 --- a/test/functional/sqlsrv/srv_053_mars_disabled_error_checks.phpt +++ b/test/functional/sqlsrv/srv_053_mars_disabled_error_checks.phpt @@ -32,27 +32,27 @@ sqlsrv_close($conn); print "Done" ?> ---EXPECT-- +--EXPECTREGEX-- Array -( - [0] => Array - ( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -44 - [code] => -44 - [2] => The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. - [message] => The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. - ) +\( + \[0\] => Array + \( + \[0\] => IMSSP + \[SQLSTATE\] => IMSSP + \[1\] => -44 + \[code\] => -44 + \[2\] => The connection cannot process this operation because there is a statement with pending results\. To make the connection available for other queries, either fetch all results or cancel or free the statement\. For more information, see the product documentation about the MultipleActiveResultSets connection option\. + \[message\] => The connection cannot process this operation because there is a statement with pending results\. To make the connection available for other queries, either fetch all results or cancel or free the statement\. For more information, see the product documentation about the MultipleActiveResultSets connection option\. + \) - [1] => Array - ( - [0] => HY000 - [SQLSTATE] => HY000 - [1] => 0 - [code] => 0 - [2] => [Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command - [message] => [Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command - ) + \[1\] => Array + \( + \[0\] => HY000 + \[SQLSTATE\] => HY000 + \[1\] => 0 + \[code\] => 0 + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Connection is busy with results for another command + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Connection is busy with results for another command + \) -) +\) diff --git a/test/functional/sqlsrv/srv_075_database_wide_string.phpt b/test/functional/sqlsrv/srv_075_database_wide_string.phpt index eb1800ec..2737a1cf 100644 --- a/test/functional/sqlsrv/srv_075_database_wide_string.phpt +++ b/test/functional/sqlsrv/srv_075_database_wide_string.phpt @@ -41,5 +41,5 @@ print "Done"; bool\(false\) string\(5\) "(42S02|08004)" int\((3701|911)\) -string\([0-9]+\) "\[Microsoft\]\[ODBC Driver 13 for SQL Server\]\[SQL Server\](Cannot drop the database 'uniqueDB01_银河系', because it does not exist or you do not have permission\.|Database 'uniqueDB01_银河系' does not exist. Make sure that the name is entered correctly\.)" +string\([0-9]+\) "\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\](Cannot drop the database 'uniqueDB01_银河系', because it does not exist or you do not have permission\.|Database 'uniqueDB01_银河系' does not exist. Make sure that the name is entered correctly\.)" Done diff --git a/test/functional/sqlsrv/test_ae_keys_setup.phpt b/test/functional/sqlsrv/test_ae_keys_setup.phpt new file mode 100644 index 00000000..a54a4569 --- /dev/null +++ b/test/functional/sqlsrv/test_ae_keys_setup.phpt @@ -0,0 +1,38 @@ +--TEST-- +retrieval of names of column master key and column encryption key generated in the database setup +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Test Successfully done. \ No newline at end of file diff --git a/test/functional/sqlsrv/test_connectionOption.phpt b/test/functional/sqlsrv/test_connectionOption.phpt index 30da4c52..981a17cc 100644 --- a/test/functional/sqlsrv/test_connectionOption.phpt +++ b/test/functional/sqlsrv/test_connectionOption.phpt @@ -49,8 +49,8 @@ Array \[SQLSTATE\] => HY000 \[1\] => 0 \[code\] => 0 - \[2\] => \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]Connection is busy with results for another command - \[message\] => \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]Connection is busy with results for another command + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Connection is busy with results for another command + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Connection is busy with results for another command \) \) diff --git a/test/functional/sqlsrv/test_scrollable.phpt b/test/functional/sqlsrv/test_scrollable.phpt index 2f72f419..00543d4f 100644 --- a/test/functional/sqlsrv/test_scrollable.phpt +++ b/test/functional/sqlsrv/test_scrollable.phpt @@ -236,8 +236,8 @@ Array \[SQLSTATE\] => HY109 \[1\] => 0 \[code\] => 0 - \[2\] => \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]Invalid cursor position - \[message\] => \[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\]Invalid cursor position + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid cursor position + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid cursor position \) \)