@@ -23,7 +23,7 @@ use super::function_translator::{get_ch_function_name, CH_PASSTHROUGH_PREFIX};
2323/// TASK-LOCAL RENDER CONTEXT: Per-async-task storage for query rendering
2424/// Each Axum async task (HTTP request) gets its own isolated context.
2525/// Multiple concurrent queries on same OS thread have zero interference.
26- ///
26+ ///
2727/// Why task_local instead of thread_local:
2828/// - thread_local: Shared by all async tasks on same OS thread (UNSAFE for concurrent queries)
2929/// - task_local: Each async task gets isolated storage (SAFE)
@@ -46,9 +46,9 @@ fn get_cte_column_from_context(cte_alias: &str, property: &str) -> Option<String
4646 // task_local! uses sync accessors, not .with()
4747 RENDER_CONTEXT_CTE_REGISTRY
4848 . try_with ( |ctx| {
49- ctx. borrow ( ) . as_ref ( ) . and_then ( |registry| {
50- registry . lookup ( cte_alias , property )
51- } )
49+ ctx. borrow ( )
50+ . as_ref ( )
51+ . and_then ( |registry| registry . lookup ( cte_alias , property ) )
5252 } )
5353 . ok ( )
5454 . flatten ( )
@@ -80,9 +80,7 @@ fn clear_render_context_cte_registry() {
8080// RELATIONSHIP COLUMNS CONTEXT (for IS NULL checks on relationship aliases)
8181// ============================================================================
8282
83- fn set_render_context_relationship_columns (
84- columns : HashMap < String , ( String , String ) > ,
85- ) {
83+ fn set_render_context_relationship_columns ( columns : HashMap < String , ( String , String ) > ) {
8684 let _ = RENDER_CONTEXT_RELATIONSHIP_COLUMNS . try_with ( |ctx| {
8785 ctx. borrow_mut ( ) . replace ( columns) ;
8886 } ) ;
@@ -109,9 +107,7 @@ fn clear_render_context_relationship_columns() {
109107// CTE PROPERTY MAPPINGS CONTEXT (for CTE property → column resolution)
110108// ============================================================================
111109
112- fn set_render_context_cte_property_mappings (
113- mappings : HashMap < String , HashMap < String , String > > ,
114- ) {
110+ fn set_render_context_cte_property_mappings ( mappings : HashMap < String , HashMap < String , String > > ) {
115111 let _ = RENDER_CONTEXT_CTE_PROPERTY_MAPPINGS . try_with ( |ctx| {
116112 ctx. borrow_mut ( ) . replace ( mappings) ;
117113 } ) ;
@@ -189,7 +185,7 @@ fn clear_all_render_contexts() {
189185/// TASK-LOCAL RENDER CONTEXT: Per-async-task storage for rendering state
190186/// Each Axum async task (HTTP request) gets its own isolated context.
191187/// Multiple concurrent queries on same OS thread have zero interference.
192- ///
188+ ///
193189/// Lifecycle:
194190/// 1. At render_to_sql() entry: Set all three contexts
195191/// 2. During rendering: Access contexts for property/column lookups
@@ -373,7 +369,7 @@ fn build_multi_type_vlp_aliases(plan: &RenderPlan) -> HashMap<String, String> {
373369fn populate_cte_property_mappings ( plan : & RenderPlan ) {
374370 let cte_mappings = build_cte_property_mappings ( plan) ;
375371 let multi_type_aliases = build_multi_type_vlp_aliases ( plan) ;
376-
372+
377373 set_render_context_cte_property_mappings ( cte_mappings) ;
378374 set_render_context_multi_type_vlp_aliases ( multi_type_aliases) ;
379375}
@@ -622,7 +618,9 @@ fn derive_cypher_property_name(db_column: &str) -> String {
622618
623619/// Extract fixed path information from a RenderPlan by analyzing SELECT items and JOINs
624620/// Returns FixedPathMetadata if the plan contains a path function call that can be resolved
625- fn extract_fixed_path_info_from_plan ( plan : & RenderPlan ) -> Option < crate :: render_plan:: FixedPathMetadata > {
621+ fn extract_fixed_path_info_from_plan (
622+ plan : & RenderPlan ,
623+ ) -> Option < crate :: render_plan:: FixedPathMetadata > {
626624 // Look for path function calls in SELECT items
627625 for item in & plan. select . items {
628626 if let Some ( path_var) = find_path_function_argument ( & item. expression ) {
@@ -632,14 +630,14 @@ fn extract_fixed_path_info_from_plan(plan: &RenderPlan) -> Option<crate::render_
632630 // For a path (a)-[:T1]->(b)-[:T2]->(c), we have 4 JOINs = 2 hops
633631 // Formula: hops = JOINs / 2 (integer division)
634632 let hop_count = plan. joins . 0 . len ( ) as u32 / 2 ;
635-
633+
636634 log:: info!(
637635 "🔧 Detected fixed path: path_variable={}, hop_count={} (from {} JOINs)" ,
638636 path_var,
639637 hop_count,
640638 plan. joins. 0 . len( )
641639 ) ;
642-
640+
643641 return Some ( crate :: render_plan:: FixedPathMetadata {
644642 path_variable : path_var,
645643 hop_count,
@@ -648,7 +646,7 @@ fn extract_fixed_path_info_from_plan(plan: &RenderPlan) -> Option<crate::render_
648646 } ) ;
649647 }
650648 }
651-
649+
652650 // Also check GROUP BY and ORDER BY expressions
653651 for expr in & plan. group_by . 0 {
654652 if let Some ( path_var) = find_path_function_argument ( expr) {
@@ -661,7 +659,7 @@ fn extract_fixed_path_info_from_plan(plan: &RenderPlan) -> Option<crate::render_
661659 } ) ;
662660 }
663661 }
664-
662+
665663 for item in & plan. order_by . 0 {
666664 if let Some ( path_var) = find_path_function_argument ( & item. expression ) {
667665 let hop_count = plan. joins . 0 . len ( ) as u32 / 2 ;
@@ -673,7 +671,7 @@ fn extract_fixed_path_info_from_plan(plan: &RenderPlan) -> Option<crate::render_
673671 } ) ;
674672 }
675673 }
676-
674+
677675 None
678676}
679677
@@ -683,14 +681,17 @@ fn find_path_function_argument(expr: &RenderExpr) -> Option<String> {
683681 match expr {
684682 RenderExpr :: ScalarFnCall ( func) => {
685683 // Check for path functions
686- if matches ! ( func. name. to_lowercase( ) . as_str( ) , "length" | "nodes" | "relationships" ) {
684+ if matches ! (
685+ func. name. to_lowercase( ) . as_str( ) ,
686+ "length" | "nodes" | "relationships"
687+ ) {
687688 if func. args . len ( ) == 1 {
688689 if let RenderExpr :: TableAlias ( alias) = & func. args [ 0 ] {
689690 return Some ( alias. 0 . clone ( ) ) ;
690691 }
691692 }
692693 }
693-
694+
694695 // Recursively check arguments
695696 for arg in & func. args {
696697 if let Some ( var) = find_path_function_argument ( arg) {
@@ -699,7 +700,7 @@ fn find_path_function_argument(expr: &RenderExpr) -> Option<String> {
699700 }
700701 None
701702 }
702-
703+
703704 RenderExpr :: OperatorApplicationExp ( op) => {
704705 for operand in & op. operands {
705706 if let Some ( var) = find_path_function_argument ( operand) {
@@ -708,7 +709,7 @@ fn find_path_function_argument(expr: &RenderExpr) -> Option<String> {
708709 }
709710 None
710711 }
711-
712+
712713 RenderExpr :: AggregateFnCall ( agg) => {
713714 for arg in & agg. args {
714715 if let Some ( var) = find_path_function_argument ( arg) {
@@ -717,7 +718,7 @@ fn find_path_function_argument(expr: &RenderExpr) -> Option<String> {
717718 }
718719 None
719720 }
720-
721+
721722 _ => None ,
722723 }
723724}
@@ -750,15 +751,22 @@ fn rewrite_fixed_path_functions(mut plan: RenderPlan) -> RenderPlan {
750751 }
751752
752753 // Also rewrite GROUP BY expressions
753- log:: info!( "🔧 Fixed path GROUP BY rewriting: {} items" , plan. group_by. 0 . len( ) ) ;
754+ log:: info!(
755+ "🔧 Fixed path GROUP BY rewriting: {} items" ,
756+ plan. group_by. 0 . len( )
757+ ) ;
754758 for group_expr in & mut plan. group_by . 0 {
755759 * group_expr = rewrite_expr_for_fixed_path ( group_expr, & path_var, hop_count) ;
756760 }
757761
758762 // Also rewrite ORDER BY expressions
759- log:: info!( "🔧 Fixed path ORDER BY rewriting: {} items" , plan. order_by. 0 . len( ) ) ;
763+ log:: info!(
764+ "🔧 Fixed path ORDER BY rewriting: {} items" ,
765+ plan. order_by. 0 . len( )
766+ ) ;
760767 for order_item in & mut plan. order_by . 0 {
761- order_item. expression = rewrite_expr_for_fixed_path ( & order_item. expression , & path_var, hop_count) ;
768+ order_item. expression =
769+ rewrite_expr_for_fixed_path ( & order_item. expression , & path_var, hop_count) ;
762770 }
763771 }
764772
@@ -1167,31 +1175,30 @@ impl SelectItems {
11671175 // 🔧 SCALAR FIX: ColumnAlias never gets `.*` expansion - it's a scalar column reference
11681176 // This handles: WITH n.email as group_key ... RETURN group_key
11691177 // where group_key is a scalar column, not a node/table
1170- let rendered_expr =
1171- if let RenderExpr :: ColumnAlias ( _) = & item. expression {
1172- // ColumnAlias is always rendered as-is (scalar reference)
1173- // No wildcard expansion: group_key stays group_key, not group_key.*
1174- item. expression . to_sql ( )
1175- } else if let RenderExpr :: TableAlias ( TableAlias ( alias_name) ) = & item. expression {
1176- if let Some ( col_alias) = & item. col_alias {
1177- if alias_name == & col_alias. 0 {
1178- // Check if this is an UNWIND alias - don't use `.*` for scalars
1179- if unwind_aliases. contains ( alias_name) {
1180- // UNWIND alias: render as just the alias (scalar value)
1181- alias_name. clone ( )
1182- } else {
1183- // Path/table alias: use `.*` expansion
1184- format ! ( "{}.*" , alias_name)
1185- }
1178+ let rendered_expr = if let RenderExpr :: ColumnAlias ( _) = & item. expression {
1179+ // ColumnAlias is always rendered as-is (scalar reference)
1180+ // No wildcard expansion: group_key stays group_key, not group_key.*
1181+ item. expression . to_sql ( )
1182+ } else if let RenderExpr :: TableAlias ( TableAlias ( alias_name) ) = & item. expression {
1183+ if let Some ( col_alias) = & item. col_alias {
1184+ if alias_name == & col_alias. 0 {
1185+ // Check if this is an UNWIND alias - don't use `.*` for scalars
1186+ if unwind_aliases. contains ( alias_name) {
1187+ // UNWIND alias: render as just the alias (scalar value)
1188+ alias_name. clone ( )
11861189 } else {
1187- item. expression . to_sql ( )
1190+ // Path/table alias: use `.*` expansion
1191+ format ! ( "{}.*" , alias_name)
11881192 }
11891193 } else {
11901194 item. expression . to_sql ( )
11911195 }
11921196 } else {
11931197 item. expression . to_sql ( )
1194- } ;
1198+ }
1199+ } else {
1200+ item. expression . to_sql ( )
1201+ } ;
11951202
11961203 sql. push_str ( & rendered_expr) ;
11971204
@@ -2055,10 +2062,18 @@ impl RenderExpr {
20552062 if is_multi_type_vlp_alias_from_context ( & table_alias. 0 ) {
20562063 log:: info!( "🎯 Found '{}' in multi-type VLP aliases!" , table_alias. 0 ) ;
20572064 // Properties like end_type, end_id, hop_count, path_relationships are direct CTE columns
2058- if col_name == VLP_START_ID_COLUMN || col_name == VLP_END_ID_COLUMN
2059- || matches ! ( col_name, "end_type" | "end_properties" | "hop_count" | "path_relationships" )
2065+ if col_name == VLP_START_ID_COLUMN
2066+ || col_name == VLP_END_ID_COLUMN
2067+ || matches ! (
2068+ col_name,
2069+ "end_type" | "end_properties" | "hop_count" | "path_relationships"
2070+ )
20602071 {
2061- log:: info!( "🎯 Multi-type VLP CTE column: {}.{}" , table_alias. 0 , col_name) ;
2072+ log:: info!(
2073+ "🎯 Multi-type VLP CTE column: {}.{}" ,
2074+ table_alias. 0 ,
2075+ col_name
2076+ ) ;
20622077 return format ! ( "{}.{}" , table_alias. 0 , col_name) ;
20632078 } else {
20642079 // Regular properties need JSON extraction from end_properties
@@ -2157,7 +2172,9 @@ impl RenderExpr {
21572172
21582173 // Look up the actual column name from the JOIN metadata (populated during rendering)
21592174 // This ensures we use the CORRECT column for the SPECIFIC relationship table
2160- if let Some ( ( from_id, _to_id) ) = get_relationship_columns_from_context ( table_alias) {
2175+ if let Some ( ( from_id, _to_id) ) =
2176+ get_relationship_columns_from_context ( table_alias)
2177+ {
21612178 // Use from_id - any ID column works since LEFT JOIN makes all NULL together
21622179 let id_sql = format ! ( "{}.{}" , table_alias, from_id) ;
21632180 return format ! ( "{} {}" , id_sql, op_str) ;
0 commit comments