Merge pull request #592 from yitam/nextResult

Fix to GitHub issues 574 and 580
This commit is contained in:
Jenny Tam 2017-11-15 15:36:46 -08:00 committed by GitHub
commit 04f50341bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 410 additions and 19 deletions

View file

@ -45,7 +45,7 @@ namespace {
// *** internal constants ***
const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field
const int INITIAL_LOB_FIELD_LEN = 2048; // base allocation size when retrieving a LOB field
// *** internal functions ***
@ -1520,9 +1520,9 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in
}
SQLLEN already_read = 0;
SQLLEN to_read = INITIAL_FIELD_STRING_LEN;
SQLLEN to_read = INITIAL_LOB_FIELD_LEN;
sqlsrv_malloc_auto_ptr<char> buffer;
buffer = static_cast<char*>( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN )));
buffer = static_cast<char*>( sqlsrv_malloc( INITIAL_LOB_FIELD_LEN + extra + sizeof( SQLULEN )));
SQLRETURN r = SQL_SUCCESS;
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLLEN last_field_len = 0;

View file

@ -1357,30 +1357,31 @@ struct sqlsrv_stmt : public sqlsrv_context {
bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row
sqlsrv_result_set* current_results; // Current result set
SQLULEN cursor_type; // Type of cursor for the current result set
int fwd_row_index; // fwd_row_index is the current row index, SQL_CURSOR_FORWARD_ONLY
int fwd_row_index; // fwd_row_index is the current row index, SQL_CURSOR_FORWARD_ONLY
int curr_result_set; // the current active result set, 0 by default but will be incremented by core_sqlsrv_next_result
bool has_rows; // Has_rows is set if there are actual rows in the row set
bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called
int last_field_index; // last field retrieved by core_sqlsrv_get_field
bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the
// last results
unsigned long query_timeout; // maximum allowed statement execution time
zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB)
zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB)
// holds output pointers for SQLBindParameter
// We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving
// memory, which is important because we pass the pointer to an element of the deque to SQLBindParameter to hold
std::deque<SQLLEN> param_ind_ptrs; // output pointers for lengths for calls to SQLBindParameter
zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP
zval output_params; // hold all the output parameters
zval param_streams; // track which streams to send data to the server
zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects
zval param_input_strings; // hold all UTF-16 input strings that aren't managed by PHP
zval output_params; // hold all the output parameters
zval param_streams; // track which streams to send data to the server
zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects
bool send_streams_at_exec; // send all stream data right after execution before returning
sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter
unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string
// to the server)
zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals
zval col_cache; // Used by get_field_as_string not to call SQLColAttribute() after every fetch.
zval active_stream; // the currently active stream reading data from the database
zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals
zval col_cache; // Used by get_field_as_string not to call SQLColAttribute() after every fetch.
zval active_stream; // the currently active stream reading data from the database
sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv TSRMLS_DC );
virtual ~sqlsrv_stmt( void );

View file

