Skip to content

fix(cli): respect sql_parser.recursion_limit when parsing and validating input - #25807

Open
KassaSana wants to merge 2 commits into
apache:mainfrom
KassaSana:cli-recursion-limit
Open

KassaSana wants to merge 2 commits into
apache:mainfrom
KassaSana:cli-recursion-limit

Conversation

@KassaSana

@KassaSana KassaSana commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

datafusion.sql_parser.recursion_limit has no effect in datafusion-cli. After
SET datafusion.sql_parser.recursion_limit = 200 (or DATAFUSION_SQL_PARSER_RECURSION_LIMIT=200),
a query with 60 nested abs(...) calls still fails with
RecursionLimitExceeded (current limit: 51) via -c, -f and the interactive shell,
while the same query works through SessionContext::sql. Lowering the limit is ignored too.

The CLI parses statements itself instead of going through SessionState::sql_to_statement,
and only reads the dialect from the session config. There are two parse sites:

  • exec_and_print (-c, -f, rc files, and REPL execution)
  • CliHelper::validate_input, the REPL's rustyline validator, which rejects the line
    before it is executed

The earlier attempt in #24914 fixed only the first one, so the REPL stayed broken.

What changes are included in this PR?

  • exec_and_print parses with DFParserBuilder::with_recursion_limit using the session's
    limit, the same way SessionState::sql_to_statement does.
  • CliHelper gets a recursion_limit (default taken from SqlParserOptions) and a new
    set_recursion_limit method. exec_from_repl sets it when the REPL starts and after each
    statement, next to the existing set_dialect call. CliHelper::new is unchanged, so
    this is additive.

Not changed:

  • is_open_quote_for_location in helper.rs (tab completion for LOCATION '...) still
    uses the default parser. It already ignores the dialect, and a parse failure there only
    means no filename completion.
  • A single -c string or REPL line containing SET ...; <query> is parsed as a whole
    before the SET runs, so the new limit does not apply to the query in that same string.
    This matches how the dialect already behaves. Separate -c arguments, file lines, or
    REPL entries work.

Question for reviewers: I added a narrow set_recursion_limit. Would you prefer a
set_parser_options(&SqlParserOptions) that also replaces set_dialect, so future parser
options can't drift the same way?

Something I noticed and did not special-case: with a limit of 1 or 2, even a SET
statement fails to parse, so in the REPL you have to restart to recover (3 and above is
fine). SessionContext::sql behaves the same way; the CLI just used to ignore the setting.

What is the testing strategy for this PR?

  • New cli_quick_test case recursion_limit with tests/sql/recursion_limit.sql: raises
    the limit and runs a 60-deep query, then lowers it to 5 and runs a 10-deep query. On
    main the snapshot shows the reverse (the deep query fails at 51, the shallow one
    succeeds).
  • New helper.rs unit test sql_recursion_limit, modelled on sql_dialect, for the REPL
    validator.
  • Manually checked a debug build with -c, the env var, and the interactive shell (via
    script for a pty).
  • Manual pty check of the REPL after \i (rustyline needs a terminal, so this is not an
    sqllogictest): \i a file that sets the limit to 100, then run a 60-deep query. Before
    the follow-up commit the query was held as incomplete and never ran; after it, the query
    runs. A SET typed directly and the default-limit rejection behave as before.

Commands run:

cargo test -p datafusion-cli --lib helper::tests
cargo test -p datafusion-cli --test cli_integration cli_quick_test
cargo test -p datafusion-cli
cargo fmt --all
cargo clippy -p datafusion-cli --all-targets --all-features -- -D warnings

Are there any user-facing changes?

Yes: datafusion.sql_parser.recursion_limit is now respected by datafusion-cli.
The only API change is the new public CliHelper::set_recursion_limit method (additive).

…ating input

`datafusion-cli` parsed statements with the parser's built-in recursion
limit, so `datafusion.sql_parser.recursion_limit` had no effect in `-c`,
`-f`, or the REPL. Parse with the session's limit in `exec_and_print`,
and keep the limit used by the REPL's input validator in sync with the
session, like the dialect.

Closes apache#24913.
Copilot AI lite review requested due to automatic review settings September 27, 2026 16:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@KassaSana

Copy link
Copy Markdown
Contributor Author

Hi @kumarUjjawal, whenever you have a moment, could you (or another committer) approve the CI run for this one? Thanks!

@codecov-commenter

codecov-commenter commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.73%. Comparing base (f029b94) to head (91f61d5).
⚠️ Report is 149 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25807      +/-   ##
==========================================
+ Coverage   82.51%   82.73%   +0.21%     
==========================================
  Files        1141     1147       +6     
  Lines      439780   449509    +9729     
  Branches   439780   449509    +9729     
==========================================
+ Hits       362888   371882    +8994     
- Misses      54950    54951       +1     
- Partials    21942    22676     +734     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kumarUjjawal kumarUjjawal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @KassaSana for working on this.

Left one comment please take a look.

Comment thread datafusion-cli/src/exec.rs Outdated
Comment on lines +189 to +194
// dialect or recursion limit might have changed
let task_ctx = ctx.task_ctx();
let sql_parser = &task_ctx.session_config().options().sql_parser;
let helper = rl.helper_mut().unwrap();
helper.set_dialect(&sql_parser.dialect);
helper.set_recursion_limit(sql_parser.recursion_limit.get());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a file run with \i changes the recursion limit, the REPL helper keeps its previous limit. Command::Include runs SQL through exec_from_lines, but this refresh runs only in the SQL-input branch. After an included file raises the limit to 100, the validator still rejects a 60-deep query at the old limit of 51. Refresh the helper after backslash commands too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. You're right: the helper only got refreshed after SQL input, so a \i that raised the limit never reached it.

Fixed in 91f61d5. The sync now runs at the top of the REPL loop, right before each readline, so SQL, \i and the other backslash commands are all covered. I removed the old post-SQL refresh, since nothing reads the helper between there and the next read. The continue in the Ctrl-C arm went too, since clippy flagged it as redundant.

I reproduced it through a pty on the previous commit (\i setting the limit to 100, then a 60-deep query: held as incomplete, never ran). On the fix it runs. A directly typed SET and the default-limit rejection behave as before.

The REPL refreshed the input validator's dialect and recursion limit
only after SQL input. A `\i` file can change
`datafusion.sql_parser.recursion_limit` without going through that
path, so the validator kept the old limit and held valid input as
incomplete.

Refresh the validator before every read instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

datafusion-cli ignores datafusion.sql_parser.recursion_limit

4 participants