@@ -125,6 +125,43 @@ pub fn evaluate_query(
125125 )
126126}
127127
128+ /// Helper function to detect empty or filtered UNION branches
129+ ///
130+ /// Track C filters branches to 0 types, but creates different "empty" representations:
131+ /// - Explicit: LogicalPlan::Empty (for nodes filtered to 0 types)
132+ /// - Implicit: GraphRel{labels: None} (for relationships filtered to 0 types)
133+ ///
134+ /// This function detects both forms and recursively checks wrapped plans.
135+ fn is_empty_or_filtered_branch ( plan : & LogicalPlan ) -> bool {
136+ match plan {
137+ // Explicit empty
138+ LogicalPlan :: Empty => true ,
139+
140+ // Implicit empty: relationship filtered to 0 types by Track C
141+ LogicalPlan :: GraphRel ( rel) if rel. labels . is_none ( ) => {
142+ log:: debug!(
143+ "Detected filtered GraphRel (labels=None) for alias '{}'" ,
144+ rel. alias
145+ ) ;
146+ true
147+ }
148+
149+ // Check if wrapped plan contains Empty
150+ LogicalPlan :: GraphNode ( node) => {
151+ matches ! ( node. input. as_ref( ) , LogicalPlan :: Empty )
152+ }
153+
154+ // Recursively check wrapped plans (common UNION branch structures)
155+ LogicalPlan :: Projection ( proj) => is_empty_or_filtered_branch ( & proj. input ) ,
156+ LogicalPlan :: Filter ( f) => is_empty_or_filtered_branch ( & f. input ) ,
157+ LogicalPlan :: GraphJoins ( joins) => is_empty_or_filtered_branch ( & joins. input ) ,
158+ LogicalPlan :: Limit ( limit) => is_empty_or_filtered_branch ( & limit. input ) ,
159+
160+ // Not empty
161+ _ => false ,
162+ }
163+ }
164+
128165/// Evaluate a complete Cypher statement which may contain UNION clauses
129166pub fn evaluate_cypher_statement (
130167 statement : CypherStatement < ' _ > ,
@@ -192,18 +229,47 @@ pub fn evaluate_cypher_statement(
192229 view_parameter_values. clone ( ) ,
193230 max_inferred_types,
194231 ) ?;
195- all_plans. push ( plan) ;
196- // Merge the context from this union branch into combined context
197- if let Some ( ref mut combined) = combined_ctx {
198- combined. merge ( ctx) ;
232+
233+ // Track C Property Optimization: Skip empty branches
234+ // When Track C filters a branch to 0 matching types, detect and skip it
235+ // This handles both explicit Empty and implicit GraphRel{labels: None}
236+ if !is_empty_or_filtered_branch ( plan. as_ref ( ) ) {
237+ all_plans. push ( plan) ;
238+ // Merge the context from this union branch into combined context
239+ if let Some ( ref mut combined) = combined_ctx {
240+ combined. merge ( ctx) ;
241+ }
242+ } else {
243+ log:: info!(
244+ "🔀 UNION branch filtered to 0 types by Track C - skipping empty branch"
245+ ) ;
199246 }
200247 }
201248
202- // Create Union logical plan
203- let union_plan = Arc :: new ( LogicalPlan :: Union ( Union {
204- inputs : all_plans,
205- union_type,
206- } ) ) ;
249+ // Handle different scenarios based on non-empty branch count
250+ let union_plan = match all_plans. len ( ) {
251+ 0 => {
252+ // All branches filtered to 0 types - return empty result
253+ log:: info!( "🔀 All UNION branches empty - returning Empty plan (0 rows)" ) ;
254+ Arc :: new ( LogicalPlan :: Empty )
255+ }
256+ 1 => {
257+ // Only one branch has data - no UNION needed
258+ log:: info!( "🔀 Only 1 non-empty UNION branch - skipping UNION wrapper" ) ;
259+ all_plans. into_iter ( ) . next ( ) . unwrap ( )
260+ }
261+ _ => {
262+ // Multiple branches with data - create UNION
263+ log:: info!(
264+ "🔀 Creating UNION with {} non-empty branches" ,
265+ all_plans. len( )
266+ ) ;
267+ Arc :: new ( LogicalPlan :: Union ( Union {
268+ inputs : all_plans,
269+ union_type,
270+ } ) )
271+ }
272+ } ;
207273
208274 let final_ctx = combined_ctx. ok_or_else ( || {
209275 LogicalPlanError :: QueryPlanningError (
0 commit comments