diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 34e5b708..98ea7afa 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -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 buffer; - buffer = static_cast( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN ))); + buffer = static_cast( sqlsrv_malloc( INITIAL_LOB_FIELD_LEN + extra + sizeof( SQLULEN ))); SQLRETURN r = SQL_SUCCESS; SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; SQLLEN last_field_len = 0; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index bad4b538..1bc680cd 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -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 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 ); diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index bf268d8b..e47bca4d 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -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); diff --git a/test/functional/pdo_sqlsrv/pdo_574_next_rowset.phpt b/test/functional/pdo_sqlsrv/pdo_574_next_rowset.phpt new file mode 100644 index 00000000..d4c0e811 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_574_next_rowset.phpt @@ -0,0 +1,170 @@ +--TEST-- +GitHub issue 574 - Fetch Next Result Test +--DESCRIPTION-- +Verifies the functionality of PDOStatement nextRowset +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $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 \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_574_next_result.phpt b/test/functional/sqlsrv/sqlsrv_574_next_result.phpt new file mode 100644 index 00000000..c21f7178 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_574_next_result.phpt @@ -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-- + +--FILE-- + + +--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 \ No newline at end of file