Skip to content

Commit

Permalink
Merge pull request #592 from yitam/nextResult
Browse files Browse the repository at this point in the history
Fix to GitHub issues 574 and 580
  • Loading branch information
yitam authored Nov 15, 2017
2 parents 04a1637 + 1a24596 commit 04f5034
Show file tree
Hide file tree
Showing 5 changed files with 410 additions and 19 deletions.
6 changes: 3 additions & 3 deletions source/shared/core_results.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ***

Expand Down Expand Up @@ -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;
Expand Down
19 changes: 10 additions & 9 deletions source/shared/core_sqlsrv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
32 changes: 25 additions & 7 deletions source/shared/core_stmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 ),
Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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 {

Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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);
Expand Down
170 changes: 170 additions & 0 deletions test/functional/pdo_sqlsrv/pdo_574_next_rowset.phpt
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 04f5034

Please sign in to comment.