概述
这是一个为Qt6开发的DuckDB数据库SQL驱动插件。该驱动允许Qt应用程序通过标准的QSqlDatabase接口连接和操作DuckDB数据库。
项目链接:https://gitee.com/whmuren/qduckdb
实际开发环境:
操作系统:windows11/deepinV25
Qt版本:Qt 6.9.2
编译器:windows11下为mingw_64 13.1.0,deepinV25下为gcc_64 12.3.0
cmake 3.31.4
qt-cmake
duckdb数据库版本:1.4.0
构建状态
✅ **编译成功** - 驱动已成功构建
生成文件
-
**Windows**: `build_qduckdb/plugins/sqldrivers/qsqlduckdb.dll` - 主驱动文件
-
**Linux**: `build_qduckdb/plugins/sqldrivers/libqsqlduckdb.so` - 主驱动文件
功能特性
-
✅ 支持标准QSqlDatabase接口
-
✅ 支持内存数据库 (`:memory:`)
-
✅ 支持文件数据库
-
✅ 支持基本SQL操作 (CREATE, INSERT, SELECT, UPDATE, DELETE)
-
✅ 支持参数化查询和批量操作
-
✅ 支持事务处理 (BEGIN, COMMIT, ROLLBACK)
-
✅ 支持多种数据类型 (INTEGER, VARCHAR, DOUBLE, DATE, TIME, TIMESTAMP, BOOLEAN, BLOB)
-
✅ 支持聚合函数 (COUNT, SUM, AVG, MIN, MAX)
-
✅ 支持结果集元数据访问
编译步骤
前置要求
-
Qt 6.9.2 或更高版本
-
CMake 3.16 或更高版本
-
C++17 兼容的编译器
-
DuckDB 原生库文件
Windows 环境编译
- **准备环境**
```cmd
确保Qt和CMake在PATH中
set QTHOME={path\to\Qt} ##设置为实际的Qt安装目录
set QTVER=6.9.2
set QTDIR=%QTHOME%\%QTVER%
set QTBASE=%QTDIR%\Src\qtbase
set PATH=%QTDIR%\mingw_64\bin;%PATH%
set PATH=%QTHOME%\Tools\CMake_64\bin;%PATH%
set QDuckdb_SRC=%QTBASE%\src\plugins\sqldrivers\duckdb # duckdb Qt插件项目目录
set Duckdb_ROOT=%QDuckdb_SRC%\libduckdb # duckdb 客户端安装目录
```
- **创建构建目录**
```cmd
cd /path/to/duckdb
mkdir build_qduckdb
cd build_qduckdb
```
- **配置和编译**
```
配置:
cmd:
qt-cmake -G Ninja %QTBASE%\src\plugins\sqldrivers ^ #源码目录
-DDuckdb_ROOT="%Duckdb_ROOT%" ^ #客户包
-DCMAKE_MAKE_PROGRAM="%QTHOME%\Tools\Ninja\ninja.exe" ^ #编译工具链
-DCMAKE_INSTALL_PREFIX="%QTDIR%\mingw_64" #安装目录
powershell:
qt-cmake -G Ninja $env:QTBASE\src\plugins\sqldrivers ` #源码目录
-DDuckdb_ROOT="$env:Duckdb_ROOT" ` #客户包
-DCMAKE_MAKE_PROGRAM="$env:QTHOME\Tools\Ninja\ninja.exe" ` #编译工具链
-DCMAKE_INSTALL_PREFIX="$env:QTDIR\mingw_64" #安装目录
编译:
cmake --build .
安装:
cmake --install .
```
Linux 环境编译
- **准备环境**
export QTHOME={path/to/Qt} ##设置为实际的Qt安装目录
export QTVER=6.9.2
export QTDIR=QTHOME/QTVER
export PATH=PATH:QTDIR/gcc_64/bin:$QTHOME/Tools/CMake/bin
export LD_LIBRARY_PATH=QTDIR/gcc_64/lib:LD_LIBRARY_PATH
export QTBASE=$QTDIR/Src/qtbase
export QDuckdb_SRC=%QTBASE%\src/plugins/sqldrivers/duckdb # duckdb Qt插件项目目录
export Duckdb_ROOT=%QDuckdb_SRC%/libduckdb # duckdb 客户端安装目录
export LD_LIBRARY_PATH=DM_DIR/drivers/dpi:Duckdb_ROOT/linux:$LD_LIBRARY_PATH
```
- **创建构建目录**
```bash
cd /path/to/duckdb
mkdir build_qduckdb
cd build_qduckdb
```
- **配置和编译**
```bash
配置:
qt-cmake -G Ninja $QTBASE/src/plugins/sqldrivers \ #源码目录
-DDuckdb_ROOT="$Duckdb_ROOT" \ #客户包
-DCMAKE_MAKE_PROGRAM="$QTHOME/Tools/Ninja/ninja" \ #编译工具链
-DCMAKE_INSTALL_PREFIX="$QTDIR\gcc_64" #安装目录
编译:
cmake --build .
安装:
cmake --install .
```
安装步骤
Windows 安装
**方法一:手动安装**
- 将 `qsqlduckdb.dll` 复制到Qt的插件目录:
```
%QTDIR%\mingw_64\plugins\sqldrivers\
```
**方法二:环境变量设置**
```cmd
set QT_PLUGIN_PATH=C:\path\to\build_qduckdb\plugins\sqldrivers
```
**方法三:CMake安装**
```cmd
cmake --install . --prefix %QTDIR%\mingw_64
```
Linux 安装
**方法一:手动安装**
```bash
复制到Qt插件目录
sudo cp build_qduckdb/plugins/sqldrivers/libqsqlduckdb.so /path/to/Qt/6.9.2/gcc_64/plugins/sqldrivers/
```
**方法二:环境变量设置**
```bash
export QT_PLUGIN_PATH=/path/to/build_qduckdb/plugins/sqldrivers:$QT_PLUGIN_PATH
```
**方法三:CMake安装**
```bash
sudo cmake --install . --prefix /path/to/Qt/6.9.2/gcc_64
```
使用说明
数据库连接示例
1. 内存数据库连接
```cpp
#include <QCoreApplication>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// 检查驱动是否可用
if (!QSqlDatabase::isDriverAvailable("QDUCKDB")) {
qWarning() << "QDUCKDB驱动不可用!";
return -1;
}
// 连接内存数据库
QSqlDatabase db = QSqlDatabase::addDatabase("QDUCKDB");
db.setDatabaseName(":memory:");
if (!db.open()) {
qWarning() << "无法打开数据库:" << db.lastError().text();
return -1;
}
qDebug() << "内存数据库连接成功!";
// 基本SQL操作
QSqlQuery query(db);
// 创建表
if (!query.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")) {
qWarning() << "创建表失败:" << query.lastError().text();
return -1;
}
// 插入数据
if (!query.exec("INSERT INTO users (name, age) VALUES ('Alice', 25)")) {
qWarning() << "插入数据失败:" << query.lastError().text();
return -1;
}
// 查询数据
if (query.exec("SELECT * FROM users")) {
while (query.next()) {
qDebug() << "ID:" << query.value("id").toInt()
<< "Name:" << query.value("name").toString()
<< "Age:" << query.value("age").toInt();
}
}
db.close();
return 0;
}
```
2. 文件数据库连接(读写模式)
```cpp
#include <QCoreApplication>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDir>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// 检查驱动是否可用
if (!QSqlDatabase::isDriverAvailable("QDUCKDB")) {
qWarning() << "QDUCKDB驱动不可用!";
return -1;
}
// 设置数据库文件路径
QString dbPath = QDir::currentPath() + "/mydata.duckdb";
qDebug() << "数据库文件路径:" << dbPath;
// 连接文件数据库(默认读写模式)
QSqlDatabase db = QSqlDatabase::addDatabase("QDUCKDB", "readwrite_connection");
db.setDatabaseName(dbPath);
if (!db.open()) {
qWarning() << "无法打开文件数据库:" << db.lastError().text();
return -1;
}
qDebug() << "文件数据库连接成功(读写模式)!";
QSqlQuery query(db);
// 创建表(如果不存在)
if (!query.exec("CREATE TABLE IF NOT EXISTS products ("
"id INTEGER PRIMARY KEY, "
"name TEXT NOT NULL, "
"price DOUBLE, "
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")) {
qWarning() << "创建表失败:" << query.lastError().text();
return -1;
}
// 插入数据
query.prepare("INSERT INTO products (name, price) VALUES (?, ?)");
query.addBindValue("笔记本电脑");
query.addBindValue(5999.99);
if (!query.exec()) {
qWarning() << "插入数据失败:" << query.lastError().text();
return -1;
}
qDebug() << "数据插入成功!";
// 查询数据
if (query.exec("SELECT * FROM products ORDER BY id DESC LIMIT 5")) {
qDebug() << "最近添加的产品:";
while (query.next()) {
qDebug() << "ID:" << query.value("id").toInt()
<< "名称:" << query.value("name").toString()
<< "价格:" << query.value("price").toDouble()
<< "创建时间:" << query.value("created_at").toDateTime();
}
}
db.close();
return 0;
}
```
3. 文件数据库连接(只读模式)
```cpp
#include <QCoreApplication>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QFile>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// 检查驱动是否可用
if (!QSqlDatabase::isDriverAvailable("QDUCKDB")) {
qWarning() << "QDUCKDB驱动不可用!";
return -1;
}
QString dbPath = "/path/to/existing/database.duckdb";
// 检查数据库文件是否存在
if (!QFile::exists(dbPath)) {
qWarning() << "数据库文件不存在:" << dbPath;
return -1;
}
// 连接文件数据库(只读模式)
QSqlDatabase db = QSqlDatabase::addDatabase("QDUCKDB", "readonly_connection");
db.setDatabaseName(dbPath);
// 设置连接选项为只读模式
db.setConnectOptions("access_mode=read_only");
if (!db.open()) {
qWarning() << "无法打开只读数据库:" << db.lastError().text();
return -1;
}
qDebug() << "文件数据库连接成功(只读模式)!";
QSqlQuery query(db);
// 只读模式下只能执行查询操作
if (query.exec("SELECT COUNT(*) as total FROM products")) {
if (query.next()) {
qDebug() << "产品总数:" << query.value("total").toInt();
}
}
// 查询数据统计
if (query.exec("SELECT "
"COUNT(*) as total_count, "
"AVG(price) as avg_price, "
"MIN(price) as min_price, "
"MAX(price) as max_price "
"FROM products")) {
if (query.next()) {
qDebug() << "数据统计:";
qDebug() << " 总数:" << query.value("total_count").toInt();
qDebug() << " 平均价格:" << query.value("avg_price").toDouble();
qDebug() << " 最低价格:" << query.value("min_price").toDouble();
qDebug() << " 最高价格:" << query.value("max_price").toDouble();
}
}
// 尝试写入操作(应该失败)
if (!query.exec("INSERT INTO products (name, price) VALUES ('测试产品', 100.0)")) {
qDebug() << "只读模式下写入操作被拒绝(这是正常的):" << query.lastError().text();
}
db.close();
return 0;
}
```
4. 多数据库连接管理
```cpp
#include <QCoreApplication>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
class DatabaseManager
{
public:
static bool setupConnections()
{
// 内存数据库连接(用于临时数据)
QSqlDatabase memDb = QSqlDatabase::addDatabase("QDUCKDB", "memory_db");
memDb.setDatabaseName(":memory:");
if (!memDb.open()) {
qWarning() << "内存数据库连接失败:" << memDb.lastError().text();
return false;
}
// 主数据库连接(读写)
QSqlDatabase mainDb = QSqlDatabase::addDatabase("QDUCKDB", "main_db");
mainDb.setDatabaseName("/data/main.duckdb");
if (!mainDb.open()) {
qWarning() << "主数据库连接失败:" << mainDb.lastError().text();
return false;
}
// 只读数据库连接(用于报表查询)
QSqlDatabase reportDb = QSqlDatabase::addDatabase("QDUCKDB", "report_db");
reportDb.setDatabaseName("/data/reports.duckdb");
reportDb.setConnectOptions("access_mode=read_only");
if (!reportDb.open()) {
qWarning() << "报表数据库连接失败:" << reportDb.lastError().text();
return false;
}
qDebug() << "所有数据库连接建立成功!";
return true;
}
static void performOperations()
{
// 在内存数据库中进行临时计算
{
QSqlDatabase memDb = QSqlDatabase::database("memory_db");
QSqlQuery query(memDb);
query.exec("CREATE TABLE temp_calc (value DOUBLE)");
query.exec("INSERT INTO temp_calc VALUES (1.5), (2.5), (3.5)");
if (query.exec("SELECT SUM(value) as total FROM temp_calc")) {
if (query.next()) {
qDebug() << "临时计算结果:" << query.value("total").toDouble();
}
}
}
// 在主数据库中进行数据操作
{
QSqlDatabase mainDb = QSqlDatabase::database("main_db");
QSqlQuery query(mainDb);
query.exec("CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, message TEXT, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
query.prepare("INSERT INTO logs (message) VALUES (?)");
query.addBindValue("应用程序启动");
query.exec();
}
// 从只读数据库查询报表数据
{
QSqlDatabase reportDb = QSqlDatabase::database("report_db");
QSqlQuery query(reportDb);
if (query.exec("SELECT * FROM monthly_reports ORDER BY report_date DESC LIMIT 3")) {
qDebug() << "最新报表数据:";
while (query.next()) {
qDebug() << "日期:" << query.value("report_date").toDate()
<< "收入:" << query.value("revenue").toDouble();
}
}
}
}
static void cleanup()
{
// 关闭所有连接
QStringList connections = QSqlDatabase::connectionNames();
for (const QString &name : connections) {
QSqlDatabase::removeDatabase(name);
}
qDebug() << "所有数据库连接已关闭";
}
};
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
if (!DatabaseManager::setupConnections()) {
return -1;
}
DatabaseManager::performOperations();
DatabaseManager::cleanup();
return 0;
}
```
参数化查询示例
```cpp
// 准备参数化查询
QSqlQuery query(db);
query.prepare("INSERT INTO users (name, age) VALUES (?, ?)");
// 绑定参数
query.addBindValue("Bob");
query.addBindValue(30);
if (!query.exec()) {
qWarning() << "参数化插入失败:" << query.lastError().text();
}
// 命名参数查询
query.prepare("SELECT * FROM users WHERE age > :min_age");
query.bindValue(":min_age", 20);
if (query.exec()) {
while (query.next()) {
qDebug() << query.value("name").toString()
<< "is" << query.value("age").toInt() << "years old";
}
}
```
批量操作示例
```cpp
// 批量插入
QSqlQuery query(db);
query.prepare("INSERT INTO users (name, age) VALUES (?, ?)");
QVariantList names;
names << "Charlie" << "David" << "Eve";
query.addBindValue(names);
QVariantList ages;
ages << 28 << 35 << 22;
query.addBindValue(ages);
if (!query.execBatch()) {
qWarning() << "批量插入失败:" << query.lastError().text();
} else {
qDebug() << "批量插入成功!";
}
```
事务处理示例
```cpp
// 检查事务支持
if (!db.driver()->hasFeature(QSqlDriver::Transactions)) {
qWarning() << "驱动不支持事务";
return;
}
// 开始事务
if (!db.transaction()) {
qWarning() << "开始事务失败:" << db.lastError().text();
return;
}
QSqlQuery query(db);
try {
// 执行多个操作
query.exec("INSERT INTO users (name, age) VALUES ('Frank', 40)");
query.exec("UPDATE users SET age = age + 1 WHERE name = 'Alice'");
// 提交事务
if (!db.commit()) {
qWarning() << "提交事务失败:" << db.lastError().text();
db.rollback();
} else {
qDebug() << "事务提交成功!";
}
} catch (...) {
// 回滚事务
db.rollback();
qWarning() << "事务回滚";
}
```
数据类型处理示例
```cpp
// 创建包含多种数据类型的表
QSqlQuery query(db);
query.exec("CREATE TABLE data_types ("
"int_col INTEGER, "
"text_col TEXT, "
"float_col DOUBLE, "
"date_col DATE, "
"time_col TIME, "
"timestamp_col TIMESTAMP, "
"bool_col BOOLEAN)");
// 插入不同类型的数据
query.prepare("INSERT INTO data_types VALUES (?, ?, ?, ?, ?, ?, ?)");
query.addBindValue(42); // INTEGER
query.addBindValue("Hello DuckDB"); // TEXT
query.addBindValue(3.14159); // DOUBLE
query.addBindValue(QDate::currentDate()); // DATE
query.addBindValue(QTime::currentTime()); // TIME
query.addBindValue(QDateTime::currentDateTime()); // TIMESTAMP
query.addBindValue(true); // BOOLEAN
if (query.exec()) {
qDebug() << "多类型数据插入成功!";
}
// 查询并验证数据类型
if (query.exec("SELECT * FROM data_types")) {
while (query.next()) {
qDebug() << "Integer:" << query.value(0).toInt();
qDebug() << "Text:" << query.value(1).toString();
qDebug() << "Float:" << query.value(2).toDouble();
qDebug() << "Date:" << query.value(3).toDate();
qDebug() << "Time:" << query.value(4).toTime();
qDebug() << "Timestamp:" << query.value(5).toDateTime();
qDebug() << "Boolean:" << query.value(6).toBool();
}
}
```
连接选项和配置
支持的连接选项
DuckDB驱动支持以下连接选项,可通过 `setConnectOptions()` 方法设置:
```cpp
// 只读模式
db.setConnectOptions("access_mode=read_only");
// 读写模式(默认)
db.setConnectOptions("access_mode=read_write");
// 自动检查点间隔(毫秒)
db.setConnectOptions("checkpoint_threshold=1000");
// 内存限制(字节)
db.setConnectOptions("memory_limit=1073741824"); // 1GB
// 线程数设置
db.setConnectOptions("threads=4");
// 组合多个选项
db.setConnectOptions("access_mode=read_only;memory_limit=536870912;threads=2");
```
连接字符串格式
```cpp
// 内存数据库
db.setDatabaseName(":memory:");
// 文件数据库(绝对路径)
db.setDatabaseName("/home/user/data/mydb.duckdb");
// 文件数据库(相对路径)
db.setDatabaseName("./data/mydb.duckdb");
// 临时数据库
db.setDatabaseName(""); // 创建临时文件数据库
```
测试验证
项目包含完整的测试套件,位于 `duckdb_test/` 目录下。
运行测试
**编译测试项目**
```bash
cd duckdb_test
mkdir build
cd build
cmake .. -DCMAKE_PREFIX_PATH=/path/to/Qt/6.9.2/gcc_64
cmake --build .
```
**运行测试**
```bash
设置插件路径
export QT_PLUGIN_PATH=/path/to/build_qduckdb/plugins/sqldrivers
运行测试
./duckdb_test
```
测试覆盖范围
-
✅ 驱动加载检测
-
✅ 数据库连接测试
-
✅ 表创建与删除
-
✅ 数据CRUD操作
-
✅ 结果集处理
-
✅ 常用SQL语法
-
✅ 数据类型覆盖
-
✅ 事务处理
-
✅ 参数化查询
-
✅ 批量操作
故障排除
常见问题
- **驱动无法加载**
-
检查插件文件是否在正确位置
-
验证 `QT_PLUGIN_PATH` 环境变量
-
确认Qt版本兼容性
- **编译错误**
-
确保Qt开发包已安装
-
检查CMake版本和路径配置
-
验证C++17编译器支持
- **运行时错误**
-
检查DuckDB库文件依赖
-
验证数据库文件权限
-
查看Qt调试输出
目录结构
```
duckdb/
├── build_qduckdb/ # 构建目录
│ └── plugins/sqldrivers/
│ ├── libqsqlduckdb.so # Linux驱动文件
│ └── qsqlduckdb.dll # Windows驱动文件
├── duckdb_test/ # 测试项目
│ ├── main.cpp # 测试入口
│ ├── duckdbsqldrivertest.cpp # 测试实现
│ ├── duckdbsqldrivertest.h # 测试头文件
│ └── CMakeLists.txt # 测试构建配置
├── libduckdb/ # DuckDB库文件
│ ├── linux/ # Linux平台库
│ └── win/ # Windows平台库
├── qsql_duckdb.h # 驱动头文件
├── qsql_duckdb.cpp # 驱动实现
├── qsql_duckdb_p.h # 私有头文件
├── qsql_duckdb_p.cpp # 私有实现
├── main.cpp # 插件入口
├── duckdb.json # 插件元数据
├── CMakeLists.txt # 构建配置
└── README.md # 本文档
```