void sqlsrv_param_inout::finalize_output_string()

in source/shared/core_stmt.cpp [2825:2908]


void sqlsrv_param_inout::finalize_output_string()
{
    zval* value_z = Z_REFVAL_P(param_ptr_z);

    // Adjust the length of the string to the value returned by SQLBindParameter in the strlen_or_indptr argument
    if (strlen_or_indptr == 0) {
        core::sqlsrv_zval_stringl(value_z, "", 0);
        return;
    }
    if (strlen_or_indptr == SQL_NULL_DATA) {
        zend_string_release(Z_STR_P(value_z));
        ZVAL_NULL(value_z);
        return;
    }

    // If there was more to output than buffer size to hold it, then throw a truncation error
    SQLLEN str_len = strlen_or_indptr;
    char* str = Z_STRVAL_P(value_z);
    int null_size = 0;

    switch (encoding) {
    case SQLSRV_ENCODING_UTF8:
        null_size = sizeof(SQLWCHAR);  // The string isn't yet converted to UTF-8, still UTF-16
        break;
    case SQLSRV_ENCODING_SYSTEM:
        null_size = sizeof(SQLCHAR);
        break;
    case SQLSRV_ENCODING_BINARY:
        null_size = 0;
        break;
    default:
        SQLSRV_ASSERT(false, "Should not have reached here - invalid encoding in sqlsrv_param_inout::process_output_string.");
        break;
    }

    CHECK_CUSTOM_ERROR(str_len > (buffer_length - null_size), stmt, SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, param_pos + 1) {
        throw core::CoreException();
    }

    // For ODBC 11+ see https://docs.microsoft.com/sql/relational-databases/native-client/features/odbc-driver-behavior-change-when-handling-character-conversions
    // A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains data up to the
    // original buffer_length and is NULL terminated.
    // The IF statement can be true when using connection pooling with unixODBC 2.3.4.
    if (str_len == SQL_NO_TOTAL) {
        str_len = buffer_length - null_size;
    }

    if (encoding == SQLSRV_ENCODING_BINARY) {
        // ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
        // so we do that here if the length of the returned data is less than the original allocation. The
        // original allocation null terminates the buffer already.
        if (str_len < buffer_length) {
            str[str_len] = '\0';
        }
        core::sqlsrv_zval_stringl(value_z, str, str_len);
    }
    else {
        if (encoding != SQLSRV_ENCODING_CHAR) {
            char* outString = NULL;
            SQLLEN outLen = 0;

            bool result = convert_string_from_utf16(encoding, reinterpret_cast<const SQLWCHAR*>(str), int(str_len / sizeof(SQLWCHAR)), &outString, outLen);
            CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
                throw core::CoreException();
            }

            if (stmt->format_decimals && (sql_data_type == SQL_DECIMAL || sql_data_type == SQL_NUMERIC)) {
                format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, decimal_digits, outString, &outLen);
            }

            core::sqlsrv_zval_stringl(value_z, outString, outLen);
            sqlsrv_free(outString);
        }
        else {
            if (stmt->format_decimals && (sql_data_type == SQL_DECIMAL || sql_data_type == SQL_NUMERIC)) {
                format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, decimal_digits, str, &str_len);
            }

            core::sqlsrv_zval_stringl(value_z, str, str_len);
        }
    }

    value_z = NULL;
}