Merge branch 'dev' into codecov

This commit is contained in:
Jenny Tam 2017-11-21 16:33:38 -08:00
commit 33b4922921
19 changed files with 758 additions and 404 deletions

View file

@ -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/) 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 ## Windows/Linux 5.1.1-preview - 2017-10-20
Updated PECL release packages. Here is the list of updates: Updated PECL release packages. Here is the list of updates:

View file

@ -9,6 +9,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \
apt-utils \ apt-utils \
autoconf \ autoconf \
curl \ curl \
libcurl3 \
g++ \ g++ \
gcc \ gcc \
git \ git \
@ -28,17 +29,14 @@ ENV TEST_PHP_SQL_SERVER sql
ENV TEST_PHP_SQL_UID sa ENV TEST_PHP_SQL_UID sa
ENV TEST_PHP_SQL_PWD Password123 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 # set locale to utf-8
RUN locale-gen en_US.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' 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 #install coveralls
RUN pip install --upgrade pip && pip install cpp-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 #another option is to copy source to build directory on image
RUN mkdir -p $PHPSQLDIR RUN mkdir -p $PHPSQLDIR
COPY . $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 chmod +x ./packagize.sh
RUN /bin/bash -c "./packagize.sh" RUN /bin/bash -c "./packagize.sh"
@ -79,4 +83,4 @@ RUN chmod +x ./entrypoint.sh
CMD /bin/bash ./entrypoint.sh CMD /bin/bash ./entrypoint.sh
ENV REPORT_EXIT_STATUS 1 ENV REPORT_EXIT_STATUS 1
ENV TEST_PHP_EXECUTABLE /usr/bin/php ENV TEST_PHP_EXECUTABLE /usr/bin/php

View file

@ -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" ); 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<sqlsrv_stmt*>( stmt->driver_data ) TSRMLS_CC ); core_sqlsrv_next_result( static_cast<sqlsrv_stmt*>( stmt->driver_data ) TSRMLS_CC );
// clear the current meta data since the new result will generate new meta data // clear the current meta data since the new result will generate new meta data

View file

@ -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_sqltype ) == sizeof( zend_long ) );
SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == 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 *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL
try { try {

View file

@ -27,6 +27,17 @@
#include <locale> #include <locale>
#define CP_UTF8 65001 #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_UTF16 1200
#define CP_ACP 0 // default to ANSI code page #define CP_ACP 0 // default to ANSI code page
@ -178,14 +189,15 @@ private:
SystemLocale & operator=( const SystemLocale & ); SystemLocale & operator=( const SystemLocale & );
std::locale * m_pLocale; std::locale * m_pLocale;
UINT m_uAnsiCP;
explicit SystemLocale( const char * localeName ); explicit SystemLocale( const char * localeName );
~SystemLocale(); ~SystemLocale();
static UINT ExpandSpecialCP( UINT codepage ) static UINT ExpandSpecialCP( UINT codepage )
{ {
// Convert CP_ACP, CP_OEM to CP_UTF8 // skip SQLSRV_ENCODING_CHAR
return (codepage < 2 ? CP_UTF8 : codepage); return (codepage <= 3 ? Singleton().m_uAnsiCP : codepage);
} }
// Returns the number of bytes this UTF8 code point expects // Returns the number of bytes this UTF8 code point expects
@ -217,7 +229,7 @@ private:
inline UINT SystemLocale::AnsiCP() const inline UINT SystemLocale::AnsiCP() const
{ {
return CP_UTF8; return m_uAnsiCP;
} }
inline UINT SystemLocale::MaxCharCchSize( UINT codepage ) inline UINT SystemLocale::MaxCharCchSize( UINT codepage )

View file

@ -1,5 +1,5 @@
//--------------------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------------------
// File: LocalizationImpl.hpp // File: localizationimpl.cpp
// //
// Contents: Contains non-inline code for the SystemLocale class // Contents: Contains non-inline code for the SystemLocale class
// Must be included in one c/cpp file per binary // 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" }, { 1256, "CP1256//TRANSLIT" },
{ 1257, "CP1257//TRANSLIT" }, { 1257, "CP1257//TRANSLIT" },
{ 1258, "CP1258//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" } { 12000, "UTF-32LE" }
}; };
const size_t cp_iconv::g_cp_iconv_count = ARRAYSIZE(cp_iconv::g_cp_iconv); 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 ) SystemLocale::SystemLocale( const char * localeName )
: m_pLocale( new std::locale(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() SystemLocale::~SystemLocale()
@ -285,7 +331,8 @@ const SystemLocale & SystemLocale::Singleton()
#if !defined(__GNUC__) || defined(NO_THREADSAFE_STATICS) #if !defined(__GNUC__) || defined(NO_THREADSAFE_STATICS)
#error "Relying on GCC's threadsafe initialization of local statics." #error "Relying on GCC's threadsafe initialization of local statics."
#endif #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; return s_Default;
} }

View file

