|
| 1 | +#include "textplot_bar.hpp" |
| 2 | +#include "duckdb/common/string_util.hpp" |
| 3 | +#include "duckdb/main/extension_util.hpp" |
| 4 | +#include "duckdb/function/scalar_function.hpp" |
| 5 | +#include "duckdb/common/vector_operations/unary_executor.hpp" |
| 6 | +#include "duckdb/planner/expression/bound_function_expression.hpp" |
| 7 | +#include "duckdb/execution/expression_executor.hpp" |
| 8 | +#include <algorithm> |
| 9 | +#include "qrcodegen.hpp" |
| 10 | + |
| 11 | +namespace duckdb { |
| 12 | + |
| 13 | +struct TextplotQRBindData : public FunctionData { |
| 14 | + string ecc = "low"; |
| 15 | + string on = ""; |
| 16 | + string off = ""; |
| 17 | + |
| 18 | + TextplotQRBindData(string ecc_p, string on_p, string off_p) |
| 19 | + : ecc(std::move(ecc_p)), on(std::move(on_p)), off(std::move(off_p)) { |
| 20 | + } |
| 21 | + |
| 22 | + unique_ptr<FunctionData> Copy() const override; |
| 23 | + bool Equals(const FunctionData &other_p) const override; |
| 24 | +}; |
| 25 | + |
| 26 | +unique_ptr<FunctionData> TextplotQRBindData::Copy() const { |
| 27 | + return make_uniq<TextplotQRBindData>(ecc, on, off); |
| 28 | +} |
| 29 | + |
| 30 | +bool TextplotQRBindData::Equals(const FunctionData &other_p) const { |
| 31 | + return true; |
| 32 | +} |
| 33 | + |
| 34 | +unique_ptr<FunctionData> TextplotQRBind(ClientContext &context, ScalarFunction &bound_function, |
| 35 | + vector<unique_ptr<Expression>> &arguments) { |
| 36 | + |
| 37 | + if (arguments.empty()) { |
| 38 | + throw BinderException("tp_qr takes at least one argument"); |
| 39 | + } |
| 40 | + |
| 41 | + if (!(arguments[0]->return_type == LogicalType::VARCHAR || arguments[0]->return_type == LogicalType::BLOB)) { |
| 42 | + throw InvalidTypeException("tp_qr first argument must a VARCHAR or BLOB"); |
| 43 | + } |
| 44 | + |
| 45 | + // Optional arguments |
| 46 | + string ecc = "low"; |
| 47 | + string on = ""; |
| 48 | + string off = ""; |
| 49 | + |
| 50 | + for (idx_t i = 1; i < arguments.size(); i++) { |
| 51 | + const auto &arg = arguments[i]; |
| 52 | + if (arg->HasParameter()) { |
| 53 | + throw ParameterNotResolvedException(); |
| 54 | + } |
| 55 | + if (!arg->IsFoldable()) { |
| 56 | + throw BinderException("tp_qr: arguments must be constant"); |
| 57 | + } |
| 58 | + const auto &alias = arg->GetAlias(); |
| 59 | + if (alias == "ecc") { |
| 60 | + if (arg->return_type.id() != LogicalTypeId::VARCHAR) { |
| 61 | + throw BinderException("tp_qr: 'ecc' argument must be a VARCHAR"); |
| 62 | + } |
| 63 | + ecc = StringValue::Get(ExpressionExecutor::EvaluateScalar(context, *arg)); |
| 64 | + } else if (alias == "on") { |
| 65 | + if (arg->return_type.id() != LogicalTypeId::VARCHAR) { |
| 66 | + throw BinderException("tp_qr: 'on' argument must be a VARCHAR"); |
| 67 | + } |
| 68 | + on = StringValue::Get(ExpressionExecutor::EvaluateScalar(context, *arg)); |
| 69 | + } else if (alias == "off") { |
| 70 | + if (arg->return_type.id() != LogicalTypeId::VARCHAR) { |
| 71 | + throw BinderException("tp_qr: 'off' argument must be a VARCHAR"); |
| 72 | + } |
| 73 | + off = StringValue::Get(ExpressionExecutor::EvaluateScalar(context, *arg)); |
| 74 | + } else { |
| 75 | + throw BinderException(StringUtil::Format("tp_qr: Unknown argument '%s'", alias)); |
| 76 | + } |
| 77 | + } |
| 78 | + |
| 79 | + if (off.empty()) { |
| 80 | + off = "⬜"; |
| 81 | + } |
| 82 | + |
| 83 | + if (on.empty()) { |
| 84 | + on = "⬛"; |
| 85 | + } |
| 86 | + |
| 87 | + return make_uniq<TextplotQRBindData>(ecc, on, off); |
| 88 | +} |
| 89 | + |
| 90 | +void TextplotQR(DataChunk &args, ExpressionState &state, Vector &result) { |
| 91 | + auto &value_vector = args.data[0]; |
| 92 | + const auto &func_expr = state.expr.Cast<BoundFunctionExpression>(); |
| 93 | + const auto &bind_data = func_expr.bind_info->Cast<TextplotQRBindData>(); |
| 94 | + |
| 95 | + UnaryExecutor::Execute<string_t, string_t>(value_vector, result, args.size(), [&](string_t value) { |
| 96 | + // std::vector<qrcodegen::QrSegment> segs = qrcodegen::QrSegment::makeSegments(value.GetData()); |
| 97 | + |
| 98 | + // auto qr = qrcodegen::QrCode::encodeSegments(segs, qrcodegen::QrCode::Ecc::LOW, bind_data.min_version, |
| 99 | + // bind_data.max_version, -1, bind_data.boost_ecc); |
| 100 | + |
| 101 | + qrcodegen::QrCode::Ecc ecc_level = qrcodegen::QrCode::Ecc::LOW; |
| 102 | + if (bind_data.ecc == "low") { |
| 103 | + ecc_level = qrcodegen::QrCode::Ecc::LOW; |
| 104 | + } else if (bind_data.ecc == "medium") { |
| 105 | + ecc_level = qrcodegen::QrCode::Ecc::MEDIUM; |
| 106 | + } else if (bind_data.ecc == "quartile") { |
| 107 | + ecc_level = qrcodegen::QrCode::Ecc::QUARTILE; |
| 108 | + } else if (bind_data.ecc == "high") { |
| 109 | + ecc_level = qrcodegen::QrCode::Ecc::HIGH; |
| 110 | + } else { |
| 111 | + throw InvalidTypeException("tp_qr: 'ecc' argument must be one of 'low', 'medium', 'quartile', 'high'"); |
| 112 | + } |
| 113 | + |
| 114 | + auto qr = qrcodegen::QrCode::encodeText(value.GetString().c_str(), ecc_level); |
| 115 | + |
| 116 | + string result_str; |
| 117 | + for (int y = 0; y < qr.getSize(); y++) { |
| 118 | + for (int x = 0; x < qr.getSize(); x++) { |
| 119 | + result_str += qr.getModule(x, y) ? bind_data.on : bind_data.off; |
| 120 | + } |
| 121 | + result_str += "\n"; |
| 122 | + } |
| 123 | + return StringVector::AddString(result, result_str); |
| 124 | + }); |
| 125 | +} |
| 126 | + |
| 127 | +} // namespace duckdb |
0 commit comments