#[cfg(feature = "duckdb")]
mod duckdb_tests {
use std::error::Error;
use std::sync::Arc;
use flowcode_core::duckdb_interchange::{record_batch_to_table, table_to_record_batch};
use flowcode_core::duckdb_session::DuckSession;
use flowcode_core::types::{TypedValue, ValueKind};
use flowcode_core::ast::ArgValue;
use duckdb::arrow::array::*;
use duckdb::arrow::datatypes::*;
use duckdb::arrow::record_batch::RecordBatch;
fn create_test_record_batch() -> Result<RecordBatch, Box<dyn Error>> {
let schema = Schema::new(vec![
Field::new("id", DataType::Int32, false),
Field::new("name", DataType::Utf8, false),
Field::new("value", DataType::Float64, true),
Field::new("active", DataType::Boolean, true),
]);
let id_array = Int32Array::from(vec![1, 2, 3]);
let name_array = StringArray::from(vec!["Alice", "Bob", "Charlie"]);
let value_array = Float64Array::from(vec![Some(1.1), None, Some(3.3)]);
let active_array = BooleanArray::from(vec![Some(true), Some(false), None]);
RecordBatch::try_new(
Arc::new(schema),
vec![
Arc::new(id_array),
Arc::new(name_array),
Arc::new(value_array),
Arc::new(active_array),
]
).map_err(Into::into)
}
#[test]
fn test_record_batch_to_table() -> Result<(), Box<dyn Error>> {
let batch = create_test_record_batch()?;
let table = record_batch_to_table(batch)?;
if let ArgValue::Table(rows) = &table.value {
assert_eq!(rows.len(), 3); assert_eq!(rows[0].len(), 4);
if let ArgValue::Number(n) = rows[0][0].value {
assert_eq!(n, 1.0);
} else {
panic!("Expected number for id");
}
if let ArgValue::String(s) = &rows[0][1].value {
assert_eq!(s, "Alice");
} else {
panic!("Expected string for name");
}
assert_eq!(rows[1][2].kind, ValueKind::Null); assert_eq!(rows[2][3].kind, ValueKind::Null);
assert_eq!(rows[0][0].kind, ValueKind::Number);
assert_eq!(rows[0][1].kind, ValueKind::String);
assert_eq!(rows[0][2].kind, ValueKind::Number);
assert_eq!(rows[0][3].kind, ValueKind::Bool);
} else {
panic!("Expected Table variant");
}
Ok(())
}
#[test]
fn test_table_to_record_batch() -> Result<(), Box<dyn Error>> {
let table = TypedValue::new(
ValueKind::Table,
ArgValue::Table(vec![
vec![
TypedValue::new(ValueKind::Number, ArgValue::Number(1.0)),
TypedValue::new(ValueKind::String, ArgValue::String("Alice".to_string())),
TypedValue::new(ValueKind::Number, ArgValue::Number(42.5)),
TypedValue::new(ValueKind::Bool, ArgValue::Bool(true)),
],
vec![
TypedValue::new(ValueKind::Number, ArgValue::Number(2.0)),
TypedValue::new(ValueKind::String, ArgValue::String("Bob".to_string())),
TypedValue::new(ValueKind::Null, ArgValue::Null),
TypedValue::new(ValueKind::Bool, ArgValue::Bool(false)),
],
vec![
TypedValue::new(ValueKind::Number, ArgValue::Number(3.0)),
TypedValue::new(ValueKind::String, ArgValue::String("Charlie".to_string())),
TypedValue::new(ValueKind::Number, ArgValue::Number(3.14)),
TypedValue::new(ValueKind::Null, ArgValue::Null),
],
]),
);
let batch = table_to_record_batch(&table)?;
assert_eq!(batch.num_columns(), 4, "Should have 4 columns");
assert_eq!(batch.num_rows(), 3, "Should have 3 rows");
assert_eq!(batch.schema().field(0).data_type(), &DataType::Float64, "First column should be Float64");
assert_eq!(batch.schema().field(1).data_type(), &DataType::Utf8, "Second column should be Utf8");
assert_eq!(batch.schema().field(2).data_type(), &DataType::Float64, "Third column should be Float64");
assert_eq!(batch.schema().field(3).data_type(), &DataType::Boolean, "Fourth column should be Boolean");
let cols = batch.columns();
let col0 = cols[0].as_any().downcast_ref::<Float64Array>().unwrap();
assert_eq!(col0.value(0), 1.0);
assert_eq!(col0.value(1), 2.0);
assert_eq!(col0.value(2), 3.0);
let col1 = cols[1].as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(col1.value(0), "Alice");
assert_eq!(col1.value(1), "Bob");
assert_eq!(col1.value(2), "Charlie");
let col2 = cols[2].as_any().downcast_ref::<Float64Array>().unwrap();
assert_eq!(col2.value(0), 42.5);
assert!(col2.is_null(1));
assert_eq!(col2.value(2), 3.14);
let col3 = cols[3].as_any().downcast_ref::<BooleanArray>().unwrap();
assert_eq!(col3.value(0), true);
assert_eq!(col3.value(1), false);
assert!(col3.is_null(2));
Ok(())
}
#[test]
fn test_duckdb_session_basic() -> Result<(), Box<dyn Error>> {
let session = DuckSession::new()?;
let result = session.execute_sql("SELECT 1 as one, 'test' as two, true as three")?;
if let ArgValue::Table(rows) = &result.value {
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].len(), 3);
if let ArgValue::Number(n) = rows[0][0].value {
assert_eq!(n, 1.0);
} else {
panic!("Expected number");
}
if let ArgValue::String(s) = &rows[0][1].value {
assert_eq!(s, "test");
} else {
panic!("Expected string");
}
if let ArgValue::Bool(b) = rows[0][2].value {
assert!(b);
} else {
panic!("Expected bool");
}
} else {
panic!("Expected Table variant");
}
let result = session.execute_with_params(
"SELECT ? as num, ? as str, ? as flag",
&[
ArgValue::Number(42.0),
ArgValue::String("param".to_string()),
ArgValue::Bool(true),
],
)?;
if let ArgValue::Table(rows) = &result.value {
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].len(), 3);
}
Ok(())
}
#[test]
fn test_execute_query() -> Result<(), Box<dyn Error>> {
let session = DuckSession::new()?;
let create_table = "CREATE TABLE test_table (id INTEGER, name VARCHAR);";
session.execute_sql(create_table)?;
let insert_data = "INSERT INTO test_table VALUES (1, 'Alice'), (2, 'Bob');";
session.execute_sql(insert_data)?;
let query = "SELECT * FROM test_table ORDER BY id;";
let result = session.execute_sql(query)?;
if let ArgValue::Table(rows) = &result.value {
assert_eq!(rows.len(), 2, "Expected 2 rows");
if let ArgValue::Number(id) = rows[0][0].value {
assert_eq!(id, 1.0, "Expected id=1");
} else {
panic!("Expected number for id");
}
if let ArgValue::String(name) = &rows[0][1].value {
assert_eq!(name, "Alice", "Expected name=Alice");
} else {
panic!("Expected string for name");
}
} else {
panic!("Expected table result");
}
Ok(())
}
#[test]
fn test_duckdb_csv_handling() -> Result<(), Box<dyn Error>> {
use std::io::Write;
use tempfile::Builder;
let mut tmp = Builder::new()
.suffix(".csv")
.tempfile()?;
writeln!(tmp, "id,name")?;
writeln!(tmp, "1,Alice")?;
writeln!(tmp, "2,Bob")?;
tmp.flush()?; let path = tmp.into_temp_path(); let path_str = path.to_str().unwrap();
let sess = DuckSession::new()?;
sess.load_csv_auto("people", path_str)?;
let result = sess.execute_sql("SELECT * FROM people")?;
if let ArgValue::Table(rows) = &result.value {
assert_eq!(rows.len(), 2, "Expected 2 rows in the result");
} else {
panic!("Expected a Table result");
}
Ok(())
}
}