Skip to content

Don't assert-crash the analyzer on a non-lambda -> operator - #8399

Merged
peterbud merged 3 commits into
sqlfluff:mainfrom
winklemad:fix/lambda-arrow-assert-non-lambda
Sep 7, 2026
Merged

peterbud merged 3 commits into
sqlfluff:mainfrom
winklemad:fix/lambda-arrow-assert-non-lambda

Conversation

@winklemad

@winklemad winklemad commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #8398

_get_lambda_argument_columns treated every -> as a lambda arrow and did assert len(argument_segments) == 1. When -> is used as a binary/JSON-arrow operator instead — e.g. 1::json -> 'a' on duckdb/trino, where the left operand is a cast rather than a single lambda parameter — argument_segments is empty and the hard assert fires. Because it runs during get_select_statement_info, BaseRule.crawl catches it and every affected rule (AL04, AL05, AM04, RF01, RF02, RF03, ST05) reports an "Unexpected exception" on otherwise-valid SQL.

This replaces the assert with a guard: when the segments before -> aren't exactly one lambda parameter, it isn't a lambda, so it contributes no lambda argument columns and we skip it instead of crashing. Genuine lambdas (x -> x + 1, (x, y) -> x + y) are unaffected.

Added RF02 fixture cases for 1::json -> 'a' on duckdb and trino; they fail on main with the AssertionError and pass with this change. The existing analysis/rule suites (including the lambda cases) stay green.

AI usage

I used an AI assistant (Claude Code) to help navigate the analysis code and draft the fix and fixtures. I found and reproduced the crash myself on sqlfluff 4.3.0 (the duckdb/trino 1::json -> 'a' case, and confirmed databricks does not trigger it), verified the root cause is the lambda-arrow assert over-matching a binary ->, and confirmed the new cases fail on main and pass after while the lambda tests still pass. I've reviewed every line and stand by it.


Summary by cubic

Prevents the analyzer from assert-crashing when -> is a binary/JSON operator (e.g., 1::json -> 'a' on duckdb/trino) instead of a lambda arrow, and stops it from misclassifying JSON extraction as a lambda.

Bug Fixes

  • Classifies lambdas by segment type: duckdb, trino, and snowflake parse a real lambda as a lambda_function holding a lambda_arrow, so a plain -> binary operator in those dialects is never a lambda.
  • athena, sparksql, and databricks parse lambdas as an ordinary expression and keep matching on the binary_operator.
  • Keeps a count check as a guard so an unexpected shape — including a non-parameter left operand — returns no columns instead of raising.
  • Adds RF02 fixtures for duckdb/trino JSON extraction and for lambda parameters staying standalone, including a case exercising the guard.

Written for commit e39bade. Summary will update on new commits.

Review in cubic

_get_lambda_argument_columns treated every `->` as a lambda arrow and
asserted that exactly one lambda-parameter segment preceded it. In
dialects that reuse `->` as a binary operator (duckdb/trino JSON
extraction, e.g. `1::json -> 'a'`) the left operand isn't a lambda
parameter, so the assert fired and surfaced as an 'Unexpected exception'
on AL04/AL05/AM04/RF01/RF02/RF03/ST05.

Skip the operator when the pre-arrow segments aren't a single lambda
parameter instead of asserting; genuine lambdas are unaffected.

@cubic-dev-ai cubic-dev-ai Bot 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.

No issues found across 2 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@github-actions

github-actions Bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Results ✅

Name    Stmts   Miss  Cover   Missing
-------------------------------------
TOTAL   23084      0   100%

270 files skipped due to complete coverage.

@peterbud peterbud 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.

Hi @winklemad , thanks for this contribution.

The fix prevents the crash, but still misclassifies DuckDB JSON expressions like payload -> 'a' as lambdas when the left operand is a single column. This causes RF02 false negatives in multi-table queries. Please distinguish lambdas using the parsed segment types rather than operand count, and add a plain-column JSON regression case.

Counting the segments before the arrow still classified a JSON extraction
like `payload -> 'a'` as a lambda whenever the left operand happened to be
a single column, which made RF02 miss the unqualified reference.

