diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index e28cbbed..b9af9075 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -83,7 +83,7 @@ bool get_bit( _In_ void* ptr, _In_ unsigned int bit ) // read in LOB field during buffered result creation SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_buffered_result_set::meta_data& meta, - _In_ zend_long mem_used TSRMLS_DC ); + _In_ zend_long mem_used, _In_ size_t row_count TSRMLS_DC ); // dtor for each row in the cache void cache_row_dtor( _In_ zval* data ); @@ -687,7 +687,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm out_buffer_length = &out_buffer_temp; SQLPOINTER* lob_addr = reinterpret_cast( &row[ meta[i].offset ] ); - *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); + *lob_addr = read_lob_field( stmt, i, meta[i], mem_used, row_count TSRMLS_CC ); // a NULL pointer means NULL field if( *lob_addr == NULL ) { *out_buffer_length = SQL_NULL_DATA; @@ -734,12 +734,12 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm break; } - row_count++; if( *out_buffer_length == SQL_NULL_DATA ) { set_bit( row, i ); } } + row_count++; SQLSRV_ASSERT( row_count < INT_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); // add it to the cache @@ -1498,7 +1498,7 @@ void cache_row_dtor( _In_ zval* data ) } SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_buffered_result_set::meta_data& meta, - _In_ zend_long mem_used TSRMLS_DC ) + _In_ zend_long mem_used, _In_ size_t row_count TSRMLS_DC ) { SQLSMALLINT extra = 0; SQLULEN* output_buffer_len = NULL; @@ -1563,6 +1563,19 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" ); + if ( stmt->conn->ce_option.enabled == true ) { + // cursor type SQLSRV_CURSOR_BUFFERED has to be FORWARD_ONLY + // thus has to close and reopen cursor to reset the cursor buffer + core::SQLCloseCursor(stmt); + core::SQLExecute(stmt); + // FETCH_NEXT until the cursor reaches the row that it was at + for (int i = 0; i <= row_count; i++) { + core::SQLFetchScroll(stmt, SQL_FETCH_NEXT, 0); + } + } + else { + already_read += to_read - already_read; + } // if the type of the field returns the total to be read, we use that and preallocate the buffer if( last_field_len != SQL_NO_TOTAL ) { @@ -1571,8 +1584,6 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in throw core::CoreException(); } - - already_read += to_read - already_read; to_read = last_field_len; buffer.resize( to_read + extra + sizeof( SQLULEN )); output_buffer_len = reinterpret_cast( buffer.get() ); @@ -1582,7 +1593,6 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in } // otherwise allocate another chunk of memory to read in else { - already_read += to_read - already_read; to_read *= 2; CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt, SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 491a96fc..52b52093 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1321,6 +1321,7 @@ 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 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 @@ -1891,6 +1892,14 @@ namespace core { } } + inline void SQLCloseCursor( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r = ::SQLCloseCursor( stmt->handle() ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } inline void SQLColAttribute( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLUSMALLINT field_identifier, _Out_writes_bytes_opt_(buffer_length) SQLPOINTER field_type_char, _In_ SQLSMALLINT buffer_length, @@ -2012,6 +2021,15 @@ namespace core { CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} } + inline void SQLGetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _Out_writes_opt_(buf_len) void* value_ptr, _In_ SQLINTEGER buf_len, _Out_opt_ SQLINTEGER* str_len TSRMLS_DC) + { + SQLRETURN r; + r = ::SQLGetStmtAttr( stmt->handle(), attr, value_ptr, buf_len, str_len ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + inline SQLRETURN SQLGetData( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_opt_ SQLLEN* out_buffer_length, _In_ bool handle_warning TSRMLS_DC ) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 54c44fe5..ecf25bbe 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -131,6 +131,7 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error past_fetch_end( false ), current_results( NULL ), cursor_type( SQL_CURSOR_FORWARD_ONLY ), + fwd_row_index( -1 ), has_rows( false ), fetch_called( false ), last_field_index( -1 ), @@ -213,6 +214,7 @@ void sqlsrv_stmt::free_param_data( TSRMLS_D ) void sqlsrv_stmt::new_result_set( TSRMLS_D ) { this->fetch_called = false; + this->fwd_row_index = -1; this->has_rows = false; this->past_next_result_end = false; this->past_fetch_end = false; @@ -796,6 +798,9 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient // move to the record requested. For absolute records, we use a 0 based offset, so +1 since // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. SQLRETURN r = stmt->current_results->fetch( fetch_orientation, ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 TSRMLS_CC ); + if ( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->conn->ce_option.enabled == true ) { + stmt->fwd_row_index++; + } if( r == SQL_NO_DATA ) { // if this is a forward only cursor, mark that we've passed the end so future calls result in an error if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { @@ -2171,6 +2176,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind else { // Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + SQLLEN sql_field_len = 0; // Calculate the field size. calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); @@ -2183,8 +2189,9 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind // if this is a large type, then read the first few bytes to get the actual length from SQLGetData 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; + SQLLEN initiallen = field_len_temp + extra; @@ -2234,19 +2241,48 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - field_len_temp -= initial_field_len; + // only handled differently when AE is on because AE does not support streaming + // SQLSRV_CURSOR_BUFFERED already fetched everything beforehand, so doesn't need to be handled differently here + if ( stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type != SQLSRV_CURSOR_BUFFERED ) { + // if the cursor is forward only, we have no choice but to close the cursor and open it again + // but if not forward only, we can simply fetch next then fetch prior to reset the cursor + if ( stmt->current_results->odbc->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { + // reopen the cursor + core::SQLCloseCursor( stmt->current_results->odbc ); + core::SQLExecute( 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 ); + } + } + else { + core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_NEXT, 0 ); + core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_PRIOR, 0 ); + } + // now that the fetch buffer has reset, fetch the original column again with a bigger buffer length + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, field_len_temp + extra, + &dummy_field_len, false /*handle_warning*/ TSRMLS_CC ); + // if field_len_temp was bit enough to hold all data, dummy_field_len contain the actual amount retrieved, + // not SQL_NO_TOTAL + if ( dummy_field_len != SQL_NO_TOTAL ) + field_len_temp = dummy_field_len; + else + field_len_temp += initial_field_len; + } + else { + field_len_temp -= initial_field_len; - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, - field_len_temp + extra, &dummy_field_len, - false /*handle_warning*/ TSRMLS_CC ); - - // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL - // so we calculate the actual length of the string with that. - if( dummy_field_len != SQL_NO_TOTAL ) - field_len_temp += dummy_field_len; - else - field_len_temp += initial_field_len; + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, + field_len_temp + extra, &dummy_field_len, + false /*handle_warning*/ TSRMLS_CC ); + // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL + // so we calculate the actual length of the string with that. + if ( dummy_field_len != SQL_NO_TOTAL ) + field_len_temp += dummy_field_len; + else + field_len_temp += initial_field_len; + } if( r == SQL_SUCCESS_WITH_INFO ) { core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len @@ -2260,13 +2296,36 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind // We got the field_len_temp from SQLGetData call. field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - // We have already recieved INITIAL_FIELD_STRING_LEN size data. - field_len_temp -= INITIAL_FIELD_STRING_LEN; + if ( stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type != SQLSRV_CURSOR_BUFFERED ) { + // if the cursor is forward only, we have no choice but to close the cursor and open it again + // but if not forward only, we can simply fetch next then fetch prior to reset the cursor + if ( stmt->current_results->odbc->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { + // reopen the cursor + core::SQLCloseCursor( stmt->current_results->odbc ); + core::SQLExecute( 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 ); + } + } + else { + core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_NEXT, 0 ); + core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_PRIOR, 0 ); + } + // now that the fetch buffer has reset, fetch the original column again with a bigger buffer length + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, field_len_temp + extra, + &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; - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, - field_len_temp + extra, &dummy_field_len, - true /*handle_warning*/ TSRMLS_CC ); + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, + field_len_temp + extra, &dummy_field_len, + true /*handle_warning*/ TSRMLS_CC ); + field_len_temp += INITIAL_FIELD_STRING_LEN; + } if( dummy_field_len == SQL_NULL_DATA ) { field_value = NULL; @@ -2277,8 +2336,6 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { throw core::CoreException(); } - - field_len_temp += INITIAL_FIELD_STRING_LEN; } } // if( is_truncation_warning ( state ) )