@ -60,7 +60,8 @@ struct col_cache {
}
};
const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field
const int INITIAL_FIELD_STRING_LEN = 2048; // base allocation size when retrieving a string field
const int INITIAL_AE_FIELD_STRING_LEN = 8000; // base allocation size when retrieving a string field when AE is enabled
// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads
const unsigned int UTF8_MIDBYTE_MASK = 0xc0;
@ -135,6 +136,7 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error
current_results( NULL ),
cursor_type( SQL_CURSOR_FORWARD_ONLY ),
fwd_row_index( -1 ),
curr_result_set( 0 ),
has_rows( false ),
fetch_called( false ),
last_field_index( -1 ),
@ -1138,9 +1140,13 @@ void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool fin
// mark we are past the end of all results
stmt->past_next_result_end = true;
// reset the current active result set
stmt->curr_result_set = 0;
return;
}
stmt->curr_result_set++;
stmt->new_result_set( TSRMLS_C );
}
catch( core::CoreException& e ) {
@ -2236,6 +2242,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
SQLLEN field_len_temp = 0;
SQLLEN sql_display_size = 0;
char* field_value_temp = NULL;
unsigned int intial_field_len = INITIAL_FIELD_STRING_LEN;
try {
@ -2262,6 +2269,11 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
break;
}
if( stmt->conn->ce_option.enabled ) {
// when AE is enabled, increase the intial field len
intial_field_len = INITIAL_AE_FIELD_STRING_LEN;
}
col_cache* cached = NULL;
if ( NULL != ( cached = static_cast< col_cache* >( zend_hash_index_find_ptr( Z_ARRVAL( stmt->col_cache ), static_cast< zend_ulong >( field_index ))))) {
sql_field_type = cached->sql_type;
@ -2282,7 +2294,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
if( sql_display_size == 0 || sql_display_size == INT_MAX ||
sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) {
field_len_temp = INITIAL_FIELD_STRING_LEN;
field_len_temp = intial_field_len;
SQLLEN initiallen = field_len_temp + extra;
@ -2323,7 +2335,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
if( field_len_temp == SQL_NO_TOTAL ) {
// reset the field_len_temp
field_len_temp = INITIAL_FIELD_STRING_LEN;
field_len_temp = intial_field_len;
do {
SQLLEN initial_field_len = field_len_temp;
@ -2378,14 +2390,14 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
&dummy_field_len, false /*handle_warning*/ TSRMLS_CC );
}
else {
// We have already recieved INITIAL_FIELD_STRING_LEN size data.
field_len_temp -= INITIAL_FIELD_STRING_LEN;
// We have already recieved intial_field_len size data.
field_len_temp -= intial_field_len;
// Get the rest of the data.
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN,
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + intial_field_len,
field_len_temp + extra, &dummy_field_len,
true /*handle_warning*/ TSRMLS_CC );
field_len_temp += INITIAL_FIELD_STRING_LEN;
field_len_temp += intial_field_len;
}
if( dummy_field_len == SQL_NULL_DATA ) {
@ -2655,6 +2667,12 @@ bool reset_ae_stream_cursor( _Inout_ sqlsrv_stmt* stmt ) {
// close and reopen the cursor
core::SQLCloseCursor(stmt->current_results->odbc);
core::SQLExecute(stmt);
// advance to the previous active result set
for (int j = 0; j < stmt->curr_result_set; j++) {
core::SQLMoreResults(stmt);
}
// FETCH_NEXT until the cursor reaches the row that it was at
for (int i = 0; i <= stmt->fwd_row_index; i++) {
core::SQLFetchScroll(stmt->current_results->odbc, SQL_FETCH_NEXT, 0);

View file

@ -0,0 +1,170 @@
--TEST--
GitHub issue 574 - Fetch Next Result Test
--DESCRIPTION--
Verifies the functionality of PDOStatement nextRowset
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
try {
$conn = connect();
$tableName = 'test574';
$tableName1 = 'test574_1';
// create two tables with max fields
$columns = array(new ColumnMeta('varchar(max)', 'col1'));
createTable($conn, $tableName, $columns);
$columns = array(new ColumnMeta('varchar(max)', 'col1'));
createTable($conn, $tableName1, $columns);
// insert one row to each table
$phrase = str_repeat('This is a test ', 25000);
$stmt = insertRow($conn, $tableName, array('col1' => $phrase));
unset($stmt);
$phrase1 = str_repeat('This is indeed very long ', 30000);
$stmt = insertRow($conn, $tableName1, array('col1' => $phrase1));
unset($stmt);
// run queries in a batch
$stmt = $conn->prepare("SELECT * FROM [$tableName]; SELECT artist FROM [cd_info]; SELECT * FROM [$tableName1]");
$stmt->execute();
// fetch from $tableName
$row = $stmt->fetch(PDO::FETCH_NUM);
if ($row) {
if ($row[0] === $phrase) {
echo(substr($row[0], 0, 15)) . PHP_EOL;
} else {
echo "Incorrect value substr($row[0], 0, 1000)...!" . PHP_EOL;
}
}
// fetch from cd_info
echo "1. next result? ";
$next = $stmt->nextRowset();
var_dump($next);
$row = $stmt->fetch(PDO::FETCH_NUM);
if ($row) {
echo $row[0] . PHP_EOL;
}
// fetch from $tableName1
echo "2. next result? ";
$next = $stmt->nextRowset();
var_dump($next);
$row = $stmt->fetch(PDO::FETCH_NUM);
if ($row) {
if ($row[0] === $phrase1) {
echo(substr($row[0], 0, 25)) . PHP_EOL;
} else {
echo "Incorrect value substr($row[0], 0, 1000)...!" . PHP_EOL;
}
}
// should be no more next results, first returns false second throws an exception
echo "3. next result? ";
$next = $stmt->nextRowset();
var_dump($next);
$row = $stmt->fetch(PDO::FETCH_NUM);
if ($row) {
fatalError("This is unexpected!\n");
}
echo "4. next result? " . PHP_EOL;
try {
$next = $stmt->nextRowset();
} catch (PDOException $e) {
echo $e->getMessage() . PHP_EOL;
}
// run queries in a batch again, different order this time
$stmt = $conn->prepare("SELECT * FROM [$tableName1]; SELECT * FROM [$tableName]; SELECT artist FROM [cd_info]");
$stmt->execute();
// skip the first two queries
$stmt->nextRowset();
$stmt->nextRowset();
// fetch from cd_info
$row = $stmt->fetch(PDO::FETCH_NUM);
if ($row) {
echo $row[0] . PHP_EOL;
}
// re-execute the statement, should return to the first query in the batch
$stmt->execute();
// fetch from $tableName1
$row = $stmt->fetch(PDO::FETCH_NUM);
if ($row) {
if ($row[0] === $phrase1) {
echo(substr($row[0], 0, 25)) . PHP_EOL;
} else {
echo "Incorrect value substr($row[0], 0, 1000)...!" . PHP_EOL;
}
}
unset($stmt);
// execute a simple query, no more batch
$stmt = $conn->query("SELECT * FROM [$tableName]");
// fetch from $tableName
$row = $stmt->fetch(PDO::FETCH_NUM);
if ($row) {
if ($row[0] === $phrase) {
echo(substr($row[0], 0, 15)) . PHP_EOL;
} else {
echo "Incorrect value substr($row[0], 0, 1000)...!" . PHP_EOL;
}
}
// should be no more next results, first returns false second throws an exception
echo "5. next result? ";
$next = $stmt->nextRowset();
var_dump($next);
echo "6. next result? " . PHP_EOL;
try {
$next = $stmt->nextRowset();
} catch (PDOException $e) {
echo $e->getMessage() . PHP_EOL;
}
dropTable($conn, $tableName);
dropTable($conn, $tableName1);
unset($stmt);
unset($conn);
echo "Done\n";
} catch (PDOException $e) {
echo $e->getMessage();
}
?>
--EXPECT--
This is a test
1. next result? bool(true)
Led Zeppelin
2. next result? bool(true)
This is indeed very long
3. next result? bool(false)
4. next result?
SQLSTATE[IMSSP]: There are no more results returned by the query.
Led Zeppelin
This is indeed very long
This is a test
5. next result? bool(false)
6. next result?
SQLSTATE[IMSSP]: There are no more results returned by the query.
Done

View file

@ -0,0 +1,202 @@
--TEST--
GitHub issue 574 - Fetch Next Result Test
--DESCRIPTION--
Verifies the functionality of sqlsrv_next_result
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
$conn = AE\connect();
$tableName = 'test574';
$tableName1 = 'test574_1';
// create two tables with max fields
$columns = array(new AE\ColumnMeta('varchar(max)', 'col1'));
$stmt = AE\createTable($conn, $tableName, $columns);
if (!$stmt) {
fatalError("Failed to create table for the test\n");
}
$columns = array(new AE\ColumnMeta('varchar(max)', 'col1'));
$stmt = AE\createTable($conn, $tableName1, $columns);
if (!$stmt) {
fatalError("Failed to create table for the test\n");
}
// insert one row to each table
$sql = "insert into $tableName (col1) VALUES (?)";
$phrase = str_repeat('This is a test ', 25000);
$stmt = sqlsrv_prepare($conn, $sql, array($phrase));
if ($stmt) {
$r = sqlsrv_execute($stmt);
if (!$r) {
print_r(sqlsrv_errors());
}
}
$phrase1 = str_repeat('This is indeed very long ', 30000);
$sql = "insert into $tableName1 (col1) VALUES (?)";
$stmt = sqlsrv_prepare($conn, $sql, array($phrase1));
if ($stmt) {
$r = sqlsrv_execute($stmt);
if (!$r) {
print_r(sqlsrv_errors());
}
}
// run queries in a batch
$stmt = sqlsrv_prepare($conn, "SELECT * FROM [$tableName]; SELECT artist FROM [cd_info]; SELECT * FROM [$tableName1]");
if ($stmt) {
$r = sqlsrv_execute($stmt);
if (!$r) {
print_r(sqlsrv_errors());
}
}
// fetch from $tableName
$row = sqlsrv_fetch($stmt);
if ($row) {
$fld = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
if ($fld === $phrase) {
echo(substr($fld, 0, 15)) . PHP_EOL;
} else {
echo "Incorrect value substr($fld, 0, 1000)...!" . PHP_EOL;
}
}
// fetch from cd_info
echo "1. next result? ";
$next = sqlsrv_next_result($stmt);
var_dump($next);
$row = sqlsrv_fetch($stmt);
if ($row) {
$fld = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
echo $fld . PHP_EOL;
}
// fetch from $tableName1
echo "2. next result? ";
$next = sqlsrv_next_result($stmt);
var_dump($next);
$row = sqlsrv_fetch($stmt);
if ($row) {
$fld = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
if ($fld === $phrase1) {
echo(substr($fld, 0, 25)) . PHP_EOL;
} else {
echo "Incorrect value substr($fld, 0, 1000)...!" . PHP_EOL;
}
}
// should be no more next results, first returns NULL second returns false
echo "3. next result? ";
$next = sqlsrv_next_result($stmt);
var_dump($next);
$row = sqlsrv_fetch($stmt);
if ($row) {
fatalError("This is unexpected!\n");
}
echo "4. next result? ";
$next = sqlsrv_next_result($stmt);
var_dump($next);
sqlsrv_free_stmt($stmt);
// run queries in a batch again, different order this time
$stmt = sqlsrv_prepare($conn, "SELECT * FROM [$tableName1]; SELECT * FROM [$tableName]; SELECT artist FROM [cd_info]");
if ($stmt) {
$r = sqlsrv_execute($stmt);
if (!$r) {
print_r(sqlsrv_errors());
}
}
// skip the first two queries
sqlsrv_next_result($stmt);
sqlsrv_next_result($stmt);
// fetch from cd_info
$row = sqlsrv_fetch($stmt);
if ($row) {
$fld = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
echo $fld . PHP_EOL;
}
// re-execute the statement, should return to the first query in the batch
$r = sqlsrv_execute($stmt);
if (!$r) {
print_r(sqlsrv_errors());
}
// fetch from $tableName1
$row = sqlsrv_fetch($stmt);
if ($row) {
$fld = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
if ($fld === $phrase1) {
echo(substr($fld, 0, 25)) . PHP_EOL;
} else {
echo "Incorrect value substr($fld, 0, 1000)...!" . PHP_EOL;
}
}
sqlsrv_free_stmt($stmt);
// execute a simple query, no more batch
$stmt = sqlsrv_prepare($conn, "SELECT * FROM [$tableName]");
if ($stmt) {
$r = sqlsrv_execute($stmt);
if (!$r) {
print_r(sqlsrv_errors());
}
}
// fetch from $tableName
$row = sqlsrv_fetch($stmt);
if ($row) {
$fld = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
if ($fld === $phrase) {
echo(substr($fld, 0, 15)) . PHP_EOL;
} else {
echo "Incorrect value substr($fld, 0, 1000)...!" . PHP_EOL;
}
}
// should be no more next results, first returns NULL second returns false
echo "5. next result? ";
$next = sqlsrv_next_result($stmt);
var_dump($next);
echo "6. next result? ";
$next = sqlsrv_next_result($stmt);
var_dump($next);
dropTable($conn, $tableName);
dropTable($conn, $tableName1);
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
echo "Done\n";
?>
--EXPECT--
This is a test
1. next result? bool(true)
Led Zeppelin
2. next result? bool(true)
This is indeed very long
3. next result? NULL
4. next result? bool(false)
Led Zeppelin
This is indeed very long
This is a test
5. next result? NULL
6. next result? bool(false)
Done