зеркало из https://github.com/microsoft/msphpsql.git
Simplified get_field_as_string and made it more robust (#1265)
This commit is contained in:
Родитель
a14cb70ad3
Коммит
af61d06bfb
|
@ -1621,8 +1621,8 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f
|
|||
*field_len = len;
|
||||
}
|
||||
|
||||
void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type,
|
||||
_Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len )
|
||||
void get_field_as_string(_Inout_ sqlsrv_stmt *stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type,
|
||||
_Inout_updates_bytes_(*field_len) void *&field_value, _Inout_ SQLLEN *field_len)
|
||||
{
|
||||
SQLRETURN r;
|
||||
SQLSMALLINT c_type;
|
||||
|
@ -1631,7 +1631,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;
|
||||
unsigned int initial_field_len = INITIAL_FIELD_STRING_LEN;
|
||||
|
||||
try {
|
||||
|
||||
|
@ -1670,222 +1670,144 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind
|
|||
if (sqlsrv_php_type.typeinfo.encoding == CP_UTF8 && !is_a_numeric_type(sql_field_type)) {
|
||||
c_type = SQL_C_WCHAR;
|
||||
extra = sizeof(SQLWCHAR);
|
||||
|
||||
sql_display_size = (sql_display_size * sizeof(SQLWCHAR));
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a large type, then read the first few bytes to get the actual length from SQLGetData
|
||||
// If this is a large type, then read the first chunk to get the actual length from SQLGetData
|
||||
// The user may use "SET TEXTSIZE" to specify the size of varchar(max), nvarchar(max),
|
||||
// varbinary(max), text, ntext, and image data returned by a SELECT statement.
|
||||
// For varchar(max) and nvarchar(max), sql_display_size will be 0, regardless
|
||||
if (sql_display_size == 0 || sql_display_size == INT_MAX ||
|
||||
sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ||
|
||||
(sql_display_size > SQL_SERVER_MAX_FIELD_SIZE &&
|
||||
(sql_field_type == SQL_WLONGVARCHAR || sql_field_type == SQL_LONGVARCHAR || sql_field_type == SQL_LONGVARBINARY))) {
|
||||
// For varbinary(max), varchar(max) and nvarchar(max), sql_display_size will be 0, regardless
|
||||
if (sql_display_size == 0 ||
|
||||
(sql_field_type == SQL_WLONGVARCHAR || sql_field_type == SQL_LONGVARCHAR || sql_field_type == SQL_LONGVARBINARY)) {
|
||||
|
||||
field_len_temp = intial_field_len;
|
||||
|
||||
SQLLEN initiallen = field_len_temp + extra;
|
||||
|
||||
field_value_temp = static_cast<char*>( sqlsrv_malloc( field_len_temp + extra + 1 ));
|
||||
|
||||
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ),
|
||||
&field_len_temp, false /*handle_warning*/ );
|
||||
|
||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
if( field_len_temp == SQL_NULL_DATA ) {
|
||||
field_value = NULL;
|
||||
sqlsrv_free( field_value_temp );
|
||||
return;
|
||||
}
|
||||
|
||||
if( r == SQL_SUCCESS_WITH_INFO ) {
|
||||
|
||||
SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {L'\0'};
|
||||
SQLSMALLINT len = 0;
|
||||
|
||||
stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
|
||||
|
||||
// with Linux connection pooling may not get a truncated warning back but the actual field_len_temp
|
||||
// can be greater than the initallen value.
|
||||
#ifndef _WIN32
|
||||
if( is_truncated_warning( state ) || initiallen < field_len_temp) {
|
||||
#else
|
||||
if( is_truncated_warning( state ) ) {
|
||||
#endif // !_WIN32
|
||||
|
||||
SQLLEN dummy_field_len = 0;
|
||||
|
||||
// for XML (and possibly other conditions) the field length returned is not the real field length, so
|
||||
// in every pass, we double the allocation size to retrieve all the contents.
|
||||
if( field_len_temp == SQL_NO_TOTAL ) {
|
||||
|
||||
// reset the field_len_temp
|
||||
field_len_temp = intial_field_len;
|
||||
|
||||
do {
|
||||
SQLLEN initial_field_len = field_len_temp;
|
||||
// Double the size.
|
||||
field_len_temp *= 2;
|
||||
|
||||
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
|
||||
|
||||
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*/ );
|
||||
// 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
|
||||
);
|
||||
}
|
||||
|
||||
} while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state ));
|
||||
}
|
||||
else {
|
||||
// the real field length is returned here, thus no need to double the allocation size here, just have to
|
||||
// allocate field_len_temp (which is the field length retrieved from the first SQLGetData
|
||||
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
|
||||
|
||||
// We have already received 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 + intial_field_len,
|
||||
field_len_temp + extra, &dummy_field_len, true /*handle_warning*/ );
|
||||
field_len_temp += intial_field_len;
|
||||
|
||||
if( dummy_field_len == SQL_NULL_DATA ) {
|
||||
field_value = NULL;
|
||||
sqlsrv_free( field_value_temp );
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
}
|
||||
|
||||
} // if( is_truncation_warning ( state ) )
|
||||
else {
|
||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
}
|
||||
} // if( r == SQL_SUCCESS_WITH_INFO )
|
||||
|
||||
if (c_type == SQL_C_WCHAR) {
|
||||
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
|
||||
&field_value_temp, field_len_temp );
|
||||
|
||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
}
|
||||
} // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. )
|
||||
|
||||
else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) {
|
||||
|
||||
// only allow binary retrievals for char and binary types. All others get a string converted
|
||||
// to the encoding type they asked for.
|
||||
|
||||
// null terminator
|
||||
if( c_type == SQL_C_CHAR ) {
|
||||
sql_display_size += sizeof( SQLCHAR );
|
||||
}
|
||||
|
||||
// For WCHAR multiply by sizeof(WCHAR) and include the null terminator
|
||||
else if( c_type == SQL_C_WCHAR ) {
|
||||
sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR);
|
||||
}
|
||||
|
||||
field_value_temp = static_cast<char*>( sqlsrv_malloc( sql_display_size + extra + 1 ));
|
||||
field_len_temp = initial_field_len;
|
||||
field_value_temp = static_cast<char*>(sqlsrv_malloc(field_len_temp + extra + 1));
|
||||
r = stmt->current_results->get_data(field_index + 1, c_type, field_value_temp, (field_len_temp + extra), &field_len_temp, false /*handle_warning*/);
|
||||
} else {
|
||||
field_len_temp = sql_display_size;
|
||||
field_value_temp = static_cast<char*>(sqlsrv_malloc(sql_display_size + extra + 1));
|
||||
|
||||
// get the data
|
||||
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size,
|
||||
&field_len_temp, true /*handle_warning*/ );
|
||||
CHECK_SQL_ERROR( r, stmt ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
if( field_len_temp == SQL_NULL_DATA ) {
|
||||
field_value = NULL;
|
||||
sqlsrv_free( field_value_temp );
|
||||
return;
|
||||
}
|
||||
|
||||
if (c_type == SQL_C_WCHAR) {
|
||||
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
|
||||
&field_value_temp, field_len_temp );
|
||||
|
||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
}
|
||||
|
||||
if (stmt->format_decimals && (sql_field_type == SQL_DECIMAL || sql_field_type == SQL_NUMERIC)) {
|
||||
// number of decimal places only affect money / smallmoney fields
|
||||
SQLSMALLINT decimal_places = (stmt->current_meta_data[field_index]->field_is_money_type) ? stmt->decimal_places : NO_CHANGE_DECIMAL_PLACES;
|
||||
format_decimal_numbers(decimal_places, stmt->current_meta_data[field_index]->field_scale, field_value_temp, &field_len_temp);
|
||||
}
|
||||
} // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE )
|
||||
|
||||
else {
|
||||
|
||||
DIE( "Invalid sql_display_size" );
|
||||
return; // to eliminate a warning
|
||||
r = stmt->current_results->get_data(field_index + 1, c_type, field_value_temp, sql_display_size + extra, &field_len_temp, false /*handle_warning*/);
|
||||
}
|
||||
|
||||
field_value = field_value_temp;
|
||||
*field_len = field_len_temp;
|
||||
CHECK_CUSTOM_ERROR((r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
if (field_len_temp == SQL_NULL_DATA) {
|
||||
field_value = NULL;
|
||||
sqlsrv_free(field_value_temp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (r == SQL_SUCCESS_WITH_INFO) {
|
||||
SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { L'\0' };
|
||||
SQLSMALLINT len = 0;
|
||||
|
||||
stmt->current_results->get_diag_field(1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len);
|
||||
if (is_truncated_warning(state)) {
|
||||
SQLLEN chunk_field_len = 0;
|
||||
|
||||
// for XML (and possibly other conditions) the field length returned is not the real field length, so
|
||||
// in every pass, we double the allocation size to retrieve all the contents.
|
||||
if (field_len_temp == SQL_NO_TOTAL) {
|
||||
|
||||
// reset the field_len_temp
|
||||
field_len_temp = initial_field_len;
|
||||
|
||||
do {
|
||||
SQLLEN buffer_len = field_len_temp;
|
||||
// Double the size.
|
||||
field_len_temp *= 2;
|
||||
|
||||
field_value_temp = static_cast<char*>(sqlsrv_realloc(field_value_temp, field_len_temp + extra + 1));
|
||||
|
||||
field_len_temp -= buffer_len;
|
||||
|
||||
// Get the rest of the data
|
||||
r = stmt->current_results->get_data(field_index + 1, c_type, field_value_temp + buffer_len,
|
||||
field_len_temp + extra, &chunk_field_len, false /*handle_warning*/);
|
||||
// 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 (chunk_field_len != SQL_NO_TOTAL)
|
||||
field_len_temp += chunk_field_len;
|
||||
else
|
||||
field_len_temp += buffer_len;
|
||||
|
||||
if (r == SQL_SUCCESS_WITH_INFO) {
|
||||
core::SQLGetDiagField(stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len);
|
||||
}
|
||||
} while (r == SQL_SUCCESS_WITH_INFO && is_truncated_warning(state));
|
||||
} // if (field_len_temp == SQL_NO_TOTAL)
|
||||
else {
|
||||
// The field length (or its estimate) is returned, thus no need to double the allocation size.
|
||||
// Allocate field_len_temp (which is the field length retrieved from the first SQLGetData) but with some padding
|
||||
// because there is a chance that the estimated field_len_temp is not accurate enough
|
||||
SQLLEN buffer_len = 50;
|
||||
field_value_temp = static_cast<char*>(sqlsrv_realloc(field_value_temp, field_len_temp + buffer_len + 1));
|
||||
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 + buffer_len, &chunk_field_len, false /*handle_warning*/);
|
||||
field_len_temp = initial_field_len + chunk_field_len;
|
||||
|
||||
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// Reallocate field_value_temp next
|
||||
field_value_temp = static_cast<char*>(sqlsrv_realloc(field_value_temp, field_len_temp + extra + 1));
|
||||
}
|
||||
} // if (is_truncated_warning(state))
|
||||
} // if (r == SQL_SUCCESS_WITH_INFO)
|
||||
|
||||
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
if (c_type == SQL_C_WCHAR) {
|
||||
bool converted = convert_string_from_utf16_inplace(static_cast<SQLSRV_ENCODING>(sqlsrv_php_type.typeinfo.encoding),
|
||||
&field_value_temp, field_len_temp);
|
||||
|
||||
CHECK_CUSTOM_ERROR(!converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
}
|
||||
|
||||
if (stmt->format_decimals && (sql_field_type == SQL_DECIMAL || sql_field_type == SQL_NUMERIC)) {
|
||||
// number of decimal places only affect money / smallmoney fields
|
||||
SQLSMALLINT decimal_places = (stmt->current_meta_data[field_index]->field_is_money_type) ? stmt->decimal_places : NO_CHANGE_DECIMAL_PLACES;
|
||||
format_decimal_numbers(decimal_places, stmt->current_meta_data[field_index]->field_scale, field_value_temp, &field_len_temp);
|
||||
}
|
||||
|
||||
// finalized the returned values and set field_len to 0 if field_len_temp is negative (which may happen with unixODBC connection pooling)
|
||||
field_value = field_value_temp;
|
||||
*field_len = (field_len_temp > 0) ? field_len_temp : 0;
|
||||
|
||||
// prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP
|
||||
// runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode.
|
||||
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary
|
||||
// operator to set add 1 to fill the null terminator
|
||||
|
||||
// with unixODBC connection pooling sometimes field_len_temp can be SQL_NO_DATA.
|
||||
// In that cause do not set null terminator and set length to 0.
|
||||
if ( field_len_temp > 0 )
|
||||
{
|
||||
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and add 1 to fill the null terminator
|
||||
if (field_len_temp > 0) {
|
||||
field_value_temp[field_len_temp] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
*field_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
catch( core::CoreException& ) {
|
||||
|
||||
catch (core::CoreException&) {
|
||||
field_value = NULL;
|
||||
*field_len = 0;
|
||||
sqlsrv_free( field_value_temp );
|
||||
sqlsrv_free(field_value_temp);
|
||||
throw;
|
||||
}
|
||||
catch ( ... ) {
|
||||
|
||||
} catch (...) {
|
||||
field_value = NULL;
|
||||
*field_len = 0;
|
||||
sqlsrv_free( field_value_temp );
|
||||
sqlsrv_free(field_value_temp);
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// return the option from the stmt_opts array that matches the key. If no option found,
|
||||
// NULL is returned.
|
||||
|
||||
|
@ -3362,18 +3284,18 @@ void sqlsrv_param_tvp::process_null_param_value(_Inout_ sqlsrv_stmt* stmt)
|
|||
if (php_type == IS_NULL) {
|
||||
// This means that the entire column contains nothing but NULLs
|
||||
sqlsrv_param::process_null_param(param_ptr_z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sqlsrv_param_tvp::bind_param(_Inout_ sqlsrv_stmt* stmt)
|
||||
{
|
||||
{
|
||||
core::SQLBindParameter(stmt, param_pos + 1, direction, c_data_type, sql_data_type, column_size, decimal_digits, buffer, buffer_length, &strlen_or_indptr);
|
||||
|
||||
// No need to continue if this is one of the constituent columns of the table-valued parameter
|
||||
if (sql_data_type != SQL_SS_TABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to continue if this is one of the constituent columns of the table-valued parameter
|
||||
if (sql_data_type != SQL_SS_TABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (num_rows == 0) {
|
||||
// TVP has no data
|
||||
return;
|
||||
|
@ -3381,18 +3303,18 @@ void sqlsrv_param_tvp::bind_param(_Inout_ sqlsrv_stmt* stmt)
|
|||
|
||||
// Bind the TVP columns one by one
|
||||
// Register this object first using SQLSetDescField() for sending TVP data post execution
|
||||
SQLHDESC desc;
|
||||
core::SQLGetStmtAttr(stmt, SQL_ATTR_APP_PARAM_DESC, &desc, 0, 0);
|
||||
SQLRETURN r = ::SQLSetDescField(desc, param_pos + 1, SQL_DESC_DATA_PTR, reinterpret_cast<SQLPOINTER>(this), 0);
|
||||
SQLHDESC desc;
|
||||
core::SQLGetStmtAttr(stmt, SQL_ATTR_APP_PARAM_DESC, &desc, 0, 0);
|
||||
SQLRETURN r = ::SQLSetDescField(desc, param_pos + 1, SQL_DESC_DATA_PTR, reinterpret_cast<SQLPOINTER>(this), 0);
|
||||
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// First set focus on this parameter
|
||||
size_t ordinal = param_pos + 1;
|
||||
core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(ordinal), SQL_IS_INTEGER);
|
||||
|
||||
// Bind the TVP columns
|
||||
|
||||
// First set focus on this parameter
|
||||
size_t ordinal = param_pos + 1;
|
||||
core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(ordinal), SQL_IS_INTEGER);
|
||||
|
||||
// Bind the TVP columns
|
||||
SQLSRV_ENCODING stmt_encoding = (stmt->encoding() == SQLSRV_ENCODING_DEFAULT) ? stmt->conn->encoding() : stmt->encoding();
|
||||
HashTable* rows_ht = Z_ARRVAL_P(param_ptr_z);
|
||||
zval* row_z = zend_hash_index_find(rows_ht, 0);
|
||||
|
@ -3428,17 +3350,17 @@ void sqlsrv_param_tvp::bind_param(_Inout_ sqlsrv_stmt* stmt)
|
|||
column_param->param_ptr_z = data_z;
|
||||
num_columns++;
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
|
||||
// Process the columns and bind each of them using the saved data
|
||||
for (int i = 0; i < num_columns; i++) {
|
||||
sqlsrv_param* column_param = tvp_columns[i];
|
||||
|
||||
// Process the columns and bind each of them using the saved data
|
||||
for (int i = 0; i < num_columns; i++) {
|
||||
sqlsrv_param* column_param = tvp_columns[i];
|
||||
|
||||
column_param->process_param(stmt, NULL);
|
||||
column_param->bind_param(stmt);
|
||||
}
|
||||
|
||||
// Reset focus
|
||||
core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(0), SQL_IS_INTEGER);
|
||||
}
|
||||
|
||||
// Reset focus
|
||||
core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(0), SQL_IS_INTEGER);
|
||||
}
|
||||
|
||||
// For each of the constituent columns of the table-valued parameter, check its PHP type
|
||||
|
@ -3446,21 +3368,21 @@ void sqlsrv_param_tvp::bind_param(_Inout_ sqlsrv_stmt* stmt)
|
|||
// member placeholder_z
|
||||
void sqlsrv_param_tvp::populate_cell_placeholder(_Inout_ sqlsrv_stmt* stmt, _In_ int ordinal)
|
||||
{
|
||||
if (sql_data_type == SQL_SS_TABLE || ordinal >= num_rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sql_data_type == SQL_SS_TABLE || ordinal >= num_rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
zval* row_z = NULL;
|
||||
HashTable* values_ht = NULL;
|
||||
zval* value_z = NULL;
|
||||
HashTable* values_ht = NULL;
|
||||
zval* value_z = NULL;
|
||||
int type = IS_NULL;
|
||||
|
||||
|
||||
switch (param_php_type) {
|
||||
case IS_TRUE:
|
||||
case IS_FALSE:
|
||||
case IS_LONG:
|
||||
case IS_DOUBLE:
|
||||
// Find the row from the TVP data based on ordinal
|
||||
// Find the row from the TVP data based on ordinal
|
||||
row_z = zend_hash_index_find(Z_ARRVAL_P(parent_tvp->param_ptr_z), ordinal);
|
||||
if (Z_ISREF_P(row_z)) {
|
||||
ZVAL_DEREF(row_z);
|
||||
|
@ -3507,9 +3429,9 @@ void sqlsrv_param_tvp::populate_cell_placeholder(_Inout_ sqlsrv_stmt* stmt, _In_
|
|||
// and param_pos)
|
||||
bool sqlsrv_param_tvp::send_data_packet(_Inout_ sqlsrv_stmt* stmt)
|
||||
{
|
||||
if (sql_data_type != SQL_SS_TABLE) {
|
||||
if (sql_data_type != SQL_SS_TABLE) {
|
||||
// This is one of the constituent columns of the table-valued parameter
|
||||
// Check current_row first
|
||||
// Check current_row first
|
||||
if (current_row >= num_rows) {
|
||||
return false;
|
||||
}
|
||||
|
@ -3590,25 +3512,25 @@ bool sqlsrv_param_tvp::send_data_packet(_Inout_ sqlsrv_stmt* stmt)
|
|||
break;
|
||||
}
|
||||
} // else not IS_NULL
|
||||
} else {
|
||||
} else {
|
||||
// This is the table-valued parameter
|
||||
if (current_row < num_rows) {
|
||||
// Loop through the table parameter columns and populate each cell's placeholder whenever applicable
|
||||
for (size_t i = 0; i < tvp_columns.size(); i++) {
|
||||
tvp_columns[i]->populate_cell_placeholder(stmt, current_row);
|
||||
}
|
||||
|
||||
// This indicates a TVP row is available
|
||||
core::SQLPutData(stmt, reinterpret_cast<SQLPOINTER>(1), 1);
|
||||
current_row++;
|
||||
} else {
|
||||
// This indicates there is no more TVP row
|
||||
core::SQLPutData(stmt, reinterpret_cast<SQLPOINTER>(0), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Return false to indicate that the current row has been sent
|
||||
return false;
|
||||
if (current_row < num_rows) {
|
||||
// Loop through the table parameter columns and populate each cell's placeholder whenever applicable
|
||||
for (size_t i = 0; i < tvp_columns.size(); i++) {
|
||||
tvp_columns[i]->populate_cell_placeholder(stmt, current_row);
|
||||
}
|
||||
|
||||
// This indicates a TVP row is available
|
||||
core::SQLPutData(stmt, reinterpret_cast<SQLPOINTER>(1), 1);
|
||||
current_row++;
|
||||
} else {
|
||||
// This indicates there is no more TVP row
|
||||
core::SQLPutData(stmt, reinterpret_cast<SQLPOINTER>(0), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Return false to indicate that the current row has been sent
|
||||
return false;
|
||||
}
|
||||
|
||||
// A helper method for sending large string data in batches
|
||||
|
@ -3618,13 +3540,13 @@ void sqlsrv_param_tvp::send_string_data_in_batches(_Inout_ sqlsrv_stmt* stmt, _I
|
|||
SQLLEN batch = (encoding == CP_UTF8) ? PHP_STREAM_BUFFER_SIZE / sizeof(SQLWCHAR) : PHP_STREAM_BUFFER_SIZE;
|
||||
|
||||
char* p = Z_STRVAL_P(value_z);
|
||||
while (len > batch) {
|
||||
core::SQLPutData(stmt, p, batch);
|
||||
len -= batch;
|
||||
p += batch;
|
||||
}
|
||||
|
||||
// Put final batch
|
||||
while (len > batch) {
|
||||
core::SQLPutData(stmt, p, batch);
|
||||
len -= batch;
|
||||
p += batch;
|
||||
}
|
||||
|
||||
// Put final batch
|
||||
core::SQLPutData(stmt, p, len);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
--TEST--
|
||||
Verify Github Issue 1261 is fixed.
|
||||
--DESCRIPTION--
|
||||
This test should already pass in Windows so it is mainly aimed for non-Windows settings where UTF-8 is the default encoding. ODBC warnings are handled differently with pdo_sqlsrv so logging is used to checking the warnings.
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once("MsSetup.inc");
|
||||
|
||||
try {
|
||||
$logFilename = 'php_errors.log';
|
||||
$logFilepath = dirname(__FILE__).'/'.$logFilename;
|
||||
ini_set('log_errors', '1');
|
||||
ini_set('pdo_sqlsrv.log_severity', '2');
|
||||
ini_set('error_log', $logFilepath);
|
||||
|
||||
$conn = new PDO("sqlsrv:Server=$server;Database=$databaseName;", $uid, $pwd);
|
||||
$conn->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_SYSTEM);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
# leading string >= 2045 leading to result length > 2048
|
||||
# column must be VARCHAR(MAX) or VARCHAR(2048) (starts working with bigger VARCHAR(n), e.g. 2060)
|
||||
# SQLSRV_ATTR_ENCODING must be set to SQLSRV_ENCODING_SYSTEM (works with PDO::SQLSRV_ENCODING_UTF8)
|
||||
# COLLATE must not be %UTF8% (e.g. Latin1_General_100_CI_AS_SC_UTF8 works)
|
||||
|
||||
$sql = "DROP TABLE IF EXISTS #tmpTest;
|
||||
SET NOCOUNT ON;
|
||||
DECLARE @val VARCHAR(8000) = REPLICATE('a', 2045) + 'ñ';
|
||||
CREATE TABLE #tmpTest (testCol VARCHAR(MAX) COLLATE SQL_Latin1_General_CP1_CI_AS);
|
||||
INSERT INTO #tmpTest (testCol) VALUES (@val);
|
||||
SELECT * from #tmpTest;";
|
||||
|
||||
$stmt = $conn->query($sql);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
var_dump($row);
|
||||
|
||||
echo file_get_contents($logFilepath);
|
||||
unlink($logFilepath);
|
||||
|
||||
unset($stmt);
|
||||
unset($conn);
|
||||
} catch (PDOException $e) {
|
||||
var_dump($e);
|
||||
}
|
||||
?>
|
||||
--EXPECTF--
|
||||
array(1) {
|
||||
["testCol"]=>
|
||||
string(2049) "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaañ"
|
||||
}
|
||||
[%s] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000
|
||||
[%s] pdo_sqlsrv_db_handle_factory: error code = 5701
|
||||
[%s] pdo_sqlsrv_db_handle_factory: message = [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Changed database context to '%s'.
|
||||
[%s] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000
|
||||
[%s] pdo_sqlsrv_db_handle_factory: error code = 5703
|
||||
[%s] pdo_sqlsrv_db_handle_factory: message = [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Changed language setting to %s.
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
--TEST--
|
||||
Verify Github Issue 1261 is fixed.
|
||||
--DESCRIPTION--
|
||||
This test should already pass in Windows so it is mainly aimed for non-Windows settings where UTF-8 is the default encoding.
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
sqlsrv_configure('WarningsReturnAsErrors', 1);
|
||||
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL);
|
||||
require_once('MsCommon.inc');
|
||||
|
||||
$conn = connect(array("CharacterSet" => SQLSRV_ENC_CHAR));
|
||||
|
||||
# leading string >= 2045 leading to result length > 2048
|
||||
# column must be VARCHAR(MAX) or VARCHAR(2048) (starts working with bigger VARCHAR(n), e.g. 2060)
|
||||
# 'CharacterSet' connInfo must be set to SQLSRV_ENC_CHAR (works with UTF-8)
|
||||
# COLLATE must not be %UTF8% (e.g. Latin1_General_100_CI_AS_SC_UTF8 works)
|
||||
|
||||
$sql = "DROP TABLE IF EXISTS #tmpTest;
|
||||
SET NOCOUNT ON;
|
||||
DECLARE @val VARCHAR(8000) = REPLICATE('a', 2045) + 'ñ';
|
||||
CREATE TABLE #tmpTest (testCol VARCHAR(MAX) COLLATE SQL_Latin1_General_CP1_CI_AS);
|
||||
INSERT INTO #tmpTest (testCol) VALUES (@val);
|
||||
SELECT * from #tmpTest;";
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
$errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
|
||||
var_dump($row, $errors);
|
||||
|
||||
sqlsrv_free_stmt($stmt);
|
||||
sqlsrv_close($conn);
|
||||
?>
|
||||
--EXPECT--
|
||||
array(1) {
|
||||
["testCol"]=>
|
||||
string(2049) "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaañ"
|
||||
}
|
||||
NULL
|
Загрузка…
Ссылка в новой задаче