@@ -177,7 +177,20 @@ public CalciteRelNodeVisitor(DataSourceService dataSourceService) {
177177 }
178178
179179 public RelNode analyze (UnresolvedPlan unresolved , CalcitePlanContext context ) {
180- return unresolved .accept (this , context );
180+ // Enable filter accumulation if this plan contains multiple filtering operations
181+ // that could create deep Filter RelNode chains
182+ if (countFilteringOperations (unresolved ) >= 2 ) {
183+ context .enableFilterAccumulation ();
184+ try {
185+ unresolved .accept (this , context );
186+ context .flushFilterConditions (); // Flush accumulated conditions before returning
187+ return context .relBuilder .peek (); // Get the result after flushing
188+ } finally {
189+ context .disableFilterAccumulation ();
190+ }
191+ } else {
192+ return unresolved .accept (this , context );
193+ }
181194 }
182195
183196 @ Override
@@ -241,7 +254,12 @@ public RelNode visitFilter(Filter node, CalcitePlanContext context) {
241254 context .relBuilder .filter (ImmutableList .of (v .get ().id ), condition );
242255 context .popCorrelVar ();
243256 } else {
244- context .relBuilder .filter (condition );
257+ // Use filter accumulation to prevent deep Filter node chains
258+ if (context .isFilterAccumulationEnabled ()) {
259+ context .addFilterCondition (condition );
260+ } else {
261+ context .relBuilder .filter (condition );
262+ }
245263 }
246264 return context .relBuilder .peek ();
247265 }
@@ -290,7 +308,12 @@ public RelNode visitRegex(Regex node, CalcitePlanContext context) {
290308 regexCondition = context .rexBuilder .makeCall (SqlStdOperatorTable .NOT , regexCondition );
291309 }
292310
293- context .relBuilder .filter (regexCondition );
311+ // Use filter accumulation to prevent deep Filter node chains
312+ if (context .isFilterAccumulationEnabled ()) {
313+ context .addFilterCondition (regexCondition );
314+ } else {
315+ context .relBuilder .filter (regexCondition );
316+ }
294317 return context .relBuilder .peek ();
295318 }
296319
@@ -381,6 +404,11 @@ private boolean containsSubqueryExpression(Node expr) {
381404 public RelNode visitProject (Project node , CalcitePlanContext context ) {
382405 visitChildren (node , context );
383406
407+ // Flush accumulated filter conditions before schema-changing operations
408+ if (context .isFilterAccumulationEnabled () && context .hasPendingFilterConditions ()) {
409+ context .flushFilterConditions ();
410+ }
411+
384412 if (isSingleAllFieldsProject (node )) {
385413 return handleAllFieldsProject (node , context );
386414 }
@@ -3237,4 +3265,36 @@ private RexNode createOptimizedTransliteration(
32373265 throw new RuntimeException ("Failed to optimize sed expression: " + sedExpression , e );
32383266 }
32393267 }
3268+
3269+ /**
3270+ * Counts the number of filtering operations in an UnresolvedPlan tree that would create Filter
3271+ * RelNodes. This is used to detect queries with multiple regex/filter operations that could cause
3272+ * deep Filter RelNode chains and memory exhaustion.
3273+ *
3274+ * @param plan the UnresolvedPlan to analyze
3275+ * @return the count of filtering operations found
3276+ */
3277+ private int countFilteringOperations (UnresolvedPlan plan ) {
3278+ if (plan == null ) {
3279+ return 0 ;
3280+ }
3281+
3282+ int count = 0 ;
3283+
3284+ // Count this node if it's a filtering operation
3285+ if (plan instanceof Regex || plan instanceof Filter ) {
3286+ count = 1 ;
3287+ }
3288+
3289+ // Recursively count filtering operations in children
3290+ if (plan .getChild () != null ) {
3291+ for (Node child : plan .getChild ()) {
3292+ if (child instanceof UnresolvedPlan ) {
3293+ count += countFilteringOperations ((UnresolvedPlan ) child );
3294+ }
3295+ }
3296+ }
3297+
3298+ return count ;
3299+ }
32403300}
0 commit comments