duckdb, trino and snowflake parse a real lambda as a lambda_function
holding a lambda_arrow, so a plain `->` binary operator in those dialects
is never a lambda. athena, sparksql and databricks parse lambdas as an
ordinary expression and have no JSON arrow, so they keep matching on the
binary operator. The count check stays as a guard so an unexpected shape
returns no columns rather than raising.
@winklemad

Copy link
Copy Markdown
Contributor Author

Good catch, you're right — counting operands still called it a lambda whenever the left side happened to be one column, so RF02 quietly stopped flagging payload there.

Switched to segment types in c928bf7. I checked how each of the six dialects actually parses this:

dialect lambda parses as -> JSON
duckdb, trino, snowflake lambda_function / lambda_arrow expression / binary_operator
athena, sparksql, databricks expression / binary_operator n/a

So for the first group a bare -> binary operator is never a lambda and gets skipped; the second group has no JSON arrow, so matching the binary operator there is still right. The length check stays purely as a guard so an unexpected shape returns nothing instead of raising.

Added the plain-column case you asked for as fail_str for duckdb and trino, both of which fail on the previous commit, plus two cases pinning that lambda parameters are still treated as standalone. test/rules and test/utils are green (2697 passed).

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 2 files (changes from recent commits).

Confidence score: 5/5

  • In test/fixtures/rules/std_rule_cases/RF02.yml, the two fail_str fixtures are masked by the unrelated unqualified n violation, so they can pass without testing the behavior described in their comments; revise the queries to isolate the intended rule condition.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/fixtures/rules/std_rule_cases/RF02.yml">

<violation number="1" location="test/fixtures/rules/std_rule_cases/RF02.yml:214">
P3: These two fail_str fixtures don't test what their comments claim. In `select list_filter(t1.l, x -> x > n) from t1 join t2 ...`, the unrelated unqualified column `n` always produces a violation, so the test passes whether or not `x` is correctly recognized as a lambda parameter. If a future change to `_get_lambda_argument_columns` stops recognizing real lambdas, `x` gets reported as unqualified but `assert_rule_fail_in_sql` still passes because `n` (line 3) is flagged. The comment says "must not be reported," but a fail_str only asserts that at least one violation exists; it can't assert which column is flagged. Make these fixtures sensitive to the invariant, e.g. scope them so the only possible violation is the lambda parameter, or assert the exact violation set/line numbers rather than relying on the always-present `n`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

core:
dialect: trino

test_lambda_parameters_are_still_standalone_duckdb:

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.

P3: These two fail_str fixtures don't test what their comments claim. In select list_filter(t1.l, x -> x > n) from t1 join t2 ..., the unrelated unqualified column n always produces a violation, so the test passes whether or not x is correctly recognized as a lambda parameter. If a future change to _get_lambda_argument_columns stops recognizing real lambdas, x gets reported as unqualified but assert_rule_fail_in_sql still passes because n (line 3) is flagged. The comment says "must not be reported," but a fail_str only asserts that at least one violation exists; it can't assert which column is flagged. Make these fixtures sensitive to the invariant, e.g. scope them so the only possible violation is the lambda parameter, or assert the exact violation set/line numbers rather than relying on the always-present n.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/fixtures/rules/std_rule_cases/RF02.yml, line 214:

<comment>These two fail_str fixtures don't test what their comments claim. In `select list_filter(t1.l, x -> x > n) from t1 join t2 ...`, the unrelated unqualified column `n` always produces a violation, so the test passes whether or not `x` is correctly recognized as a lambda parameter. If a future change to `_get_lambda_argument_columns` stops recognizing real lambdas, `x` gets reported as unqualified but `assert_rule_fail_in_sql` still passes because `n` (line 3) is flagged. The comment says "must not be reported," but a fail_str only asserts that at least one violation exists; it can't assert which column is flagged. Make these fixtures sensitive to the invariant, e.g. scope them so the only possible violation is the lambda parameter, or assert the exact violation set/line numbers rather than relying on the always-present `n`.</comment>

