Files
qiming/qiming-rcoder/crates/duckdb_manager/src/connection.rs
2026-06-01 13:54:52 +08:00

238 lines
7.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! DuckDB 连接管理
//!
//! 提供线程安全的数据库连接管理
use crate::error::{DuckDbError, DuckDbResult};
use duckdb::Connection;
use parking_lot::Mutex;
use std::sync::Arc;
/// 线程安全的 DuckDB 连接包装器
///
/// 使用 `Arc<Mutex<Connection>>` 实现线程安全访问
#[derive(Clone)]
pub struct DuckDbConnection {
inner: Arc<Mutex<Connection>>,
}
impl DuckDbConnection {
/// DuckDB 内存限制默认值2GB
const DEFAULT_MEMORY_LIMIT: &str = "2GB";
/// 创建内存模式的数据库连接
pub fn open_in_memory() -> DuckDbResult<Self> {
let conn = Connection::open_in_memory()
.map_err(|e| DuckDbError::ConnectionError(e.to_string()))?;
// 设置 DuckDB 内存限制,防止无限占用内存
// 注意memory_limit 只限制 buffer manager部分数据结构如向量、查询结果
// 可能在 buffer manager 外部分配,因此实际内存可能略高于此限制
conn.execute(
&format!("PRAGMA memory_limit='{}'", Self::DEFAULT_MEMORY_LIMIT),
[],
)
.map_err(|e| DuckDbError::ConnectionError(format!("Failed to set memory_limit: {}", e)))?;
Ok(Self {
inner: Arc::new(Mutex::new(conn)),
})
}
/// 创建内存模式的数据库连接(带自定义内存限制)
///
/// # Arguments
/// * `memory_limit` - 内存限制字符串,如 "2GB", "512MB", "1GB"
pub fn open_in_memory_with_limit(memory_limit: &str) -> DuckDbResult<Self> {
let conn = Connection::open_in_memory()
.map_err(|e| DuckDbError::ConnectionError(e.to_string()))?;
// 设置自定义内存限制
conn.execute(
&format!("PRAGMA memory_limit='{}'", memory_limit),
[],
)
.map_err(|e| DuckDbError::ConnectionError(format!("Failed to set memory_limit: {}", e)))?;
Ok(Self {
inner: Arc::new(Mutex::new(conn)),
})
}
/// 执行需要独占连接的操作
///
/// 使用闭包模式确保锁的正确释放
pub fn with_connection<F, T>(&self, f: F) -> DuckDbResult<T>
where
F: FnOnce(&Connection) -> DuckDbResult<T>,
{
let conn = self.inner.lock();
f(&conn)
}
/// 执行需要可变连接的操作
pub fn with_connection_mut<F, T>(&self, f: F) -> DuckDbResult<T>
where
F: FnOnce(&mut Connection) -> DuckDbResult<T>,
{
let mut conn = self.inner.lock();
f(&mut conn)
}
/// 尝试获取连接(非阻塞)
pub fn try_with_connection<F, T>(&self, f: F) -> DuckDbResult<Option<T>>
where
F: FnOnce(&Connection) -> DuckDbResult<T>,
{
match self.inner.try_lock() {
Some(conn) => Ok(Some(f(&conn)?)),
None => Ok(None),
}
}
/// 获取 DuckDB 内存使用统计
///
/// 返回格式化的内存使用信息字符串,用于调试和监控
pub fn get_memory_stats(&self) -> DuckDbResult<String> {
self.with_connection(|c| {
// 查询内存使用情况
let mut stmt = c.prepare(
"SELECT name, size, reservation FROM duckdb_memory() ORDER BY size DESC LIMIT 10",
)?;
let mut rows = stmt.query([])?;
let mut result = String::from("DuckDB Memory Usage (Top 10):\n");
while let Some(row) = rows.next()? {
let name: String = row.get(0).unwrap_or_default();
let size: i64 = row.get(1).unwrap_or(0);
let reservation: i64 = row.get(2).unwrap_or(0);
result.push_str(&format!(
" {}: size={}, reservation={}\n",
name, size, reservation
));
}
Ok(result)
})
}
/// 执行事务
///
/// 自动处理事务的提交和回滚
pub fn transaction<F, T>(&self, f: F) -> DuckDbResult<T>
where
F: FnOnce(&Connection) -> DuckDbResult<T>,
{
let conn = self.inner.lock();
// 开始事务
conn.execute("BEGIN TRANSACTION", [])
.map_err(|e| DuckDbError::TransactionError(format!("failed to begin transaction: {}", e)))?;
// 执行操作
match f(&conn) {
Ok(result) => {
// 提交事务
conn.execute("COMMIT", [])
.map_err(|e| DuckDbError::TransactionError(format!("failed to commit transaction: {}", e)))?;
Ok(result)
}
Err(e) => {
// 回滚事务
let _ = conn.execute("ROLLBACK", []);
Err(e)
}
}
}
}
impl std::fmt::Debug for DuckDbConnection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DuckDbConnection")
.field("inner", &"<Connection>")
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_open_in_memory() {
let conn = DuckDbConnection::open_in_memory();
assert!(conn.is_ok());
}
#[test]
fn test_with_connection() {
let conn = DuckDbConnection::open_in_memory().unwrap();
let result = conn.with_connection(|c| {
c.execute("SELECT 1", [])?;
Ok(42)
});
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_transaction_commit() {
let conn = DuckDbConnection::open_in_memory().unwrap();
// 创建测试表
conn.with_connection(|c| {
c.execute("CREATE TABLE test (id INTEGER)", [])?;
Ok(())
})
.unwrap();
// 使用事务插入数据
conn.transaction(|c| {
c.execute("INSERT INTO test VALUES (1)", [])?;
Ok(())
})
.unwrap();
// 验证数据存在
let count: i32 = conn
.with_connection(|c| {
let mut stmt = c.prepare("SELECT COUNT(*) FROM test")?;
let mut rows = stmt.query([])?;
let row = rows.next()?.unwrap();
Ok(row.get(0)?)
})
.unwrap();
assert_eq!(count, 1);
}
#[test]
fn test_transaction_rollback() {
let conn = DuckDbConnection::open_in_memory().unwrap();
// 创建测试表
conn.with_connection(|c| {
c.execute("CREATE TABLE test (id INTEGER)", [])?;
Ok(())
})
.unwrap();
// 使用事务插入数据,但返回错误触发回滚
let result: DuckDbResult<()> = conn.transaction(|c| {
c.execute("INSERT INTO test VALUES (1)", [])?;
Err(DuckDbError::InternalError("故意触发回滚".to_string()))
});
assert!(result.is_err());
// 验证数据不存在(已回滚)
let count: i32 = conn
.with_connection(|c| {
let mut stmt = c.prepare("SELECT COUNT(*) FROM test")?;
let mut rows = stmt.query([])?;
let row = rows.next()?.unwrap();
Ok(row.get(0)?)
})
.unwrap();
assert_eq!(count, 0);
}
}