mod common;
use common::connect_test_client;
use futures::StreamExt;
use serde_json::Value;
async fn collect_results(
mut stream: fraiseql_wire::stream::QueryStream<Value>,
limit: Option<usize>,
) -> Result<Vec<Value>, Box<dyn std::error::Error>> {
let mut results = Vec::new();
while let Some(result) = stream.next().await {
let json = result?;
results.push(json);
if let Some(max) = limit {
if results.len() >= max {
break;
}
}
}
Ok(results)
}
#[tokio::test]
async fn test_operator_jsonb_eq_string() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.where_sql("(data->>'status')::text = 'active'")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Found {} active projects", data.len());
for value in data {
assert_eq!(
value["status"].as_str(),
Some("active"),
"All returned projects should have status='active'"
);
}
}
#[tokio::test]
async fn test_operator_jsonb_neq() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.where_sql("(data->>'status')::text != 'active'")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Found {} non-active projects", data.len());
for value in data {
assert_ne!(
value["status"].as_str(),
Some("active"),
"No returned project should have status='active'"
);
}
}
#[tokio::test]
async fn test_operator_jsonb_in() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.where_sql("(data->>'status')::text IN ('active', 'paused')")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!(
"Found {} projects with status in (active, paused)",
data.len()
);
for value in data {
let status = value["status"].as_str().expect("status field");
assert!(
status == "active" || status == "paused",
"All results should have status in (active, paused)"
);
}
}
#[tokio::test]
async fn test_operator_jsonb_contains() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.where_sql("(data->>'name')::text LIKE '%Project%'")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Found {} projects with 'Project' in name", data.len());
for value in data {
let name = value["name"].as_str().expect("name field");
assert!(
name.contains("Project"),
"All results should have 'Project' in name"
);
}
}
#[tokio::test]
async fn test_operator_direct_column_timestamp() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_user")
.where_sql("(data->>'created_at')::timestamp > '2024-01-02'::timestamp")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Found {} users created after 2024-01-02", data.len());
let _ = data.len(); }
#[tokio::test]
async fn test_mixed_filters_jsonb_and_direct() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.where_sql("(data->>'status')::text = 'active'")
.where_sql("(data->>'name')::text LIKE '%Project%'")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Found {} projects (active + contain 'Project')", data.len());
for value in data {
assert_eq!(
value["status"].as_str(),
Some("active"),
"All results should have status='active'"
);
let name = value["name"].as_str().expect("name");
assert!(
name.contains("Project"),
"All results should contain 'Project' in name"
);
}
}
#[tokio::test]
async fn test_limit_clause() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.limit(2)
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("LIMIT 2: Got {} results", data.len());
assert!(data.len() <= 2, "LIMIT 2 should return at most 2 results");
}
#[tokio::test]
async fn test_offset_clause() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.offset(2)
.limit(10)
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("OFFSET 2 LIMIT 10: Got {} results", data.len());
assert!(
data.len() <= 3,
"OFFSET 2 with 5 total projects should return at most 3"
);
}
#[tokio::test]
async fn test_order_by_jsonb_field() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.order_by("(data->>'name')::text ASC")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Ordered by name: Got {} results", data.len());
if data.len() > 1 {
let first_name = data[0]["name"].as_str().expect("name");
let second_name = data[1]["name"].as_str().expect("name");
assert!(
first_name <= second_name,
"Results should be ordered by name ASC"
);
}
}
#[tokio::test]
async fn test_order_by_jsonb_field_multiple() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.order_by("(data->>'status')::text ASC, (data->>'name')::text DESC")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!(
"Ordered by status ASC, name DESC: Got {} results",
data.len()
);
assert!(!data.is_empty(), "Should get results when ordering");
}
#[tokio::test]
async fn test_operator_array_length() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_user")
.where_sql("jsonb_array_length(data->'roles') = 2")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Found {} users with 2 roles", data.len());
for value in data {
let roles = value["roles"].as_array().expect("roles array");
assert_eq!(roles.len(), 2, "All results should have exactly 2 roles");
}
}
#[tokio::test]
async fn test_operator_array_length_gt() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_user")
.where_sql("jsonb_array_length(data->'roles') > 1")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Found {} users with > 1 role", data.len());
for value in data {
let roles = value["roles"].as_array().expect("roles array");
assert!(roles.len() > 1, "All results should have more than 1 role");
}
}
#[tokio::test]
async fn test_pagination_full_cycle() {
let client1 = connect_test_client().await.expect("connect");
let results1 = client1
.query::<Value>("test.v_project")
.limit(2)
.offset(0)
.order_by("(data->>'name')::text ASC")
.execute()
.await
.expect("query");
let page1 = collect_results(results1, None).await.expect("collect");
println!("Page 1 (LIMIT 2 OFFSET 0): {} items", page1.len());
let client2 = connect_test_client().await.expect("connect");
let results2 = client2
.query::<Value>("test.v_project")
.limit(2)
.offset(2)
.order_by("(data->>'name')::text ASC")
.execute()
.await
.expect("query");
let page2 = collect_results(results2, None).await.expect("collect");
println!("Page 2 (LIMIT 2 OFFSET 2): {} items", page2.len());
if !page1.is_empty() && !page2.is_empty() {
assert_ne!(
page1[0], page2[0],
"First page and second page should contain different results"
);
}
}
#[tokio::test]
async fn test_complex_filters_with_ordering_and_pagination() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.where_sql("(data->>'status')::text = 'active'")
.order_by("(data->>'name')::text ASC")
.limit(10)
.offset(0)
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Complex filter: Got {} results", data.len());
for value in data {
assert_eq!(
value["status"].as_str(),
Some("active"),
"All results should have status='active'"
);
}
}
#[tokio::test]
async fn test_order_by_with_collation_c() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.order_by("(data->>'name')::text COLLATE \"C\" ASC")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Ordered with C collation: Got {} results", data.len());
assert!(!data.is_empty(), "Should get results with collation");
}
#[tokio::test]
async fn test_operator_like_pattern() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.where_sql("(data->>'name')::text LIKE 'A%'")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Found {} projects starting with 'A'", data.len());
for value in data {
let name = value["name"].as_str().expect("name");
assert!(name.starts_with('A'), "All results should start with 'A'");
}
}
#[tokio::test]
async fn test_operator_ilike_case_insensitive() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.where_sql("(data->>'name')::text ILIKE 'a%'")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!(
"Found {} projects matching 'a%' (case-insensitive)",
data.len()
);
for value in data {
let name = value["name"].as_str().expect("name");
assert!(
name.to_lowercase().starts_with('a'),
"All results should start with 'a' (case-insensitive)"
);
}
}
#[tokio::test]
async fn test_streaming_large_result_set() {
let client = connect_test_client().await.expect("connect");
let mut stream = client
.query::<Value>("test.v_project")
.chunk_size(2) .execute()
.await
.expect("query");
let mut count = 0;
while let Some(result) = stream.next().await {
let _json = result.expect("item");
count += 1;
}
println!("Streamed {} items in chunks of 2", count);
assert!(count > 0, "Should stream some items");
}
#[tokio::test]
async fn test_operator_is_null() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_user")
.where_sql("(data->'profile'->>'website') IS NULL")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Found {} users with NULL website", data.len());
assert!(!data.is_empty() || data.is_empty(), "Query should execute");
}
#[tokio::test]
async fn test_query_without_filters() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Found {} total projects", data.len());
assert!(!data.is_empty(), "Should find some projects");
}
#[tokio::test]
async fn test_query_with_empty_result() {
let client = connect_test_client().await.expect("connect");
let results = client
.query::<Value>("test.v_project")
.where_sql("(data->>'status')::text = 'nonexistent_status'")
.execute()
.await
.expect("query");
let data = collect_results(results, None).await.expect("collect");
println!("Empty result set: {} items", data.len());
assert_eq!(data.len(), 0, "Should return empty result set");
}