Skip to content

Commit 22cd476

Browse files
committed
feat: add tp_qr to generate qr codes
1 parent 0341f69 commit 22cd476

File tree

7 files changed

+238
-6
lines changed

7 files changed

+238
-6
lines changed

CMakeLists.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,22 @@ set(LOADABLE_EXTENSION_NAME ${TARGET_NAME}_loadable_extension)
1616
project(${TARGET_NAME})
1717
include_directories(src/include)
1818

19+
20+
find_package(unofficial-nayuki-qr-code-generator CONFIG REQUIRED)
21+
1922
set(EXTENSION_SOURCES
2023
src/textplot_extension.cpp
2124
src/textplot_bar.cpp
2225
src/textplot_density.cpp
2326
src/textplot_sparkline.cpp
27+
src/textplot_qr.cpp
2428
)
2529

2630
build_static_extension(${TARGET_NAME} ${EXTENSION_SOURCES})
2731
build_loadable_extension(${TARGET_NAME} " " ${EXTENSION_SOURCES})
2832

29-
# Link OpenSSL in both the static library as the loadable extension
30-
target_link_libraries(${EXTENSION_NAME})
31-
target_link_libraries(${LOADABLE_EXTENSION_NAME})
33+
target_link_libraries(${EXTENSION_NAME} unofficial::nayuki-qr-code-generator::nayuki-qr-code-generator)
34+
target_link_libraries(${LOADABLE_EXTENSION_NAME} unofficial::nayuki-qr-code-generator::nayuki-qr-code-generator)
3235

