@@ -12,198 +12,39 @@ use crate::{
1212 OrderByItems , OrderByOrder , RenderPlan , SelectItems , ToSql , UnionItems , UnionType ,
1313 } ,
1414 } ,
15+ server:: query_context:: {
16+ self , clear_all_render_contexts, get_cte_column_registry, get_cte_property_mapping,
17+ get_relationship_columns, is_multi_type_vlp_alias, set_all_render_contexts,
18+ } ,
1519} ;
16- use std:: cell:: RefCell ;
1720use std:: collections:: HashMap ;
1821
1922// Import function translator for Neo4j -> ClickHouse function mappings
2023use super :: function_registry:: get_function_mapping;
2124use super :: function_translator:: { get_ch_function_name, CH_PASSTHROUGH_PREFIX } ;
2225
23- /// TASK-LOCAL RENDER CONTEXT: Per-async-task storage for query rendering
24- /// Each Axum async task (HTTP request) gets its own isolated context.
25- /// Multiple concurrent queries on same OS thread have zero interference.
26- ///
27- /// Why task_local instead of thread_local:
28- /// - thread_local: Shared by all async tasks on same OS thread (UNSAFE for concurrent queries)
29- /// - task_local: Each async task gets isolated storage (SAFE)
30- ///
31- /// Usage:
32- /// 1. At query handler entry: RENDER_CONTEXT_REGISTRY.scope(registry, async { ... }).await
33- /// 2. During rendering: get_cte_column_from_context() accesses current task's registry
34- /// 3. When task completes: context automatically cleaned up
35- tokio:: task_local! {
36- /// Task-local CTE column registry: (cte_alias, cypher_property) → cte_output_column
37- /// Populated once per query at render_to_sql() entry
38- /// Isolated to current async task - no interference with concurrent queries
39- pub static RENDER_CONTEXT_CTE_REGISTRY : RefCell <Option <CteColumnRegistry >>;
40- }
41-
42- /// Retrieve CTE column for property within current render context (task-local)
43- /// Returns None if not in task context or property not found (safe fallback)
44- fn get_cte_column_from_context ( cte_alias : & str , property : & str ) -> Option < String > {
45- // Access the task-local context
46- // task_local! uses sync accessors, not .with()
47- RENDER_CONTEXT_CTE_REGISTRY
48- . try_with ( |ctx| {
49- ctx. borrow ( )
50- . as_ref ( )
51- . and_then ( |registry| registry. lookup ( cte_alias, property) )
52- } )
53- . ok ( )
54- . flatten ( )
55- }
56-
57- /// Set the CTE registry for the current async task
58- /// Call at render_to_sql() entry to make registry available to rendering code
59- fn set_render_context_cte_registry ( registry : CteColumnRegistry ) {
60- // task_local! doesn't support runtime initialization, so we use try_with
61- // If we're not in a task context, this returns an error which we ignore
62- let _ = RENDER_CONTEXT_CTE_REGISTRY . try_with ( |ctx| {
63- let prev = ctx. borrow_mut ( ) . replace ( registry) ;
64- if prev. is_some ( ) {
65- log:: warn!( "⚠️ Overwriting previous render context - possible re-entrancy" ) ;
66- }
67- } ) ;
68- }
69-
70- /// Clear the CTE registry for the current async task
71- /// Call at render_to_sql() exit to clean up
72- fn clear_render_context_cte_registry ( ) {
73- // task_local! sync cleanup
74- let _ = RENDER_CONTEXT_CTE_REGISTRY . try_with ( |ctx| {
75- ctx. borrow_mut ( ) . take ( ) ;
76- } ) ;
77- }
78-
7926// ============================================================================
80- // RELATIONSHIP COLUMNS CONTEXT (for IS NULL checks on relationship aliases )
27+ // RENDER CONTEXT ACCESSORS (delegating to unified query_context )
8128// ============================================================================
8229
83- fn set_render_context_relationship_columns ( columns : HashMap < String , ( String , String ) > ) {
84- let _ = RENDER_CONTEXT_RELATIONSHIP_COLUMNS . try_with ( |ctx| {
85- ctx. borrow_mut ( ) . replace ( columns) ;
86- } ) ;
30+ /// Retrieve CTE column for property within current render context
31+ fn get_cte_column_from_context ( cte_alias : & str , property : & str ) -> Option < String > {
32+ get_cte_column_registry ( ) . and_then ( |registry| registry. lookup ( cte_alias, property) )
8733}
8834
35+ /// Get relationship columns for IS NULL checks
8936fn get_relationship_columns_from_context ( alias : & str ) -> Option < ( String , String ) > {
90- RENDER_CONTEXT_RELATIONSHIP_COLUMNS
91- . try_with ( |ctx| {
92- ctx. borrow ( )
93- . as_ref ( )
94- . and_then ( |cols| cols. get ( alias) . cloned ( ) )
95- } )
96- . ok ( )
97- . flatten ( )
98- }
99-
100- fn clear_render_context_relationship_columns ( ) {
101- let _ = RENDER_CONTEXT_RELATIONSHIP_COLUMNS . try_with ( |ctx| {
102- ctx. borrow_mut ( ) . take ( ) ;
103- } ) ;
104- }
105-
106- // ============================================================================
107- // CTE PROPERTY MAPPINGS CONTEXT (for CTE property → column resolution)
108- // ============================================================================
109-
110- fn set_render_context_cte_property_mappings ( mappings : HashMap < String , HashMap < String , String > > ) {
111- let _ = RENDER_CONTEXT_CTE_PROPERTY_MAPPINGS . try_with ( |ctx| {
112- ctx. borrow_mut ( ) . replace ( mappings) ;
113- } ) ;
37+ get_relationship_columns ( alias)
11438}
11539
40+ /// Get CTE property mapping
11641fn get_cte_property_from_context ( cte_alias : & str , property : & str ) -> Option < String > {
117- RENDER_CONTEXT_CTE_PROPERTY_MAPPINGS
118- . try_with ( |ctx| {
119- ctx. borrow ( ) . as_ref ( ) . and_then ( |mappings| {
120- mappings
121- . get ( cte_alias)
122- . and_then ( |props| props. get ( property) . cloned ( ) )
123- } )
124- } )
125- . ok ( )
126- . flatten ( )
127- }
128-
129- fn clear_render_context_cte_property_mappings ( ) {
130- let _ = RENDER_CONTEXT_CTE_PROPERTY_MAPPINGS . try_with ( |ctx| {
131- ctx. borrow_mut ( ) . take ( ) ;
132- } ) ;
133- }
134-
135- // ============================================================================
136- // MULTI-TYPE VLP ALIASES CONTEXT (for multi-type VLP JSON property extraction)
137- // ============================================================================
138-
139- fn set_render_context_multi_type_vlp_aliases ( aliases : HashMap < String , String > ) {
140- let _ = RENDER_CONTEXT_MULTI_TYPE_VLP_ALIASES . try_with ( |ctx| {
141- ctx. borrow_mut ( ) . replace ( aliases) ;
142- } ) ;
42+ get_cte_property_mapping ( cte_alias, property)
14343}
14444
45+ /// Check if alias is a multi-type VLP endpoint
14546fn is_multi_type_vlp_alias_from_context ( alias : & str ) -> bool {
146- RENDER_CONTEXT_MULTI_TYPE_VLP_ALIASES
147- . try_with ( |ctx| {
148- ctx. borrow ( )
149- . as_ref ( )
150- . map ( |aliases| aliases. contains_key ( alias) )
151- . unwrap_or ( false )
152- } )
153- . unwrap_or ( false )
154- }
155-
156- fn clear_render_context_multi_type_vlp_aliases ( ) {
157- let _ = RENDER_CONTEXT_MULTI_TYPE_VLP_ALIASES . try_with ( |ctx| {
158- ctx. borrow_mut ( ) . take ( ) ;
159- } ) ;
160- }
161-
162- /// Set ALL render context for the current async task
163- /// Call at render_to_sql() entry
164- fn set_all_render_contexts (
165- cte_registry : CteColumnRegistry ,
166- relationship_columns : HashMap < String , ( String , String ) > ,
167- cte_mappings : HashMap < String , HashMap < String , String > > ,
168- multi_type_aliases : HashMap < String , String > ,
169- ) {
170- set_render_context_cte_registry ( cte_registry) ;
171- set_render_context_relationship_columns ( relationship_columns) ;
172- set_render_context_cte_property_mappings ( cte_mappings) ;
173- set_render_context_multi_type_vlp_aliases ( multi_type_aliases) ;
174- }
175-
176- /// Clear ALL render contexts for the current async task
177- /// Call at render_to_sql() exit (before final return)
178- fn clear_all_render_contexts ( ) {
179- clear_render_context_cte_registry ( ) ;
180- clear_render_context_relationship_columns ( ) ;
181- clear_render_context_cte_property_mappings ( ) ;
182- clear_render_context_multi_type_vlp_aliases ( ) ;
183- }
184-
185- /// TASK-LOCAL RENDER CONTEXT: Per-async-task storage for rendering state
186- /// Each Axum async task (HTTP request) gets its own isolated context.
187- /// Multiple concurrent queries on same OS thread have zero interference.
188- ///
189- /// Lifecycle:
190- /// 1. At render_to_sql() entry: Set all three contexts
191- /// 2. During rendering: Access contexts for property/column lookups
192- /// 3. At render_to_sql() exit: Clear all contexts before return
193- tokio:: task_local! {
194- /// Task-local mapping of relationship alias → (from_id_column, to_id_column)
195- /// Populated during JOIN rendering, used for IS NULL checks on relationship aliases
196- pub static RENDER_CONTEXT_RELATIONSHIP_COLUMNS : RefCell <Option <HashMap <String , ( String , String ) >>>;
197-
198- /// Task-local mapping of CTE alias → property mapping (Cypher property → CTE column name)
199- /// Example: "cnt_friend" → { "id" → "friend_id", "firstName" → "friend_firstName" }
200- /// Populated from RenderPlan CTEs during SQL generation
201- pub static RENDER_CONTEXT_CTE_PROPERTY_MAPPINGS : RefCell <Option <HashMap <String , HashMap <String , String >>>>;
202-
203- /// Task-local set of table aliases that are multi-type VLP endpoints
204- /// Example: "x" for query (u)-[:FOLLOWS|AUTHORED*1..2]->(x)
205- /// Properties on these aliases need JSON extraction from end_properties column
206- pub static RENDER_CONTEXT_MULTI_TYPE_VLP_ALIASES : RefCell <Option <HashMap <String , String >>>;
47+ is_multi_type_vlp_alias ( alias)
20748}
20849
20950/// Check if an expression contains a string literal (recursively for nested + operations)
@@ -270,10 +111,10 @@ fn build_relationship_columns_from_plan(plan: &RenderPlan) -> HashMap<String, (S
270111}
271112
272113/// Pre-populate the relationship columns mapping from a RenderPlan
273- /// This builds the mapping and sets it in the task-local context
114+ /// This builds the mapping and sets it in the query context
274115fn populate_relationship_columns_from_plan ( plan : & RenderPlan ) {
275116 let map = build_relationship_columns_from_plan ( plan) ;
276- set_render_context_relationship_columns ( map) ;
117+ query_context :: set_relationship_columns ( map) ;
277118}
278119
279120/// Build CTE property mappings from RenderPlan CTEs (for collecting data)
@@ -370,8 +211,8 @@ fn populate_cte_property_mappings(plan: &RenderPlan) {
370211 let cte_mappings = build_cte_property_mappings ( plan) ;
371212 let multi_type_aliases = build_multi_type_vlp_aliases ( plan) ;
372213
373- set_render_context_cte_property_mappings ( cte_mappings) ;
374- set_render_context_multi_type_vlp_aliases ( multi_type_aliases) ;
214+ query_context :: set_cte_property_mappings ( cte_mappings) ;
215+ query_context :: set_multi_type_vlp_aliases ( multi_type_aliases) ;
375216}
376217
377218/// Rewrite property access in SELECT, GROUP BY items for VLP queries
0 commit comments