1#include <Functions/IFunctionImpl.h>
2#include <Functions/FunctionHelpers.h>
3#include <Functions/FunctionFactory.h>
4#include <DataTypes/DataTypeString.h>
5#include <Columns/ColumnString.h>
6#include <Columns/ColumnVector.h>
7#include <Common/UnicodeBar.h>
8#include <Common/FieldVisitors.h>
9#include <IO/WriteHelpers.h>
10
11
12namespace DB
13{
14
15namespace ErrorCodes
16{
17 extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
18 extern const int ARGUMENT_OUT_OF_BOUND;
19 extern const int ILLEGAL_COLUMN;
20 extern const int ILLEGAL_TYPE_OF_ARGUMENT;
21}
22
23/** bar(x, min, max, width) - draws a strip from the number of characters proportional to (x - min) and equal to width for x == max.
24 * Returns a string with nice Unicode-art bar with resolution of 1/8 part of symbol.
25 */
26class FunctionBar : public IFunction
27{
28public:
29 static constexpr auto name = "bar";
30 static FunctionPtr create(const Context &)
31 {
32 return std::make_shared<FunctionBar>();
33 }
34
35 String getName() const override
36 {
37 return name;
38 }
39
40 bool isVariadic() const override
41 {
42 return true;
43 }
44 size_t getNumberOfArguments() const override
45 {
46 return 0;
47 }
48
49 DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
50 {
51 if (arguments.size() != 3 && arguments.size() != 4)
52 throw Exception("Function " + getName()
53 + " requires from 3 or 4 parameters: value, min_value, max_value, [max_width_of_bar = 80]. Passed "
54 + toString(arguments.size())
55 + ".",
56 ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
57
58 if (!isNativeNumber(arguments[0]) || !isNativeNumber(arguments[1]) || !isNativeNumber(arguments[2])
59 || (arguments.size() == 4 && !isNativeNumber(arguments[3])))
60 throw Exception("All arguments for function " + getName() + " must be numeric.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
61
62 return std::make_shared<DataTypeString>();
63 }
64
65 bool useDefaultImplementationForConstants() const override { return true; }
66 ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {1, 2, 3}; }
67
68 void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t /*input_rows_count*/) override
69 {
70 Int64 min = extractConstant<Int64>(block, arguments, 1, "Second"); /// The level at which the line has zero length.
71 Int64 max = extractConstant<Int64>(block, arguments, 2, "Third"); /// The level at which the line has the maximum length.
72
73 /// The maximum width of the bar in characters, by default.
74 Float64 max_width = arguments.size() == 4 ? extractConstant<Float64>(block, arguments, 3, "Fourth") : 80;
75
76 if (max_width < 1)
77 throw Exception("Max_width argument must be >= 1.", ErrorCodes::ARGUMENT_OUT_OF_BOUND);
78
79 if (max_width > 1000)
80 throw Exception("Too large max_width.", ErrorCodes::ARGUMENT_OUT_OF_BOUND);
81
82 const auto & src = *block.getByPosition(arguments[0]).column;
83
84 auto res_column = ColumnString::create();
85
86 if (executeNumber<UInt8>(src, *res_column, min, max, max_width)
87 || executeNumber<UInt16>(src, *res_column, min, max, max_width)
88 || executeNumber<UInt32>(src, *res_column, min, max, max_width)
89 || executeNumber<UInt64>(src, *res_column, min, max, max_width)
90 || executeNumber<Int8>(src, *res_column, min, max, max_width)
91 || executeNumber<Int16>(src, *res_column, min, max, max_width)
92 || executeNumber<Int32>(src, *res_column, min, max, max_width)
93 || executeNumber<Int64>(src, *res_column, min, max, max_width)
94 || executeNumber<Float32>(src, *res_column, min, max, max_width)
95 || executeNumber<Float64>(src, *res_column, min, max, max_width))
96 {
97 block.getByPosition(result).column = std::move(res_column);
98 }
99 else
100 throw Exception(
101 "Illegal column " + block.getByPosition(arguments[0]).column->getName() + " of argument of function " + getName(),
102 ErrorCodes::ILLEGAL_COLUMN);
103 }
104
105private:
106 template <typename T>
107 T extractConstant(Block & block, const ColumnNumbers & arguments, size_t argument_pos, const char * which_argument) const
108 {
109 const auto & column = *block.getByPosition(arguments[argument_pos]).column;
110
111 if (!isColumnConst(column))
112 throw Exception(
113 which_argument + String(" argument for function ") + getName() + " must be constant.", ErrorCodes::ILLEGAL_COLUMN);
114
115 return applyVisitor(FieldVisitorConvertToNumber<T>(), column[0]);
116 }
117
118 template <typename T>
119 static void fill(const PaddedPODArray<T> & src,
120 ColumnString::Chars & dst_chars,
121 ColumnString::Offsets & dst_offsets,
122 Int64 min,
123 Int64 max,
124 Float64 max_width)
125 {
126 size_t size = src.size();
127 size_t current_offset = 0;
128
129 dst_offsets.resize(size);
130 dst_chars.reserve(size * (UnicodeBar::getWidthInBytes(max_width) + 1)); /// lines 0-terminated.
131
132 for (size_t i = 0; i < size; ++i)
133 {
134 Float64 width = UnicodeBar::getWidth(src[i], min, max, max_width);
135 size_t next_size = current_offset + UnicodeBar::getWidthInBytes(width) + 1;
136 dst_chars.resize(next_size);
137 UnicodeBar::render(width, reinterpret_cast<char *>(&dst_chars[current_offset]));
138 current_offset = next_size;
139 dst_offsets[i] = current_offset;
140 }
141 }
142
143 template <typename T>
144 static void fill(T src, String & dst_chars, Int64 min, Int64 max, Float64 max_width)
145 {
146 Float64 width = UnicodeBar::getWidth(src, min, max, max_width);
147 dst_chars.resize(UnicodeBar::getWidthInBytes(width));
148 UnicodeBar::render(width, dst_chars.data());
149 }
150
151 template <typename T>
152 static bool executeNumber(const IColumn & src, ColumnString & dst, Int64 min, Int64 max, Float64 max_width)
153 {
154 if (const ColumnVector<T> * col = checkAndGetColumn<ColumnVector<T>>(&src))
155 {
156 fill(col->getData(), dst.getChars(), dst.getOffsets(), min, max, max_width);
157 return true;
158 }
159 else
160 return false;
161 }
162};
163
164
165void registerFunctionBar(FunctionFactory & factory)
166{
167 factory.registerFunction<FunctionBar>();
168}
169
170}
171