前文提到,DuckDB的动态链接库没有格式化输出,这个说法是错误的。今天阅读头文件query_result.hpp
时看到,有一个ToBox
函数。但是自己调用时总是设不对参数,又把这个任务交给了DeepSeek,一举成功,以下是对话。
我:
我在duckdb头文件query_result.hpp中看到 //! Converts the QueryResult to a box-rendered string
DUCKDB_API virtual string ToBox(ClientContext &context, const BoxRendererConfig &config);如何在我们的程序中调用这个函数实现格式化输出结果?
DeepSeek:
要在你的程序中调用 ToBox
方法实现类似 DuckDB CLI 的格式化表格输出,你需要按照以下步骤操作:
1. 首先包含必要的头文件
#include "duckdb/main/query_result.hpp"
#include "duckdb/common/box_renderer.hpp"
2. 修改你的查询执行代码
在你的 executeQuery
函数中(或类似执行查询的地方),替换原有的 result->Print()
调用:
auto executeQuery = [&](const std::string& query) {
try {
auto result = conn_->Query(query);
if (result->HasError()) {
std::cerr << "Error: " << result->GetError() << std::endl;
} else {
// 获取当前客户端上下文
auto &context = *conn_->context;
// 创建默认的 BoxRenderer 配置
BoxRendererConfig config;
// 调用 ToBox 方法获取格式化字符串
std::string box_output = result->ToBox(context, config);
// 输出结果
std::cout << box_output << std::endl;
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
};
3. 自定义表格样式(可选)
你可以通过修改 BoxRendererConfig
来自定义输出样式:
BoxRendererConfig config;
config.border = true; // 显示边框
config.header = true; // 显示表头
config.limit = 50; // 最大显示行数
config.max_width = 80; // 最大宽度(字符)
config.max_col_width = 20; // 单列最大宽度
config.null_value = "NULL"; // NULL值的显示方式
std::string box_output = result->ToBox(context, config);
4. 完整示例
以下是整合到你的交互式 shell 中的完整示例:
void interactiveShell() {
// ... 其他代码保持不变 ...
auto executeQuery = [&]() {
// ... 之前的特殊命令处理代码 ...
// 执行普通SQL查询
try {
auto start = std::chrono::high_resolution_clock::now();
auto result = conn_->Query(query);
auto end = std::chrono::high_resolution_clock::now();
if (result->HasError()) {
std::cerr << "Error: " << result->GetError() << std::endl;
} else {
// 使用 ToBox 格式化输出
auto &context = *conn_->context;
BoxRendererConfig config;
config.border = true;
std::cout << result->ToBox(context, config) << std::endl;
}
if (timing_enabled_) {
std::chrono::duration<double> elapsed = end - start;
std::cout << "[Time: " << std::fixed << std::setprecision(3)
<< elapsed.count() << "s]" << std::endl;
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
query.clear();
};
// ... 后续代码 ...
}
5. 输出示例
执行 SELECT * FROM table
后,输出会变成类似这样:
┌───────┬────────────┬────────┐
│ id │ name │ age │
├───────┼────────────┼────────┤
│ 1 │ Alice │ 25 │
│ 2 │ Bob │ 30 │
│ 3 │ Charlie │ 35 │
└───────┴────────────┴────────┘
注意事项
- 确保你的 DuckDB 版本支持
ToBox
方法(v0.6.0 及以上版本) - 对于非常大的结果集,考虑添加行数限制
- 如果列内容过长,可以调整
max_col_width
或启用wrap_text
选项 - 要处理 NULL 值的特殊显示,可以设置
null_value
属性
这样修改后,你的程序将拥有和 DuckDB 命令行客户端相同的漂亮表格输出效果。
以上代码,除了config.border = true
报错duckcmdb.cpp:318:24: error: 'struct duckdb::BoxRendererConfig' has no member named 'border'
以外,一切正常。另外,他的回答显示的表格格式与实际输出不同,应该是用早期版本(但晚于v0.6.0)的duckdb训练的。
以下是示例输出,因为ToBox
给出了输出字符串,想要输出前面不含数据类型的表格也很容易,只要将第2个换行和第三个换行之间的内容去掉即可。
duckdb> select 1 a;
┌───────┐
│ a │
│ int32 │
├───────┤
│ 1 │
└───────┘
duckdb> select r_regionkey,r_name from region;
┌─────────────┬─────────────┐
│ r_regionkey │ r_name │
│ int32 │ varchar │
├─────────────┼─────────────┤
│ 0 │ AFRICA │
│ 1 │ AMERICA │
│ 2 │ ASIA │
│ 3 │ EUROPE │
│ 4 │ MIDDLE EAST │
└─────────────┴─────────────┘
题外话,前文的删除行尾换行bug也已解决。只要将script += line;
修改为script += line + "\n";
因为std::getline(file, line)
不会保存换行符到line
中。line.erase(line.find_last_not_of("\r\n") + 1);
同样是多余的。