diff --git a/CHANGELOG.md b/CHANGELOG.md index e6eb607f..b96bb0cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,34 @@ 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/macOS 5.1.2-preview - 2017-11-21 +Updated PECL release packages. Here is the list of updates: + +### Fixed +- Support for non-UTF8 locales in Linux and macOS +- Fixed crash caused by executing an invalid query in a transaction (Issue [#434](https://github.com/Microsoft/msphpsql/issues/434)) +- Fixed regression in sqlsrv_next_result returning a no fields error when the active result set is null (Issue [#581](https://github.com/Microsoft/msphpsql/issues/581)) +- Fixed incorrect active result set when sqlsrv_next_result or PDOStatement::nextRowset is called when Column Encryption is enabled (Issue [#574](https://github.com/Microsoft/msphpsql/issues/574)) +- Fixed data corruption in fetching from an encrypted max column after calling sqlsrv_next_result or PDOStatemet::nextRowset (Issue [#580](https://github.com/Microsoft/msphpsql/issues/580)) +- Added error handling for using PDO::SQLSRV_ATTR_DIRECT_QUERY or PDO::ATTR_EMULATE_PREPARES in a Column Encryption enabled connection +- Added error handling for binding TEXT, NTEXT or IMAGE as output parameter (Issue [#231](https://github.com/Microsoft/msphpsql/issues/231)) + +### Limitations +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. The subsequent locale setting will not work +- 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 in 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 +- Binding decimal input as a string when Column Encryption is enabled may change the precision of the input +- Connection pooling on Linux doesn't work properly when using the MSODBC17 preview +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) 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 5.1.1-preview - 2017-10-20 Updated PECL release packages. Here is the list of updates: diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 6cff1116..2b13ab6d 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -9,6 +9,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \ apt-utils \ autoconf \ curl \ + libcurl3 \ g++ \ gcc \ git \ @@ -28,17 +29,14 @@ ENV TEST_PHP_SQL_SERVER sql ENV TEST_PHP_SQL_UID sa ENV TEST_PHP_SQL_PWD Password123 +# add locale iso-8859-1 +RUN sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen +RUN locale-gen en_US + # set locale to utf-8 RUN locale-gen en_US.UTF-8 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' -#install ODBC driver -RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - -RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list - -RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql mssql-tools -ENV PATH="/opt/mssql-tools/bin:${PATH}" - #install coveralls RUN pip install --upgrade pip && pip install cpp-coveralls @@ -47,8 +45,14 @@ RUN pip install --upgrade pip && pip install cpp-coveralls #another option is to copy source to build directory on image RUN mkdir -p $PHPSQLDIR COPY . $PHPSQLDIR -WORKDIR $PHPSQLDIR/source/ +#install ODBC 17 preview driver +WORKDIR $PHPSQLDIR +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y dpkg -i "./ODBC 17 binaries preview/Ubuntu 16/msodbcsql_17.0.0.1-1_amd64.deb" +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y dpkg -i "./ODBC 17 binaries preview/Ubuntu 16/mssql-tools_17.0.0.1-1_amd64.deb" +ENV PATH="/opt/mssql-tools/bin:${PATH}" + +WORKDIR $PHPSQLDIR/source/ RUN chmod +x ./packagize.sh RUN /bin/bash -c "./packagize.sh" @@ -79,4 +83,4 @@ RUN chmod +x ./entrypoint.sh CMD /bin/bash ./entrypoint.sh ENV REPORT_EXIT_STATUS 1 -ENV TEST_PHP_EXECUTABLE /usr/bin/php \ No newline at end of file +ENV TEST_PHP_EXECUTABLE /usr/bin/php diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 6db17d29..19def08c 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1067,33 +1067,6 @@ int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); - // Return the correct error in case the user calls nextRowset() on a null result set. - // Null means that SQLNumResultCols() returns 0 and SQLRowCount does not return > 0. But first - // check that the statement has been executed and that we are not past the end of a non-null - // result set to make sure the user gets the correct error message. These checks are also - // done in core_sqlsrv_next_result(), but we cannot check for null results there because that - // function can be called without calling this one, and SQLSRV_ERROR_NO_FIELDS can then - // be triggered incorrectly. - CHECK_CUSTOM_ERROR( !driver_stmt->executed, driver_stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( driver_stmt->past_next_result_end, driver_stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) { - throw core::CoreException(); - } - - // Now make sure the result set is not null. - bool has_result = core_sqlsrv_has_any_result( driver_stmt ); - - // Note that if fetch_called is false but has_result is true (i.e. the user is calling - // nextRowset() on a non-null result set before calling fetch()), it is handled - // in core_sqlsrv_next_result() below. - if( !driver_stmt->fetch_called ) { - CHECK_CUSTOM_ERROR( !has_result, driver_stmt, SQLSRV_ERROR_NO_FIELDS ) { - throw core::CoreException(); - } - } - core_sqlsrv_next_result( static_cast( stmt->driver_data ) TSRMLS_CC ); // clear the current meta data since the new result will generate new meta data diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index e7f85514..b67881c2 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -39,6 +39,12 @@ void core_sqlsrv_minit( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_contex SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); +#ifndef _WIN32 + // set locale from environment + // this is necessary for ODBC and MUST be done before connection + setlocale(LC_ALL, ""); +#endif + *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL try { diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index ecb4f936..b2020f54 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -27,6 +27,17 @@ #include #define CP_UTF8 65001 +#define CP_ISO8859_1 28591 +#define CP_ISO8859_2 28592 +#define CP_ISO8859_3 28593 +#define CP_ISO8859_4 28594 +#define CP_ISO8859_5 28595 +#define CP_ISO8859_6 28596 +#define CP_ISO8859_7 28597 +#define CP_ISO8859_8 28598 +#define CP_ISO8859_9 28599 +#define CP_ISO8859_13 28603 +#define CP_ISO8859_15 28605 #define CP_UTF16 1200 #define CP_ACP 0 // default to ANSI code page @@ -178,14 +189,15 @@ private: SystemLocale & operator=( const SystemLocale & ); std::locale * m_pLocale; + UINT m_uAnsiCP; explicit SystemLocale( const char * localeName ); ~SystemLocale(); static UINT ExpandSpecialCP( UINT codepage ) { - // Convert CP_ACP, CP_OEM to CP_UTF8 - return (codepage < 2 ? CP_UTF8 : codepage); + // skip SQLSRV_ENCODING_CHAR + return (codepage <= 3 ? Singleton().m_uAnsiCP : codepage); } // Returns the number of bytes this UTF8 code point expects @@ -217,7 +229,7 @@ private: inline UINT SystemLocale::AnsiCP() const { - return CP_UTF8; + return m_uAnsiCP; } inline UINT SystemLocale::MaxCharCchSize( UINT codepage ) diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 94c8e676..5ecc8c2a 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -1,5 +1,5 @@ //--------------------------------------------------------------------------------------------------------------------------------- -// File: LocalizationImpl.hpp +// File: localizationimpl.cpp // // Contents: Contains non-inline code for the SystemLocale class // Must be included in one c/cpp file per binary @@ -71,6 +71,17 @@ const cp_iconv cp_iconv::g_cp_iconv[] = { { 1256, "CP1256//TRANSLIT" }, { 1257, "CP1257//TRANSLIT" }, { 1258, "CP1258//TRANSLIT" }, + { CP_ISO8859_1, "ISO8859-1//TRANSLIT" }, + { CP_ISO8859_2, "ISO8859-2//TRANSLIT" }, + { CP_ISO8859_3, "ISO8859-3//TRANSLIT" }, + { CP_ISO8859_4, "ISO8859-4//TRANSLIT" }, + { CP_ISO8859_5, "ISO8859-5//TRANSLIT" }, + { CP_ISO8859_6, "ISO8859-6//TRANSLIT" }, + { CP_ISO8859_7, "ISO8859-7//TRANSLIT" }, + { CP_ISO8859_8, "ISO8859-8//TRANSLIT" }, + { CP_ISO8859_9, "ISO8859-9//TRANSLIT" }, + { CP_ISO8859_13, "ISO8859-13//TRANSLIT" }, + { CP_ISO8859_15, "ISO8859-15//TRANSLIT" }, { 12000, "UTF-32LE" } }; const size_t cp_iconv::g_cp_iconv_count = ARRAYSIZE(cp_iconv::g_cp_iconv); @@ -270,7 +281,42 @@ using namespace std; SystemLocale::SystemLocale( const char * localeName ) : m_pLocale( new std::locale(localeName) ) + , m_uAnsiCP(CP_UTF8) { + struct LocaleCP + { + const char* localeName; + UINT codePage; + }; +#define CPxxx(cp) { "CP" #cp, cp } +#define ISO8859(n) { "ISO-8859-" #n, CP_ISO8859_ ## n }, \ + { "8859_" #n, CP_ISO8859_ ## n }, \ + { "ISO8859-" #n, CP_ISO8859_ ## n }, \ + { "ISO8859" #n, CP_ISO8859_ ## n }, \ + { "ISO_8859-" #n, CP_ISO8859_ ## n }, \ + { "ISO_8859_" #n, CP_ISO8859_ ## n } + const LocaleCP lcpTable[] = { + { "utf8", CP_UTF8 }, + { "UTF-8", CP_UTF8 }, + CPxxx(1252), CPxxx(850), CPxxx(437), CPxxx(874), CPxxx(932), CPxxx(936), CPxxx(949), CPxxx(950), + CPxxx(1250), CPxxx(1251), CPxxx(1253), CPxxx(1254), CPxxx(1255), CPxxx(1256), CPxxx(1257), CPxxx(1258), + ISO8859(1), ISO8859(2), ISO8859(3), ISO8859(4), ISO8859(5), ISO8859(6), + ISO8859(7), ISO8859(8), ISO8859(9), ISO8859(13), ISO8859(15), + { "UTF-32LE", 12000 } + }; + if (localeName) + { + const char *charsetName = strchr(localeName, '.'); + charsetName = charsetName ? charsetName + 1 : localeName; + for (const LocaleCP& lcp : lcpTable) + { + if (!strncasecmp(lcp.localeName, charsetName, strlen(lcp.localeName))) + { + m_uAnsiCP = lcp.codePage; + return; + } + } + } } SystemLocale::~SystemLocale() @@ -285,7 +331,8 @@ const SystemLocale & SystemLocale::Singleton() #if !defined(__GNUC__) || defined(NO_THREADSAFE_STATICS) #error "Relying on GCC's threadsafe initialization of local statics." #endif - static const SystemLocale s_Default( "en_US.utf-8" ); + // get locale from environment and set as default + static const SystemLocale s_Default(setlocale(LC_ALL, NULL)); return s_Default; } diff --git a/source/shared/version.h b/source/shared/version.h index 94c47ceb..cb4b28dd 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -28,7 +28,7 @@ // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 #define SQLVERSION_MINOR 1 -#define SQLVERSION_PATCH 1 +#define SQLVERSION_PATCH 2 #define SQLVERSION_BUILD 0 // Semantic versioning pre-release diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index d9b980c2..a0f88e94 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -560,32 +560,6 @@ PHP_FUNCTION( sqlsrv_next_result ) PROCESS_PARAMS( stmt, "r", _FN_, 0 ); try { - - // Return the correct error in case the user calls sqlsrv_next_result() on a null result set. - // Null means that SQLNumResultCols() returns 0 and SQLRowCount does not return > 0. But first - // check that the statement has been executed and that we are not past the end of a non-null - // result set to make sure the user gets the correct error message. These checks are also - // done in core_sqlsrv_next_result(), but we cannot check for null results there because that - // function can be called without calling this one, and SQLSRV_ERROR_NO_FIELDS can then - // be triggered incorrectly. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) { - throw core::CoreException(); - } - - bool has_result = core_sqlsrv_has_any_result( stmt ); - - // Note that if fetch_called is false but has_result is true (i.e. the user is calling - // sqlsrv_next_result() on a non-null result set before calling fetch()), it is handled - // in core_sqlsrv_next_result() below. - if( !stmt->fetch_called ) { - CHECK_CUSTOM_ERROR( !has_result, stmt, SQLSRV_ERROR_NO_FIELDS ) { - throw core::CoreException(); - } - } core_sqlsrv_next_result( stmt TSRMLS_CC, true ); diff --git a/test/functional/pdo_sqlsrv/pdo_empty_result_error.phpt b/test/functional/pdo_sqlsrv/pdo_empty_result_error.phpt index 02631f48..0d2b730a 100644 --- a/test/functional/pdo_sqlsrv/pdo_empty_result_error.phpt +++ b/test/functional/pdo_sqlsrv/pdo_empty_result_error.phpt @@ -1,100 +1,179 @@ ---TEST-- -Error messages from null result sets ---DESCRIPTION-- -Test that calling nextRowset() on an empty result set produces the correct error message. Fix for Github 507. ---SKIPIF-- - ---FILE-- -setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); - -DropTable($conn, 'TestEmptySetTable'); -$stmt = $conn->query("CREATE TABLE TestEmptySetTable ([c1] nvarchar(10),[c2] nvarchar(10))"); -$stmt = $conn->query("INSERT INTO TestEmptySetTable (c1, c2) VALUES ('a', 'b')"); - -// Create a procedure that can return a result set or can return nothing -DropProc($conn, 'TestEmptySetProc'); -$stmt = $conn->query("CREATE PROCEDURE TestEmptySetProc @a nvarchar(10), @b nvarchar(10) - AS SET NOCOUNT ON - BEGIN - IF @b='b' - BEGIN - SELECT 'a' as testValue - END - ELSE - BEGIN - UPDATE TestEmptySetTable SET c2 = 'c' WHERE c1 = @a - END - END"); - -// errors out when reaching the second nextRowset() call -// returned error indicates there are no more results -echo "Return a nonempty result set:\n"; -try -{ - $stmt = $conn->query("TestEmptySetProc @a='a', @b='b'"); - $result = $stmt->fetchAll(); - print_r($result); - $stmt->nextRowset(); - $result = $stmt->fetchAll(); - print_r($result); - $stmt->nextRowset(); -} -catch(Exception $e) -{ - echo $e->getMessage()."\n"; -} - -// errors out indicating the result set contains no fields -echo "Return an empty result set, call nextRowset on it before fetching anything:\n"; -try -{ - $stmt = $conn->query("TestEmptySetProc @a='a', @b='c'"); - $stmt->nextRowset(); -} -catch(Exception $e) -{ - echo $e->getMessage()."\n"; -} - -// errors out indicating the result set contains no fields -echo "Return an empty result set, call fetch on it:\n"; -try -{ - $stmt = $conn->query("TestEmptySetProc @a='a', @b='c'"); - $result = $stmt->fetchAll(); - print_r($result); -} -catch(Exception $e) -{ - echo $e->getMessage()."\n"; -} - -$stmt = $conn->query("DROP TABLE TestEmptySetTable"); -$stmt = $conn->query("DROP PROCEDURE TestEmptySetProc"); - -$conn = null; -?> ---EXPECT-- -Return a nonempty result set: -Array -( - [0] => Array - ( - [testValue] => a - [0] => a - ) - -) -Array -( -) -SQLSTATE[IMSSP]: There are no more results returned by the query. -Return an empty result set, call nextRowset on it before fetching anything: -SQLSTATE[IMSSP]: The active result for the query contains no fields. -Return an empty result set, call fetch on it: -SQLSTATE[IMSSP]: The active result for the query contains no fields. +--TEST-- +Error messages from nonempty, empty, and null result sets +--DESCRIPTION-- +Test that calling nextRowset() and fetching on nonempty, empty, and null result sets produces the correct results or error messages. +--SKIPIF-- + +--FILE-- +errorInfo(); + + if ($actualError[2] != $expectedError) { + echo "Wrong error message:\n"; + print_r($actualError); + } +} + +function Fetch($stmt, $error=NULL) +{ + echo "Fetch...\n"; + $result = $stmt->fetchObject(); + print_r($result); + CheckError($stmt, $error); +} + +function NextResult($stmt, $error=NULL) +{ + echo "Next result...\n"; + $stmt->nextRowset(); + CheckError($stmt, $error); +} + +$conn = new PDO( "sqlsrv:Server = $server; Database = $databaseName; ", $uid, $pwd ); +$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); + +DropTable($conn, 'TestEmptySetTable'); +$stmt = $conn->query("CREATE TABLE TestEmptySetTable ([c1] nvarchar(10),[c2] nvarchar(10))"); +$stmt = $conn->query("INSERT INTO TestEmptySetTable (c1, c2) VALUES ('a', 'b')"); + +// Create a procedure that can return a nonempty result set, an empty result set, or a null result +DropProc($conn, 'TestEmptySetProc'); +$stmt = $conn->query("CREATE PROCEDURE TestEmptySetProc @a nvarchar(10), @b nvarchar(10) + AS SET NOCOUNT ON + BEGIN + IF @b='b' + BEGIN + SELECT 'a' as testValue + END + ELSE IF @b='w' + BEGIN + SELECT * FROM TestEmptySetTable WHERE c1 = @b + END + ELSE + BEGIN + UPDATE TestEmptySetTable SET c2 = 'c' WHERE c1 = @a + END + END"); + +// Call fetch on a nonempty result set +echo "Nonempty result set, call fetch first: ###############################\n"; + +$stmt = $conn->query("TestEmptySetProc @a='a', @b='b'"); +Fetch($stmt); +NextResult($stmt); +Fetch($stmt); +NextResult($stmt, $errorNoMoreResults); + +// Call nextRowset on a nonempty result set +echo "Nonempty result set, call nextRowset first: #########################\n"; + +$stmt = $conn->query("TestEmptySetProc @a='a', @b='b'"); +NextResult($stmt); +Fetch($stmt); +NextResult($stmt, $errorNoMoreResults); + +// Call nextRowset twice in succession on a nonempty result set +echo "Nonempty result set, call nextRowset twice: #########################\n"; + +$stmt = $conn->query("TestEmptySetProc @a='a', @b='b'"); +NextResult($stmt); +NextResult($stmt, $errorNoMoreResults); + +// Call fetch on an empty result set +echo "Empty result set, call fetch first: ##################################\n"; + +$stmt = $conn->query("TestEmptySetProc @a='a', @b='w'"); +Fetch($stmt); +NextResult($stmt); +Fetch($stmt); +NextResult($stmt, $errorNoMoreResults); + +// Call nextRowset on an empty result set +echo "Empty result set, call nextRowset first: ############################\n"; + +$stmt = $conn->query("TestEmptySetProc @a='a', @b='w'"); +NextResult($stmt); +Fetch($stmt); +NextResult($stmt, $errorNoMoreResults); + +// Call nextRowset twice in succession on an empty result set +echo "Empty result set, call nextRowset twice: ############################\n"; + +$stmt = $conn->query("TestEmptySetProc @a='a', @b='w'"); +NextResult($stmt); +NextResult($stmt, $errorNoMoreResults); + +// Call fetch on a null result set +echo "Null result set, call fetch first: ###################################\n"; + +$stmt = $conn->query("TestEmptySetProc @a='a', @b='c'"); +Fetch($stmt, $errorNoFields); +NextResult($stmt); + +// Call nextRowset on a null result set +echo "Null result set, call next result first: #############################\n"; + +$stmt = $conn->query("TestEmptySetProc @a='a', @b='c'"); +NextResult($stmt); +Fetch($stmt); + +// Call nextRowset twice in succession on a null result set +echo "Null result set, call next result twice: #############################\n"; + +$stmt = $conn->query("TestEmptySetProc @a='a', @b='c'"); +NextResult($stmt); +NextResult($stmt, $errorNoMoreResults); + +$stmt = $conn->query("DROP TABLE TestEmptySetTable"); +$stmt = $conn->query("DROP PROCEDURE TestEmptySetProc"); +$stmt = null; +$conn = null; +?> +--EXPECT-- +Nonempty result set, call fetch first: ############################### +Fetch... +stdClass Object +( + [testValue] => a +) +Next result... +Fetch... +Next result... +Nonempty result set, call nextRowset first: ######################### +Next result... +Fetch... +Next result... +Nonempty result set, call nextRowset twice: ######################### +Next result... +Next result... +Empty result set, call fetch first: ################################## +Fetch... +Next result... +Fetch... +Next result... +Empty result set, call nextRowset first: ############################ +Next result... +Fetch... +Next result... +Empty result set, call nextRowset twice: ############################ +Next result... +Next result... +Null result set, call fetch first: ################################### +Fetch... +Next result... +Null result set, call next result first: ############################# +Next result... +Fetch... +Null result set, call next result twice: ############################# +Next result... +Next result... \ No newline at end of file diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index f0e829ee..dc9ba15b 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -454,4 +454,15 @@ function handleErrors() } } -?> \ No newline at end of file +// non-UTF8 locale support in ODBC 17 and above only +function isLocaleSupported() +{ + $conn = AE\connect(); + + $msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer']; + if (explode(".", $msodbcsql_ver)[0] < 17) { + return false; + } +} + +?> diff --git a/test/functional/sqlsrv/MsData_UTF8.inc b/test/functional/sqlsrv/MsData_UTF8.inc index 0a483a41..3169c3de 100644 --- a/test/functional/sqlsrv/MsData_UTF8.inc +++ b/test/functional/sqlsrv/MsData_UTF8.inc @@ -9,7 +9,7 @@ */ -function getTestData($index) +function getTestData_UTF8($index) { $inputs = null; switch ($index) diff --git a/test/functional/sqlsrv/MsHelper.inc b/test/functional/sqlsrv/MsHelper.inc index e1cbb7e0..4cb5cbfa 100644 --- a/test/functional/sqlsrv/MsHelper.inc +++ b/test/functional/sqlsrv/MsHelper.inc @@ -732,12 +732,13 @@ function getInsertArray($index) { if (! UseUTF8data()) { require_once('MsData.inc'); + return getTestData($index); } else { require_once('MsData_UTF8.inc'); + return getTestData_UTF8($index); } // get array of input values - return getTestData($index); } /** @@ -1184,4 +1185,4 @@ function getSampleData($k) } } -?> \ No newline at end of file +?> diff --git a/test/functional/sqlsrv/TC43_FetchData.phpt b/test/functional/sqlsrv/TC43_FetchData.phpt index f520672c..58a7c9d6 100644 --- a/test/functional/sqlsrv/TC43_FetchData.phpt +++ b/test/functional/sqlsrv/TC43_FetchData.phpt @@ -1,9 +1,14 @@ --TEST-- -Fetch Field Data Test verifies the data retrieved via "sqlsrv_get_field" +Fetch Field Data Test verifies the data retrieved via sqlsrv_get_field --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- 'UTF-8')); + } else { + $conn1 = AE\connect(); + } + AE\createTestTable($conn1, $tableName); $startRow = 1; @@ -62,8 +69,6 @@ function fetchFields() dropTable($conn1, $tableName); sqlsrv_close($conn1); - - endTest($testName); } function checkData($col, $actual, $expected) @@ -91,16 +96,36 @@ function checkData($col, $actual, $expected) return ($success); } -if (! isWindows()) { - setUTF8Data(true); +if (!isWindows()) { + setlocale(LC_ALL, "en_US.ISO-8859-1"); } + +$testName = "Fetch - Field Data"; + +// test ansi only if windows or non-UTF8 locales are supported (ODBC 17 and above) +startTest($testName); +if (isWindows() || isLocaleSupported()) { + + try { + setUTF8Data(false); + fetchFields(); + } catch (Exception $e) { + echo $e->getMessage(); + } +} +endTest($testName); + +// test utf8 +startTest($testName); try { + setUTF8Data(true); fetchFields(); } catch (Exception $e) { echo $e->getMessage(); } -setUTF8Data(false); +endTest($testName); ?> --EXPECT-- Test "Fetch - Field Data" completed successfully. +Test "Fetch - Field Data" completed successfully. diff --git a/test/functional/sqlsrv/TC44_FetchArray.phpt b/test/functional/sqlsrv/TC44_FetchArray.phpt index 866e4cd6..7e338257 100644 --- a/test/functional/sqlsrv/TC44_FetchArray.phpt +++ b/test/functional/sqlsrv/TC44_FetchArray.phpt @@ -6,24 +6,25 @@ by checking all fetch type modes. --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- 'UTF-8' )); } else { $conn1 = AE\connect(); @@ -43,7 +44,6 @@ function fetchRow($minFetchMode, $maxFetchMode) } else { $count = sqlsrv_num_fields($stmt1); if ($count != $numFields) { - setUTF8Data(false); die("Unexpected number of fields: $count"); } } @@ -73,8 +73,6 @@ function fetchRow($minFetchMode, $maxFetchMode) dropTable($conn1, $tableName); sqlsrv_close($conn1); - - endTest($testName); } function fetchArray($stmt, $stmtRef, $mode, $rows, $fields) @@ -104,13 +102,11 @@ function fetchArray($stmt, $stmtRef, $mode, $rows, $fields) } $rowSize = count($row); if ($rowSize != $size) { - setUTF8Data(false); die("Row array has an incorrect size: ".$rowSize); } $rowRref = sqlsrv_fetch($stmtRef); for ($j = 0; $j < $fields; $j++) { if (!checkData($row, $stmtRef, $j, $fetchMode)) { - setUTF8Data(false); die("Data corruption on row ".($i + 1)." column ".($j + 1)); } } @@ -155,17 +151,37 @@ function checkData($row, $stmt, $index, $mode) return ($success); } - +// locale must be set before 1st connection if (!isWindows()) { - setUTF8Data(true); + setlocale(LC_ALL, "en_US.ISO-8859-1"); } + +global $testName; +$testName = "Fetch - Array"; + +// test ansi only if windows or non-UTF8 locales are supported (ODBC 17 and above) +startTest($testName); +if (isWindows() || isLocaleSupported()) { + try { + setUTF8Data(false); + fetchRow(1, 4); + } catch (Exception $e) { + echo $e->getMessage(); + } +} +endTest($testName); + +// test utf8 +startTest($testName); try { + setUTF8Data(true); fetchRow(1, 4); } catch (Exception $e) { echo $e->getMessage(); } -setUTF8Data(false); +endTest($testName); ?> --EXPECT-- Test "Fetch - Array" completed successfully. +Test "Fetch - Array" completed successfully. diff --git a/test/functional/sqlsrv/TC46_FetchNextResult.phpt b/test/functional/sqlsrv/TC46_FetchNextResult.phpt index f12a0c3f..6848d2e6 100644 --- a/test/functional/sqlsrv/TC46_FetchNextResult.phpt +++ b/test/functional/sqlsrv/TC46_FetchNextResult.phpt @@ -5,24 +5,29 @@ Verifies the functionality of "sqlsrv_next_result" --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- 'UTF-8')); + } else { + $conn1 = AE\connect(); + } AE\createTestTable($conn1, $tableName); $noRows = 10; @@ -77,21 +82,39 @@ function fetchFields() dropTable($conn1, $tableName); sqlsrv_close($conn1); - - endTest($testName); } +// locale must be set before 1st connection if (!isWindows()) { - setUTF8Data(true); + setlocale(LC_ALL, "en_US.ISO-8859-1"); } +global $testName; +$testName = "Fetch - Next Result"; + +// test ansi only if windows or non-UTF8 locales are supported (ODBC 17 and above) +startTest($testName); +if (isWindows() || isLocaleSupported()) { + try { + setUTF8Data(false); + fetchFields(); + } catch (Exception $e) { + echo $e->getMessage(); + } +} +endTest($testName); + +// test utf8 +startTest($testName); try { + setUTF8Data(true); fetchFields(); } catch (Exception $e) { echo $e->getMessage(); } -setUTF8Data(false); +endTest($testName); ?> --EXPECT-- Test "Fetch - Next Result" completed successfully. +Test "Fetch - Next Result" completed successfully. diff --git a/test/functional/sqlsrv/TC51_StreamRead.phpt b/test/functional/sqlsrv/TC51_StreamRead.phpt index cc1b39e6..3e42243a 100644 --- a/test/functional/sqlsrv/TC51_StreamRead.phpt +++ b/test/functional/sqlsrv/TC51_StreamRead.phpt @@ -6,19 +6,20 @@ can be successfully retrieved as streams. --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- 'UTF-8' )); } else { $conn1 = AE\connect(); @@ -51,8 +52,6 @@ function streamRead($noRows, $startRow) dropTable($conn1, $tableName); sqlsrv_close($conn1); - - endTest($testName); } function verifyStream($stmt, $row, $colIndex) @@ -80,7 +79,6 @@ function verifyStream($stmt, $row, $colIndex) fclose($stream); $data = AE\getInsertData($row, $col); if (!checkData($col, $value, $data)) { - setUTF8Data(false); trace("Data corruption on row $row column $col\n"); die("Data corruption on row $row column $col\n"); } @@ -117,18 +115,37 @@ function checkData($col, $actual, $expected) return ($success); } -if (! isWindows()) { - setUTF8Data(true); +// locale must be set before 1st connection +if (!isWindows()) { + setlocale(LC_ALL, "en_US.ISO-8859-1"); } +global $testName; +$testName = "Stream - Read"; + +// test ansi only if windows or non-UTF8 locales are supported (ODBC 17 and above) +startTest($testName); +if (isWindows() || isLocaleSupported()) { + try { + setUTF8Data(false); + streamRead(20, 1); + } catch (Exception $e) { + echo $e->getMessage(); + } +} +endTest($testName); + +// test utf8 +startTest($testName); try { + setUTF8Data(true); streamRead(20, 1); } catch (Exception $e) { echo $e->getMessage(); } - -setUTF8Data(false); +endTest($testName); ?> --EXPECT-- Test "Stream - Read" completed successfully. +Test "Stream - Read" completed successfully. diff --git a/test/functional/sqlsrv/TC55_StreamScrollable.phpt b/test/functional/sqlsrv/TC55_StreamScrollable.phpt index f2fe5407..82adc4b3 100644 --- a/test/functional/sqlsrv/TC55_StreamScrollable.phpt +++ b/test/functional/sqlsrv/TC55_StreamScrollable.phpt @@ -5,20 +5,23 @@ Verifies the streaming behavior with scrollable resultsets. --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- 'UTF-8' )); + + if (useUTF8Data()) { + $conn1 = AE\connect(array('CharacterSet'=>'UTF-8')); } else { $conn1 = AE\connect(); } @@ -84,8 +87,6 @@ function streamScroll($noRows, $startRow) dropTable($conn1, $tableName); sqlsrv_close($conn1); - - endTest($testName); } function verifyStream($stmt, $row, $colIndex) @@ -150,16 +151,37 @@ function checkData($col, $actual, $expected) return ($success); } -if (! isWindows()) { - setUTF8Data(true); +// locale must be set before 1st connection +if (!isWindows()) { + setlocale(LC_ALL, "en_US.ISO-8859-1"); } + +global $testName; +$testName = "Stream - Scrollable"; + +// test ansi only if windows or non-UTF8 locales are supported (ODBC 17 and above) +startTest($testName); +if (isWindows() || isLocaleSupported()) { + try { + setUTF8Data(false); + streamScroll(20, 1); + } catch (Exception $e) { + echo $e->getMessage(); + } +} +endTest($testName); + +// test utf8 +startTest($testName); try { + setUTF8Data(true); streamScroll(20, 1); } catch (Exception $e) { echo $e->getMessage(); } -setUTF8Data(false); +endTest($testName); ?> --EXPECT-- Test "Stream - Scrollable" completed successfully. +Test "Stream - Scrollable" completed successfully. diff --git a/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt b/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt index 200ec1ac..7f751324 100644 --- a/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt +++ b/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt @@ -1,124 +1,208 @@ ---TEST-- -Error messages from null result sets ---DESCRIPTION-- -Test that calling sqlsrv_next_result() on a null result set produces the correct error message. Fix for Github 507. ---SKIPIF-- - ---FILE-- -$databaseName, "uid"=>$uid, "pwd"=>$pwd)); - -DropTable($conn, 'TestEmptySetTable'); -$stmt = sqlsrv_query($conn, "CREATE TABLE TestEmptySetTable ([c1] nvarchar(10),[c2] nvarchar(10))"); -$stmt = sqlsrv_query($conn, "INSERT INTO TestEmptySetTable (c1, c2) VALUES ('a', 'b')"); - -// Create a procedure that can return a result set or can return nothing -DropProc($conn, 'TestEmptySetProc'); -$stmt = sqlsrv_query($conn, "CREATE PROCEDURE TestEmptySetProc @a nvarchar(10), @b nvarchar(10) - AS SET NOCOUNT ON - BEGIN - IF @b='b' - BEGIN - SELECT 'a' as testValue - END - ELSE - BEGIN - UPDATE TestEmptySetTable SET c2 = 'c' WHERE c1 = @a - END - END"); - -// errors out when reaching the second nextRowset() call -// returned error indicates there are no more results -echo "Return a nonempty result set:\n"; - -$stmt = sqlsrv_query($conn,"TestEmptySetProc @a='a', @b='b'"); -$result = sqlsrv_fetch_array($stmt); -print_r($result); -sqlsrv_next_result($stmt); -$result = sqlsrv_fetch_array($stmt); -print_r($result); -sqlsrv_next_result($stmt); - -print_r(sqlsrv_errors()); - -// errors out indicating the result set contains no fields -echo "Return an empty result set, call nextRowset on it before fetching anything:\n"; - -$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'"); -sqlsrv_next_result($stmt); -print_r(sqlsrv_errors()); - -// errors out indicating the result set contains no fields -echo "Return an empty result set, call fetch on it:\n"; - -$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'"); -$result = sqlsrv_fetch_array($stmt); -print_r($result); -print_r(sqlsrv_errors()); - -$stmt = sqlsrv_query($conn, "DROP TABLE TestEmptySetTable"); -$stmt = sqlsrv_query($conn, "DROP PROCEDURE TestEmptySetProc"); -sqlsrv_free_stmt($stmt); -sqlsrv_close($conn); -?> ---EXPECTF-- -Return a nonempty result set: -Array -( - [0] => a - [testValue] => a -) -Array -( - [0] => Array - ( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -26 - [code] => -26 - [2] => There are no more results returned by the query. - [message] => There are no more results returned by the query. - ) - - [1] => Array - ( - [0] => HY010 - [SQLSTATE] => HY010 - [1] => 0 - [code] => 0 - [2] => [%rMicrosoft|unixODBC%r][%rODBC D|D%rriver Manager]%r[ ]{0,1}%rFunction sequence error - [message] => [%rMicrosoft|unixODBC%r][%rODBC D|D%rriver Manager]%r[ ]{0,1}%rFunction sequence error - ) - -) -Return an empty result set, call nextRowset on it before fetching anything: -Array -( - [0] => Array - ( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -28 - [code] => -28 - [2] => The active result for the query contains no fields. - [message] => The active result for the query contains no fields. - ) - -) -Return an empty result set, call fetch on it: -Array -( - [0] => Array - ( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -28 - [code] => -28 - [2] => The active result for the query contains no fields. - [message] => The active result for the query contains no fields. - ) - -) +--TEST-- +Error messages from nonempty, empty, and null result sets +--DESCRIPTION-- +Test that calling sqlsrv_next_result() and fetching on nonempty, empty, and null result sets produces the correct results or error messages. +--SKIPIF-- + +--FILE-- +$databaseName, "uid"=>$uid, "pwd"=>$pwd)); + +DropTable($conn, 'TestEmptySetTable'); +$stmt = sqlsrv_query($conn, "CREATE TABLE TestEmptySetTable ([c1] nvarchar(10),[c2] nvarchar(10))"); +$stmt = sqlsrv_query($conn, "INSERT INTO TestEmptySetTable (c1, c2) VALUES ('a', 'b')"); + +// Create a procedure that can return a nonempty result set, an empty result set, or a null result +DropProc($conn, 'TestEmptySetProc'); +$stmt = sqlsrv_query($conn, "CREATE PROCEDURE TestEmptySetProc @a nvarchar(10), @b nvarchar(10) + AS SET NOCOUNT ON + BEGIN + IF @b='b' + BEGIN + SELECT 'a' as testValue + END + ELSE IF @b='w' + BEGIN + SELECT * FROM TestEmptySetTable WHERE c1 = @b + END + ELSE + BEGIN + UPDATE TestEmptySetTable SET c2 = 'c' WHERE c1 = @a + END + END"); + +// Call fetch on a nonempty result set +echo "Nonempty result set, call fetch first: ###############################\n"; + +$stmt = sqlsrv_query($conn,"TestEmptySetProc @a='a', @b='b'"); +Fetch($stmt, []); +NextResult($stmt, []); +Fetch($stmt, [$errorFuncSeq()]); +NextResult($stmt, [$errorNoMoreResults, $errorFuncSeq()]); + +// Call next_result on a nonempty result set +echo "Nonempty result set, call next_result first: #########################\n"; + +$stmt = sqlsrv_query($conn,"TestEmptySetProc @a='a', @b='b'"); +NextResult($stmt, []); +Fetch($stmt, [$errorFuncSeq()]); +NextResult($stmt, [$errorNoMoreResults, $errorFuncSeq()]); + +// Call next_result twice in succession on a nonempty result set +echo "Nonempty result set, call next_result twice: #########################\n"; + +$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='b'"); +NextResult($stmt, []); +NextResult($stmt, [$errorNoMoreResults]); + +// Call fetch on an empty result set +echo "Empty result set, call fetch first: ##################################\n"; + +$stmt = sqlsrv_query($conn,"TestEmptySetProc @a='a', @b='w'"); +Fetch($stmt, []); +NextResult($stmt, []); +Fetch($stmt, [$errorNoMoreRows]); +NextResult($stmt, [$errorNoMoreResults]); + +// Call next_result on an empty result set +echo "Empty result set, call next_result first: ############################\n"; + +$stmt = sqlsrv_query($conn,"TestEmptySetProc @a='a', @b='w'"); +NextResult($stmt, []); +Fetch($stmt, [$errorFuncSeq()]); +NextResult($stmt, [$errorNoMoreResults, $errorFuncSeq()]); + +// Call next_result twice in succession on an empty result set +echo "Empty result set, call next_result twice: ############################\n"; + +$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='w'"); +NextResult($stmt, []); +NextResult($stmt, [$errorNoMoreResults]); + +// Call fetch on a null result set +echo "Null result set, call fetch first: ###################################\n"; + +$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'"); +Fetch($stmt, [$errorNoFields]); +NextResult($stmt, []); + +// Call next_result on a null result set +echo "Null result set, call next result first: #############################\n"; + +$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'"); +NextResult($stmt, []); +Fetch($stmt, [$errorFuncSeq()]); + +// Call next_result twice in succession on a null result set +echo "Null result set, call next result twice: #############################\n"; + +$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'"); +NextResult($stmt, []); +NextResult($stmt, [$errorNoMoreResults]); + +$stmt = sqlsrv_query($conn, "DROP TABLE TestEmptySetTable"); +$stmt = sqlsrv_query($conn, "DROP PROCEDURE TestEmptySetProc"); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); +?> +--EXPECT-- +Nonempty result set, call fetch first: ############################### +Fetch... +Array +( + [0] => a + [testValue] => a +) +Next result... +Fetch... +Next result... +Nonempty result set, call next_result first: ######################### +Next result... +Fetch... +Next result... +Nonempty result set, call next_result twice: ######################### +Next result... +Next result... +Empty result set, call fetch first: ################################## +Fetch... +Next result... +Fetch... +Next result... +Empty result set, call next_result first: ############################ +Next result... +Fetch... +Next result... +Empty result set, call next_result twice: ############################ +Next result... +Next result... +Null result set, call fetch first: ################################### +Fetch... +Next result... +Null result set, call next result first: ############################# +Next result... +Fetch... +Null result set, call next result twice: ############################# +Next result... +Next result... diff --git a/test/functional/sqlsrv/test_stream_large_data.phpt b/test/functional/sqlsrv/test_stream_large_data.phpt index d09f2dfd..c14f9e27 100644 --- a/test/functional/sqlsrv/test_stream_large_data.phpt +++ b/test/functional/sqlsrv/test_stream_large_data.phpt @@ -1,20 +1,32 @@ --TEST-- streaming large amounts of data into a database and getting it out as a string exactly the same. --SKIPIF-- - + --FILE-- 'UTF-8' )) : connect() ; + $useUTF8 = useUTF8Data(); + if ($useUTF8) { + $conn1 = connect(array('CharacterSet'=>'UTF-8')); + } else { + $conn1 = connect(); + } if ($conn1 === false) { fatalError("Failed to connect"); } @@ -31,18 +43,17 @@ streaming large amounts of data into a database and getting it out as a string e sqlsrv_free_stmt($stmt2); sqlsrv_close($conn1); - if ($notWindows) { - $conn2 = connect(array( 'CharacterSet' =>'utf-8' )); + if ($useUTF8) { + $conn2 = connect(array('CharacterSet'=>'UTF-8')); } else { $conn2 = connect(); } - if ($conn2 === false) { echo "sqlsrv_connect failed 2nd.\n"; die(print_r(sqlsrv_errors(), true)); } - if ($notWindows) { + if ($useUTF8) { require('test_stream_large_data_UTF8.inc'); GenerateInputUTF8Data(); } else { @@ -147,27 +158,27 @@ streaming large amounts of data into a database and getting it out as a string e $metadata1 = sqlsrv_field_metadata($stmt8); $count = count($metadata1); sqlsrv_fetch($stmt8); - $value1 = GetField($stmt8, 13, $notWindows); + $value1 = GetField($stmt8, 13, $useUTF8); $lens1[$i++] = strlen($value1) . "\n"; $fout = fopen("varchar_max.out", "w"); fwrite($fout, $value1); fclose($fout); - $value2 = GetField($stmt8, 16, $notWindows); + $value2 = GetField($stmt8, 16, $useUTF8); $lens1[$i++] = strlen($value2) . "\n"; $fout = fopen("nvarchar_max.out", "w"); fwrite($fout, $value2); fclose($fout); - $value3 = GetField($stmt8, 17, $notWindows); + $value3 = GetField($stmt8, 17, $useUTF8); $lens1[$i++] = strlen($value3) . "\n"; $fout = fopen("text.out", "w"); fwrite($fout, $value3); fclose($fout); - $value4 = GetField($stmt8, 18, $notWindows); + $value4 = GetField($stmt8, 18, $useUTF8); $lens1[$i++] = strlen($value4) . "\n"; $fout = fopen("ntext.out", "w"); fwrite($fout, $value4); fclose($fout); - $value5 = GetField($stmt8, 27, $notWindows); + $value5 = GetField($stmt8, 27, $useUTF8); $lens1[$i++] = strlen($value5) . "\n"; $fout = fopen("xml.out", "w"); fwrite($fout, $value5); @@ -189,57 +200,78 @@ streaming large amounts of data into a database and getting it out as a string e $metadata1 = sqlsrv_field_metadata($stmt8); $count = count($metadata1); sqlsrv_fetch($stmt8); - $value1 = GetField($stmt8, 13, $notWindows); + $value1 = GetField($stmt8, 13, $useUTF8); $lens2[$i++] = strlen($value1); - $value2 = GetField($stmt8, 16, $notWindows); + $value2 = GetField($stmt8, 16, $useUTF8); $lens2[$i++] = strlen($value2) . "\n"; - $value3 = GetField($stmt8, 17, $notWindows); + $value3 = GetField($stmt8, 17, $useUTF8); $lens2[$i++] = strlen($value3) . "\n"; - $value4 = GetField($stmt8, 18, $notWindows); + $value4 = GetField($stmt8, 18, $useUTF8); $lens2[$i++] = strlen($value4) . "\n"; - $value5 = GetField($stmt8, 27, $notWindows); + $value5 = GetField($stmt8, 27, $useUTF8); $lens2[$i++] = strlen($value5) . "\n"; - CompareLengths($filesizes, $lens1, $lens2, $i, $notWindows); + CompareLengths($filesizes, $lens1, $lens2, $i, $useUTF8); echo "Test finished\n"; sqlsrv_free_stmt($stmt8); sqlsrv_close($conn2); +} - function GetField($stmt, $idx, $notWindows) - { - if ($notWindows) { - return sqlsrv_get_field($stmt, $idx, SQLSRV_PHPTYPE_STRING('UTF-8')); - } else { - return sqlsrv_get_field($stmt, $idx, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); - } +function GetField($stmt, $idx, $useUTF8) +{ + if ($useUTF8) { + return sqlsrv_get_field($stmt, $idx, SQLSRV_PHPTYPE_STRING('UTF-8')); + } else { + return sqlsrv_get_field($stmt, $idx, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); } +} - function CompareLengths($filesizes, $lens1, $lens2, $count, $notWindows) - { - if ($notWindows) { - // in Linux or Mac, same field should return same length, and strlen() for Unicode data is different - for ($i = 0; $i < $count; $i++) { - $length = $filesizes[$i]; - if ($lens1[$i] != $length || $lens2[$i] != $length) { - echo "Data length mismatched!\n"; - } - } - } else { - // in Windows, all lengths are equal - $length = 1048576; // number of characters in the data (in ANSI encoding) - for ($i = 0; $i < $count; $i++) { - if ($filesizes[$i] != $length) { - echo "File $i size unexpected\n"; - } - - if ($lens1[$i] != $length || $lens2[$i] != $length) { - echo "Data length mismatched!\n"; - } +function CompareLengths($filesizes, $lens1, $lens2, $count, $useUTF8) +{ + if ($useUTF8) { + // in Linux or Mac, same field should return same length, and strlen() for Unicode data is different + for ($i = 0; $i < $count; $i++) { + $length = $filesizes[$i]; + if ($lens1[$i] != $length || $lens2[$i] != $length) { + echo "Data length mismatched!\n"; } } + } else { + // in Windows, all lengths are equal + $length = 1048576; // number of characters in the data (in ANSI encoding) + for ($i = 0; $i < $count; $i++) { + if ($filesizes[$i] != $length) { + echo "File $i size unexpected\n"; + } + + if ($lens1[$i] != $length || $lens2[$i] != $length) { + echo "Data length mismatched!\n"; + } + } } +} + +// locale must be set before 1st connection +if (!isWindows()) { + setlocale(LC_ALL, "en_US.ISO-8859-1"); +} + +// test ansi only if windows or non-UTF8 locales are supported (ODBC 17 and above) +if (isWindows() || isLocaleSupported()) { + setUTF8Data(false); + runtest(); +} +else { + echo "Test finished\n"; +} + +// test utf8 +setUTF8Data(true); +runtest(); + ?> --EXPECT-- Test finished +Test finished