Qt DuckDB SQL 驱动插件

概述

这是一个为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 环境编译

  1. **准备环境**

```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 客户端安装目录

```

  1. **创建构建目录**

```cmd

cd /path/to/duckdb

mkdir build_qduckdb

cd build_qduckdb

```

  1. **配置和编译**

```

配置:

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 环境编译

  1. **准备环境**

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

```

  1. **创建构建目录**

```bash

cd /path/to/duckdb

mkdir build_qduckdb

cd build_qduckdb

```

  1. **配置和编译**

```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 安装

**方法一:手动安装**

  1. 将 `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语法

  • ✅ 数据类型覆盖

  • ✅ 事务处理

  • ✅ 参数化查询

  • ✅ 批量操作

故障排除

常见问题

  1. **驱动无法加载**
  • 检查插件文件是否在正确位置

  • 验证 `QT_PLUGIN_PATH` 环境变量

  • 确认Qt版本兼容性

  1. **编译错误**
  • 确保Qt开发包已安装

  • 检查CMake版本和路径配置

  • 验证C++17编译器支持

  1. **运行时错误**
  • 检查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 # 本文档

```

相关推荐
一匹电信狗3 小时前
【MySQL】数据库基础
linux·运维·服务器·数据库·mysql·ubuntu·小程序
LoneEon4 小时前
Ubuntu 部署 ClickHouse:高性能分析型数据库(附shell脚本一键部署↓)
数据库·clickhouse
189228048614 小时前
NX482NX486美光固态闪存NX507NX508
大数据·网络·数据库·人工智能·性能优化
有一个好名字5 小时前
从 3.6 亿订单表到毫秒级查询:分库分表指南
数据库·oracle
奥尔特星云大使7 小时前
MySQL 备份基础(一)
数据库·sql·mysql·备份·mysql备份
努力学习的小廉8 小时前
初识MYSQL —— 库和表的操作
数据库·mysql·oracle
浔川python社9 小时前
网络爬虫技术规范与应用指南系列(xc—1)
数据库·爬虫
代码不停9 小时前
计算机工作原理(简单介绍)
数据库·redis·缓存
偷心伊普西隆9 小时前
Python Access:删除数据库中指定的表和查询
数据库·python