summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ydb/public/lib/ydb_cli/common/pretty_table.cpp347
-rw-r--r--ydb/public/lib/ydb_cli/common/pretty_table.h2
-rw-r--r--ydb/tests/functional/ydb_cli/canondata/result.json3
-rw-r--r--ydb/tests/functional/ydb_cli/canondata/test_ydb_scripting.TestExecuteScriptFromStdinWithWideOutput.test_wide_table/result.output4
-rw-r--r--ydb/tests/functional/ydb_cli/test_ydb_scripting.py55
5 files changed, 165 insertions, 246 deletions
diff --git a/ydb/public/lib/ydb_cli/common/pretty_table.cpp b/ydb/public/lib/ydb_cli/common/pretty_table.cpp
index 96c2d39a20f..5a23be0c372 100644
--- a/ydb/public/lib/ydb_cli/common/pretty_table.cpp
+++ b/ydb/public/lib/ydb_cli/common/pretty_table.cpp
@@ -1,13 +1,15 @@
#include "pretty_table.h"
-#include "common.h"
#include <library/cpp/colorizer/colors.h>
#include <util/generic/algorithm.h>
#include <util/generic/xrange.h>
#include <util/stream/format.h>
+#include <util/charset/utf8.h>
+#include <contrib/restricted/patched/replxx/src/utf8string.hxx>
#include <ydb/public/lib/ydb_cli/common/interactive.h>
+
namespace NYdb {
namespace NConsoleClient {
@@ -16,260 +18,130 @@ TPrettyTable::TRow::TRow(size_t nColumns)
{
}
+enum {
+ COLOR_BEGIN = '\033',
+ COLOR_END = 'm',
+};
+
size_t TPrettyTable::TRow::ColumnWidth(size_t columnIndex) const {
Y_ABORT_UNLESS(columnIndex < Columns.size());
- enum {
- TEXT,
- COLOR,
- UTF8,
- } state = TEXT;
size_t width = 0;
- TStringBuf data;
- for (const auto& line : Columns.at(columnIndex)) {
- data = line;
- // flag of first symbol in color
- bool first = false;
- // flag of enfing of color
- bool endcolor = false;
- int utf8Len = 0;
- // count of visible chars
- size_t curLen = 0;
- for (char ch : data) {
- switch (state) {
- case TEXT:
- // begin of color
- if (ch == '\033') {
- state = COLOR;
- first = true;
- endcolor = false;
- // begin utf8
- } else if ((ch & 0x80) != 0) {
- curLen++;
- utf8Len = 0;
- // if the first bit of the character is not 0, we met a multibyte
- // counting the number of single bits at the beginning of a byte
- while ((ch & 0x80) != 0) {
- utf8Len++;
- ch <<= 1;
- }
- state = UTF8;
- // common text
- } else {
- curLen++;
- }
- break;
- case UTF8:
- // skip n chars
- utf8Len -= 1;
- if (utf8Len == 0) {
- curLen++;
- while ((ch & 0x80) != 0) {
- utf8Len++;
- ch <<= 1;
- }
- if (utf8Len != 0) {
- state = UTF8;
- } else {
- state = TEXT;
- }
- }
- break;
- case COLOR:
- // first symbol must be [
- if (first) {
- if (ch != '[') {
- state = TEXT;
- }
- first = false;
- // at the end of color can be digits, m and ;
- } else if (endcolor) {
- if (ch != ';' && !isdigit(ch) && ch != 'm' ) {
- curLen++;
- state = TEXT;
- }
- // ending after ;
- } else {
- if (ch == ';') {
- endcolor = true;
- }
- }
- break;
+ for (const auto& column: Columns.at(columnIndex)) {
+ size_t printableSymbols = 0;
+
+ for (size_t i = 0; i < column.size();) {
+ if (column[i] == COLOR_BEGIN) {
+ while (i < column.size() && column[i] != COLOR_END) {
+ ++i;
+ }
+ continue;
+ }
+
+
+ ++i;
+ while (i < column.size() && IsUTF8ContinuationByte(column[i])) {
+ ++i;
}
+ ++printableSymbols;
}
- width = Max(width, curLen);
+ width = Max(width, printableSymbols);
}
return width;
}
-size_t TPrettyTable::TRow::PrintColumns(IOutputStream& o, const TVector<size_t>& widths, size_t offset, TString& oldColor) const {
- bool next = false;
- size_t firstColLen = 0;
- NColorizer::TColors colors = NColorizer::AutoColors(Cout);
-
- for (size_t columnIndex : xrange(Columns.size())) {
- enum {
- TEXT,
- COLOR,
- UTF8,
- } state = TEXT;
- o << colors.Default();
- if (columnIndex == 0) {
- o << "│ ";
- o << oldColor;
- } else {
- o << " │ ";
+class TColumnLinesPrinter {
+public:
+ TColumnLinesPrinter(
+ IOutputStream& o,
+ const TVector<TVector<TString>>& columns,
+ const TVector<size_t>& widths
+ )
+ : Output_(o)
+ , Columns_(columns)
+ , Widths_(widths)
+ , PrintedIndexByColumnIndex_(columns.size())
+ {}
+
+ bool HasNext() {
+ bool allColumnsPrinted = true;
+
+ for (size_t i = 0; i < PrintedIndexByColumnIndex_.size(); ++i) {
+ if (!Columns_[i].empty() && Columns_[i][0].size() > PrintedIndexByColumnIndex_[i]) {
+ allColumnsPrinted = false;
+ }
}
+
+ return !allColumnsPrinted;
+ }
+
+ void Print() {
+ NColorizer::TColors colors = NColorizer::AutoColors(Cout);
- if (size_t width = widths.at(columnIndex)) {
- const auto& column = Columns.at(columnIndex);
+ Output_ << colors.Default();
+ Output_ << "│ ";
- TStringBuf data;
- // count of visible chars
- size_t curLen = 0;
- // count of chars
- size_t absCurLen = 0;
- // flag of first symbol in color
- bool first = false;
- // flag of enfing of color
- bool endcolor = false;
+ for (size_t columnIndex : xrange(Columns_.size())) {
+ Output_ << colors.Default();
+ if (columnIndex != 0) {
+ Output_ << " │ ";
+ }
- absCurLen = 0;
- for (const auto& line : column) {
- data = line;
- TString s = "";
-
- data.Skip(offset);
- // len of utf8 symbol
- int utf8Len = 0;
-
- for (char ch : data) {
- if (next && columnIndex == 0) {
- break;
- }
- switch (state) {
- case TEXT:
- // begin of color
- if (ch == '\033') {
-
- state = COLOR;
- first = true;
- endcolor = false;
- o << s;
- s = ch;
- // begin utf8
- } else if ((ch & 0x80) != 0) {
- o << s;
- s = "";
- utf8Len = 0;
- curLen++;
- // if the first bit of the character is not 0, we met a multibyte
- // counting the number of single bits at the beginning of a byte
- while ((ch & 0x80) != 0) {
- utf8Len++;
- ch <<= 1;
- }
- if (curLen + utf8Len > width) {
- next = true;
- curLen -= 1;
- break;
- }
- o << data.SubStr(absCurLen, utf8Len);
- state = UTF8;
- // common text
- } else {
- curLen++;
- if (curLen > width) {
- next = true;
- curLen -= 1;
- break;
- }
- s += ch;
- }
- break;
- case UTF8:
- // skip n chars
- utf8Len -= 1;
- if (utf8Len == 0) {
- while ((ch & 0x80) != 0) {
- utf8Len++;
- ch <<= 1;
- }
- if (curLen + utf8Len > width) {
- next = true;
- curLen -= 1;
- break;
- }
- curLen++;
- if (utf8Len != 0) {
- o << data.SubStr(absCurLen, utf8Len);
- state = UTF8;
- } else {
- s = ch;
- state = TEXT;
- }
- }
- break;
- case COLOR:
- // first symbol must be [
- if (first) {
- if (ch != '[') {
- o << s;
- s = ch;
- state = TEXT;
- } else {
- s += ch;
- }
- first = false;
- // at the end of color can be digits, m and ;
- } else if (endcolor) {
- if (ch != ';' && !isdigit(ch) && ch != 'm' ) {
- o << s;
- oldColor = s;
- curLen++;
- if (curLen > width) {
- next = true;
- curLen -= 1;
- break;
- }
- s = ch;
- state = TEXT;
- } else {
- s += ch;
- }
- // ending after ;
- } else {
- if (ch == ';') {
- endcolor = true;
- }
- s += ch;
- }
-
- break;
- }
- absCurLen++;
- }
-
- if (s != "") {
- o << s;
+ size_t printedSymbols = PrintColumnLine(columnIndex);
+ Output_ << TString(Widths_[columnIndex] - printedSymbols, ' ');
+ }
+
+ Output_ << colors.Default();
+ Output_ << " │" << Endl;
+ }
+
+private:
+ /* return's printed symbols cnt */
+ size_t PrintColumnLine(size_t columnIndex) {
+ if (Columns_[columnIndex].empty()) {
+ return 0;
+ }
+
+ size_t printedSymbols = 0;
+ const auto& column = Columns_[columnIndex][0];
+
+ size_t i = PrintedIndexByColumnIndex_[columnIndex];
+
+ for (; i < column.size() && printedSymbols < Widths_[columnIndex];) {
+ if (column[i] == COLOR_BEGIN) {
+ while (i < column.size() && column[i] != COLOR_END) {
+ Output_ << column[i++];
}
+ continue;
}
- o << TString(width - curLen, ' ');
- if (columnIndex == 0) {
- firstColLen = absCurLen - 1;
+
+
+ Output_ << column[i++];
+ while (i < column.size() && IsUTF8ContinuationByte(column[i])) {
+ Output_ << column[i++];
}
+ ++printedSymbols;
}
+
+ PrintedIndexByColumnIndex_[columnIndex] = i;
+ return printedSymbols;
}
-
- o << colors.Default();
- o << " │" << Endl;
- if (next) {
- return firstColLen;
- } else {
- return 0;
+
+private:
+ IOutputStream& Output_;
+ const TVector<TVector<TString>>& Columns_;
+ const TVector<size_t>& Widths_;
+ TVector<size_t> PrintedIndexByColumnIndex_;
+};
+
+void TPrettyTable::TRow::PrintColumns(IOutputStream& o, const TVector<size_t>& widths) const {
+ TColumnLinesPrinter printer(o, Columns, widths);
+
+ while (printer.HasNext()) {
+ printer.Print();
}
}
@@ -324,14 +196,7 @@ void TPrettyTable::Print(IOutputStream& o) const {
PrintDelim(o, widths, "┌", "┬", "┐");
for (auto i : xrange(Rows.size())) {
const auto& row = Rows.at(i);
-
- size_t res = 1;
- size_t offset = 0;
- TString oldColor = "";
- while (res != 0) {
- res = row.PrintColumns(o, widths, offset, oldColor);
- offset += res;
- }
+ row.PrintColumns(o, widths);
if (row.HasFreeText()) {
PrintDelim(o, widths, "├", "┴", "┤", true);
diff --git a/ydb/public/lib/ydb_cli/common/pretty_table.h b/ydb/public/lib/ydb_cli/common/pretty_table.h
index 36de1df6da4..290aeb2d855 100644
--- a/ydb/public/lib/ydb_cli/common/pretty_table.h
+++ b/ydb/public/lib/ydb_cli/common/pretty_table.h
@@ -76,7 +76,7 @@ public:
private:
size_t ColumnWidth(size_t columnIndex) const;
size_t ExtraBytes(TStringBuf data) const;
- size_t PrintColumns(IOutputStream& o, const TVector<size_t>& widths, size_t offset, TString& oldColor) const;
+ void PrintColumns(IOutputStream& o, const TVector<size_t>& widths) const;
bool HasFreeText() const;
void PrintFreeText(IOutputStream& o, size_t width) const;
diff --git a/ydb/tests/functional/ydb_cli/canondata/result.json b/ydb/tests/functional/ydb_cli/canondata/result.json
index 9653de68f85..5dacdfc4b34 100644
--- a/ydb/tests/functional/ydb_cli/canondata/result.json
+++ b/ydb/tests/functional/ydb_cli/canondata/result.json
@@ -227,6 +227,9 @@
"test_ydb_impex.TestImpex.test_stdin[tsv-additional_args3-row]": {
"uri": "file://test_ydb_impex.TestImpex.test_stdin_tsv-additional_args3-row_/result.output"
},
+ "test_ydb_scripting.TestExecuteScriptFromStdinWithWideOutput.test_wide_table": {
+ "uri": "file://test_ydb_scripting.TestExecuteScriptFromStdinWithWideOutput.test_wide_table/result.output"
+ },
"test_ydb_scripting.TestExecuteScriptWithFormats.test_stream_yql_script_json_base64": {
"uri": "file://test_ydb_scripting.TestExecuteScriptWithFormats.test_stream_yql_script_json_base64/result.output"
},
diff --git a/ydb/tests/functional/ydb_cli/canondata/test_ydb_scripting.TestExecuteScriptFromStdinWithWideOutput.test_wide_table/result.output b/ydb/tests/functional/ydb_cli/canondata/test_ydb_scripting.TestExecuteScriptFromStdinWithWideOutput.test_wide_table/result.output
new file mode 100644
index 00000000000..36bbdf13246
--- /dev/null
+++ b/ydb/tests/functional/ydb_cli/canondata/test_ydb_scripting.TestExecuteScriptFromStdinWithWideOutput.test_wide_table/result.output
@@ -0,0 +1,4 @@
+┌─────┬──────────────┬─────────┬──────┬───────────┬───────────────┬─────────────┬─────────┬───────────┬─────┬─────────────────────┬────────────────────┬────────────┬──────────┬─────┬─────────────┬─────────────┬───────────┬─────────┬─────────┬──────────┐
+│ box │ container_id │ context │ host │ log_level │ log_level_int │ logger_name │ message │ node_fqdn │ pod │ pod_persistent_fqdn │ pod_transient_fqdn │ request_id │ saved_at │ seq │ stack_trace │ thread_name │ timestamp │ user_id │ version │ workload │
+├─────┼──────────────┼─────────┼──────┼───────────┼───────────────┼─────────────┼─────────┼───────────┼─────┼─────────────────────┼────────────────────┼────────────┼──────────┼─────┼─────────────┼─────────────┼───────────┼─────────┼─────────┼──────────┤
+└─────┴──────────────┴─────────┴──────┴───────────┴───────────────┴─────────────┴─────────┴───────────┴─────┴─────────────────────┴────────────────────┴────────────┴──────────┴─────┴─────────────┴─────────────┴───────────┴─────────┴─────────┴──────────┘
diff --git a/ydb/tests/functional/ydb_cli/test_ydb_scripting.py b/ydb/tests/functional/ydb_cli/test_ydb_scripting.py
index d9c1efcee55..2750034e2b1 100644
--- a/ydb/tests/functional/ydb_cli/test_ydb_scripting.py
+++ b/ydb/tests/functional/ydb_cli/test_ydb_scripting.py
@@ -49,8 +49,8 @@ def create_table_with_data(session, path):
class BaseTestScriptingService(object):
@classmethod
- def execute_ydb_cli_command(cls, args, stdin=None):
- execution = yatest_common.execute([ydb_bin()] + args, stdin=stdin)
+ def execute_ydb_cli_command(cls, args, stdin=None, env=None):
+ execution = yatest_common.execute([ydb_bin()] + args, stdin=stdin, env=env)
result = execution.std_out
logger.debug("std_out:\n" + result.decode('utf-8'))
return result
@@ -81,13 +81,13 @@ class BaseTestScriptingServiceWithDatabase(BaseTestScriptingService):
cls.cluster.stop()
@classmethod
- def execute_ydb_cli_command_with_db(cls, args, stdin=None):
+ def execute_ydb_cli_command_with_db(cls, args, stdin=None, env=None):
return cls.execute_ydb_cli_command(
[
"--endpoint", "grpc://localhost:%d" % cls.cluster.nodes[1].grpc_port,
"--database", cls.root_dir
] +
- args, stdin
+ args, stdin, env=env
)
@@ -849,3 +849,50 @@ class TestExecuteScriptWithParamsFromStdin(BaseTestScriptingServiceWithDatabase)
def test_skip_rows_tsv(self, command):
return self.skip_rows(self.get_command(command), "tsv")
+
+
+def create_wide_table_with_data(session, path):
+ session.create_table(
+ path,
+ ydb.TableDescription()
+ .with_column(ydb.Column("timestamp", ydb.PrimitiveType.Timestamp))
+ .with_column(ydb.Column("pod", ydb.PrimitiveType.Utf8))
+ .with_column(ydb.Column("seq", ydb.PrimitiveType.Uint64))
+ .with_column(ydb.Column("container_id", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("host", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("box", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("workload", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("logger_name", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("user_id", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("request_id", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("message", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("log_level", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("log_level_int", ydb.OptionalType(ydb.PrimitiveType.Int64)))
+ .with_column(ydb.Column("stack_trace", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("thread_name", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("pod_transient_fqdn", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("pod_persistent_fqdn", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("node_fqdn", ydb.OptionalType(ydb.PrimitiveType.Utf8)))
+ .with_column(ydb.Column("context", ydb.OptionalType(ydb.PrimitiveType.JsonDocument)))
+ .with_column(ydb.Column("version", ydb.OptionalType(ydb.PrimitiveType.Int32)))
+ .with_column(ydb.Column("saved_at", ydb.OptionalType(ydb.PrimitiveType.Timestamp)))
+ .with_primary_keys("timestamp", "pod", "seq")
+ )
+
+class TestExecuteScriptFromStdinWithWideOutput(BaseTestScriptingServiceWithDatabase):
+ @classmethod
+ def setup_class(cls):
+ BaseTestScriptingServiceWithDatabase.setup_class()
+ cls.session = cls.driver.table_client.session().create()
+
+ @pytest.fixture(autouse=True, scope='function')
+ def init_test(self, tmp_path):
+ self.tmp_path = tmp_path
+ self.table_path = self.root_dir + "/" + self.tmp_path.name
+ create_wide_table_with_data(self.session, self.table_path)
+
+ def test_wide_table(self):
+ script = "SELECT * FROM `{}`;".format(self.table_path)
+ output = self.execute_ydb_cli_command_with_db(["yql", "-s", script])
+ return self.canonical_result(output, self.tmp_path)
+ \ No newline at end of file