#![cfg(feature = "duckdb")]
use dbnexus::{DbConfig, DbPool};
async fn make_duckdb_pool() -> DbPool {
let config = DbConfig {
url: "duckdb::memory:".to_string(),
max_connections: 5,
min_connections: 1,
idle_timeout: 300,
acquire_timeout: 5000,
admin_role: "admin".to_string(),
..Default::default()
};
DbPool::with_config(config).await.expect("Failed to create DuckDB pool")
}
#[tokio::test]
async fn test_duckdb_pool_creation_memory() {
let pool = make_duckdb_pool().await;
let status = pool.status();
assert_eq!(
status.total,
status.active + status.idle,
"total should equal active + idle"
);
}
#[tokio::test]
async fn test_duckdb_pool_creation_with_config() {
let config = DbConfig {
url: "duckdb::memory:".to_string(),
max_connections: 3,
min_connections: 1,
..Default::default()
};
let pool = DbPool::with_config(config).await;
assert!(pool.is_ok(), "DuckDB pool creation with config should succeed");
}
#[tokio::test]
async fn test_duckdb_session_ddl_and_query() {
let pool = make_duckdb_pool().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let ddl_result = session
.execute_duckdb_raw("CREATE TABLE test_users (id INTEGER PRIMARY KEY, name VARCHAR)")
.await;
assert!(
ddl_result.is_ok(),
"CREATE TABLE should succeed: {:?}",
ddl_result.err()
);
let insert_result = session
.execute_duckdb_raw("INSERT INTO test_users VALUES (1, 'Alice')")
.await;
assert!(
insert_result.is_ok(),
"INSERT should succeed: {:?}",
insert_result.err()
);
let insert_result2 = session
.execute_duckdb_raw("INSERT INTO test_users VALUES (2, 'Bob')")
.await;
assert!(insert_result2.is_ok(), "Second INSERT should succeed");
let rows = session
.execute_duckdb("SELECT id, name FROM test_users ORDER BY id")
.await
.expect("SELECT should succeed");
assert_eq!(rows.len(), 2, "Should have 2 rows");
assert_eq!(rows[0].column_count(), 2, "Should have 2 columns");
let name = rows[0].get("name").expect("Should have 'name' column");
if let duckdb::types::Value::Text(s) = name {
assert_eq!(s, "Alice", "First row name should be Alice");
} else {
panic!("Expected Text value, got {:?}", name);
}
}
#[tokio::test]
async fn test_duckdb_session_aggregate_query() {
let pool = make_duckdb_pool().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
session
.execute_duckdb_raw("CREATE TABLE orders (id INTEGER, amount DOUBLE)")
.await
.expect("CREATE TABLE should succeed");
session
.execute_duckdb_raw("INSERT INTO orders VALUES (1, 100.5), (2, 200.0), (3, 50.0)")
.await
.expect("INSERT should succeed");
let rows = session
.execute_duckdb("SELECT COUNT(*) AS cnt, SUM(amount) AS total FROM orders")
.await
.expect("Aggregate query should succeed");
assert_eq!(rows.len(), 1, "Aggregate should return 1 row");
let count = rows[0].get("cnt").expect("Should have 'cnt' column");
if let duckdb::types::Value::BigInt(n) = count {
assert_eq!(*n, 3, "Count should be 3");
} else {
panic!("Expected BigInt for count, got {:?}", count);
}
}
#[tokio::test]
async fn test_duckdb_pool_health_check() {
let pool = make_duckdb_pool().await;
let session = pool.get_session("admin").await;
assert!(session.is_ok(), "get_session should succeed with healthy connection");
let session = session.unwrap();
let rows = session
.execute_duckdb("SELECT 1 AS health")
.await
.expect("Health check query should succeed");
assert_eq!(rows.len(), 1, "Health check should return 1 row");
}
#[tokio::test]
async fn test_duckdb_session_seaorm_method_fails() {
let pool = make_duckdb_pool().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let result = session.execute_raw_ddl("CREATE TABLE test (id INTEGER)").await;
assert!(
result.is_err(),
"SeaORM execute_raw_ddl should fail on DuckDB connection"
);
let err = result.unwrap_err();
assert!(
format!("{err}").contains("SeaORM") || format!("{err}").contains("Operation requires"),
"Error should mention SeaORM requirement, got: {err}"
);
}
#[tokio::test]
async fn test_duckdb_session_transaction_fails() {
let pool = make_duckdb_pool().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let result = session.begin_transaction().await;
assert!(
result.is_err(),
"begin_transaction should fail on DuckDB connection (requires SeaORM)"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_duckdb_pool_concurrent_sessions() {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
let config = DbConfig {
url: "duckdb::memory:".to_string(),
max_connections: 1,
min_connections: 1,
idle_timeout: 300,
acquire_timeout: 10000,
admin_role: "admin".to_string(),
..Default::default()
};
let pool = Arc::new(DbPool::with_config(config).await.expect("Failed to create DuckDB pool"));
{
let session = pool.get_session("admin").await.expect("Failed to get setup session");
session
.execute_duckdb_raw("CREATE TABLE concurrent_test (id INTEGER, value INTEGER)")
.await
.expect("CREATE TABLE should succeed");
}
let success_count = Arc::new(AtomicUsize::new(0));
let mut handles = Vec::new();
for i in 0..4 {
let pool_clone = pool.clone();
let success_clone = success_count.clone();
handles.push(tokio::spawn(async move {
let session = match pool_clone.get_session("admin").await {
Ok(s) => s,
Err(_) => return,
};
let sql = format!("INSERT INTO concurrent_test VALUES ({i}, {i} * 10)");
if session.execute_duckdb_raw(&sql).await.is_ok() {
success_clone.fetch_add(1, Ordering::SeqCst);
}
}));
}
for handle in handles {
handle.await.expect("Task panicked");
}
assert_eq!(
success_count.load(Ordering::SeqCst),
4,
"All concurrent inserts should succeed"
);
let session = pool
.get_session("admin")
.await
.expect("Failed to get verification session");
let rows = session
.execute_duckdb("SELECT COUNT(*) AS cnt FROM concurrent_test")
.await
.expect("COUNT query should succeed");
let count = rows[0].get("cnt").expect("Should have 'cnt' column");
if let duckdb::types::Value::BigInt(n) = count {
assert_eq!(*n, 4, "Should have 4 rows after concurrent inserts");
} else {
panic!("Expected BigInt for count, got {:?}", count);
}
}
#[tokio::test]
async fn test_duckdb_pool_status_invariants() {
let pool = make_duckdb_pool().await;
let status = pool.status();
assert!(
status.total >= status.active,
"total ({}) should be >= active ({})",
status.total,
status.active
);
assert_eq!(
status.total,
status.active + status.idle,
"total ({}) should equal active ({}) + idle ({})",
status.total,
status.active,
status.idle
);
}