use std::sync::Arc;
use datafusion::arrow::array::{Array, Int64Array, StringArray};
use datafusion::arrow::datatypes::{DataType, Field, Schema};
use datafusion::prelude::SessionContext;
use datafusion_opensearch::OpenSearchTableProvider;
use serde_json::json;
#[tokio::test]
#[ignore = "needs a running OpenSearch (OS_URL)"]
async fn base_filter_gates_every_scan() {
let url = std::env::var("OS_URL").unwrap_or_else(|_| "http://localhost:9200".into());
let index = "datafusion-opensearch-basefilter-test";
let http = reqwest::Client::new();
let _ = http.delete(format!("{url}/{index}")).send().await.unwrap();
http.put(format!("{url}/{index}"))
.json(&json!({ "mappings": { "properties": { "id": { "type": "keyword" }, "level": { "type": "integer" } } } }))
.send()
.await
.unwrap();
for (id, level) in [("a", 0), ("b", 20), ("c", 20), ("d", 40)] {
http.put(format!("{url}/{index}/_doc/{id}?refresh=true"))
.json(&json!({ "id": id, "level": level }))
.send()
.await
.unwrap();
}
let schema = Arc::new(Schema::new(vec![
Field::new("id", DataType::Utf8, true),
Field::new("level", DataType::Int64, true),
]));
let provider = OpenSearchTableProvider::new(&url, index, schema)
.with_base_filter(vec![json!({ "range": { "level": { "lte": 20 } } })]);
let ctx = SessionContext::new();
ctx.register_table("t", Arc::new(provider)).unwrap();
let batches = ctx
.sql("SELECT id, level FROM t")
.await
.unwrap()
.collect()
.await
.unwrap();
let mut seen = vec![];
for b in &batches {
let ids = b.column(0).as_any().downcast_ref::<StringArray>().unwrap();
let levels = b.column(1).as_any().downcast_ref::<Int64Array>().unwrap();
for i in 0..b.num_rows() {
assert!(
levels.value(i) <= 20,
"base_filter leaked a level-{} row",
levels.value(i)
);
seen.push(ids.value(i).to_string());
}
}
seen.sort();
assert_eq!(seen, vec!["a", "b", "c"], "expected exactly the level<=20 docs");
println!("[live] base_filter gated SELECT *: visible = {seen:?} (level-40 'd' correctly excluded)");
let n: usize = ctx
.sql("SELECT id FROM t WHERE level = 40")
.await
.unwrap()
.collect()
.await
.unwrap()
.iter()
.map(|b| b.num_rows())
.sum();
assert_eq!(n, 0, "base_filter must AND with the user predicate, not be overridable");
println!("[live] WHERE level=40 → 0 rows (base_filter is not opt-out-able)");
http.delete(format!("{url}/{index}")).send().await.unwrap();
}