-
Notifications
You must be signed in to change notification settings - Fork 375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Save meta data for the fetched result set #855
Changes from 8 commits
8f9ecde
9654020
ff3c6a4
393399d
c4b4a69
ce094c0
c51ff6c
26fcc18
f183b2e
cd89b7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -124,7 +124,6 @@ void send_param_streams( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); | |
void sqlsrv_output_param_dtor( _Inout_ zval* data ); | ||
// called when a bound stream parameter is to be destroyed. | ||
void sqlsrv_stream_dtor( _Inout_ zval* data ); | ||
bool is_streamable_type( _In_ SQLINTEGER sql_type ); | ||
|
||
} | ||
|
||
|
@@ -997,22 +996,24 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i | |
efree( field_value ); | ||
field_value = NULL; | ||
*field_len = 0; | ||
} | ||
} | ||
} | ||
|
||
// If the php type was not specified set the php type to be the default type. | ||
if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { | ||
|
||
// Get the SQL type of the field. | ||
core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); | ||
} | ||
} | ||
} | ||
|
||
// Get the length of the field. | ||
core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); | ||
// If the php type was not specified set the php type to be the default type. | ||
if (sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID) { | ||
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_sqlsrv_get_field - meta data vector not in sync" ); | ||
sql_field_type = stmt->current_meta_data[field_index]->field_type; | ||
if (stmt->current_meta_data[field_index]->field_precision > 0) { | ||
sql_field_len = stmt->current_meta_data[field_index]->field_precision; | ||
} | ||
else { | ||
sql_field_len = stmt->current_meta_data[field_index]->field_size; | ||
} | ||
|
||
// Get the corresponding php type from the sql type. | ||
sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast<SQLINTEGER>( sql_field_type ), static_cast<SQLUINTEGER>( sql_field_len ), prefer_string ); | ||
} | ||
// Get the corresponding php type from the sql type. | ||
sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast<SQLINTEGER>(sql_field_type), static_cast<SQLUINTEGER>(sql_field_len), prefer_string); | ||
} | ||
|
||
// Verify that we have an acceptable type to convert. | ||
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { | ||
|
@@ -1441,7 +1442,7 @@ void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) | |
|
||
namespace { | ||
|
||
bool is_streamable_type( _In_ SQLLEN sql_type ) | ||
bool is_streamable_type( _In_ SQLSMALLINT sql_type ) | ||
{ | ||
switch( sql_type ) { | ||
case SQL_CHAR: | ||
|
@@ -1460,6 +1461,25 @@ bool is_streamable_type( _In_ SQLLEN sql_type ) | |
return false; | ||
} | ||
|
||
bool is_a_numeric_type(_In_ SQLSMALLINT sql_type) | ||
{ | ||
switch (sql_type) { | ||
case SQL_BIGINT: | ||
case SQL_BIT: | ||
case SQL_INTEGER: | ||
case SQL_SMALLINT: | ||
case SQL_TINYINT: | ||
case SQL_FLOAT: | ||
case SQL_DOUBLE: | ||
case SQL_REAL: | ||
case SQL_DECIMAL: | ||
case SQL_NUMERIC: | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size TSRMLS_DC ) | ||
{ | ||
try { | ||
|
@@ -1693,12 +1713,10 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i | |
{ | ||
php_stream* stream = NULL; | ||
sqlsrv_stream* ss = NULL; | ||
SQLLEN sql_type; | ||
SQLSMALLINT sql_type; | ||
|
||
SQLRETURN r = SQLColAttributeW( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); | ||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { | ||
throw core::CoreException(); | ||
} | ||
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_get_field_common - meta data vector not in sync" ); | ||
sql_type = stmt->current_meta_data[field_index]->field_type; | ||
|
||
CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { | ||
throw core::CoreException(); | ||
|
@@ -2208,9 +2226,30 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind | |
DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, | ||
"Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); | ||
|
||
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; | ||
sql_display_size = cached->display_size; | ||
} | ||
else { | ||
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "get_field_as_string - meta data vector not in sync" ); | ||
sql_field_type = stmt->current_meta_data[field_index]->field_type; | ||
|
||
// Calculate the field size. | ||
calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); | ||
|
||
col_cache cache( sql_field_type, sql_display_size ); | ||
core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC ); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why has this part been moved up? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because we need to get the sql_field_type first before we can decide whether to use CHAR encoding for the numeric types |
||
// Determine the correct encoding | ||
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { | ||
sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); | ||
} | ||
// For numbers, no need to convert | ||
if (is_a_numeric_type(sql_field_type)) { | ||
sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_CHAR; | ||
} | ||
|
||
// Set the C type and account for null characters at the end of the data. | ||
switch( sqlsrv_php_type.typeinfo.encoding ) { | ||
|
@@ -2228,22 +2267,6 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind | |
break; | ||
} | ||
|
||
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; | ||
sql_display_size = cached->display_size; | ||
} | ||
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 ); | ||
|
||
// Calculate the field size. | ||
calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); | ||
|
||
col_cache cache( sql_field_type, sql_display_size ); | ||
core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC ); | ||
} | ||
|
||
// 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 ) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -91,7 +91,7 @@ const char SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF[] = "Variable parameter %d not pa | |
/* internal functions */ | ||
|
||
void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval ); | ||
|
||
SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt* stmt); | ||
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names | ||
TSRMLS_DC ); | ||
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size, | ||
|
@@ -110,6 +110,15 @@ bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_p | |
|
||
} | ||
|
||
// internal helper function to free meta data structures allocated | ||
void meta_data_free( _Inout_ field_meta_data* meta ) | ||
{ | ||
if( meta->field_name ) { | ||
meta->field_name.reset(); | ||
} | ||
sqlsrv_free( meta ); | ||
} | ||
|
||
// query options for cursor types | ||
namespace SSCursorTypes { | ||
|
||
|
@@ -137,6 +146,9 @@ ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ | |
|
||
ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) | ||
{ | ||
std::for_each(current_meta_data.begin(), current_meta_data.end(), meta_data_free); | ||
current_meta_data.clear(); | ||
|
||
if( fetch_field_names != NULL ) { | ||
|
||
for( int i=0; i < fetch_fields_count; ++i ) { | ||
|
@@ -460,26 +472,25 @@ PHP_FUNCTION( sqlsrv_field_metadata ) | |
try { | ||
|
||
// get the number of fields in the resultset | ||
num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); | ||
SQLSMALLINT num_cols = get_resultset_meta_data(stmt); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is where you get the full metadata and not just the number of fields, isn't it? The comment should be updated to reflect that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
zval result_meta_data; | ||
ZVAL_UNDEF( &result_meta_data ); | ||
core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC ); | ||
|
||
for( SQLSMALLINT f = 0; f < num_cols; ++f ) { | ||
|
||
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data; | ||
core_meta_data = core_sqlsrv_field_metadata( stmt, f TSRMLS_CC ); | ||
|
||
field_meta_data* core_meta_data = stmt->current_meta_data[f]; | ||
|
||
// initialize the array | ||
zval field_array; | ||
ZVAL_UNDEF( &field_array ); | ||
core::sqlsrv_array_init( *stmt, &field_array TSRMLS_CC ); | ||
|
||
core::sqlsrv_add_assoc_string( *stmt, &field_array, FieldMetaData::NAME, | ||
reinterpret_cast<char*>( core_meta_data->field_name.get() ), 0 TSRMLS_CC ); | ||
// add the field name to the associative array but keep a copy | ||
core::sqlsrv_add_assoc_string(*stmt, &field_array, FieldMetaData::NAME, | ||
reinterpret_cast<char*>(core_meta_data->field_name.get()), 1 TSRMLS_CC); | ||
|
||
core_meta_data->field_name.transferred(); | ||
// core_meta_data->field_name.transferred(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this commented out but left in? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will remove it :) |
||
|
||
core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC ); | ||
|
||
|
@@ -521,7 +532,7 @@ PHP_FUNCTION( sqlsrv_field_metadata ) | |
core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC ); | ||
|
||
// always good to call destructor for allocations done through placement new operator. | ||
core_meta_data->~field_meta_data(); | ||
// core_meta_data->~field_meta_data(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this commented out but left in? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will remove it :) |
||
} | ||
|
||
// return our built collection and transfer ownership | ||
|
@@ -567,6 +578,10 @@ PHP_FUNCTION( sqlsrv_next_result ) | |
|
||
core_sqlsrv_next_result( stmt TSRMLS_CC, true ); | ||
|
||
// clear the current meta data since the new result will generate new meta data | ||
std::for_each(stmt->current_meta_data.begin(), stmt->current_meta_data.end(), meta_data_free); | ||
stmt->current_meta_data.clear(); | ||
|
||
if( stmt->past_next_result_end ) { | ||
|
||
RETURN_NULL(); | ||
|
@@ -1084,7 +1099,7 @@ PHP_FUNCTION( sqlsrv_get_field ) | |
try { | ||
|
||
// validate that the field index is within range | ||
int num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); | ||
SQLSMALLINT num_cols = get_resultset_meta_data(stmt); | ||
|
||
if( field_index < 0 || field_index >= num_cols ) { | ||
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); | ||
|
@@ -1759,6 +1774,37 @@ void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) | |
} | ||
} | ||
|
||
SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) | ||
{ | ||
// get the numer of columns in the result set | ||
SQLSMALLINT num_cols = -1; | ||
|
||
num_cols = stmt->current_meta_data.size(); | ||
bool getMetaData = false; | ||
|
||
if (num_cols == 0) { | ||
getMetaData = true; | ||
num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); | ||
} | ||
|
||
try { | ||
if (getMetaData) { | ||
for (int i = 0; i < num_cols; i++) { | ||
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data; | ||
core_meta_data = core_sqlsrv_field_metadata(stmt, i TSRMLS_CC); | ||
stmt->current_meta_data.push_back(core_meta_data.get()); | ||
core_meta_data.transferred(); | ||
} | ||
} | ||
} catch( core::CoreException& ) { | ||
throw; | ||
} | ||
|
||
SQLSRV_ASSERT(num_cols > 0 && stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" ); | ||
|
||
return num_cols; | ||
} | ||
|
||
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names | ||
TSRMLS_DC ) | ||
{ | ||
|
@@ -1772,40 +1818,25 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ | |
throw ss::SSException(); | ||
} | ||
|
||
// get the numer of columns in the result set | ||
SQLSMALLINT num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); | ||
// get the numer of columns in the result set | ||
SQLSMALLINT num_cols = get_resultset_meta_data(stmt); | ||
|
||
// if this is the first fetch in a new result set, then get the field names and | ||
// store them off for successive fetches. | ||
if(( fetch_type & SQLSRV_FETCH_ASSOC ) && stmt->fetch_field_names == NULL ) { | ||
if ((fetch_type & SQLSRV_FETCH_ASSOC) && stmt->fetch_field_names == NULL) { | ||
|
||
SQLLEN field_name_len = 0; | ||
SQLSMALLINT field_name_len_w = 0; | ||
SQLWCHAR field_name_w[( SS_MAXCOLNAMELEN + 1 ) * 2] = {L'\0'}; | ||
sqlsrv_malloc_auto_ptr<char> field_name; | ||
sqlsrv_malloc_auto_ptr<sqlsrv_fetch_field_name> field_names; | ||
field_names = static_cast<sqlsrv_fetch_field_name*>( sqlsrv_malloc( num_cols * sizeof( sqlsrv_fetch_field_name ))); | ||
SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); | ||
for( int i = 0; i < num_cols; ++i ) { | ||
|
||
core::SQLColAttributeW ( stmt, i + 1, SQL_DESC_NAME, field_name_w, ( SS_MAXCOLNAMELEN + 1 ) * 2, &field_name_len_w, NULL TSRMLS_CC ); | ||
|
||
//Conversion function expects size in characters | ||
field_name_len_w = field_name_len_w / sizeof ( SQLWCHAR ); | ||
bool converted = convert_string_from_utf16( encoding, field_name_w, | ||
field_name_len_w, ( char** ) &field_name, field_name_len ); | ||
|
||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { | ||
throw core::CoreException(); | ||
} | ||
|
||
field_names[i].name = static_cast<char*>( sqlsrv_malloc( field_name_len, sizeof( char ), 1 )); | ||
memcpy_s(( void* )field_names[i].name, ( field_name_len * sizeof( char )) , ( void* ) field_name, field_name_len ); | ||
field_names[i].name[field_name_len] = '\0'; // null terminate the field name since SQLColAttribute doesn't. | ||
field_names[i].len = field_name_len + 1; | ||
field_name.reset(); | ||
field_names = static_cast<sqlsrv_fetch_field_name*>(sqlsrv_malloc(num_cols * sizeof(sqlsrv_fetch_field_name))); | ||
for (int i = 0; i < num_cols; ++i) { | ||
// The meta data field name is already null-terminated, and the field name len is correct. | ||
field_name_len = stmt->current_meta_data[i]->field_name_len; | ||
field_names[i].name = static_cast<char*>(sqlsrv_malloc(field_name_len, sizeof(char), 1)); | ||
memcpy_s((void*)field_names[i].name, (field_name_len * sizeof(char)), (void*)stmt->current_meta_data[i]->field_name, field_name_len); | ||
field_names[i].name[field_name_len] = '\0'; // null terminate the field name after the memcpy | ||
field_names[i].len = field_name_len; // field_name_len should not need to include the null char | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The old code seems to correct for wide char encoding. Is that no longer necessary with your changes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because when getting the meta data the field names have been converted if necessary There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In core_sqlsrv_field_metadata? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, convert_string_from_utf16() is invoked based on the encoding |
||
} | ||
|
||
stmt->fetch_field_names = field_names; | ||
stmt->fetch_fields_count = num_cols; | ||
field_names.transferred(); | ||
|
@@ -1840,12 +1871,12 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ | |
|
||
if( fetch_type & SQLSRV_FETCH_ASSOC ) { | ||
|
||
CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 1 && !allow_empty_field_names ), stmt, | ||
CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 0 && !allow_empty_field_names ), stmt, | ||
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) { | ||
throw ss::SSException(); | ||
} | ||
|
||
if( stmt->fetch_field_names[i].len > 1 || allow_empty_field_names ) { | ||
if( stmt->fetch_field_names[i].len > 0 || allow_empty_field_names ) { | ||
|
||
zr = add_assoc_zval( &fields, stmt->fetch_field_names[i].name, &field ); | ||
CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can write
>>
in c++11. Or leave a space after the first<
?