void get_field_as_string()

in source/shared/core_stmt.cpp [1631:1816]


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;
    SQLSMALLINT sql_field_type = 0;
    SQLSMALLINT extra = 0;
    SQLLEN field_len_temp = 0;
    SQLLEN sql_display_size = 0;
    char* field_value_temp = NULL;
    unsigned int initial_field_len = INITIAL_FIELD_STRING_LEN;

    try {

        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 );

            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 ) );
        }

        // Determine the correct encoding
        if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
            sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding();
        }
        // Set the C type and account for null characters at the end of the data.
        if (sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_BINARY) {
            c_type = SQL_C_BINARY;
            extra = 0;
        } else {
            c_type = SQL_C_CHAR;
            extra = sizeof(SQLCHAR);
            
            // For numbers, no need to convert
            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 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 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 = 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 + 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);
            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 add 1 to fill the null terminator
        if (field_len_temp > 0) {
            field_value_temp[field_len_temp] = '\0';
        }
    }
    catch (core::CoreException&) {
        field_value = NULL;
        *field_len = 0;
        sqlsrv_free(field_value_temp);
        throw;
    } catch (...) {
        field_value = NULL;
        *field_len = 0;
        sqlsrv_free(field_value_temp);
        throw;
    }
}