1 | #include <sys/ioctl.h> |
2 | #include <port/unistd.h> |
3 | #include <Processors/Formats/Impl/PrettyBlockOutputFormat.h> |
4 | #include <Formats/FormatFactory.h> |
5 | #include <IO/WriteBuffer.h> |
6 | #include <IO/WriteHelpers.h> |
7 | #include <IO/WriteBufferFromString.h> |
8 | #include <Common/PODArray.h> |
9 | #include <Common/UTF8Helpers.h> |
10 | |
11 | namespace DB |
12 | { |
13 | |
14 | namespace ErrorCodes |
15 | { |
16 | extern const int ILLEGAL_COLUMN; |
17 | } |
18 | |
19 | |
20 | PrettyBlockOutputFormat::PrettyBlockOutputFormat( |
21 | WriteBuffer & out_, const Block & , const FormatSettings & format_settings_) |
22 | : IOutputFormat(header_, out_), format_settings(format_settings_) |
23 | { |
24 | struct winsize w; |
25 | if (0 == ioctl(STDOUT_FILENO, TIOCGWINSZ, &w)) |
26 | terminal_width = w.ws_col; |
27 | } |
28 | |
29 | |
30 | /// Evaluate the visible width of the values and column names. |
31 | /// Note that number of code points is just a rough approximation of visible string width. |
32 | void PrettyBlockOutputFormat::calculateWidths( |
33 | const Block & , const Chunk & chunk, |
34 | WidthsPerColumn & widths, Widths & max_widths, Widths & name_widths) |
35 | { |
36 | size_t num_rows = chunk.getNumRows(); |
37 | size_t num_columns = chunk.getNumColumns(); |
38 | auto & columns = chunk.getColumns(); |
39 | |
40 | widths.resize(num_columns); |
41 | max_widths.resize_fill(num_columns); |
42 | name_widths.resize(num_columns); |
43 | |
44 | /// Calculate widths of all values. |
45 | String serialized_value; |
46 | size_t prefix = 2; // Tab character adjustment |
47 | for (size_t i = 0; i < num_columns; ++i) |
48 | { |
49 | auto & elem = header.getByPosition(i); |
50 | auto & column = columns[i]; |
51 | |
52 | widths[i].resize(num_rows); |
53 | |
54 | for (size_t j = 0; j < num_rows; ++j) |
55 | { |
56 | { |
57 | WriteBufferFromString out_(serialized_value); |
58 | elem.type->serializeAsText(*column, j, out_, format_settings); |
59 | } |
60 | |
61 | widths[i][j] = std::min<UInt64>(format_settings.pretty.max_column_pad_width, |
62 | UTF8::computeWidth(reinterpret_cast<const UInt8 *>(serialized_value.data()), serialized_value.size(), prefix)); |
63 | max_widths[i] = std::max(max_widths[i], widths[i][j]); |
64 | } |
65 | |
66 | /// And also calculate widths for names of columns. |
67 | { |
68 | // name string doesn't contain Tab, no need to pass `prefix` |
69 | name_widths[i] = std::min<UInt64>(format_settings.pretty.max_column_pad_width, |
70 | UTF8::computeWidth(reinterpret_cast<const UInt8 *>(elem.name.data()), elem.name.size())); |
71 | max_widths[i] = std::max(max_widths[i], name_widths[i]); |
72 | } |
73 | prefix += max_widths[i] + 3; |
74 | } |
75 | } |
76 | |
77 | |
78 | void PrettyBlockOutputFormat::write(const Chunk & chunk, PortKind port_kind) |
79 | { |
80 | UInt64 max_rows = format_settings.pretty.max_rows; |
81 | |
82 | if (total_rows >= max_rows) |
83 | { |
84 | total_rows += chunk.getNumRows(); |
85 | return; |
86 | } |
87 | |
88 | auto num_rows = chunk.getNumRows(); |
89 | auto num_columns = chunk.getNumColumns(); |
90 | auto & columns = chunk.getColumns(); |
91 | auto & = getPort(port_kind).getHeader(); |
92 | |
93 | WidthsPerColumn widths; |
94 | Widths max_widths; |
95 | Widths name_widths; |
96 | calculateWidths(header, chunk, widths, max_widths, name_widths); |
97 | |
98 | /// Create separators |
99 | std::stringstream top_separator; |
100 | std::stringstream middle_names_separator; |
101 | std::stringstream middle_values_separator; |
102 | std::stringstream bottom_separator; |
103 | |
104 | top_separator << "┏" ; |
105 | middle_names_separator << "┡" ; |
106 | middle_values_separator << "├" ; |
107 | bottom_separator << "└" ; |
108 | for (size_t i = 0; i < num_columns; ++i) |
109 | { |
110 | if (i != 0) |
111 | { |
112 | top_separator << "┳" ; |
113 | middle_names_separator << "╇" ; |
114 | middle_values_separator << "┼" ; |
115 | bottom_separator << "┴" ; |
116 | } |
117 | |
118 | for (size_t j = 0; j < max_widths[i] + 2; ++j) |
119 | { |
120 | top_separator << "━" ; |
121 | middle_names_separator << "━" ; |
122 | middle_values_separator << "─" ; |
123 | bottom_separator << "─" ; |
124 | } |
125 | } |
126 | top_separator << "┓\n" ; |
127 | middle_names_separator << "┩\n" ; |
128 | middle_values_separator << "┤\n" ; |
129 | bottom_separator << "┘\n" ; |
130 | |
131 | std::string top_separator_s = top_separator.str(); |
132 | std::string middle_names_separator_s = middle_names_separator.str(); |
133 | std::string middle_values_separator_s = middle_values_separator.str(); |
134 | std::string bottom_separator_s = bottom_separator.str(); |
135 | |
136 | /// Output the block |
137 | writeString(top_separator_s, out); |
138 | |
139 | /// Names |
140 | writeCString("┃ " , out); |
141 | for (size_t i = 0; i < num_columns; ++i) |
142 | { |
143 | if (i != 0) |
144 | writeCString(" ┃ " , out); |
145 | |
146 | auto & col = header.getByPosition(i); |
147 | |
148 | if (format_settings.pretty.color) |
149 | writeCString("\033[1m" , out); |
150 | |
151 | if (col.type->shouldAlignRightInPrettyFormats()) |
152 | { |
153 | for (size_t k = 0; k < max_widths[i] - name_widths[i]; ++k) |
154 | writeChar(' ', out); |
155 | |
156 | writeString(col.name, out); |
157 | } |
158 | else |
159 | { |
160 | writeString(col.name, out); |
161 | |
162 | for (size_t k = 0; k < max_widths[i] - name_widths[i]; ++k) |
163 | writeChar(' ', out); |
164 | } |
165 | |
166 | if (format_settings.pretty.color) |
167 | writeCString("\033[0m" , out); |
168 | } |
169 | writeCString(" ┃\n" , out); |
170 | |
171 | writeString(middle_names_separator_s, out); |
172 | |
173 | for (size_t i = 0; i < num_rows && total_rows + i < max_rows; ++i) |
174 | { |
175 | if (i != 0) |
176 | writeString(middle_values_separator_s, out); |
177 | |
178 | writeCString("│ " , out); |
179 | |
180 | for (size_t j = 0; j < num_columns; ++j) |
181 | { |
182 | if (j != 0) |
183 | writeCString(" │ " , out); |
184 | |
185 | auto & type = *header.getByPosition(j).type; |
186 | writeValueWithPadding(*columns[j], type, i, widths[j].empty() ? max_widths[j] : widths[j][i], max_widths[j]); |
187 | } |
188 | |
189 | writeCString(" │\n" , out); |
190 | } |
191 | |
192 | writeString(bottom_separator_s, out); |
193 | |
194 | total_rows += num_rows; |
195 | } |
196 | |
197 | |
198 | void PrettyBlockOutputFormat::writeValueWithPadding( |
199 | const IColumn & column, const IDataType & type, size_t row_num, size_t value_width, size_t pad_to_width) |
200 | { |
201 | auto writePadding = [&]() |
202 | { |
203 | for (size_t k = 0; k < pad_to_width - value_width; ++k) |
204 | writeChar(' ', out); |
205 | }; |
206 | |
207 | if (type.shouldAlignRightInPrettyFormats()) |
208 | { |
209 | writePadding(); |
210 | type.serializeAsText(column, row_num, out, format_settings); |
211 | } |
212 | else |
213 | { |
214 | type.serializeAsText(column, row_num, out, format_settings); |
215 | writePadding(); |
216 | } |
217 | } |
218 | |
219 | |
220 | void PrettyBlockOutputFormat::consume(Chunk chunk) |
221 | { |
222 | write(chunk, PortKind::Main); |
223 | } |
224 | |
225 | void PrettyBlockOutputFormat::consumeTotals(Chunk chunk) |
226 | { |
227 | total_rows = 0; |
228 | writeSuffixIfNot(); |
229 | writeCString("\nExtremes:\n" , out); |
230 | write(chunk, PortKind::Totals); |
231 | } |
232 | |
233 | void PrettyBlockOutputFormat::consumeExtremes(Chunk chunk) |
234 | { |
235 | total_rows = 0; |
236 | writeSuffixIfNot(); |
237 | writeCString("\nTotals:\n" , out); |
238 | write(chunk, PortKind::Extremes); |
239 | } |
240 | |
241 | |
242 | void PrettyBlockOutputFormat::writeSuffix() |
243 | { |
244 | if (total_rows >= format_settings.pretty.max_rows) |
245 | { |
246 | writeCString(" Showed first " , out); |
247 | writeIntText(format_settings.pretty.max_rows, out); |
248 | writeCString(".\n" , out); |
249 | } |
250 | } |
251 | |
252 | void PrettyBlockOutputFormat::finalize() |
253 | { |
254 | writeSuffixIfNot(); |
255 | } |
256 | |
257 | |
258 | void registerOutputFormatProcessorPretty(FormatFactory & factory) |
259 | { |
260 | factory.registerOutputFormatProcessor("Pretty" , []( |
261 | WriteBuffer & buf, |
262 | const Block & sample, |
263 | FormatFactory::WriteCallback, |
264 | const FormatSettings & format_settings) |
265 | { |
266 | return std::make_shared<PrettyBlockOutputFormat>(buf, sample, format_settings); |
267 | }); |
268 | |
269 | factory.registerOutputFormatProcessor("PrettyNoEscapes" , []( |
270 | WriteBuffer & buf, |
271 | const Block & sample, |
272 | FormatFactory::WriteCallback, |
273 | const FormatSettings & format_settings) |
274 | { |
275 | FormatSettings changed_settings = format_settings; |
276 | changed_settings.pretty.color = false; |
277 | return std::make_shared<PrettyBlockOutputFormat>(buf, sample, changed_settings); |
278 | }); |
279 | } |
280 | |
281 | } |
282 | |