diff --git a/axiom/cli/Console.cpp b/axiom/cli/Console.cpp index 4400f3a52..22a1a1fae 100644 --- a/axiom/cli/Console.cpp +++ b/axiom/cli/Console.cpp @@ -21,10 +21,12 @@ #include #include #include +#include #include #include #include #include +#include #include "axiom/cli/LiveProgressDisplay.h" #include "axiom/cli/QueryInterruptHandler.h" #include "axiom/cli/ResultPrinter.h" @@ -49,6 +51,13 @@ DEFINE_uint64( DEFINE_int32(max_rows, 100, "Max number of printed result rows"); +DEFINE_string( + pager, + "less -SFX", + "Command used to view query results on a terminal. The default disables " + "line wrapping and exits automatically for short results. Set to empty to " + "print directly to stdout."); + DEFINE_int32(num_workers, 4, "Number of in-process workers"); DEFINE_int32(num_drivers, 4, "Number of drivers per worker"); @@ -104,6 +113,25 @@ int terminalWidth(int fileDescriptor) { return kDefaultTerminalWidth; } +// Sends formatted query results to the configured terminal pager. A pager is +// deliberately used only when stdout is a terminal: redirected output remains +// plain text for scripts and pipes. Return false so callers can fall back to +// stdout when the pager cannot be started or fails. +bool printWithPager(const std::string& text) { + if (!isatty(STDOUT_FILENO) || FLAGS_pager.empty()) { + return false; + } + + std::cout.flush(); + FILE* pager = popen(FLAGS_pager.c_str(), "w"); + if (pager == nullptr) { + return false; + } + + const auto written = std::fwrite(text.data(), 1, text.size(), pager); + return written == text.size() && pclose(pager) == 0; +} + // Extracts a file path argument from a dot-command string like ".run ". // Returns the trimmed path, or empty string if none. std::string parseDotCommandPath(const std::string& command, size_t prefixLen) { @@ -275,7 +303,12 @@ bool Console::runOnce( if (FLAGS_debug && !result.results.empty()) { std::cout << result.results.front()->rowType()->toString() << std::endl; } - cli::printResults(result.results, FLAGS_max_rows); + std::ostringstream formattedResults; + cli::printResults(result.results, FLAGS_max_rows, formattedResults); + const auto output = formattedResults.str(); + if (!printWithPager(output)) { + std::cout << output; + } } if (printTiming) { diff --git a/axiom/cli/Console.h b/axiom/cli/Console.h index 402935e8c..f52cde4ab 100644 --- a/axiom/cli/Console.h +++ b/axiom/cli/Console.h @@ -21,6 +21,7 @@ DECLARE_string(data_path); DECLARE_string(data_format); DECLARE_string(etc_dir); DECLARE_bool(debug); +DECLARE_string(pager); namespace axiom::sql { @@ -61,6 +62,9 @@ class Console { /// Runs the CLI. Executes `--query` if set, otherwise reads piped /// stdin if non-interactive, otherwise enters the interactive REPL. /// Honors `--repeat` for `--query` and piped-stdin paths. + /// Result tables written to a terminal are shown through `--pager` (by + /// default `less -SFX`), which keeps wide columns on one logical line for + /// horizontal navigation. Set `--pager=` to print directly to stdout. /// /// Throws VeloxUserError on invalid CLI flags. Query failures during /// execution are caught internally and printed to stderr; they do diff --git a/axiom/cli/README.md b/axiom/cli/README.md index 467945eeb..563b94b71 100644 --- a/axiom/cli/README.md +++ b/axiom/cli/README.md @@ -36,6 +36,7 @@ The examples below use `axiom_sql` for brevity. With Buck, replace | `--num_drivers` | `4` | Number of drivers per worker (parallelism). | | `--v2` | `false` | Route queries through the v2 optimizer. `EXPLAIN (type graph\|optimized)` is not supported under v2 yet. | | `--max_rows` | `100` | Maximum number of printed result rows. | +| `--pager` | `less -SFX` | Terminal command used for result tables. `-S` keeps wide columns on one logical line so they can be viewed with left/right navigation. Set `--pager=` to disable it. | | `--show_live_progress` | `false` | Draw the [live progress](#live-progress) grid for `--query` and piped-stdin runs (when stderr is a terminal). The interactive REPL always shows it; this only opts the non-interactive paths in. | | `--debug` | `false` | Enable debug mode (logging to stderr; adds per-split and CPU-time detail lines to the live progress display). | diff --git a/axiom/cli/ResultPrinter.cpp b/axiom/cli/ResultPrinter.cpp index 68a670f8a..7cf287acb 100644 --- a/axiom/cli/ResultPrinter.cpp +++ b/axiom/cli/ResultPrinter.cpp @@ -38,13 +38,14 @@ int64_t countResults(const std::vector& results) { int32_t printResults( const std::vector& results, - int64_t maxRows) { + int64_t maxRows, + std::ostream& output) { const auto numRows = countResults(results); auto printFooter = [&]() { - std::cout << "(" << numRows << " rows in " << results.size() << " batches)" - << std::endl - << std::endl; + output << "(" << numRows << " rows in " << results.size() << " batches)" + << std::endl + << std::endl; }; if (numRows == 0) { @@ -65,35 +66,35 @@ int32_t printResults( } auto printSeparator = [&]() { - std::cout << std::setfill('-'); + output << std::setfill('-'); for (auto i = 0; i < numColumns; ++i) { if (i > 0) { - std::cout << "-+-"; + output << "-+-"; } - std::cout << std::setw(widths[i]) << ""; + output << std::setw(widths[i]) << ""; } - std::cout << std::endl; - std::cout << std::setfill(' '); + output << std::endl; + output << std::setfill(' '); }; auto printRow = [&](const auto& row) { for (auto i = 0; i < numColumns; ++i) { if (i > 0) { - std::cout << " | "; + output << " | "; } if (alignLeft[i]) { - std::cout << std::left; + output << std::left; // Skip padding on the last column to avoid trailing whitespace. if (i < numColumns - 1) { - std::cout << std::setw(widths[i]); + output << std::setw(widths[i]); } } else { - std::cout << std::right; - std::cout << std::setw(widths[i]); + output << std::right; + output << std::setw(widths[i]); } - std::cout << row[i]; + output << row[i]; } - std::cout << std::endl; + output << std::endl; }; int32_t numPrinted = 0; @@ -108,9 +109,8 @@ int32_t printResults( } if (numPrinted < numRows) { - std::cout << std::endl; - std::cout << "..." << (numRows - numPrinted) << " more rows." - << std::endl; + output << std::endl; + output << "..." << (numRows - numPrinted) << " more rows." << std::endl; } printFooter(); diff --git a/axiom/cli/ResultPrinter.h b/axiom/cli/ResultPrinter.h index 86316b6c4..f25e06c27 100644 --- a/axiom/cli/ResultPrinter.h +++ b/axiom/cli/ResultPrinter.h @@ -16,17 +16,20 @@ #pragma once +#include #include #include "velox/vector/ComplexVector.h" namespace axiom::cli { -/// Prints results to stdout in a formatted table. +/// Prints results to a formatted table. /// @param results The results to print. /// @param maxRows The maximum number of rows to print. +/// @param output The stream that receives the formatted table. /// @return The total number of rows in the results. int32_t printResults( const std::vector& results, - int64_t maxRows); + int64_t maxRows, + std::ostream& output); } // namespace axiom::cli