From c421ebc4c0494c5576c8253f31f9436ecc50bb59 Mon Sep 17 00:00:00 2001 From: edubraqd Date: Thu, 3 Sep 2026 01:51:16 -0300 Subject: [PATCH 1/2] fix: honour datafusion.sql_parser.recursion_limit in datafusion-cli `exec_and_print` parsed statements with `DFParser::parse_sql_with_dialect`, which always uses the parser's built-in recursion limit. Setting `datafusion.sql_parser.recursion_limit` therefore had no effect in the CLI: > SET datafusion.sql_parser.recursion_limit = 100; > SELECT abs(abs(... 60 levels ...)); SQL error: RecursionLimitExceeded (current limit: 50) Build the parser with `DFParserBuilder` and the session's recursion limit, the same way `SessionState::sql_to_statement` does. Co-Authored-By: Claude Fable 5.1 --- datafusion-cli/src/exec.rs | 44 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/datafusion-cli/src/exec.rs b/datafusion-cli/src/exec.rs index 288ce4b7351b6..cf0dc02783b92 100644 --- a/datafusion-cli/src/exec.rs +++ b/datafusion-cli/src/exec.rs @@ -36,7 +36,7 @@ use datafusion::logical_expr::{DdlStatement, LogicalPlan}; use datafusion::physical_plan::execution_plan::EmissionType; use datafusion::physical_plan::spill::get_record_batch_memory_size; use datafusion::physical_plan::{ExecutionPlanProperties, execute_stream}; -use datafusion::sql::parser::{DFParser, Statement}; +use datafusion::sql::parser::{DFParser, DFParserBuilder, Statement}; use datafusion::sql::sqlparser; use datafusion::sql::sqlparser::dialect::dialect_from_str; use futures::StreamExt; @@ -222,7 +222,13 @@ pub(super) async fn exec_and_print( ) })?; - let statements = DFParser::parse_sql_with_dialect(&sql, dialect.as_ref())?; + // Honour `datafusion.sql_parser.recursion_limit` like `SessionState::sql_to_statement` + // does, instead of the parser's built-in default. + let statements = DFParserBuilder::new(sql.as_str()) + .with_dialect(dialect.as_ref()) + .with_recursion_limit(options.sql_parser.recursion_limit.get()) + .build()? + .parse_statements()?; for statement in statements { StatementExecutor::new(statement) .execute(ctx, print_options) @@ -524,6 +530,40 @@ mod tests { use datafusion_common::assert_contains; use url::Url; + /// `datafusion.sql_parser.recursion_limit` used to be ignored by the CLI: + /// statements were parsed with the parser's built-in default. + #[tokio::test] + async fn exec_and_print_honours_parser_recursion_limit() -> Result<()> { + use crate::object_storage::instrumented::InstrumentedObjectStoreRegistry; + use datafusion::prelude::SessionConfig; + use std::sync::Arc; + + // 60 nested calls exceed the parser's default recursion limit of 50. + let depth = 60; + let sql = format!("SELECT {}1{}", "abs(".repeat(depth), ")".repeat(depth)); + let print_options = PrintOptions { + format: PrintFormat::Automatic, + quiet: true, + maxrows: MaxRows::Unlimited, + color: false, + instrumented_registry: Arc::new(InstrumentedObjectStoreRegistry::new()), + }; + + let ctx = SessionContext::new(); + let err = exec_and_print(&ctx, &print_options, sql.clone()) + .await + .unwrap_err() + .to_string(); + assert_contains!(err, "RecursionLimitExceeded"); + + let config = + SessionConfig::new().set_usize("datafusion.sql_parser.recursion_limit", 100); + let ctx = SessionContext::new_with_config(config); + exec_and_print(&ctx, &print_options, sql).await?; + + Ok(()) + } + async fn create_external_table_test(location: &str, sql: &str) -> Result<()> { let ctx = SessionContext::new(); let plan = ctx.state().create_logical_plan(sql).await?; From 503ac50f7aba8d576063c2996b1a45eb1e79ed3d Mon Sep 17 00:00:00 2001 From: edubraqd Date: Thu, 3 Sep 2026 01:52:33 -0300 Subject: [PATCH 2/2] chore: import DFParser only where the tests still use it Co-Authored-By: Claude Fable 5.1 --- datafusion-cli/src/exec.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datafusion-cli/src/exec.rs b/datafusion-cli/src/exec.rs index cf0dc02783b92..80528dc6c1cec 100644 --- a/datafusion-cli/src/exec.rs +++ b/datafusion-cli/src/exec.rs @@ -36,7 +36,7 @@ use datafusion::logical_expr::{DdlStatement, LogicalPlan}; use datafusion::physical_plan::execution_plan::EmissionType; use datafusion::physical_plan::spill::get_record_batch_memory_size; use datafusion::physical_plan::{ExecutionPlanProperties, execute_stream}; -use datafusion::sql::parser::{DFParser, DFParserBuilder, Statement}; +use datafusion::sql::parser::{DFParserBuilder, Statement}; use datafusion::sql::sqlparser; use datafusion::sql::sqlparser::dialect::dialect_from_str; use futures::StreamExt; @@ -525,6 +525,7 @@ mod tests { use super::*; use datafusion::common::plan_err; + use datafusion::sql::parser::DFParser; use datafusion::prelude::SessionContext; use datafusion_common::assert_contains;