3336
install(
3437
TARGETS ${EXTENSION_NAME}

docs/README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,84 @@ SELECT tp_sparkline([150, 145, 147, 180, 120], mode := 'trend', theme := 'arrows
362362
- `theme`: Theme name (varies by mode, see lists above)
363363
- `width`: Sparkline width in characters (default: 20)
364364

365+
### `tp_qr(value, ...options)`
366+
Creates QR codes with customizable error correction levels and display styles.
367+
368+
**Basic Usage:**
369+
```sql
370+
.mode ascii
371+
-- Simple QR code
372+
SELECT tp_qr('Hello, World!');
373+
select tp_qr('hello world', ecc := 'high');
374+
⬛⬛⬛⬛⬛⬛⬛⬜⬛⬜⬛⬜⬛⬜⬜⬜⬛⬜⬛⬛⬛⬛⬛⬛⬛
375+
⬛⬜⬜⬜⬜⬜⬛⬜⬛⬛⬜⬜⬜⬛⬛⬛⬜⬜⬛⬜⬜⬜⬜⬜⬛
376+
⬛⬜⬛⬛⬛⬜⬛⬜⬛⬜⬛⬜⬛⬛⬜⬛⬛⬜⬛⬜⬛⬛⬛⬜⬛
377+
⬛⬜⬛⬛⬛⬜⬛⬜⬜⬜⬜⬛⬛⬜⬛⬛⬜⬜⬛⬜⬛⬛⬛⬜⬛
378+
⬛⬜⬛⬛⬛⬜⬛⬜⬜⬛⬜⬜⬜⬛⬛⬜⬛⬜⬛⬜⬛⬛⬛⬜⬛
379+
⬛⬜⬜⬜⬜⬜⬛⬜⬛⬜⬜⬛⬜⬜⬛⬛⬛⬜⬛⬜⬜⬜⬜⬜⬛
380+
⬛⬛⬛⬛⬛⬛⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬛⬛⬛⬛⬛⬛
381+
⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬛⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜
382+
⬜⬜⬛⬛⬛⬜⬛⬜⬛⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬛
383+
⬛⬛⬛⬜⬜⬛⬜⬜⬛⬜⬜⬜⬛⬛⬜⬛⬜⬜⬜⬛⬜⬜⬛⬜⬜
384+
⬛⬜⬜⬜⬛⬜⬛⬛⬛⬛⬜⬜⬜⬜⬛⬛⬜⬛⬜⬜⬛⬛⬜⬛⬛
385+
⬛⬛⬜⬛⬜⬛⬜⬛⬛⬜⬜⬜⬛⬜⬜⬜⬛⬜⬛⬜⬜⬜⬜⬛⬛
386+
⬜⬜⬛⬜⬜⬛⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛
387+
⬛⬜⬛⬛⬛⬜⬜⬜⬛⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬛⬜⬜⬛⬜⬜
388+
⬛⬜⬜⬜⬜⬜⬛⬜⬜⬛⬜⬛⬜⬜⬜⬜⬜⬛⬛⬛⬛⬛⬜⬛⬛
389+
⬛⬜⬛⬛⬛⬜⬜⬜⬜⬜⬛⬜⬜⬜⬛⬜⬛⬜⬛⬛⬛⬜⬜⬜⬛
390+
⬛⬜⬛⬜⬜⬛⬛⬛⬜⬜⬜⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛⬛⬛⬜⬜
391+
⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬛⬛⬜⬛⬜⬜⬜⬛⬜⬛⬜⬜
392+
⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬛⬛⬛⬛⬜⬛⬜⬛⬜⬛⬛⬛
393+
⬛⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬛⬜⬜⬜⬛⬛⬜⬜⬜⬛⬛⬜⬛⬜
394+
⬛⬜⬛⬛⬛⬜⬛⬜⬛⬜⬛⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜
395+
⬛⬜⬛⬛⬛⬜⬛⬜⬛⬛⬜⬜⬜⬛⬛⬜⬜⬜⬛⬜⬛⬛⬜⬜⬛
396+
⬛⬜⬛⬛⬛⬜⬛⬜⬛⬛⬜⬛⬛⬛⬛⬛⬜⬜⬛⬛⬜⬛⬜⬜⬛
397+
⬛⬜⬜⬜⬜⬜⬛⬜⬜⬛⬛⬛⬜⬜⬛⬛⬜⬛⬜⬛⬛⬜⬜⬜⬛
398+
399+
```
400+
401+
**Advanced Options:**
402+
```sql
403+
-- Custom colors and shapes
404+
SELECT tp_qr('https://query.farm', ecc := 'high', "on" := '🟡', off := '');
405+
🟡🟡🟡🟡🟡🟡🟡⚫🟡⚫🟡⚫🟡⚫🟡⚫⚫⚫🟡⚫🟡⚫🟡🟡🟡🟡🟡🟡🟡
406+
🟡⚫⚫⚫⚫⚫🟡⚫🟡⚫⚫⚫🟡⚫🟡⚫🟡⚫⚫🟡⚫⚫🟡⚫⚫⚫⚫⚫🟡
407+
🟡⚫🟡🟡🟡⚫🟡⚫🟡🟡🟡🟡🟡🟡⚫🟡🟡⚫⚫🟡🟡⚫🟡⚫🟡🟡🟡⚫🟡
408+
🟡⚫🟡🟡🟡⚫🟡⚫⚫⚫⚫🟡🟡🟡⚫⚫🟡🟡⚫🟡⚫⚫🟡⚫🟡🟡🟡⚫🟡
409+
🟡⚫🟡🟡🟡⚫🟡⚫⚫⚫🟡🟡🟡🟡🟡🟡⚫⚫⚫🟡🟡⚫🟡⚫🟡🟡🟡⚫🟡
410+
🟡⚫⚫⚫⚫⚫🟡⚫🟡⚫⚫🟡⚫⚫🟡⚫🟡⚫🟡⚫🟡⚫🟡⚫⚫⚫⚫⚫🟡
411+
🟡🟡🟡🟡🟡🟡🟡⚫🟡⚫🟡⚫🟡⚫🟡⚫🟡⚫🟡⚫🟡⚫🟡🟡🟡🟡🟡🟡🟡
412+
⚫⚫⚫⚫⚫⚫⚫⚫🟡🟡⚫🟡⚫🟡🟡🟡🟡⚫⚫⚫🟡⚫⚫⚫⚫⚫⚫⚫⚫
413+
⚫⚫🟡🟡🟡⚫🟡⚫🟡🟡🟡🟡⚫⚫🟡🟡🟡⚫🟡⚫🟡🟡🟡🟡⚫⚫🟡🟡🟡
414+
🟡🟡🟡🟡⚫🟡⚫🟡⚫🟡🟡🟡⚫🟡⚫🟡⚫⚫⚫⚫⚫🟡⚫⚫🟡🟡🟡⚫🟡
415+
🟡🟡🟡⚫⚫🟡🟡🟡🟡⚫⚫🟡⚫⚫🟡⚫🟡🟡🟡⚫🟡🟡🟡🟡🟡⚫🟡⚫⚫
416+
🟡⚫⚫⚫⚫⚫⚫⚫🟡🟡⚫🟡⚫🟡⚫⚫⚫⚫⚫⚫⚫🟡🟡🟡⚫🟡⚫🟡⚫
417+
⚫🟡🟡🟡🟡🟡🟡⚫⚫⚫⚫🟡🟡🟡🟡🟡⚫⚫🟡⚫🟡🟡⚫🟡⚫⚫🟡🟡🟡
418+
🟡⚫🟡⚫🟡🟡⚫⚫⚫⚫⚫⚫🟡🟡🟡⚫🟡🟡⚫🟡🟡🟡🟡⚫🟡🟡⚫🟡🟡
419+
⚫⚫⚫🟡⚫⚫🟡⚫🟡🟡⚫🟡🟡⚫⚫⚫🟡⚫⚫⚫🟡⚫🟡🟡⚫⚫⚫🟡⚫
420+
⚫⚫🟡⚫⚫🟡⚫⚫🟡⚫🟡🟡⚫⚫🟡🟡🟡⚫🟡⚫⚫🟡🟡⚫🟡🟡⚫⚫⚫
421+
⚫🟡⚫⚫🟡🟡🟡🟡⚫⚫⚫⚫🟡⚫⚫⚫⚫🟡🟡⚫⚫⚫🟡⚫⚫🟡🟡🟡🟡
422+
🟡🟡🟡⚫⚫🟡⚫🟡🟡⚫🟡⚫⚫⚫🟡🟡⚫🟡⚫⚫🟡🟡⚫🟡🟡🟡⚫🟡🟡
423+
🟡⚫⚫🟡⚫🟡🟡🟡⚫⚫🟡🟡⚫⚫⚫🟡🟡⚫🟡🟡⚫⚫🟡⚫⚫⚫🟡⚫⚫
424+
🟡⚫🟡⚫⚫⚫⚫⚫🟡🟡🟡🟡🟡🟡🟡⚫⚫⚫⚫🟡🟡⚫🟡⚫🟡🟡⚫🟡🟡
425+
🟡⚫🟡🟡⚫🟡🟡⚫⚫⚫🟡⚫🟡🟡⚫⚫⚫⚫⚫🟡🟡🟡🟡🟡🟡⚫🟡🟡⚫
426+
⚫⚫⚫⚫⚫⚫⚫⚫🟡🟡⚫⚫🟡⚫⚫⚫🟡🟡🟡🟡🟡⚫⚫⚫🟡🟡⚫⚫🟡
427+
🟡🟡🟡🟡🟡🟡🟡⚫⚫⚫⚫⚫⚫⚫🟡⚫⚫⚫🟡🟡🟡⚫🟡⚫🟡⚫⚫⚫⚫
428+
🟡⚫⚫⚫⚫⚫🟡⚫⚫🟡⚫⚫🟡⚫🟡🟡🟡🟡⚫🟡🟡⚫⚫⚫🟡🟡⚫🟡🟡
429+
🟡⚫🟡🟡🟡⚫🟡⚫🟡🟡🟡🟡⚫🟡⚫🟡⚫🟡⚫⚫🟡🟡🟡🟡🟡🟡🟡🟡⚫
430+
🟡⚫🟡🟡🟡⚫🟡⚫🟡🟡⚫🟡⚫🟡🟡⚫🟡🟡🟡🟡🟡🟡⚫🟡⚫⚫⚫⚫⚫
431+
🟡⚫🟡🟡🟡⚫🟡⚫🟡🟡⚫⚫⚫⚫🟡🟡⚫⚫⚫🟡🟡🟡🟡🟡🟡🟡⚫🟡⚫
432+
🟡⚫⚫⚫⚫⚫🟡⚫⚫⚫⚫⚫🟡🟡⚫🟡🟡⚫🟡🟡⚫🟡⚫⚫🟡🟡⚫🟡⚫
433+
🟡🟡🟡🟡🟡🟡🟡⚫⚫🟡🟡⚫🟡🟡⚫⚫🟡🟡⚫⚫⚫⚫⚫⚫🟡🟡🟡⚫⚫
434+
435+
```
436+
437+
**Parameters:**
438+
- `value`: String value to encode in the QR code.
439+
- `ecc`: Error correction level ('low', 'medium', 'quartile', 'high', default: 'low')
440+
- `on`: Character for filled modules (default: '⬛')
441+
- `off`: Character for empty modules (default: '⬜')
442+
365443

366444
## Tips and Best Practices
367445

duckdb

Submodule duckdb updated 517 files

src/include/textplot_qr.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include "duckdb.hpp"
4+
#include "duckdb/function/scalar_function.hpp"
5+
#include "duckdb/common/exception.hpp"
6+
#include <unordered_map>
7+
#include <vector>
8+
9+
namespace duckdb {
10+
11+
// Function declarations
12+
unique_ptr<FunctionData> TextplotQRBind(ClientContext &context, ScalarFunction &bound_function,
13+
vector<unique_ptr<Expression>> &arguments);
14+
15+
void TextplotQR(DataChunk &args, ExpressionState &state, Vector &result);
16+
17+
} // namespace duckdb

src/textplot_extension.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@
22
#include "textplot_bar.hpp"
33
#include "textplot_density.hpp"
44
#include "textplot_sparkline.hpp"
5+
#include "textplot_qr.hpp"
56
#include "duckdb.hpp"
67
#include "duckdb/main/extension_util.hpp"
78
#include "duckdb/function/scalar_function.hpp"
89

910
namespace duckdb {
1011

1112
static void LoadInternal(DatabaseInstance &instance) {
12-
auto bar_function = ScalarFunction("tp_bar", {LogicalType::DOUBLE}, LogicalType::VARCHAR, TextplotBar,
13+
auto bar_function = ScalarFunction("tp_bar", {LogicalType::DOUBLE}, LogicalType::VARCHAR, TextplotQR,
1314
TextplotBarBind, nullptr, nullptr, nullptr, LogicalType(LogicalTypeId::ANY));
1415
ExtensionUtil::RegisterFunction(instance, bar_function);
1516

17+
auto qr_function = ScalarFunction("tp_qr", {LogicalType::VARCHAR}, LogicalType::VARCHAR, TextplotQR, TextplotQRBind,
18+
nullptr, nullptr, nullptr, LogicalType(LogicalTypeId::ANY));
19+
ExtensionUtil::RegisterFunction(instance, qr_function);
20+
1621
// Lets register a density function.
1722
auto density_function =
1823
ScalarFunction("tp_density", {LogicalType::LIST(LogicalType::DOUBLE)}, LogicalType::VARCHAR, TextplotDensity,

src/textplot_qr.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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

vcpkg.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
2-
"dependencies": [],
2+
"dependencies": [
3+
"nayuki-qr-code-generator"
4+
],
35
"vcpkg-configuration": {
46
"overlay-ports": [
57
"./extension-ci-tools/vcpkg_ports"

0 commit comments

Comments
 (0)