#![cfg(feature = "postgres")]
use postgrest_parser::{parse_query_string, QueryBuilder, SchemaCache};
use sqlx::postgres::PgPoolOptions;
use std::sync::Arc;
async fn get_pool() -> sqlx::PgPool {
PgPoolOptions::new()
.max_connections(5)
.connect("postgres://postgres:postgres@localhost:5433/postgrest_parser_test")
.await
.expect("Failed to connect to test database. Is docker-compose up?")
}
#[tokio::test]
async fn test_many_to_one_relation() {
let pool = get_pool().await;
let cache = SchemaCache::load_from_database(&pool).await.unwrap();
let cache = Arc::new(cache);
let params = parse_query_string("select=id,total_amount,customers(name,email)").unwrap();
let mut builder = QueryBuilder::new()
.with_schema_cache(cache.clone())
.with_schema("public");
let result = builder.build_select("orders", ¶ms).unwrap();
println!("Generated SQL:\n{}\n", result.query);
assert!(result.query.contains("WHERE"));
assert!(result
.query
.contains(r#""orders"."customer_id" = "customers"."id""#));
assert!(result.query.contains("COALESCE"));
let rows = sqlx::query(&result.query).fetch_all(&pool).await.unwrap();
assert!(!rows.is_empty(), "Should return orders with customers");
}
#[tokio::test]
async fn test_one_to_many_relation() {
let pool = get_pool().await;
let cache = SchemaCache::load_from_database(&pool).await.unwrap();
let cache = Arc::new(cache);
let params = parse_query_string("select=id,name,email,orders(id,total_amount,status)").unwrap();
let mut builder = QueryBuilder::new()
.with_schema_cache(cache.clone())
.with_schema("public");
let result = builder.build_select("customers", ¶ms).unwrap();
println!("Generated SQL:\n{}\n", result.query);
assert!(result
.query
.contains(r#""orders"."customer_id" = "customers"."id""#));
assert!(result.query.contains("json_agg"));
let rows = sqlx::query(&result.query).fetch_all(&pool).await.unwrap();
assert!(!rows.is_empty(), "Should return customers with orders");
}
#[tokio::test]
async fn test_nested_relations() {
let pool = get_pool().await;
let cache = SchemaCache::load_from_database(&pool).await.unwrap();
let cache = Arc::new(cache);
let params =
parse_query_string("select=name,orders(id,order_items(quantity,products(name,price)))")
.unwrap();
let mut builder = QueryBuilder::new()
.with_schema_cache(cache.clone())
.with_schema("public");
let result = builder.build_select("customers", ¶ms);
if let Ok(result) = result {
println!("Generated SQL:\n{}\n", result.query);
assert!(result.query.contains("orders"));
} else {
println!("Nested relations not yet fully supported");
}
}
#[tokio::test]
async fn test_no_schema_cache_fails_gracefully() {
let params = parse_query_string("select=id,customers(name)").unwrap();
let mut builder = QueryBuilder::new();
let result = builder.build_select("orders", ¶ms);
match result {
Ok(r) => {
assert!(!r.query.contains(r#""customer_id" = "customers"."id""#));
}
Err(_) => {
}
}
}
#[tokio::test]
async fn test_relation_not_found() {
let pool = get_pool().await;
let cache = SchemaCache::load_from_database(&pool).await.unwrap();
let cache = Arc::new(cache);
let params = parse_query_string("select=id,nonexistent_table(name)").unwrap();
let mut builder = QueryBuilder::new()
.with_schema_cache(cache.clone())
.with_schema("public");
let result = builder.build_select("customers", ¶ms);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("relation not found"));
}
#[tokio::test]
async fn test_schema_cache_foreign_keys() {
let pool = get_pool().await;
let cache = SchemaCache::load_from_database(&pool).await.unwrap();
let fks = cache.get_foreign_keys("public", "orders");
assert!(!fks.is_empty(), "orders should have foreign keys");
let customer_fk = fks.iter().find(|fk| fk.to_table == "customers");
assert!(customer_fk.is_some(), "orders should have FK to customers");
let fk = customer_fk.unwrap();
assert_eq!(fk.from_column, "customer_id");
assert_eq!(fk.to_column, "id");
}
#[tokio::test]
async fn test_find_relationship() {
let pool = get_pool().await;
let cache = SchemaCache::load_from_database(&pool).await.unwrap();
let rel = cache.find_relationship("public", "orders", "customers");
assert!(rel.is_some());
let rel = rel.unwrap();
assert_eq!(rel.from_table, "orders");
assert_eq!(rel.to_table, "customers");
let rel = cache.find_relationship("public", "customers", "orders");
assert!(rel.is_some());
let rel = rel.unwrap();
assert_eq!(rel.from_table, "customers");
assert_eq!(rel.to_table, "orders");
}
#[tokio::test]
async fn test_complete_workflow() {
let pool = get_pool().await;
let cache = SchemaCache::load_from_database(&pool).await.unwrap();
let cache = Arc::new(cache);
let params = parse_query_string(
"select=id,name,email,orders(id,status,total_amount)&email=like.*@example.com&limit=5",
)
.unwrap();
let mut builder = QueryBuilder::new()
.with_schema_cache(cache)
.with_schema("public");
let result = builder.build_select("customers", ¶ms).unwrap();
println!("Complete workflow SQL:\n{}\n", result.query);
assert!(result.query.contains("SELECT"));
assert!(result
.query
.contains(r#""orders"."customer_id" = "customers"."id""#));
assert!(result.query.contains("WHERE"));
assert!(result.query.contains("LIKE"));
assert!(result.query.contains("LIMIT"));
let email_pattern = result.params[0].as_str().unwrap();
let limit_value = result.params[1].as_i64().unwrap();
let rows = sqlx::query(&result.query)
.bind(email_pattern) .bind(limit_value) .fetch_all(&pool)
.await
.unwrap();
println!("Returned {} rows", rows.len());
assert!(rows.len() <= 5, "Should respect LIMIT");
}