#include "shell_renderer.hpp" #include "shell_state.hpp" #include "duckdb_shell_wrapper.h" #include "sqlite3.h" #include #include namespace duckdb_shell { bool ShellRenderer::IsColumnar(RenderMode mode) { switch (mode) { case RenderMode::COLUMN: case RenderMode::TABLE: case RenderMode::BOX: case RenderMode::MARKDOWN: case RenderMode::LATEX: return true; default: return false; } } ShellRenderer::ShellRenderer(ShellState &state) : state(state), show_header(state.showHeader), col_sep(state.colSeparator), row_sep(state.rowSeparator) { } //===--------------------------------------------------------------------===// // Column Renderers //===--------------------------------------------------------------------===// ColumnRenderer::ColumnRenderer(ShellState &state) : ShellRenderer(state) { } void ColumnRenderer::RenderFooter(ColumnarResult &result) { } void ColumnRenderer::RenderAlignedValue(ColumnarResult &result, idx_t i) { idx_t w = result.column_width[i]; idx_t n = state.RenderLength(result.data[i]); state.PrintPadded("", (w - n) / 2); state.Print(result.data[i]); state.PrintPadded("", (w - n + 1) / 2); } class ModeColumnRenderer : public ColumnRenderer { public: explicit ModeColumnRenderer(ShellState &state) : ColumnRenderer(state) { } void RenderHeader(ColumnarResult &result) override { if (!show_header) { return; } for (idx_t i = 0; i < result.column_count; i++) { state.UTF8WidthPrint(state.out, result.column_width[i], result.data[i], result.right_align[i]); state.Print(i == result.column_count - 1 ? "\n" : " "); } for (idx_t i = 0; i < result.column_count; i++) { state.PrintDashes(result.column_width[i]); state.Print(i == result.column_count - 1 ? "\n" : " "); } } const char *GetColumnSeparator() override { return " "; } const char *GetRowSeparator() override { return "\n"; } }; class ModeTableRenderer : public ColumnRenderer { public: explicit ModeTableRenderer(ShellState &state) : ColumnRenderer(state) { } void RenderHeader(ColumnarResult &result) override { state.PrintRowSeparator(result.column_count, "+", result.column_width); state.Print("| "); for (idx_t i = 0; i < result.column_count; i++) { RenderAlignedValue(result, i); state.Print(i == result.column_count - 1 ? " |\n" : " | "); } state.PrintRowSeparator(result.column_count, "+", result.column_width); } void RenderFooter(ColumnarResult &result) override { state.PrintRowSeparator(result.column_count, "+", result.column_width); } const char *GetColumnSeparator() override { return " | "; } const char *GetRowSeparator() override { return " |\n"; } const char *GetRowStart() override { return "| "; } }; class ModeMarkdownRenderer : public ColumnRenderer { public: explicit ModeMarkdownRenderer(ShellState &state) : ColumnRenderer(state) { } void RenderHeader(ColumnarResult &result) override { state.Print(GetRowStart()); for (idx_t i = 0; i < result.column_count; i++) { if (i > 0) { state.Print(GetColumnSeparator()); } RenderAlignedValue(result, i); } state.Print(GetRowSeparator()); state.PrintMarkdownSeparator(result.column_count, "|", result.types, result.column_width); } const char *GetColumnSeparator() override { return " | "; } const char *GetRowSeparator() override { return " |\n"; } const char *GetRowStart() override { return "| "; } }; /* ** UTF8 box-drawing characters. Imagine box lines like this: ** ** 1 ** | ** 4 --+-- 2 ** | ** 3 ** ** Each box characters has between 2 and 4 of the lines leading from ** the center. The characters are here identified by the numbers of ** their corresponding lines. */ #define BOX_24 "\342\224\200" /* U+2500 --- */ #define BOX_13 "\342\224\202" /* U+2502 | */ #define BOX_23 "\342\224\214" /* U+250c ,- */ #define BOX_34 "\342\224\220" /* U+2510 -, */ #define BOX_12 "\342\224\224" /* U+2514 '- */ #define BOX_14 "\342\224\230" /* U+2518 -' */ #define BOX_123 "\342\224\234" /* U+251c |- */ #define BOX_134 "\342\224\244" /* U+2524 -| */ #define BOX_234 "\342\224\254" /* U+252c -,- */ #define BOX_124 "\342\224\264" /* U+2534 -'- */ #define BOX_1234 "\342\224\274" /* U+253c -|- */ class ModeBoxRenderer : public ColumnRenderer { public: explicit ModeBoxRenderer(ShellState &state) : ColumnRenderer(state) { } void RenderHeader(ColumnarResult &result) override { print_box_row_separator(result.column_count, BOX_23, BOX_234, BOX_34, result.column_width); state.Print(BOX_13 " "); for (idx_t i = 0; i < result.column_count; i++) { RenderAlignedValue(result, i); state.Print(i == result.column_count - 1 ? " " BOX_13 "\n" : " " BOX_13 " "); } print_box_row_separator(result.column_count, BOX_123, BOX_1234, BOX_134, result.column_width); } void RenderFooter(ColumnarResult &result) override { print_box_row_separator(result.column_count, BOX_12, BOX_124, BOX_14, result.column_width); } const char *GetColumnSeparator() override { return " " BOX_13 " "; } const char *GetRowSeparator() override { return " " BOX_13 "\n"; } const char *GetRowStart() override { return BOX_13 " "; } private: /* Draw horizontal line N characters long using unicode box ** characters */ void print_box_line(idx_t N) { string box_line; for (idx_t i = 0; i < N; i++) { box_line += BOX_24; } state.Print(box_line); } /* ** Draw a horizontal separator for a RenderMode::Box table. */ void print_box_row_separator(int nArg, const char *zSep1, const char *zSep2, const char *zSep3, const vector &actualWidth) { int i; if (nArg > 0) { state.Print(zSep1); print_box_line(actualWidth[0] + 2); for (i = 1; i < nArg; i++) { state.Print(zSep2); print_box_line(actualWidth[i] + 2); } state.Print(zSep3); } state.Print("\n"); } }; class ModeLatexRenderer : public ColumnRenderer { public: explicit ModeLatexRenderer(ShellState &state) : ColumnRenderer(state) { } void RenderHeader(ColumnarResult &result) override { state.Print("\\begin{tabular}{|"); for (idx_t i = 0; i < result.column_count; i++) { if (state.ColumnTypeIsInteger(result.type_names[i])) { state.Print("r"); } else { state.Print("l"); } } state.Print("|}\n"); state.Print("\\hline\n"); for (idx_t i = 0; i < result.column_count; i++) { RenderAlignedValue(result, i); state.Print(i == result.column_count - 1 ? GetRowSeparator() : GetColumnSeparator()); } state.Print("\\hline\n"); } void RenderFooter(ColumnarResult &) override { state.Print("\\hline\n"); state.Print("\\end{tabular}\n"); } const char *GetColumnSeparator() override { return " & "; } const char *GetRowSeparator() override { return " \\\\\n"; } }; unique_ptr ShellState::GetColumnRenderer() { switch (cMode) { case RenderMode::COLUMN: return unique_ptr(new ModeColumnRenderer(*this)); case RenderMode::TABLE: return unique_ptr(new ModeTableRenderer(*this)); case RenderMode::MARKDOWN: return unique_ptr(new ModeMarkdownRenderer(*this)); case RenderMode::BOX: return unique_ptr(new ModeBoxRenderer(*this)); case RenderMode::LATEX: return unique_ptr(new ModeLatexRenderer(*this)); default: throw std::runtime_error("Unsupported mode for GetColumnRenderer"); } } //===--------------------------------------------------------------------===// // Row Renderers //===--------------------------------------------------------------------===// RowRenderer::RowRenderer(ShellState &state) : ShellRenderer(state) { } void RowRenderer::Render(RowResult &result) { if (first_row) { RenderHeader(result); first_row = false; } RenderRow(result); } void RowRenderer::RenderHeader(RowResult &result) { } void RowRenderer::RenderFooter(RowResult &result) { } class ModeLineRenderer : public RowRenderer { public: explicit ModeLineRenderer(ShellState &state) : RowRenderer(state) { } void Render(RowResult &result) override { if (first_row) { auto &col_names = result.column_names; // determine the render width by going over the column names header_width = 5; for (idx_t i = 0; i < col_names.size(); i++) { auto len = ShellState::StringLength(col_names[i] ? col_names[i] : ""); if (len > header_width) { header_width = len; } } first_row = false; } else { state.Print(state.rowSeparator); } // render the row RenderRow(result); } void RenderRow(RowResult &result) override { auto &data = result.data; auto &col_names = result.column_names; for (idx_t i = 0; i < data.size(); i++) { state.PrintPadded(col_names[i], header_width); state.Print(" = "); state.PrintValue(data[i]); state.Print(state.rowSeparator); } } idx_t header_width = 0; }; class ModeExplainRenderer : public RowRenderer { public: explicit ModeExplainRenderer(ShellState &state) : RowRenderer(state) { } void RenderRow(RowResult &result) override { auto &data = result.data; if (data.size() != 2) { return; } if (strcmp(data[0], "logical_plan") == 0 || strcmp(data[0], "logical_opt") == 0 || strcmp(data[0], "physical_plan") == 0) { state.Print("\n┌─────────────────────────────┐\n"); state.Print("│┌───────────────────────────┐│\n"); if (strcmp(data[0], "logical_plan") == 0) { state.Print("││ Unoptimized Logical Plan ││\n"); } else if (strcmp(data[0], "logical_opt") == 0) { state.Print("││ Optimized Logical Plan ││\n"); } else if (strcmp(data[0], "physical_plan") == 0) { state.Print("││ Physical Plan ││\n"); } state.Print("│└───────────────────────────┘│\n"); state.Print("└─────────────────────────────┘\n"); } state.Print(data[1]); } }; class ModeListRenderer : public RowRenderer { public: explicit ModeListRenderer(ShellState &state) : RowRenderer(state) { } void RenderHeader(RowResult &result) override { if (!show_header) { return; } auto &col_names = result.column_names; for (idx_t i = 0; i < col_names.size(); i++) { if (i > 0) { state.Print(col_sep); } state.Print(col_names[i]); } state.Print(row_sep); } void RenderRow(RowResult &result) override { auto &data = result.data; for (idx_t i = 0; i < data.size(); i++) { if (i > 0) { state.Print(col_sep); } state.PrintValue(data[i]); } state.Print(row_sep); } }; class ModeHtmlRenderer : public RowRenderer { public: explicit ModeHtmlRenderer(ShellState &state) : RowRenderer(state) { } void RenderHeader(RowResult &result) override { if (!show_header) { return; } auto &col_names = result.column_names; state.Print(""); for (idx_t i = 0; i < col_names.size(); i++) { state.Print(""); output_html_string(col_names[i]); state.Print("\n"); } state.Print("\n"); } void RenderRow(RowResult &result) override { auto &data = result.data; state.Print(""); for (idx_t i = 0; i < data.size(); i++) { state.Print(""); output_html_string(data[i] ? data[i] : state.nullValue.c_str()); state.Print("\n"); } state.Print("\n"); } /* ** Output the given string with characters that are special to ** HTML escaped. */ void output_html_string(const char *z) { if (z == 0) z = ""; string escaped; for (; *z; z++) { switch (*z) { case '<': escaped += "<"; break; case '&': escaped += "&"; break; case '>': escaped += ">"; break; case '\"': escaped += """; break; case '\'': escaped += "'"; break; default: escaped += *z; } } state.Print(escaped); } }; class ModeTclRenderer : public RowRenderer { public: explicit ModeTclRenderer(ShellState &state) : RowRenderer(state) { } void RenderHeader(RowResult &result) override { if (!show_header) { return; } auto &col_names = result.column_names; for (idx_t i = 0; i < col_names.size(); i++) { if (i > 0) { state.Print(col_sep); } state.OutputCString(col_names[i] ? col_names[i] : ""); } state.Print(row_sep); } void RenderRow(RowResult &result) override { auto &data = result.data; for (idx_t i = 0; i < data.size(); i++) { if (i > 0) { state.Print(col_sep); } state.OutputCString(data[i] ? data[i] : state.nullValue.c_str()); } state.Print(row_sep); } }; class ModeCsvRenderer : public RowRenderer { public: explicit ModeCsvRenderer(ShellState &state) : RowRenderer(state) { } void Render(RowResult &result) override { state.SetBinaryMode(); RowRenderer::Render(result); state.SetTextMode(); } void RenderHeader(RowResult &result) override { if (!show_header) { return; } auto &col_names = result.column_names; for (idx_t i = 0; i < col_names.size(); i++) { state.OutputCSV(col_names[i] ? col_names[i] : "", i < col_names.size() - 1); } state.Print(row_sep); } void RenderRow(RowResult &result) override { auto &data = result.data; for (idx_t i = 0; i < data.size(); i++) { state.OutputCSV(data[i], i < data.size() - 1); } state.Print(row_sep); } }; class ModeAsciiRenderer : public RowRenderer { public: explicit ModeAsciiRenderer(ShellState &state) : RowRenderer(state) { col_sep = "\n"; row_sep = "\n"; } void RenderHeader(RowResult &result) override { if (!show_header) { return; } auto &col_names = result.column_names; for (idx_t i = 0; i < col_names.size(); i++) { if (i > 0) { state.Print(col_sep); } state.Print(col_names[i] ? col_names[i] : ""); } state.Print(row_sep); } void RenderRow(RowResult &result) override { auto &data = result.data; for (idx_t i = 0; i < data.size(); i++) { if (i > 0) { state.Print(col_sep); } state.PrintValue(data[i]); } state.Print(row_sep); } }; class ModeQuoteRenderer : public RowRenderer { public: explicit ModeQuoteRenderer(ShellState &state) : RowRenderer(state) { } void RenderHeader(RowResult &result) override { if (!show_header) { return; } auto &col_names = result.column_names; for (idx_t i = 0; i < col_names.size(); i++) { if (i > 0) { state.Print(col_sep); } state.OutputQuotedString(col_names[i]); } state.Print(row_sep); } void RenderRow(RowResult &result) override { auto &data = result.data; auto &types = result.types; for (idx_t i = 0; i < data.size(); i++) { if (i > 0) state.Print(col_sep); if ((data[i] == 0) || (!types.empty() && types[i] == SQLITE_NULL)) { state.Print("NULL"); } else if (!types.empty() && (types[i] == SQLITE_TEXT || types[i] == SQLITE_BLOB)) { state.OutputQuotedString(data[i]); } else if (!types.empty() && (types[i] == SQLITE_INTEGER || types[i] == SQLITE_FLOAT)) { state.Print(data[i]); } else if (state.IsNumber(data[i], 0)) { state.Print(data[i]); } else { state.OutputQuotedString(data[i]); } } state.Print(row_sep); } }; class ModeJsonRenderer : public RowRenderer { public: explicit ModeJsonRenderer(ShellState &state, bool json_array) : RowRenderer(state), json_array(json_array) { } void Render(RowResult &result) override { if (first_row) { if (json_array) { // wrap all JSON objects in an array state.Print("["); } state.Print("{"); first_row = false; } else { if (json_array) { // wrap all JSON objects in an array state.Print(","); } state.Print("\n{"); } RenderRow(result); } void RenderRow(RowResult &result) override { auto &data = result.data; auto &types = result.types; auto &col_names = result.column_names; for (idx_t i = 0; i < col_names.size(); i++) { if (i > 0) { state.Print(","); } state.OutputJSONString(col_names[i], -1); state.Print(":"); if ((data[i] == 0) || (!types.empty() && types[i] == SQLITE_NULL)) { state.Print("null"); } else if (!types.empty() && types[i] == SQLITE_FLOAT) { if (strcmp(data[i], "inf") == 0) { state.Print("1e999"); } else if (strcmp(data[i], "-inf") == 0) { state.Print("-1e999"); } else if (strcmp(data[i], "nan") == 0) { state.Print("null"); } else if (strcmp(data[i], "-nan") == 0) { state.Print("null"); } else { state.Print(data[i]); } } else if (!types.empty() && types[i] == SQLITE_BLOB && result.pStmt) { const void *pBlob = sqlite3_column_blob(result.pStmt, i); int nBlob = sqlite3_column_bytes(result.pStmt, i); state.OutputJSONString((const char *)pBlob, nBlob); } else if (!types.empty() && types[i] == SQLITE_TEXT) { state.OutputJSONString(data[i], -1); } else { state.Print(data[i]); } } state.Print("}"); } void RenderFooter(RowResult &result) override { if (json_array) { state.Print("]\n"); } else { state.Print("\n"); } } bool json_array; }; class ModeInsertRenderer : public RowRenderer { public: explicit ModeInsertRenderer(ShellState &state) : RowRenderer(state) { } void RenderRow(RowResult &result) override { auto &data = result.data; auto &types = result.types; auto &col_names = result.column_names; state.Print("INSERT INTO "); state.Print(state.zDestTable); if (show_header) { state.Print("("); for (idx_t i = 0; i < col_names.size(); i++) { if (i > 0) { state.Print(","); } state.PrintOptionallyQuotedIdentifier(col_names[i]); } state.Print(")"); } for (idx_t i = 0; i < data.size(); i++) { state.Print(i > 0 ? "," : " VALUES("); if ((data[i] == 0) || (!types.empty() && types[i] == SQLITE_NULL)) { state.Print("NULL"); } else if (state.IsNumber(data[i], nullptr)) { state.Print(data[i]); } else if (state.ShellHasFlag(SHFLG_Newlines)) { state.OutputQuotedString(data[i]); } else { state.OutputQuotedEscapedString(data[i]); } } state.Print(");\n"); } }; class ModeSemiRenderer : public RowRenderer { public: explicit ModeSemiRenderer(ShellState &state) : RowRenderer(state) { } void RenderRow(RowResult &result) override { /* .schema and .fullschema output */ state.PrintSchemaLine(result.data[0], "\n"); } }; class ModePrettyRenderer : public RowRenderer { public: explicit ModePrettyRenderer(ShellState &state) : RowRenderer(state) { } void RenderRow(RowResult &result) override { auto &data = result.data; /* .schema and .fullschema with --indent */ if (data.size() != 1) { throw std::runtime_error("row must have exactly one value for pretty rendering"); } char *z; int j; int nParen = 0; char cEnd = 0; char c; int nLine = 0; if (!data[0]) { return; } if (sqlite3_strlike("CREATE VIEW%", data[0], 0) == 0 || sqlite3_strlike("CREATE TRIG%", data[0], 0) == 0) { state.Print(data[0]); state.Print(";\n"); return; } z = sqlite3_mprintf("%s", data[0]); j = 0; idx_t i; for (i = 0; IsSpace(z[i]); i++) { } for (; (c = z[i]) != 0; i++) { if (IsSpace(c)) { if (z[j - 1] == '\r') z[j - 1] = '\n'; if (IsSpace(z[j - 1]) || z[j - 1] == '(') continue; } else if ((c == '(' || c == ')') && j > 0 && IsSpace(z[j - 1])) { j--; } z[j++] = c; } while (j > 0 && IsSpace(z[j - 1])) { j--; } z[j] = 0; if (state.StringLength(z) >= 79) { for (i = j = 0; (c = z[i]) != 0; i++) { /* Copy from z[i] back to z[j] */ if (c == cEnd) { cEnd = 0; } else if (c == '"' || c == '\'' || c == '`') { cEnd = c; } else if (c == '[') { cEnd = ']'; } else if (c == '-' && z[i + 1] == '-') { cEnd = '\n'; } else if (c == '(') { nParen++; } else if (c == ')') { nParen--; if (nLine > 0 && nParen == 0 && j > 0) { state.PrintSchemaLineN(z, j, "\n"); j = 0; } } z[j++] = c; if (nParen == 1 && cEnd == 0 && (c == '(' || c == '\n' || (c == ',' && !wsToEol(z + i + 1)))) { if (c == '\n') j--; state.PrintSchemaLineN(z, j, "\n "); j = 0; nLine++; while (IsSpace(z[i + 1])) { i++; } } } z[j] = 0; } state.PrintSchemaLine(z, ";\n"); sqlite3_free(z); } /* ** Return true if string z[] has nothing but whitespace and comments to the ** end of the first line. */ static bool wsToEol(const char *z) { int i; for (i = 0; z[i]; i++) { if (z[i] == '\n') { return true; } if (IsSpace(z[i])) { continue; } if (z[i] == '-' && z[i + 1] == '-') { return true; } return false; } return true; } }; unique_ptr ShellState::GetRowRenderer() { return GetRowRenderer(cMode); } unique_ptr ShellState::GetRowRenderer(RenderMode mode) { switch (mode) { case RenderMode::LINE: return unique_ptr(new ModeLineRenderer(*this)); case RenderMode::EXPLAIN: return unique_ptr(new ModeExplainRenderer(*this)); case RenderMode::LIST: return unique_ptr(new ModeListRenderer(*this)); case RenderMode::HTML: return unique_ptr(new ModeHtmlRenderer(*this)); case RenderMode::TCL: return unique_ptr(new ModeTclRenderer(*this)); case RenderMode::CSV: return unique_ptr(new ModeCsvRenderer(*this)); case RenderMode::ASCII: return unique_ptr(new ModeAsciiRenderer(*this)); case RenderMode::QUOTE: return unique_ptr(new ModeQuoteRenderer(*this)); case RenderMode::JSON: return unique_ptr(new ModeJsonRenderer(*this, true)); case RenderMode::JSONLINES: return unique_ptr(new ModeJsonRenderer(*this, false)); case RenderMode::INSERT: return unique_ptr(new ModeInsertRenderer(*this)); case RenderMode::SEMI: return unique_ptr(new ModeSemiRenderer(*this)); case RenderMode::PRETTY: return unique_ptr(new ModePrettyRenderer(*this)); default: throw std::runtime_error("Unsupported mode for GetRowRenderer"); } } } // namespace duckdb_shell