@ -28,7 +28,7 @@
// Increase Patch for backward compatible fixes. // Increase Patch for backward compatible fixes.
#define SQLVERSION_MAJOR 5 #define SQLVERSION_MAJOR 5
#define SQLVERSION_MINOR 1 #define SQLVERSION_MINOR 1
#define SQLVERSION_PATCH 1 #define SQLVERSION_PATCH 2
#define SQLVERSION_BUILD 0 #define SQLVERSION_BUILD 0
// Semantic versioning pre-release // Semantic versioning pre-release

View file

@ -560,32 +560,6 @@ PHP_FUNCTION( sqlsrv_next_result )
PROCESS_PARAMS( stmt, "r", _FN_, 0 ); PROCESS_PARAMS( stmt, "r", _FN_, 0 );
try { 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 ); core_sqlsrv_next_result( stmt TSRMLS_CC, true );

View file

@ -1,100 +1,179 @@
--TEST-- --TEST--
Error messages from null result sets Error messages from nonempty, empty, and null result sets
--DESCRIPTION-- --DESCRIPTION--
Test that calling nextRowset() on an empty result set produces the correct error message. Fix for Github 507. Test that calling nextRowset() and fetching on nonempty, empty, and null result sets produces the correct results or error messages.
--SKIPIF-- --SKIPIF--
<?php require('skipif.inc'); ?> <?php require('skipif.inc'); ?>
--FILE-- --FILE--
<?php <?php
require_once("MsSetup.inc"); require_once("MsSetup.inc");
require_once("MsCommon.inc"); require_once("MsCommon.inc");
$conn = new PDO( "sqlsrv:Server = $server; Database = $databaseName; ", $uid, $pwd ); // These are the error messages we expect at various points below
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); $errorNoMoreResults = "There are no more results returned by the query.";
$errorNoFields = "The active result for the query contains no fields.";
DropTable($conn, 'TestEmptySetTable');
$stmt = $conn->query("CREATE TABLE TestEmptySetTable ([c1] nvarchar(10),[c2] nvarchar(10))"); // This function compares the expected error message and the error returned by errorInfo().
$stmt = $conn->query("INSERT INTO TestEmptySetTable (c1, c2) VALUES ('a', 'b')"); function CheckError($stmt, $expectedError=NULL)
{
// Create a procedure that can return a result set or can return nothing $actualError = $stmt->errorInfo();
DropProc($conn, 'TestEmptySetProc');
$stmt = $conn->query("CREATE PROCEDURE TestEmptySetProc @a nvarchar(10), @b nvarchar(10) if ($actualError[2] != $expectedError) {
AS SET NOCOUNT ON echo "Wrong error message:\n";
BEGIN print_r($actualError);
IF @b='b' }
BEGIN }
SELECT 'a' as testValue
END function Fetch($stmt, $error=NULL)
ELSE {
BEGIN echo "Fetch...\n";
UPDATE TestEmptySetTable SET c2 = 'c' WHERE c1 = @a $result = $stmt->fetchObject();
END print_r($result);
END"); CheckError($stmt, $error);
}
// errors out when reaching the second nextRowset() call
// returned error indicates there are no more results function NextResult($stmt, $error=NULL)
echo "Return a nonempty result set:\n"; {
try echo "Next result...\n";
{ $stmt->nextRowset();
$stmt = $conn->query("TestEmptySetProc @a='a', @b='b'"); CheckError($stmt, $error);
$result = $stmt->fetchAll(); }
print_r($result);
$stmt->nextRowset(); $conn = new PDO( "sqlsrv:Server = $server; Database = $databaseName; ", $uid, $pwd );
$result = $stmt->fetchAll(); $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
print_r($result);
$stmt->nextRowset(); DropTable($conn, 'TestEmptySetTable');
} $stmt = $conn->query("CREATE TABLE TestEmptySetTable ([c1] nvarchar(10),[c2] nvarchar(10))");
catch(Exception $e) $stmt = $conn->query("INSERT INTO TestEmptySetTable (c1, c2) VALUES ('a', 'b')");
{
echo $e->getMessage()."\n"; // 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)
// errors out indicating the result set contains no fields AS SET NOCOUNT ON
echo "Return an empty result set, call nextRowset on it before fetching anything:\n"; BEGIN
try IF @b='b'
{ BEGIN
$stmt = $conn->query("TestEmptySetProc @a='a', @b='c'"); SELECT 'a' as testValue
$stmt->nextRowset(); END
} ELSE IF @b='w'
catch(Exception $e) BEGIN
{ SELECT * FROM TestEmptySetTable WHERE c1 = @b
echo $e->getMessage()."\n"; END
} ELSE
BEGIN
// errors out indicating the result set contains no fields UPDATE TestEmptySetTable SET c2 = 'c' WHERE c1 = @a
echo "Return an empty result set, call fetch on it:\n"; END
try END");
{
$stmt = $conn->query("TestEmptySetProc @a='a', @b='c'"); // Call fetch on a nonempty result set
$result = $stmt->fetchAll(); echo "Nonempty result set, call fetch first: ###############################\n";
print_r($result);
} $stmt = $conn->query("TestEmptySetProc @a='a', @b='b'");
catch(Exception $e) Fetch($stmt);
{ NextResult($stmt);
echo $e->getMessage()."\n"; Fetch($stmt);
} NextResult($stmt, $errorNoMoreResults);
$stmt = $conn->query("DROP TABLE TestEmptySetTable"); // Call nextRowset on a nonempty result set
$stmt = $conn->query("DROP PROCEDURE TestEmptySetProc"); echo "Nonempty result set, call nextRowset first: #########################\n";
$conn = null; $stmt = $conn->query("TestEmptySetProc @a='a', @b='b'");
?> NextResult($stmt);
--EXPECT-- Fetch($stmt);
Return a nonempty result set: NextResult($stmt, $errorNoMoreResults);
Array
( // Call nextRowset twice in succession on a nonempty result set
[0] => Array echo "Nonempty result set, call nextRowset twice: #########################\n";
(
[testValue] => a $stmt = $conn->query("TestEmptySetProc @a='a', @b='b'");
[0] => a NextResult($stmt);
) NextResult($stmt, $errorNoMoreResults);
) // Call fetch on an empty result set
Array echo "Empty result set, call fetch first: ##################################\n";
(
) $stmt = $conn->query("TestEmptySetProc @a='a', @b='w'");
SQLSTATE[IMSSP]: There are no more results returned by the query. Fetch($stmt);
Return an empty result set, call nextRowset on it before fetching anything: NextResult($stmt);
SQLSTATE[IMSSP]: The active result for the query contains no fields. Fetch($stmt);
Return an empty result set, call fetch on it: NextResult($stmt, $errorNoMoreResults);
SQLSTATE[IMSSP]: The active result for the query contains no fields.
// 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...

View file

@ -454,4 +454,15 @@ function handleErrors()
} }
} }
?> // 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;
}
}
?>

