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

@ -1358,6 +1358,7 @@ struct sqlsrv_stmt : public sqlsrv_context {
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 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

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