diff --git a/helix-db/src/helixc/analyzer/methods/query_validation.rs b/helix-db/src/helixc/analyzer/methods/query_validation.rs index c5502a07..1b8e7d24 100644 --- a/helix-db/src/helixc/analyzer/methods/query_validation.rs +++ b/helix-db/src/helixc/analyzer/methods/query_validation.rs @@ -637,6 +637,7 @@ fn process_object_literal<'a>( field_infos: vec![], aggregate_properties: Vec::new(), is_count_aggregate: false, + closure_param_name: None, } } @@ -1007,6 +1008,7 @@ fn analyze_return_expr<'a>( is_group_by, aggregate_properties, is_count_aggregate, + traversal.closure_param_name.clone(), )); } @@ -1075,6 +1077,7 @@ fn analyze_return_expr<'a>( is_group_by, aggregate_properties, is_count_aggregate, + traversal.closure_param_name.clone(), )); // Generate map closure (direct return, no variable assignment to update) @@ -1160,6 +1163,7 @@ fn analyze_return_expr<'a>( is_group_by, aggregate_properties, is_count_aggregate, + traversal.closure_param_name.clone(), )); } diff --git a/helix-db/src/helixc/analyzer/methods/traversal_validation.rs b/helix-db/src/helixc/analyzer/methods/traversal_validation.rs index 949f10ef..eada2c0d 100644 --- a/helix-db/src/helixc/analyzer/methods/traversal_validation.rs +++ b/helix-db/src/helixc/analyzer/methods/traversal_validation.rs @@ -1817,6 +1817,9 @@ pub(crate) fn validate_traversal<'a>( ) .ok()?; + // Tag the main traversal with the closure parameter name + gen_traversal.closure_param_name = Some(cl.identifier.clone()); + // Tag all nested traversals with closure context for (_field_name, nested_info) in gen_traversal.nested_traversals.iter_mut() { nested_info.closure_param_name = Some(cl.identifier.clone()); diff --git a/helix-db/src/helixc/generator/queries.rs b/helix-db/src/helixc/generator/queries.rs index c89f3732..cda55eab 100644 --- a/helix-db/src/helixc/generator/queries.rs +++ b/helix-db/src/helixc/generator/queries.rs @@ -284,7 +284,9 @@ impl Query { } } else if struct_def.is_collection { // Collection - generate mapping code - let singular_var = struct_def.source_variable.trim_end_matches('s'); + // Use HQL closure param name if available, otherwise fall back to singular form + let singular_var = struct_def.closure_param_name.as_deref() + .unwrap_or_else(|| struct_def.source_variable.trim_end_matches('s')); // Check if any field is a nested traversal (needs Result handling) let has_nested = struct_def.fields.iter().any(|f| f.is_nested_traversal); @@ -317,29 +319,23 @@ impl Query { // Handle scalar nested traversals with closure parameters (e.g., username: u::{name}) // or anonymous traversals (e.g., creatorID: _::In::ID) if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { - closure_source_var: Some(closure_var), + closure_source_var: Some(_), accessed_field_name: accessed_field, nested_struct_name: None, .. } = &field_info.source { - // Resolve "_" and "val" placeholders to actual iteration variable - let resolved_var = if closure_var == "_" || closure_var == "val" { - singular_var - } else { - closure_var.as_str() - }; - + // Use singular_var which is the actual closure parameter (e.g., "e" from entries::|e|) // This is a scalar field accessing a closure parameter or anonymous variable let field_to_access = accessed_field.as_ref() .map(|s| s.as_str()) .unwrap_or(field.name.as_str()); if field_to_access == "id" || field_to_access == "ID" { - format!("uuid_str({}.id(), &arena)", resolved_var) + format!("uuid_str({}.id(), &arena)", singular_var) } else if field_to_access == "label" || field_to_access == "Label" { - format!("{}.label()", resolved_var) + format!("{}.label()", singular_var) } else { - format!("{}.get_property(\"{}\")", resolved_var, field_to_access) + format!("{}.get_property(\"{}\")", singular_var, field_to_access) } } else if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { traversal_code: Some(trav_code), @@ -415,7 +411,7 @@ impl Query { // This is a deeply nested traversal - generate nested traversal code // Extract the source variable for this deeply nested traversal - let inner_source_var = if let Some(inner_trav_type) = inner_traversal_type { + let _inner_source_var = if let Some(inner_trav_type) = inner_traversal_type { use crate::helixc::generator::traversal_steps::TraversalType; match inner_trav_type { TraversalType::FromSingle(var) | TraversalType::FromIter(var) => { @@ -447,12 +443,12 @@ impl Query { } else { ".collect::>()".to_string() }; - format!("G::from_iter(&db, &txn, std::iter::once({}.clone()), &arena){}{}", inner_source_var, inner_trav_code, inner_fields_str) + format!("G::from_iter(&db, &txn, std::iter::once({}.clone()), &arena){}{}", closure_param, inner_trav_code, inner_fields_str) } else { // Check if this field itself is a nested traversal that accesses the closure parameter // Extract both the access variable and the actual field being accessed let (access_var, accessed_field_name) = if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { - closure_source_var: Some(closure_var), + closure_source_var: Some(_), accessed_field_name: accessed_field, .. } = &nested_field.source { @@ -461,7 +457,7 @@ impl Query { let field_to_access = accessed_field.as_ref() .map(|s| s.as_str()) .unwrap_or(nested_field.name.as_str()); - (closure_var.as_str(), field_to_access) + (closure_param, field_to_access) } else { (closure_param, nested_field.name.as_str()) }; @@ -546,29 +542,23 @@ impl Query { // Handle scalar nested traversals with closure parameters (e.g., username: u::{name}) // or anonymous traversals (e.g., creatorID: _::In::ID) if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { - closure_source_var: Some(closure_var), + closure_source_var: Some(_), accessed_field_name: accessed_field, nested_struct_name: None, .. } = &field_info.source { - // Resolve "_" and "val" placeholders to actual iteration variable - let resolved_var = if closure_var == "_" || closure_var == "val" { - singular_var - } else { - closure_var.as_str() - }; - + // Use singular_var which is the actual closure parameter (e.g., "e" from entries::|e|) // This is a scalar field accessing a closure parameter or anonymous variable let field_to_access = accessed_field.as_ref() .map(|s| s.as_str()) .unwrap_or(field.name.as_str()); if field_to_access == "id" || field_to_access == "ID" { - format!("uuid_str({}.id(), &arena)", resolved_var) + format!("uuid_str({}.id(), &arena)", singular_var) } else if field_to_access == "label" || field_to_access == "Label" { - format!("{}.label()", resolved_var) + format!("{}.label()", singular_var) } else { - format!("{}.get_property(\"{}\")", resolved_var, field_to_access) + format!("{}.get_property(\"{}\")", singular_var, field_to_access) } } else if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { traversal_code: Some(trav_code), @@ -626,7 +616,7 @@ impl Query { // This is a deeply nested traversal - generate nested traversal code // Extract the source variable for this deeply nested traversal - let inner_source_var = if let Some(inner_trav_type) = inner_traversal_type { + let _inner_source_var = if let Some(inner_trav_type) = inner_traversal_type { use crate::helixc::generator::traversal_steps::TraversalType; match inner_trav_type { TraversalType::FromSingle(var) | TraversalType::FromIter(var) => { @@ -658,12 +648,12 @@ impl Query { } else { ".collect::>()".to_string() }; - format!("G::from_iter(&db, &txn, std::iter::once({}.clone()), &arena){}{}", inner_source_var, inner_trav_code, inner_fields_str) + format!("G::from_iter(&db, &txn, std::iter::once({}.clone()), &arena){}{}", closure_param, inner_trav_code, inner_fields_str) } else { // Check if this field itself is a nested traversal that accesses the closure parameter // Extract both the access variable and the actual field being accessed let (access_var, accessed_field_name) = if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { - closure_source_var: Some(closure_var), + closure_source_var: Some(_), accessed_field_name: accessed_field, .. } = &nested_field.source { @@ -672,7 +662,7 @@ impl Query { let field_to_access = accessed_field.as_ref() .map(|s| s.as_str()) .unwrap_or(nested_field.name.as_str()); - (closure_var.as_str(), field_to_access) + (closure_param, field_to_access) } else { (closure_param, nested_field.name.as_str()) }; @@ -900,7 +890,9 @@ impl Query { )?; } else if struct_def.is_collection { // Collection - generate mapping code - let singular_var = struct_def.source_variable.trim_end_matches('s'); + // Use HQL closure param name if available, otherwise fall back to singular form + let singular_var = struct_def.closure_param_name.as_deref() + .unwrap_or_else(|| struct_def.source_variable.trim_end_matches('s')); // Check if any field is a nested traversal (needs Result handling) let has_nested = struct_def.fields.iter().any(|f| f.is_nested_traversal); @@ -933,29 +925,23 @@ impl Query { // Handle scalar nested traversals with closure parameters (e.g., username: u::{name}) // or anonymous traversals (e.g., creatorID: _::In::ID) if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { - closure_source_var: Some(closure_var), + closure_source_var: Some(_), accessed_field_name: accessed_field, nested_struct_name: None, .. } = &field_info.source { - // Resolve "_" and "val" placeholders to actual iteration variable - let resolved_var = if closure_var == "_" || closure_var == "val" { - singular_var - } else { - closure_var.as_str() - }; - + // Use singular_var which is the actual closure parameter (e.g., "e" from entries::|e|) // This is a scalar field accessing a closure parameter or anonymous variable let field_to_access = accessed_field.as_ref() .map(|s| s.as_str()) .unwrap_or(field.name.as_str()); if field_to_access == "id" || field_to_access == "ID" { - format!("uuid_str({}.id(), &arena)", resolved_var) + format!("uuid_str({}.id(), &arena)", singular_var) } else if field_to_access == "label" || field_to_access == "Label" { - format!("{}.label()", resolved_var) + format!("{}.label()", singular_var) } else { - format!("{}.get_property(\"{}\")", resolved_var, field_to_access) + format!("{}.get_property(\"{}\")", singular_var, field_to_access) } } else if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { traversal_code: Some(trav_code), @@ -1031,7 +1017,7 @@ impl Query { // This is a deeply nested traversal - generate nested traversal code // Extract the source variable for this deeply nested traversal - let inner_source_var = if let Some(inner_trav_type) = inner_traversal_type { + let _inner_source_var = if let Some(inner_trav_type) = inner_traversal_type { use crate::helixc::generator::traversal_steps::TraversalType; match inner_trav_type { TraversalType::FromSingle(var) | TraversalType::FromIter(var) => { @@ -1063,12 +1049,12 @@ impl Query { } else { ".collect::>()".to_string() }; - format!("G::from_iter(&db, &txn, std::iter::once({}.clone()), &arena){}{}", inner_source_var, inner_trav_code, inner_fields_str) + format!("G::from_iter(&db, &txn, std::iter::once({}.clone()), &arena){}{}", closure_param, inner_trav_code, inner_fields_str) } else { // Check if this field itself is a nested traversal that accesses the closure parameter // Extract both the access variable and the actual field being accessed let (access_var, accessed_field_name) = if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { - closure_source_var: Some(closure_var), + closure_source_var: Some(_), accessed_field_name: accessed_field, .. } = &nested_field.source { @@ -1077,7 +1063,7 @@ impl Query { let field_to_access = accessed_field.as_ref() .map(|s| s.as_str()) .unwrap_or(nested_field.name.as_str()); - (closure_var.as_str(), field_to_access) + (closure_param, field_to_access) } else { (closure_param, nested_field.name.as_str()) }; @@ -1162,29 +1148,23 @@ impl Query { // Handle scalar nested traversals with closure parameters (e.g., username: u::{name}) // or anonymous traversals (e.g., creatorID: _::In::ID) if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { - closure_source_var: Some(closure_var), + closure_source_var: Some(_), accessed_field_name: accessed_field, nested_struct_name: None, .. } = &field_info.source { - // Resolve "_" and "val" placeholders to actual iteration variable - let resolved_var = if closure_var == "_" || closure_var == "val" { - singular_var - } else { - closure_var.as_str() - }; - + // Use singular_var which is the actual closure parameter (e.g., "e" from entries::|e|) // This is a scalar field accessing a closure parameter or anonymous variable let field_to_access = accessed_field.as_ref() .map(|s| s.as_str()) .unwrap_or(field.name.as_str()); if field_to_access == "id" || field_to_access == "ID" { - format!("uuid_str({}.id(), &arena)", resolved_var) + format!("uuid_str({}.id(), &arena)", singular_var) } else if field_to_access == "label" || field_to_access == "Label" { - format!("{}.label()", resolved_var) + format!("{}.label()", singular_var) } else { - format!("{}.get_property(\"{}\")", resolved_var, field_to_access) + format!("{}.get_property(\"{}\")", singular_var, field_to_access) } } else if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { traversal_code: Some(trav_code), @@ -1242,7 +1222,7 @@ impl Query { // This is a deeply nested traversal - generate nested traversal code // Extract the source variable for this deeply nested traversal - let inner_source_var = if let Some(inner_trav_type) = inner_traversal_type { + let _inner_source_var = if let Some(inner_trav_type) = inner_traversal_type { use crate::helixc::generator::traversal_steps::TraversalType; match inner_trav_type { TraversalType::FromSingle(var) | TraversalType::FromIter(var) => { @@ -1274,12 +1254,12 @@ impl Query { } else { ".collect::,_>>()?".to_string() }; - format!("G::from_iter(&db, &txn, std::iter::once({}.clone()), &arena){}{}", inner_source_var, inner_trav_code, inner_fields_str) + format!("G::from_iter(&db, &txn, std::iter::once({}.clone()), &arena){}{}", closure_param, inner_trav_code, inner_fields_str) } else { // Check if this field itself is a nested traversal that accesses the closure parameter // Extract both the access variable and the actual field being accessed let (access_var, accessed_field_name) = if let crate::helixc::generator::return_values::ReturnFieldSource::NestedTraversal { - closure_source_var: Some(closure_var), + closure_source_var: Some(_), accessed_field_name: accessed_field, .. } = &nested_field.source { @@ -1288,7 +1268,7 @@ impl Query { let field_to_access = accessed_field.as_ref() .map(|s| s.as_str()) .unwrap_or(nested_field.name.as_str()); - (closure_var.as_str(), field_to_access) + (closure_param, field_to_access) } else { (closure_param, nested_field.name.as_str()) }; diff --git a/helix-db/src/helixc/generator/return_values.rs b/helix-db/src/helixc/generator/return_values.rs index 12ee82c7..db0386f8 100644 --- a/helix-db/src/helixc/generator/return_values.rs +++ b/helix-db/src/helixc/generator/return_values.rs @@ -51,6 +51,7 @@ pub struct ReturnValueStruct { pub field_infos: Vec, // Original field info for nested struct generation pub aggregate_properties: Vec, // Properties to group by (for closure-style aggregates) pub is_count_aggregate: bool, // True for COUNT mode aggregates + pub closure_param_name: Option, // HQL closure parameter name (e.g., "e" from entries::|e|) } impl ReturnValueStruct { @@ -68,6 +69,7 @@ impl ReturnValueStruct { field_infos: Vec::new(), aggregate_properties: Vec::new(), is_count_aggregate: false, + closure_param_name: None, } } @@ -192,6 +194,7 @@ impl ReturnValueStruct { is_group_by: bool, aggregate_properties: Vec, is_count_aggregate: bool, + closure_param_name: Option, ) -> Self { // First, recursively build nested structs to determine if they have lifetimes let mut nested_has_lifetime = std::collections::HashMap::new(); @@ -217,6 +220,7 @@ impl ReturnValueStruct { false, // Not group_by Vec::new(), // No aggregate properties for nested structs false, // Not count aggregate + None, // Nested structs don't have their own closure param ); nested_has_lifetime.insert(nested_name, nested_struct.has_lifetime); } @@ -279,6 +283,7 @@ impl ReturnValueStruct { struct_def.field_infos = field_infos; // Store for nested generation struct_def.aggregate_properties = aggregate_properties; struct_def.is_count_aggregate = is_count_aggregate; + struct_def.closure_param_name = closure_param_name; struct_def } @@ -309,6 +314,7 @@ impl ReturnValueStruct { false, // Not group_by Vec::new(), // No aggregate properties for nested structs false, // Not count aggregate + None, // Nested structs don't have their own closure param ); // Recursively generate nested struct defs output.push_str(&nested_struct.generate_all_struct_defs()); diff --git a/helix-db/src/helixc/generator/traversal_steps.rs b/helix-db/src/helixc/generator/traversal_steps.rs index e60ff4a8..26ffde8c 100644 --- a/helix-db/src/helixc/generator/traversal_steps.rs +++ b/helix-db/src/helixc/generator/traversal_steps.rs @@ -95,6 +95,7 @@ pub struct Traversal { pub excluded_fields: Vec, pub nested_traversals: std::collections::HashMap, pub is_reused_variable: bool, + pub closure_param_name: Option, // HQL closure parameter name (e.g., "e" from entries::|e|) } impl Display for Traversal { @@ -177,6 +178,7 @@ impl Default for Traversal { excluded_fields: vec![], nested_traversals: std::collections::HashMap::new(), is_reused_variable: false, + closure_param_name: None, } } } diff --git a/hql-tests/tests/user_test_5/queries.hx b/hql-tests/tests/user_test_5/queries.hx index d1217474..7eed1ea4 100644 --- a/hql-tests/tests/user_test_5/queries.hx +++ b/hql-tests/tests/user_test_5/queries.hx @@ -1,35 +1,32 @@ -N::Post { - subreddit: String, - title: String, - content: String, - url: String, - score: I32, -} +QUERY GetLexicalEntriesByLexiconSlug(lexiconSlug: String) => + lexicon <- N({ slug: lexiconSlug }) + entries <- lexicon::Out + RETURN entries::|e|{ + entryID: e::ID, + senses: e::Out::{ + senseID: ID, + .. + }, + .. + } -V::Content { - chunk: String -} -E::EmbeddingOf { - From: Post, - To: Content, +N::Lexicon { + INDEX slug: String, } -N::Comment { - content: String, - score: I32, +N::Entry { } -E::CommentOf { - From: Post, - To: Comment, +N::Sense { } -QUERY get_all_posts() => - posts <- N - RETURN posts +E::LexiconHasEntry { + From: Lexicon, + To: Entry +} -QUERY search_posts_vec(query: [F64], k: I32) => - vecs <- SearchV(query, k) - posts <- vecs::In - RETURN posts::{subreddit, title, content, url} +E::EntryHasSense { + From: Entry, + To: Sense +}