#![allow(clippy::unwrap_used)] #![allow(clippy::useless_let_if_seq)] use std::time::Instant;
use fraiseql_core::{
db::{
projection_generator::{
MySqlProjectionGenerator, PostgresProjectionGenerator, SqliteProjectionGenerator,
},
types::JsonbValue,
},
runtime::{FieldMapping, ResultProjector},
};
use serde_json::{Map, Value as JsonValue, json};
fn generate_sample_rows(row_count: usize, field_count: usize) -> Vec<JsonbValue> {
let mut rows = Vec::with_capacity(row_count);
for row_id in 0..row_count {
let mut obj = Map::new();
obj.insert("id".to_string(), json!(format!("id_{}", row_id)));
obj.insert("name".to_string(), json!(format!("User {}", row_id)));
obj.insert("email".to_string(), json!(format!("user{}@example.com", row_id)));
for field_idx in 3..field_count {
obj.insert(format!("field_{}", field_idx), json!(format!("value_{}", field_idx)));
}
obj.insert("status".to_string(), json!("active"));
obj.insert("created_at".to_string(), json!("2024-01-14T00:00:00Z"));
obj.insert("metadata".to_string(), json!({"score": 100}));
rows.push(JsonbValue::new(JsonValue::Object(obj)));
}
rows
}
#[cfg(test)]
mod query_optimization_tests {
use super::*;
#[test]
fn test_postgres_projection_sql_generation_small() {
let generator = PostgresProjectionGenerator::new();
let fields = vec!["id".to_string(), "name".to_string(), "email".to_string()];
let _ = generator.generate_projection_sql(&fields);
let start = Instant::now();
let sql = generator.generate_projection_sql(&fields);
let elapsed = start.elapsed();
let sql = sql.unwrap_or_else(|e| panic!("Should generate valid SQL for 3 fields: {e}"));
assert!(!sql.is_empty(), "Generated SQL should not be empty");
assert!(
elapsed.as_millis() < 50,
"PostgreSQL projection for 3 fields should be <50ms (actual: {:?})",
elapsed
);
}
#[test]
fn test_postgres_projection_sql_generation_large() {
let generator = PostgresProjectionGenerator::new();
let fields: Vec<String> = (0..20).map(|i| format!("field_{}", i)).collect();
let start = Instant::now();
let sql = generator.generate_projection_sql(&fields);
let elapsed = start.elapsed();
let sql = sql.unwrap_or_else(|e| panic!("Should generate valid SQL for 20 fields: {e}"));
assert!(!sql.is_empty(), "Generated SQL should not be empty");
assert!(
elapsed.as_millis() < 50,
"PostgreSQL projection for 20 fields should be <50ms (actual: {:?})",
elapsed
);
}
#[test]
fn test_multi_database_projection_generation() {
let postgres_gen = PostgresProjectionGenerator::new();
let mysql_gen = MySqlProjectionGenerator::new();
let sqlite_gen = SqliteProjectionGenerator::new();
let fields = vec!["id".to_string(), "name".to_string()];
let start_pg = Instant::now();
let pg_sql = postgres_gen.generate_projection_sql(&fields);
let elapsed_pg = start_pg.elapsed();
let start_mysql = Instant::now();
let mysql_sql = mysql_gen.generate_projection_sql(&fields);
let elapsed_mysql = start_mysql.elapsed();
let start_sqlite = Instant::now();
let sqlite_sql = sqlite_gen.generate_projection_sql(&fields);
let elapsed_sqlite = start_sqlite.elapsed();
let _pg =
pg_sql.unwrap_or_else(|e| panic!("PostgreSQL should generate projection SQL: {e}"));
let _mysql =
mysql_sql.unwrap_or_else(|e| panic!("MySQL should generate projection SQL: {e}"));
let _sqlite =
sqlite_sql.unwrap_or_else(|e| panic!("SQLite should generate projection SQL: {e}"));
assert!(
elapsed_pg.as_millis() < 50,
"PostgreSQL generation should be fast (actual: {elapsed_pg:?})"
);
assert!(
elapsed_mysql.as_millis() < 50,
"MySQL generation should be fast (actual: {elapsed_mysql:?})"
);
assert!(
elapsed_sqlite.as_millis() < 50,
"SQLite generation should be fast (actual: {elapsed_sqlite:?})"
);
}
#[test]
fn test_result_projection_small_dataset() {
let projector = ResultProjector::with_mappings(vec![
FieldMapping::simple("id"),
FieldMapping::simple("name"),
FieldMapping::simple("email"),
]);
let rows = generate_sample_rows(100, 10);
let start = Instant::now();
let result = projector.project_results(&rows, true);
let elapsed = start.elapsed();
let projected =
result.unwrap_or_else(|e| panic!("Should project 100 rows successfully: {e}"));
if let JsonValue::Array(arr) = &projected {
assert_eq!(arr.len(), 100, "Should project all 100 rows");
} else {
panic!("Expected JSON array from list projection, got: {projected}");
}
assert!(
elapsed.as_millis() < 50,
"Projecting 100 rows should be <50ms (actual: {:?})",
elapsed
);
}
#[test]
fn test_result_projection_medium_dataset() {
let projector = ResultProjector::with_mappings(vec![
FieldMapping::simple("id"),
FieldMapping::simple("name"),
FieldMapping::simple("email"),
FieldMapping::simple("status"),
FieldMapping::simple("created_at"),
]);
let rows = generate_sample_rows(1000, 8);
let start = Instant::now();
let result = projector.project_results(&rows, true);
let elapsed = start.elapsed();
let projected =
result.unwrap_or_else(|e| panic!("Should project 1000 rows successfully: {e}"));
if let JsonValue::Array(arr) = &projected {
assert_eq!(arr.len(), 1000, "Should project all 1000 rows");
} else {
panic!("Expected JSON array from list projection, got: {projected}");
}
assert!(
elapsed.as_millis() < 100,
"Projecting 1000 rows should be <100ms (actual: {:?})",
elapsed
);
}
#[test]
fn test_result_projection_with_aliasing() {
let projector = ResultProjector::with_mappings(vec![
FieldMapping::aliased("user_id", "id"),
FieldMapping::aliased("user_name", "name"),
FieldMapping::simple("email"),
]);
let mut row = Map::new();
row.insert("user_id".to_string(), json!("123"));
row.insert("user_name".to_string(), json!("Alice"));
row.insert("email".to_string(), json!("alice@example.com"));
let jsonb = JsonbValue::new(JsonValue::Object(row));
let result = projector
.project_results(&[jsonb], false)
.unwrap_or_else(|e| panic!("Should project with aliasing: {e}"));
if let JsonValue::Object(ref result_obj) = result {
assert!(result_obj.get("id").is_some(), "Aliased user_id → id");
assert!(result_obj.get("name").is_some(), "Aliased user_name → name");
assert!(result_obj.get("email").is_some(), "Simple field email");
assert!(result_obj.get("user_id").is_none(), "Original user_id should not appear");
} else {
panic!("Expected JSON object from single-row projection, got: {result}");
}
}
#[test]
fn test_typename_addition_latency() {
let projector = ResultProjector::with_mappings(vec![
FieldMapping::simple("id"),
FieldMapping::simple("name"),
])
.with_typename("User");
let rows = generate_sample_rows(1000, 5);
let start = Instant::now();
let result = projector.project_results(&rows, true);
let elapsed = start.elapsed();
let projected =
result.unwrap_or_else(|e| panic!("Should project 1000 rows with __typename: {e}"));
if let JsonValue::Array(ref arr) = projected {
assert_eq!(arr.len(), 1000, "Should project all 1000 rows");
if let Some(JsonValue::Object(first)) = arr.first() {
assert_eq!(
first.get("__typename").and_then(|v| v.as_str()),
Some("User"),
"Should have __typename = User"
);
}
}
assert!(
elapsed.as_millis() < 150,
"__typename addition for 1000 rows should be <150ms (actual: {:?})",
elapsed
);
}
#[test]
fn test_typename_in_projection() {
let projector = ResultProjector::with_mappings(vec![
FieldMapping::simple("id"),
FieldMapping::simple("name"),
])
.with_typename("Post");
let mut row = Map::new();
row.insert("id".to_string(), json!("post1"));
row.insert("name".to_string(), json!("Alice"));
let jsonb = JsonbValue::new(JsonValue::Object(row));
let result = projector
.project_results(&[jsonb], false)
.unwrap_or_else(|e| panic!("Should project single row with __typename: {e}"));
if let JsonValue::Object(ref result_obj) = result {
assert_eq!(
result_obj.get("__typename").and_then(|v| v.as_str()),
Some("Post"),
"Should have __typename = Post"
);
} else {
panic!("Expected JSON object from single-row projection, got: {result}");
}
}
#[test]
fn test_payload_size_without_projection() {
let rows = generate_sample_rows(100, 15);
let mut total_size = 0;
for row in &rows {
total_size += serde_json::to_string(&row.as_value()).map_or(0, |s| s.len());
}
assert!(
total_size > 0,
"Baseline payload should have size (measured: {} bytes)",
total_size
);
}
#[test]
fn test_payload_size_with_projection() {
let projector = ResultProjector::with_mappings(vec![
FieldMapping::simple("id"),
FieldMapping::simple("name"),
FieldMapping::simple("email"),
]);
let rows = generate_sample_rows(100, 15);
let projected = projector
.project_results(&rows, true)
.unwrap_or_else(|e| panic!("Should project 100 rows with 3 fields: {e}"));
let total_size = serde_json::to_string(&projected).map_or(0, |s| s.len());
assert!(
total_size > 0,
"Projected payload should have size (measured: {} bytes)",
total_size
);
assert!(
total_size < 10000,
"Projected payload for 100 rows should be reasonable (<10KB)"
);
}
#[test]
fn test_field_filtering_includes_only_requested() {
let projector = ResultProjector::with_mappings(vec![
FieldMapping::simple("id"),
FieldMapping::simple("name"),
]);
let mut row = Map::new();
row.insert("id".to_string(), json!("123"));
row.insert("name".to_string(), json!("Alice"));
row.insert("secret".to_string(), json!("REDACTED"));
row.insert("internal_id".to_string(), json!("internal123"));
let jsonb = JsonbValue::new(JsonValue::Object(row));
let result = projector
.project_results(&[jsonb], false)
.unwrap_or_else(|e| panic!("Should project and filter fields: {e}"));
if let JsonValue::Object(ref result_obj) = result {
assert!(result_obj.get("id").is_some(), "Should include requested field id");
assert!(result_obj.get("name").is_some(), "Should include requested field name");
assert!(result_obj.get("secret").is_none(), "Should exclude unrequested field secret");
assert!(
result_obj.get("internal_id").is_none(),
"Should exclude unrequested field internal_id"
);
} else {
panic!("Expected JSON object from single-row projection, got: {result}");
}
}
#[test]
fn test_field_filtering_handles_missing_fields() {
let projector = ResultProjector::with_mappings(vec![
FieldMapping::simple("id"),
FieldMapping::simple("missing_field"),
FieldMapping::simple("name"),
]);
let mut row = Map::new();
row.insert("id".to_string(), json!("123"));
row.insert("name".to_string(), json!("Alice"));
let jsonb = JsonbValue::new(JsonValue::Object(row));
let result = projector.project_results(&[jsonb], false).unwrap_or_else(|e| {
panic!("Should handle missing fields gracefully without error: {e}")
});
assert!(
matches!(result, JsonValue::Object(_)),
"Expected JSON object from single-row projection, got: {result}"
);
}
#[test]
fn test_field_filtering_with_multiple_aliases() {
let projector = ResultProjector::with_mappings(vec![
FieldMapping::aliased("user_id", "id"),
FieldMapping::aliased("full_name", "name"),
FieldMapping::aliased("email_address", "email"),
]);
let mut row = Map::new();
row.insert("user_id".to_string(), json!("u1"));
row.insert("full_name".to_string(), json!("Bob"));
row.insert("email_address".to_string(), json!("bob@example.com"));
let jsonb = JsonbValue::new(JsonValue::Object(row));
let result = projector
.project_results(&[jsonb], false)
.unwrap_or_else(|e| panic!("Should project with multiple aliases: {e}"));
if let JsonValue::Object(ref result_obj) = result {
assert_eq!(result_obj.get("id").and_then(|v| v.as_str()), Some("u1"));
assert_eq!(result_obj.get("name").and_then(|v| v.as_str()), Some("Bob"));
assert_eq!(result_obj.get("email").and_then(|v| v.as_str()), Some("bob@example.com"));
assert!(
result_obj.get("user_id").is_none(),
"Original key user_id should not appear after aliasing"
);
assert!(
result_obj.get("full_name").is_none(),
"Original key full_name should not appear after aliasing"
);
assert!(
result_obj.get("email_address").is_none(),
"Original key email_address should not appear after aliasing"
);
} else {
panic!("Expected JSON object from single-row projection, got: {result}");
}
}
#[test]
fn test_postgres_projection_sql_structure() {
let generator = PostgresProjectionGenerator::new();
let fields = vec!["id".to_string(), "name".to_string()];
let sql = generator.generate_projection_sql(&fields).expect("Should generate SQL");
assert!(sql.contains("jsonb_build_object("), "Should use jsonb_build_object");
assert!(sql.contains("'id'"), "Should include id field");
assert!(sql.contains("'name'"), "Should include name field");
assert!(sql.contains("\"data\""), "Should reference data column");
}
#[test]
fn test_postgres_select_clause_generation() {
let generator = PostgresProjectionGenerator::new();
let fields = vec!["id".to_string(), "email".to_string()];
let select = generator
.generate_select_clause("users", &fields)
.expect("Should generate SELECT");
assert!(
select.starts_with("SELECT jsonb_build_object("),
"Should start with SELECT jsonb_build_object"
);
assert!(select.contains("FROM"), "Should include FROM clause");
assert!(
select.contains("\"users\"") || select.contains("`users`") || select.contains("users"),
"Should include table name"
);
}
}