modules/util/upgrade_check_formatter.cc (272 lines of code) (raw):
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "modules/util/upgrade_check_formatter.h"
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#include <rapidjson/stringbuffer.h>
#include <sstream>
#include "mysqlshdk/include/shellcore/console.h"
#include "mysqlshdk/include/shellcore/shell_options.h"
#include "mysqlshdk/libs/utils/utils_string.h"
namespace mysqlsh {
namespace {
std::string format_upgrade_issue(const Upgrade_issue &problem) {
std::stringstream ss;
const char *item = "Schema";
ss << problem.schema;
if (!problem.table.empty()) {
item = "Table";
ss << "." << problem.table;
if (!problem.column.empty()) {
item = "Column";
ss << "." << problem.column;
}
}
return shcore::str_format("%-8s: %s (%s) - %s",
Upgrade_issue::level_to_string(problem.level), item,
ss.str().c_str(), problem.description.c_str());
}
} // namespace
class Text_upgrade_checker_output : public Upgrade_check_output_formatter {
public:
Text_upgrade_checker_output() : m_console(mysqlsh::current_console()) {}
void check_info(const std::string &server_addres,
const std::string &server_version,
const std::string &target_version) override {
print_paragraph(
shcore::str_format(
"The MySQL server at %s, version %s, will now be checked for "
"compatibility issues for upgrade to MySQL %s...",
server_addres.c_str(), server_version.c_str(),
target_version.c_str()),
0, 0);
}
void check_results(const Upgrade_check &check,
const std::vector<Upgrade_issue> &results) override {
print_title(check.get_title());
std::function<std::string(const Upgrade_issue &)> issue_formater(
upgrade_issue_to_string);
if (results.empty()) {
print_paragraph("No issues found");
} else if (check.get_description() != nullptr) {
log(check.get_level(), check.get_description());
print_paragraph(check.get_description());
print_doc_links(check.get_doc_link());
m_console->println();
} else {
issue_formater = format_upgrade_issue;
}
for (const auto &issue : results) {
const auto msg = issue_formater(issue);
log(issue.level, msg.c_str());
print_paragraph(msg);
}
}
void check_error(const Upgrade_check &check, const char *description,
bool runtime_error = true) override {
print_title(check.get_title());
m_console->print(" ");
if (runtime_error) {
static constexpr auto k_check_failed = "Check failed: ";
log(Upgrade_issue::Level::ERROR, k_check_failed);
m_console->print_diag(k_check_failed);
}
log(Upgrade_issue::Level::ERROR, description);
m_console->println(description);
print_doc_links(check.get_doc_link());
}
void manual_check(const Upgrade_check &check) override {
print_title(check.get_title());
log(check.get_level(), check.get_description());
print_paragraph(check.get_description());
print_doc_links(check.get_doc_link());
}
void summarize(int error, int warning, int notice,
const std::string &text) override {
m_console->println();
m_console->println(shcore::str_format("Errors: %d", error));
m_console->println(shcore::str_format("Warnings: %d", warning));
m_console->println(shcore::str_format("Notices: %d\n", notice));
if (error > 0) {
m_console->print_error(text);
} else if (warning > 0 || notice > 0) {
m_console->print_note(text);
} else {
m_console->println(text);
}
}
private:
void print_title(const char *title) {
m_console->println();
print_paragraph(shcore::str_format("%d) %s", ++m_check_count, title), 0, 0);
}
void print_paragraph(const std::string &s, std::size_t base_indent = 2,
std::size_t indent_by = 2) {
std::string indent(base_indent, ' ');
auto descr = shcore::str_break_into_lines(s, 80 - base_indent);
for (std::size_t i = 0; i < descr.size(); i++) {
m_console->println(indent + descr[i]);
if (i == 0) indent.append(std::string(indent_by, ' '));
}
}
void print_doc_links(const char *links) {
if (links != nullptr) {
std::string docs("More information:\n");
print_paragraph(docs + links);
}
}
void log(Upgrade_issue::Level level, const char *msg) {
switch (level) {
case Upgrade_issue::Level::ERROR:
log_error("%s", msg);
break;
case Upgrade_issue::Level::WARNING:
log_warning("%s", msg);
break;
case Upgrade_issue::Level::NOTICE:
log_info("%s", msg);
break;
}
}
int m_check_count = 0;
std::shared_ptr<IConsole> m_console;
};
class JSON_upgrade_checker_output : public Upgrade_check_output_formatter {
public:
JSON_upgrade_checker_output()
: m_json_document(),
m_allocator(m_json_document.GetAllocator()),
m_checks(rapidjson::kArrayType),
m_manual_checks(rapidjson::kArrayType) {
m_json_document.SetObject();
}
void check_info(const std::string &server_addres,
const std::string &server_version,
const std::string &target_version) override {
rapidjson::Value addr;
addr.SetString(server_addres.c_str(), server_addres.length(), m_allocator);
m_json_document.AddMember("serverAddress", addr, m_allocator);
rapidjson::Value svr;
svr.SetString(server_version.c_str(), server_version.length(), m_allocator);
m_json_document.AddMember("serverVersion", svr, m_allocator);
rapidjson::Value tvr;
tvr.SetString(target_version.c_str(), target_version.length(), m_allocator);
m_json_document.AddMember("targetVersion", tvr, m_allocator);
}
void check_results(const Upgrade_check &check,
const std::vector<Upgrade_issue> &results) override {
rapidjson::Value check_object(rapidjson::kObjectType);
rapidjson::Value id;
check_object.AddMember("id", rapidjson::StringRef(check.get_name()),
m_allocator);
check_object.AddMember("title", rapidjson::StringRef(check.get_title()),
m_allocator);
check_object.AddMember("status", rapidjson::StringRef("OK"), m_allocator);
if (!results.empty()) {
if (check.get_description() != nullptr)
check_object.AddMember("description",
rapidjson::StringRef(check.get_description()),
m_allocator);
if (check.get_doc_link() != nullptr)
check_object.AddMember("documentationLink",
rapidjson::StringRef(check.get_doc_link()),
m_allocator);
}
rapidjson::Value issues(rapidjson::kArrayType);
for (const auto &issue : results) {
if (issue.empty()) continue;
rapidjson::Value issue_object(rapidjson::kObjectType);
issue_object.AddMember(
"level",
rapidjson::StringRef(Upgrade_issue::level_to_string(issue.level)),
m_allocator);
std::string db_object = issue.get_db_object();
rapidjson::Value dbov;
dbov.SetString(db_object.c_str(), db_object.length(), m_allocator);
issue_object.AddMember("dbObject", dbov, m_allocator);
rapidjson::Value description;
description.SetString(issue.description.c_str(),
issue.description.length(), m_allocator);
issue_object.AddMember("description", description, m_allocator);
issues.PushBack(issue_object, m_allocator);
}
check_object.AddMember("detectedProblems", issues, m_allocator);
m_checks.PushBack(check_object, m_allocator);
}
void check_error(const Upgrade_check &check, const char *description,
bool runtime_error = true) override {
rapidjson::Value check_object(rapidjson::kObjectType);
rapidjson::Value id;
check_object.AddMember("id", rapidjson::StringRef(check.get_name()),
m_allocator);
check_object.AddMember("title", rapidjson::StringRef(check.get_title()),
m_allocator);
if (runtime_error)
check_object.AddMember("status", rapidjson::StringRef("ERROR"),
m_allocator);
else
check_object.AddMember(
"status", rapidjson::StringRef("CONFIGURATION_ERROR"), m_allocator);
rapidjson::Value descr;
descr.SetString(description, strlen(description), m_allocator);
check_object.AddMember("description", descr, m_allocator);
if (check.get_doc_link() != nullptr)
check_object.AddMember("documentationLink",
rapidjson::StringRef(check.get_doc_link()),
m_allocator);
rapidjson::Value issues(rapidjson::kArrayType);
m_checks.PushBack(check_object, m_allocator);
}
void manual_check(const Upgrade_check &check) override {
rapidjson::Value check_object(rapidjson::kObjectType);
rapidjson::Value id;
check_object.AddMember("id", rapidjson::StringRef(check.get_name()),
m_allocator);
check_object.AddMember("title", rapidjson::StringRef(check.get_title()),
m_allocator);
check_object.AddMember("description",
rapidjson::StringRef(check.get_description()),
m_allocator);
if (check.get_doc_link() != nullptr)
check_object.AddMember("documentationLink",
rapidjson::StringRef(check.get_doc_link()),
m_allocator);
m_manual_checks.PushBack(check_object, m_allocator);
}
void summarize(int error, int warning, int notice,
const std::string &text) override {
m_json_document.AddMember("errorCount", error, m_allocator);
m_json_document.AddMember("warningCount", warning, m_allocator);
m_json_document.AddMember("noticeCount", notice, m_allocator);
rapidjson::Value val;
val.SetString(text.c_str(), text.length(), m_allocator);
m_json_document.AddMember("summary", val, m_allocator);
m_json_document.AddMember("checksPerformed", m_checks, m_allocator);
m_json_document.AddMember("manualChecks", m_manual_checks, m_allocator);
rapidjson::StringBuffer buffer;
if (mysqlsh::current_shell_options()->get().wrap_json == "json/raw") {
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
m_json_document.Accept(writer);
} else {
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
m_json_document.Accept(writer);
}
mysqlsh::current_console()->raw_print(
buffer.GetString(), mysqlsh::Output_stream::STDOUT, false);
mysqlsh::current_console()->raw_print("\n", mysqlsh::Output_stream::STDOUT,
false);
}
private:
rapidjson::Document m_json_document;
rapidjson::Document::AllocatorType &m_allocator;
rapidjson::Value m_checks;
rapidjson::Value m_manual_checks;
};
std::unique_ptr<Upgrade_check_output_formatter>
Upgrade_check_output_formatter::get_formatter(const std::string &format) {
if (shcore::str_caseeq(format, "JSON"))
return std::make_unique<JSON_upgrade_checker_output>();
else if (shcore::str_caseeq(format, "TEXT"))
return std::make_unique<Text_upgrade_checker_output>();
throw std::invalid_argument(
"Allowed values for outputFormat parameter are TEXT or JSON");
}
} // namespace mysqlsh