View file

@ -9,7 +9,7 @@
*/ */
function getTestData($index) function getTestData_UTF8($index)
{ {
$inputs = null; $inputs = null;
switch ($index) switch ($index)

View file

@ -732,12 +732,13 @@ function getInsertArray($index)
{ {
if (! UseUTF8data()) { if (! UseUTF8data()) {
require_once('MsData.inc'); require_once('MsData.inc');
return getTestData($index);
} else { } else {
require_once('MsData_UTF8.inc'); require_once('MsData_UTF8.inc');
return getTestData_UTF8($index);
} }
// get array of input values // get array of input values
return getTestData($index);
} }
/** /**
@ -1184,4 +1185,4 @@ function getSampleData($k)
} }
} }
?> ?>

View file

@ -1,9 +1,14 @@
--TEST-- --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-- --ENV--
PHPT_EXEC=true PHPT_EXEC=true
--SKIPIF-- --SKIPIF--
<?php require('skipif_versions_old.inc'); ?> <?
// locale must be set before 1st connection
if ( !isWindows() ) {
setlocale(LC_ALL, "en_US.ISO-8859-1");
}
?>
--FILE-- --FILE--
<?php <?php
@ -11,13 +16,15 @@ require_once('MsCommon.inc');
function fetchFields() function fetchFields()
{ {
$testName = "Fetch - Field Data";
startTest($testName);
setup(); setup();
$tableName = 'TC43test'; $tableName = 'TC43test';
$conn1 = AE\connect(); if (useUTF8Data()) {
$conn1 = AE\connect(array('CharacterSet'=>'UTF-8'));
} else {
$conn1 = AE\connect();
}
AE\createTestTable($conn1, $tableName); AE\createTestTable($conn1, $tableName);
$startRow = 1; $startRow = 1;
@ -62,8 +69,6 @@ function fetchFields()
dropTable($conn1, $tableName); dropTable($conn1, $tableName);
sqlsrv_close($conn1); sqlsrv_close($conn1);
endTest($testName);
} }
function checkData($col, $actual, $expected) function checkData($col, $actual, $expected)
@ -91,16 +96,36 @@ function checkData($col, $actual, $expected)
return ($success); return ($success);
} }
if (! isWindows()) { if (!isWindows()) {
setUTF8Data(true); 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 { try {
setUTF8Data(true);
fetchFields(); fetchFields();
} catch (Exception $e) { } catch (Exception $e) {
echo $e->getMessage(); echo $e->getMessage();
} }
setUTF8Data(false); endTest($testName);
?> ?>
--EXPECT-- --EXPECT--
Test "Fetch - Field Data" completed successfully. Test "Fetch - Field Data" completed successfully.
Test "Fetch - Field Data" completed successfully.

View file

@ -6,24 +6,25 @@ by checking all fetch type modes.
--ENV-- --ENV--
PHPT_EXEC=true PHPT_EXEC=true
--SKIPIF-- --SKIPIF--
<?php require('skipif_versions_old.inc'); ?> <?
// locale must be set before 1st connection
if ( !isWindows() ) {
setlocale(LC_ALL, "en_US.ISO-8859-1");
}
?>
--FILE-- --FILE--
<?php <?php
require_once('MsCommon.inc'); require_once('MsCommon.inc');
function fetchRow($minFetchMode, $maxFetchMode) function fetchRow($minFetchMode, $maxFetchMode)
{ {
$testName = "Fetch - Array";
startTest($testName);
if (!isMarsSupported()) { if (!isMarsSupported()) {
endTest($testName);
return; return;
} }
setup(); setup();
$tableName = 'TC44test'; $tableName = 'TC44test';
if (!isWindows()) { if (useUTF8Data()) {
$conn1 = AE\connect(array( 'CharacterSet'=>'UTF-8' )); $conn1 = AE\connect(array( 'CharacterSet'=>'UTF-8' ));
} else { } else {
$conn1 = AE\connect(); $conn1 = AE\connect();
@ -43,7 +44,6 @@ function fetchRow($minFetchMode, $maxFetchMode)
} else { } else {
$count = sqlsrv_num_fields($stmt1); $count = sqlsrv_num_fields($stmt1);
if ($count != $numFields) { if ($count != $numFields) {
setUTF8Data(false);
die("Unexpected number of fields: $count"); die("Unexpected number of fields: $count");
} }
} }
@ -73,8 +73,6 @@ function fetchRow($minFetchMode, $maxFetchMode)
dropTable($conn1, $tableName); dropTable($conn1, $tableName);
sqlsrv_close($conn1); sqlsrv_close($conn1);
endTest($testName);
} }
function fetchArray($stmt, $stmtRef, $mode, $rows, $fields) function fetchArray($stmt, $stmtRef, $mode, $rows, $fields)
@ -104,13 +102,11 @@ function fetchArray($stmt, $stmtRef, $mode, $rows, $fields)
} }
$rowSize = count($row); $rowSize = count($row);
if ($rowSize != $size) { if ($rowSize != $size) {
setUTF8Data(false);
die("Row array has an incorrect size: ".$rowSize); die("Row array has an incorrect size: ".$rowSize);
} }
$rowRref = sqlsrv_fetch($stmtRef); $rowRref = sqlsrv_fetch($stmtRef);
for ($j = 0; $j < $fields; $j++) { for ($j = 0; $j < $fields; $j++) {
if (!checkData($row, $stmtRef, $j, $fetchMode)) { if (!checkData($row, $stmtRef, $j, $fetchMode)) {
setUTF8Data(false);
die("Data corruption on row ".($i + 1)." column ".($j + 1)); die("Data corruption on row ".($i + 1)." column ".($j + 1));
} }
} }
@ -155,17 +151,37 @@ function checkData($row, $stmt, $index, $mode)
return ($success); return ($success);
} }
// locale must be set before 1st connection
if (!isWindows()) { 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 { try {
setUTF8Data(true);
fetchRow(1, 4); fetchRow(1, 4);
} catch (Exception $e) { } catch (Exception $e) {
echo $e->getMessage(); echo $e->getMessage();
} }
setUTF8Data(false); endTest($testName);
?> ?>
--EXPECT-- --EXPECT--
Test "Fetch - Array" completed successfully. Test "Fetch - Array" completed successfully.
Test "Fetch - Array" completed successfully.

View file

@ -5,24 +5,29 @@ Verifies the functionality of "sqlsrv_next_result"
--ENV-- --ENV--
PHPT_EXEC=true PHPT_EXEC=true
--SKIPIF-- --SKIPIF--
<?php require('skipif_versions_old.inc'); ?> <?
// locale must be set before 1st connection
if ( !isWindows() ) {
setlocale(LC_ALL, "en_US.ISO-8859-1");
}
?>
--FILE-- --FILE--
<?php <?php
require_once('MsCommon.inc'); require_once('MsCommon.inc');
function fetchFields() function fetchFields()
{ {
$testName = "Fetch - Next Result";
startTest($testName);
if (!IsMarsSupported()) { if (!IsMarsSupported()) {
endTest($testName);
return; return;
} }
setup(); setup();
$tableName = 'TC46test'; $tableName = 'TC46test';
$conn1 = AE\connect(); if (useUTF8Data()) {
$conn1 = AE\connect(array('CharacterSet'=>'UTF-8'));
} else {
$conn1 = AE\connect();
}
AE\createTestTable($conn1, $tableName); AE\createTestTable($conn1, $tableName);
$noRows = 10; $noRows = 10;
@ -77,21 +82,39 @@ function fetchFields()
dropTable($conn1, $tableName); dropTable($conn1, $tableName);
sqlsrv_close($conn1); sqlsrv_close($conn1);
endTest($testName);
} }
// locale must be set before 1st connection
if (!isWindows()) { 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 { try {
setUTF8Data(true);
fetchFields(); fetchFields();
} catch (Exception $e) { } catch (Exception $e) {
echo $e->getMessage(); echo $e->getMessage();
} }
setUTF8Data(false); endTest($testName);
?> ?>
--EXPECT-- --EXPECT--
Test "Fetch - Next Result" completed successfully. Test "Fetch - Next Result" completed successfully.
Test "Fetch - Next Result" completed successfully.

View file

@ -6,19 +6,20 @@ can be successfully retrieved as streams.
--ENV-- --ENV--
PHPT_EXEC=true PHPT_EXEC=true
--SKIPIF-- --SKIPIF--
<?php require('skipif_versions_old.inc'); ?> <?// locale must be set before 1st connection
if ( !isWindows() ) {
setlocale(LC_ALL, "en_US.ISO-8859-1");
}
?>
--FILE-- --FILE--
<?php <?php
require_once('MsCommon.inc'); require_once('MsCommon.inc');
function streamRead($noRows, $startRow) function streamRead($noRows, $startRow)
{ {
$testName = "Stream - Read";
startTest($testName);
setup(); setup();
$tableName = 'TC51test'; $tableName = 'TC51test';
if (! isWindows()) { if (useUTF8Data()) {
$conn1 = AE\connect(array( 'CharacterSet'=>'UTF-8' )); $conn1 = AE\connect(array( 'CharacterSet'=>'UTF-8' ));
} else { } else {
$conn1 = AE\connect(); $conn1 = AE\connect();
@ -51,8 +52,6 @@ function streamRead($noRows, $startRow)
dropTable($conn1, $tableName); dropTable($conn1, $tableName);
sqlsrv_close($conn1); sqlsrv_close($conn1);
endTest($testName);
} }
function verifyStream($stmt, $row, $colIndex) function verifyStream($stmt, $row, $colIndex)
@ -80,7 +79,6 @@ function verifyStream($stmt, $row, $colIndex)
fclose($stream); fclose($stream);
$data = AE\getInsertData($row, $col); $data = AE\getInsertData($row, $col);
if (!checkData($col, $value, $data)) { if (!checkData($col, $value, $data)) {
setUTF8Data(false);
trace("Data corruption on row $row column $col\n"); trace("Data corruption on row $row column $col\n");
die("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); return ($success);
} }
if (! isWindows()) { // locale must be set before 1st connection
setUTF8Data(true); 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 { try {
setUTF8Data(true);
streamRead(20, 1); streamRead(20, 1);
} catch (Exception $e) { } catch (Exception $e) {
echo $e->getMessage(); echo $e->getMessage();
} }
endTest($testName);
setUTF8Data(false);
?> ?>
--EXPECT-- --EXPECT--
Test "Stream - Read" completed successfully. Test "Stream - Read" completed successfully.
Test "Stream - Read" completed successfully.

View file

@ -5,20 +5,23 @@ Verifies the streaming behavior with scrollable resultsets.
--ENV-- --ENV--
PHPT_EXEC=true PHPT_EXEC=true
--SKIPIF-- --SKIPIF--
<?php require('skipif_versions_old.inc'); ?> <?
// locale must be set before 1st connection
if ( !isWindows() ) {
setlocale(LC_ALL, "en_US.ISO-8859-1");
}
?>
--FILE-- --FILE--
<?php <?php
require_once('MsCommon.inc'); require_once('MsCommon.inc');
function streamScroll($noRows, $startRow) function streamScroll($noRows, $startRow)
{ {
$testName = "Stream - Scrollable";
startTest($testName);
setup(); setup();
$tableName = "TC55test"; $tableName = "TC55test";
if (! isWindows()) {
$conn1 = AE\connect(array( 'CharacterSet'=>'UTF-8' )); if (useUTF8Data()) {
$conn1 = AE\connect(array('CharacterSet'=>'UTF-8'));
} else { } else {
$conn1 = AE\connect(); $conn1 = AE\connect();
} }
@ -84,8 +87,6 @@ function streamScroll($noRows, $startRow)
dropTable($conn1, $tableName); dropTable($conn1, $tableName);
sqlsrv_close($conn1); sqlsrv_close($conn1);
endTest($testName);
} }
function verifyStream($stmt, $row, $colIndex) function verifyStream($stmt, $row, $colIndex)
@ -150,16 +151,37 @@ function checkData($col, $actual, $expected)
return ($success); return ($success);
} }
if (! isWindows()) { // locale must be set before 1st connection
setUTF8Data(true); 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 { try {
setUTF8Data(true);
streamScroll(20, 1); streamScroll(20, 1);
} catch (Exception $e) { } catch (Exception $e) {
echo $e->getMessage(); echo $e->getMessage();
} }
setUTF8Data(false); endTest($testName);
?> ?>
--EXPECT-- --EXPECT--
Test "Stream - Scrollable" completed successfully. Test "Stream - Scrollable" completed successfully.
Test "Stream - Scrollable" completed successfully.

View file

@ -1,124 +1,208 @@
--TEST-- --TEST--
Error messages from null result sets Error messages from nonempty, empty, and null result sets
--DESCRIPTION-- --DESCRIPTION--
Test that calling sqlsrv_next_result() on a null result set produces the correct error message. Fix for Github 507. Test that calling sqlsrv_next_result() and fetching on nonempty, empty, and null result sets produces the correct results or error messages.
--SKIPIF-- --SKIPIF--
<?php require('skipif.inc'); ?> <?php require('skipif.inc'); ?>
--FILE-- --FILE--
<?php <?php
require_once("MsSetup.inc"); require_once("MsSetup.inc");
require_once("MsCommon.inc"); require_once("MsCommon.inc");
$conn = sqlsrv_connect($server, array("Database"=>$databaseName, "uid"=>$uid, "pwd"=>$pwd)); // These are the error messages we expect at various points below
$errorNoMoreResults = "There are no more results returned by the query.";
DropTable($conn, 'TestEmptySetTable'); $errorNoMoreRows = "There are no more rows in the active result set. Since this result set is not scrollable, no more data may be retrieved.";
$stmt = sqlsrv_query($conn, "CREATE TABLE TestEmptySetTable ([c1] nvarchar(10),[c2] nvarchar(10))"); $errorNoFields = "The active result for the query contains no fields.";
$stmt = sqlsrv_query($conn, "INSERT INTO TestEmptySetTable (c1, c2) VALUES ('a', 'b')");
// Variable function gets an error message that depends on the OS
// Create a procedure that can return a result set or can return nothing function getFuncSeqError()
DropProc($conn, 'TestEmptySetProc'); {
$stmt = sqlsrv_query($conn, "CREATE PROCEDURE TestEmptySetProc @a nvarchar(10), @b nvarchar(10) if ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' ) {
AS SET NOCOUNT ON return "[Microsoft][ODBC Driver Manager] Function sequence error";
BEGIN } else {
IF @b='b' return "[unixODBC][Driver Manager]Function sequence error";
BEGIN }
SELECT 'a' as testValue }
END
ELSE $errorFuncSeq = 'getFuncSeqError';
BEGIN
UPDATE TestEmptySetTable SET c2 = 'c' WHERE c1 = @a // This function takes an array of expected error messages and compares the
END // contents to the actual errors
END"); function CheckError($expectedErrors)
{
// errors out when reaching the second nextRowset() call $actualErrors = sqlsrv_errors();
// returned error indicates there are no more results $sizeActualErrors = 0;
echo "Return a nonempty result set:\n"; if (!is_null($actualErrors)) {
$sizeActualErrors = sizeof($actualErrors);
$stmt = sqlsrv_query($conn,"TestEmptySetProc @a='a', @b='b'"); }
$result = sqlsrv_fetch_array($stmt);
print_r($result); if (($sizeActualErrors) != sizeof($expectedErrors)) {
sqlsrv_next_result($stmt); echo "Wrong size for error array\n";
$result = sqlsrv_fetch_array($stmt); print_r($actualErrors);
print_r($result); return;
sqlsrv_next_result($stmt); }
print_r(sqlsrv_errors()); $i = 0;
// errors out indicating the result set contains no fields foreach ($expectedErrors as $e) {
echo "Return an empty result set, call nextRowset on it before fetching anything:\n"; if ($actualErrors[$i]['message'] != $e) {
echo "Wrong error message:\n";
$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'"); print_r($actualErrors[$i]);
sqlsrv_next_result($stmt); }
print_r(sqlsrv_errors()); $i++;
}
// errors out indicating the result set contains no fields }
echo "Return an empty result set, call fetch on it:\n";
function Fetch($stmt, $errors)
$stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'"); {
$result = sqlsrv_fetch_array($stmt); echo "Fetch...\n";
print_r($result); $result = sqlsrv_fetch_array($stmt);
print_r(sqlsrv_errors()); print_r($result);
CheckError($errors);
$stmt = sqlsrv_query($conn, "DROP TABLE TestEmptySetTable"); }
$stmt = sqlsrv_query($conn, "DROP PROCEDURE TestEmptySetProc");
sqlsrv_free_stmt($stmt); function NextResult($stmt, $errors)
sqlsrv_close($conn); {
?> echo "Next result...\n";
--EXPECTF-- sqlsrv_next_result($stmt);
Return a nonempty result set: CheckError($errors);
Array }
(
[0] => a $conn = sqlsrv_connect($server, array("Database"=>$databaseName, "uid"=>$uid, "pwd"=>$pwd));
[testValue] => a
) DropTable($conn, 'TestEmptySetTable');
Array $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')");
[0] => Array
( // Create a procedure that can return a nonempty result set, an empty result set, or a null result
[0] => IMSSP DropProc($conn, 'TestEmptySetProc');
[SQLSTATE] => IMSSP $stmt = sqlsrv_query($conn, "CREATE PROCEDURE TestEmptySetProc @a nvarchar(10), @b nvarchar(10)
[1] => -26 AS SET NOCOUNT ON
[code] => -26 BEGIN
[2] => There are no more results returned by the query. IF @b='b'
[message] => There are no more results returned by the query. BEGIN
) SELECT 'a' as testValue
END
[1] => Array ELSE IF @b='w'
( BEGIN
[0] => HY010 SELECT * FROM TestEmptySetTable WHERE c1 = @b
[SQLSTATE] => HY010 END
[1] => 0 ELSE
[code] => 0 BEGIN
[2] => [%rMicrosoft|unixODBC%r][%rODBC D|D%rriver Manager]%r[ ]{0,1}%rFunction sequence error UPDATE TestEmptySetTable SET c2 = 'c' WHERE c1 = @a
[message] => [%rMicrosoft|unixODBC%r][%rODBC D|D%rriver Manager]%r[ ]{0,1}%rFunction sequence error END
) END");
) // Call fetch on a nonempty result set
Return an empty result set, call nextRowset on it before fetching anything: echo "Nonempty result set, call fetch first: ###############################\n";
Array
( $stmt = sqlsrv_query($conn,"TestEmptySetProc @a='a', @b='b'");
[0] => Array Fetch($stmt, []);
( NextResult($stmt, []);
[0] => IMSSP Fetch($stmt, [$errorFuncSeq()]);
[SQLSTATE] => IMSSP NextResult($stmt, [$errorNoMoreResults, $errorFuncSeq()]);
[1] => -28
[code] => -28 // Call next_result on a nonempty result set
[2] => The active result for the query contains no fields. echo "Nonempty result set, call next_result first: #########################\n";
[message] => The active result for the query contains no fields.
) $stmt = sqlsrv_query($conn,"TestEmptySetProc @a='a', @b='b'");
NextResult($stmt, []);
) Fetch($stmt, [$errorFuncSeq()]);
Return an empty result set, call fetch on it: NextResult($stmt, [$errorNoMoreResults, $errorFuncSeq()]);
Array
( // Call next_result twice in succession on a nonempty result set
[0] => Array echo "Nonempty result set, call next_result twice: #########################\n";
(
[0] => IMSSP $stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='b'");
[SQLSTATE] => IMSSP NextResult($stmt, []);
[1] => -28 NextResult($stmt, [$errorNoMoreResults]);
[code] => -28
[2] => The active result for the query contains no fields. // Call fetch on an empty result set
[message] => The active result for the query contains no fields. 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...

View file

@ -1,20 +1,32 @@
--TEST-- --TEST--
streaming large amounts of data into a database and getting it out as a string exactly the same. streaming large amounts of data into a database and getting it out as a string exactly the same.
--SKIPIF-- --SKIPIF--
<?php require('skipif.inc'); ?> <?
// locale must be set before 1st connection
if ( !isWindows() ) {
setlocale(LC_ALL, "en_US.ISO-8859-1");
}
php require('skipif.inc');
?>
--FILE-- --FILE--
<?php <?php
require_once("MsCommon.inc");
function runtest()
{
set_time_limit(0); set_time_limit(0);
sqlsrv_configure('WarningsReturnAsErrors', 0); sqlsrv_configure('WarningsReturnAsErrors', 0);
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL); sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL);
sqlsrv_configure('LogSubsystems', SQLSRV_LOG_SYSTEM_OFF); sqlsrv_configure('LogSubsystems', SQLSRV_LOG_SYSTEM_OFF);
require_once("MsCommon.inc"); $useUTF8 = useUTF8Data();
if ($useUTF8) {
$notWindows = (! isWindows()); $conn1 = connect(array('CharacterSet'=>'UTF-8'));
// required charset UTF-8 when NOT running in Windows } else {
$conn1 = connect();
$conn1 = ($notWindows) ? connect(array( 'CharacterSet'=>'UTF-8' )) : connect() ; }
if ($conn1 === false) { if ($conn1 === false) {
fatalError("Failed to connect"); 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_free_stmt($stmt2);
sqlsrv_close($conn1); sqlsrv_close($conn1);
if ($notWindows) { if ($useUTF8) {
$conn2 = connect(array( 'CharacterSet' =>'utf-8' )); $conn2 = connect(array('CharacterSet'=>'UTF-8'));
} else { } else {
$conn2 = connect(); $conn2 = connect();
} }
if ($conn2 === false) { if ($conn2 === false) {
echo "sqlsrv_connect failed 2nd.\n"; echo "sqlsrv_connect failed 2nd.\n";
die(print_r(sqlsrv_errors(), true)); die(print_r(sqlsrv_errors(), true));
} }
if ($notWindows) { if ($useUTF8) {
require('test_stream_large_data_UTF8.inc'); require('test_stream_large_data_UTF8.inc');
GenerateInputUTF8Data(); GenerateInputUTF8Data();
} else { } 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); $metadata1 = sqlsrv_field_metadata($stmt8);
$count = count($metadata1); $count = count($metadata1);
sqlsrv_fetch($stmt8); sqlsrv_fetch($stmt8);
$value1 = GetField($stmt8, 13, $notWindows); $value1 = GetField($stmt8, 13, $useUTF8);
$lens1[$i++] = strlen($value1) . "\n"; $lens1[$i++] = strlen($value1) . "\n";
$fout = fopen("varchar_max.out", "w"); $fout = fopen("varchar_max.out", "w");
fwrite($fout, $value1); fwrite($fout, $value1);
fclose($fout); fclose($fout);
$value2 = GetField($stmt8, 16, $notWindows); $value2 = GetField($stmt8, 16, $useUTF8);
$lens1[$i++] = strlen($value2) . "\n"; $lens1[$i++] = strlen($value2) . "\n";
$fout = fopen("nvarchar_max.out", "w"); $fout = fopen("nvarchar_max.out", "w");
fwrite($fout, $value2); fwrite($fout, $value2);
fclose($fout); fclose($fout);
$value3 = GetField($stmt8, 17, $notWindows); $value3 = GetField($stmt8, 17, $useUTF8);
$lens1[$i++] = strlen($value3) . "\n"; $lens1[$i++] = strlen($value3) . "\n";
$fout = fopen("text.out", "w"); $fout = fopen("text.out", "w");
fwrite($fout, $value3); fwrite($fout, $value3);
fclose($fout); fclose($fout);
$value4 = GetField($stmt8, 18, $notWindows); $value4 = GetField($stmt8, 18, $useUTF8);
$lens1[$i++] = strlen($value4) . "\n"; $lens1[$i++] = strlen($value4) . "\n";
$fout = fopen("ntext.out", "w"); $fout = fopen("ntext.out", "w");
fwrite($fout, $value4); fwrite($fout, $value4);
fclose($fout); fclose($fout);
$value5 = GetField($stmt8, 27, $notWindows); $value5 = GetField($stmt8, 27, $useUTF8);
$lens1[$i++] = strlen($value5) . "\n"; $lens1[$i++] = strlen($value5) . "\n";
$fout = fopen("xml.out", "w"); $fout = fopen("xml.out", "w");
fwrite($fout, $value5); 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); $metadata1 = sqlsrv_field_metadata($stmt8);
$count = count($metadata1); $count = count($metadata1);
sqlsrv_fetch($stmt8); sqlsrv_fetch($stmt8);
$value1 = GetField($stmt8, 13, $notWindows); $value1 = GetField($stmt8, 13, $useUTF8);
$lens2[$i++] = strlen($value1); $lens2[$i++] = strlen($value1);
$value2 = GetField($stmt8, 16, $notWindows); $value2 = GetField($stmt8, 16, $useUTF8);
$lens2[$i++] = strlen($value2) . "\n"; $lens2[$i++] = strlen($value2) . "\n";
$value3 = GetField($stmt8, 17, $notWindows); $value3 = GetField($stmt8, 17, $useUTF8);
$lens2[$i++] = strlen($value3) . "\n"; $lens2[$i++] = strlen($value3) . "\n";
$value4 = GetField($stmt8, 18, $notWindows); $value4 = GetField($stmt8, 18, $useUTF8);
$lens2[$i++] = strlen($value4) . "\n"; $lens2[$i++] = strlen($value4) . "\n";
$value5 = GetField($stmt8, 27, $notWindows); $value5 = GetField($stmt8, 27, $useUTF8);
$lens2[$i++] = strlen($value5) . "\n"; $lens2[$i++] = strlen($value5) . "\n";
CompareLengths($filesizes, $lens1, $lens2, $i, $notWindows); CompareLengths($filesizes, $lens1, $lens2, $i, $useUTF8);
echo "Test finished\n"; echo "Test finished\n";
sqlsrv_free_stmt($stmt8); sqlsrv_free_stmt($stmt8);
sqlsrv_close($conn2); sqlsrv_close($conn2);
}
function GetField($stmt, $idx, $notWindows) function GetField($stmt, $idx, $useUTF8)
{ {
if ($notWindows) { if ($useUTF8) {
return sqlsrv_get_field($stmt, $idx, SQLSRV_PHPTYPE_STRING('UTF-8')); return sqlsrv_get_field($stmt, $idx, SQLSRV_PHPTYPE_STRING('UTF-8'));
} else { } else {
return sqlsrv_get_field($stmt, $idx, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); return sqlsrv_get_field($stmt, $idx, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
}
} }
}
function CompareLengths($filesizes, $lens1, $lens2, $count, $notWindows) function CompareLengths($filesizes, $lens1, $lens2, $count, $useUTF8)
{ {
if ($notWindows) { if ($useUTF8) {
// in Linux or Mac, same field should return same length, and strlen() for Unicode data is different // in Linux or Mac, same field should return same length, and strlen() for Unicode data is different
for ($i = 0; $i < $count; $i++) { for ($i = 0; $i < $count; $i++) {
$length = $filesizes[$i]; $length = $filesizes[$i];
if ($lens1[$i] != $length || $lens2[$i] != $length) { if ($lens1[$i] != $length || $lens2[$i] != $length) {
echo "Data length mismatched!\n"; 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";
}
} }
} }
} 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-- --EXPECT--
Test finished Test finished
Test finished