use alopex_dataframe::DataFrameError;
use alopex_embedded::{Database, Error, JoinType};
use arrow::array::{Array, Int32Array, StringArray};
#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn dataframe_query_join_sort_supported_types() {
let db = Database::new();
db.execute_sql(
r#"
CREATE TABLE left_t (id INTEGER, name TEXT, score DOUBLE);
CREATE TABLE right_t (id INTEGER, value INTEGER);
INSERT INTO left_t (id, name, score) VALUES
(1, 'alpha', 10.0),
(2, 'beta', 20.0),
(3, 'gamma', 30.0);
INSERT INTO right_t (id, value) VALUES
(1, 100),
(3, 300);
"#,
)
.unwrap();
let left = db
.query_df("SELECT id, name, score FROM left_t ORDER BY id;")
.unwrap();
let right = db
.query_df("SELECT id, value FROM right_t ORDER BY id;")
.unwrap();
let joined = left
.join(&right, vec!["id".to_string()], JoinType::Left)
.unwrap();
let sorted = joined.sort(vec!["id".to_string()], vec![true]).unwrap();
assert_eq!(sorted.height(), 3);
let id = sorted.column("id").unwrap().to_arrow();
let id = id[0].as_any().downcast_ref::<Int32Array>().unwrap();
assert_eq!(id.value(0), 3);
assert_eq!(id.value(1), 2);
assert_eq!(id.value(2), 1);
let name = sorted.column("name").unwrap().to_arrow();
let name = name[0].as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(name.value(0), "gamma");
assert_eq!(name.value(1), "beta");
assert_eq!(name.value(2), "alpha");
let value = sorted.column("value").unwrap().to_arrow();
let value = value[0].as_any().downcast_ref::<Int32Array>().unwrap();
assert_eq!(value.value(0), 300);
assert!(value.is_null(1));
assert_eq!(value.value(2), 100);
}
#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn dataframe_query_join_variants() {
let db = Database::new();
db.execute_sql(
r#"
CREATE TABLE left_t (id INTEGER, value INTEGER);
CREATE TABLE right_t (id INTEGER, value INTEGER);
INSERT INTO left_t (id, value) VALUES (1, 10), (2, 20), (3, 30);
INSERT INTO right_t (id, value) VALUES (2, 200), (3, 300), (4, 400);
"#,
)
.unwrap();
let left = db
.query_df("SELECT id, value FROM left_t ORDER BY id;")
.unwrap();
let right = db
.query_df("SELECT id, value FROM right_t ORDER BY id;")
.unwrap();
let inner = left
.join(&right, vec!["id".to_string()], JoinType::Inner)
.unwrap();
let inner_names: Vec<String> = inner
.schema()
.fields()
.iter()
.map(|f| f.name().to_string())
.collect();
assert_eq!(
inner_names,
vec![
"id".to_string(),
"value".to_string(),
"value_right".to_string()
]
);
assert_eq!(inner.height(), 2);
let left_join = left
.join(&right, vec!["id".to_string()], JoinType::Left)
.unwrap();
assert_eq!(left_join.height(), 3);
let right_join = left
.join(&right, vec!["id".to_string()], JoinType::Right)
.unwrap();
assert_eq!(right_join.height(), 3);
let full = left
.join(&right, vec!["id".to_string()], JoinType::Full)
.unwrap();
assert_eq!(full.height(), 4);
let semi = left
.join(&right, vec!["id".to_string()], JoinType::Semi)
.unwrap();
let semi_names: Vec<String> = semi
.schema()
.fields()
.iter()
.map(|f| f.name().to_string())
.collect();
assert_eq!(semi_names, vec!["id".to_string(), "value".to_string()]);
assert_eq!(semi.height(), 2);
let anti = left
.join(&right, vec!["id".to_string()], JoinType::Anti)
.unwrap();
let anti_names: Vec<String> = anti
.schema()
.fields()
.iter()
.map(|f| f.name().to_string())
.collect();
assert_eq!(anti_names, vec!["id".to_string(), "value".to_string()]);
assert_eq!(anti.height(), 1);
}
#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn dataframe_query_rejects_vector_columns() {
let db = Database::new();
db.execute_sql(
"CREATE TABLE items (id INT PRIMARY KEY, embedding VECTOR(2, L2)); \
INSERT INTO items (id, embedding) VALUES (1, [0.0, 1.0]);",
)
.unwrap();
let err = db
.query_df("SELECT id, embedding FROM items ORDER BY id;")
.unwrap_err();
assert!(matches!(err, Error::DataFrame(_)));
}
#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn dataframe_sort_rejects_blob_columns() {
let db = Database::new();
db.execute_sql(
"CREATE TABLE blobs (id INTEGER, data BLOB); \
INSERT INTO blobs (id, data) VALUES (1, NULL), (2, NULL);",
)
.unwrap();
let df = db
.query_df("SELECT id, data FROM blobs ORDER BY id;")
.unwrap();
let err = df.sort(vec!["data".to_string()], vec![false]).unwrap_err();
assert!(matches!(err, DataFrameError::InvalidOperation { .. }));
}
#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn dataframe_sort_multi_column_stable_nulls_last() {
let db = Database::new();
db.execute_sql(
r#"
CREATE TABLE sort_t (id INTEGER, category TEXT, score INTEGER, tag TEXT);
INSERT INTO sort_t (id, category, score, tag) VALUES
(1, 'a', 2, 'x'),
(2, 'a', 2, 'y'),
(3, 'a', NULL, 'z'),
(4, 'b', 1, 'b1'),
(5, 'b', 3, 'b3');
"#,
)
.unwrap();
let df = db
.query_df("SELECT id, category, score, tag FROM sort_t ORDER BY id;")
.unwrap();
let sorted = df
.sort(
vec!["category".to_string(), "score".to_string()],
vec![false, true],
)
.unwrap();
let ids = sorted.column("id").unwrap().to_arrow();
let ids = ids[0].as_any().downcast_ref::<Int32Array>().unwrap();
assert_eq!(ids.value(0), 1);
assert_eq!(ids.value(1), 2);
assert_eq!(ids.value(2), 3);
assert_eq!(ids.value(3), 5);
assert_eq!(ids.value(4), 4);
let tags = sorted.column("tag").unwrap().to_arrow();
let tags = tags[0].as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(tags.value(0), "x");
assert_eq!(tags.value(1), "y");
let scores = sorted.column("score").unwrap().to_arrow();
let scores = scores[0].as_any().downcast_ref::<Int32Array>().unwrap();
assert!(scores.is_null(2));
}
#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn dataframe_join_validation_errors() {
let db = Database::new();
db.execute_sql(
r#"
CREATE TABLE left_t (id INTEGER, value INTEGER);
CREATE TABLE right_t (id INTEGER, value INTEGER);
INSERT INTO left_t (id, value) VALUES (1, 10), (2, 20);
INSERT INTO right_t (id, value) VALUES (2, 200), (3, 300);
"#,
)
.unwrap();
let left = db
.query_df("SELECT id, value FROM left_t ORDER BY id;")
.unwrap();
let right = db
.query_df("SELECT id, value FROM right_t ORDER BY id;")
.unwrap();
let err = left
.join(
&right,
(
vec!["id".to_string()],
vec!["id".to_string(), "value".to_string()],
),
JoinType::Inner,
)
.unwrap_err();
assert!(matches!(err, DataFrameError::InvalidOperation { .. }));
let err = left
.join(&right, vec!["missing".to_string()], JoinType::Inner)
.unwrap_err();
assert!(matches!(err, DataFrameError::ColumnNotFound { .. }));
}