<file context>
@@ -195,6 +195,37 @@ test_json_arrow_operator_is_not_a_lambda_trino:
+    core:
+      dialect: trino
+
+test_lambda_parameters_are_still_standalone_duckdb:
+  # The lambda parameter `x` is declared in place and must not be reported.
+  fail_str: |
</file context>

Add an RF02 case where the left operand of `->` is a function call rather
than a single lambda parameter, exercising the guard that skips such a
shape instead of crashing. Keeps the module at 100% line coverage.

@peterbud peterbud 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.

Thanks @winklemad

@peterbud
peterbud added this pull request to the merge queue Sep 7, 2026
Merged via the queue into sqlfluff:main with commit cc9b54e Sep 7, 2026
50 checks passed
@winklemad

Copy link
Copy Markdown
Contributor Author

Thanks @peterbud — and good catch on the RF02 false-negative; switching to segment types made it more robust than the operand-count check I started with.

benfdking pushed a commit to quarylabs/sqruff that referenced this pull request Oct 28, 2026
…399)

## Summary
- Port SQLFluff #8399: `get_lambda_argument_columns` no longer asserts on the
  shape of the segment before a `->`. In dialects with a dedicated lambda
  function (duckdb, trino, snowflake) a bare `->` is JSON extraction, not a
  lambda, so those expressions are skipped; dialects that model lambdas as a
  plain `->` expression (athena, sparksql, databricks) keep the old path. An
  unrecognised parameter list now contributes no columns instead of crashing.
- Port the RF02 fixture cases, omitting the two upstream trino JSON cases:
  sqruff's trino dialect does not yet parse the `->` JSON extraction operator,
  so the duckdb equivalents (cast operand, plain-column operand) cover the same
  analyzer paths.

Ported from SQLFluff cc9b54e47a44b6f644b6777626b46269a68f121e
sqlfluff/sqlfluff#8399
sqlfluff/sqlfluff@cc9b54e

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01A9tYxcHmKFezu3vWeSHAbv
benfdking pushed a commit to quarylabs/sqruff that referenced this pull request Oct 28, 2026
…399)

## Summary
- Port SQLFluff #8399: `get_lambda_argument_columns` no longer asserts on the
  shape of the segment before a `->`. In dialects with a dedicated lambda
  function (duckdb, trino, snowflake) a bare `->` is JSON extraction, not a
  lambda, so those expressions are skipped; dialects that model lambdas as a
  plain `->` expression (athena, sparksql, databricks) keep the old path. An
  unrecognised parameter list now contributes no columns instead of crashing.
- Port the RF02 fixture cases, omitting the two upstream trino JSON cases:
  sqruff's trino dialect does not yet parse the `->` JSON extraction operator,
  so the duckdb equivalents (cast operand, plain-column operand) cover the same
  analyzer paths.

Ported from SQLFluff cc9b54e47a44b6f644b6777626b46269a68f121e
sqlfluff/sqlfluff#8399
sqlfluff/sqlfluff@cc9b54e

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01A9tYxcHmKFezu3vWeSHAbv
benfdking pushed a commit to quarylabs/sqruff that referenced this pull request Oct 28, 2026
…399)

## Summary
- Port SQLFluff #8399: `get_lambda_argument_columns` no longer asserts on the
  shape of the segment before a `->`. In dialects with a dedicated lambda
  function (duckdb, trino, snowflake) a bare `->` is JSON extraction, not a
  lambda, so those expressions are skipped; dialects that model lambdas as a
  plain `->` expression (athena, sparksql, databricks) keep the old path. An
  unrecognised parameter list now contributes no columns instead of crashing.
- Port the RF02 fixture cases, omitting the two upstream trino JSON cases:
  sqruff's trino dialect does not yet parse the `->` JSON extraction operator,
  so the duckdb equivalents (cast operand, plain-column operand) cover the same
  analyzer paths.

Ported from SQLFluff cc9b54e47a44b6f644b6777626b46269a68f121e
sqlfluff/sqlfluff#8399
sqlfluff/sqlfluff@cc9b54e

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01A9tYxcHmKFezu3vWeSHAbv
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.

Analyzer crashes with AssertionError on a non-lambda -> operator (duckdb/trino JSON arrow)

2 participants