Skip to content

[SPARK-59628][SQL] Reject ASOF JOIN without MATCH_CONDITION instead of a silent plain join - #59001

Open
LukaZdravic wants to merge 2 commits into
apache:masterfrom
LukaZdravic:asof-require-match-condition
Open

LukaZdravic wants to merge 2 commits into
apache:masterfrom
LukaZdravic:asof-require-match-condition

Conversation

@LukaZdravic

@LukaZdravic LukaZdravic commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Stop the parser from reading asof as a table alias:

  • Default mode: ASOF becomes strict-non-reserved.
  • ANSI keyword mode (spark.sql.ansi.enforceReservedKeywords=true): ASOF becomes reserved.

Why are the changes needed?

The parser read asof as the alias of the left table, so these queries became plain joins with no error:

SELECT * FROM t ASOF JOIN u USING (a)               -- no MATCH_CONDITION
SELECT * FROM t ASOF JOIN u MATCH_CONDITION (flag)  -- no comparison

Now the first fails with PARSE_SYNTAX_ERROR. The second fails with ASOF_JOIN_MATCH_CONDITION_INVALID_OPERATOR.

Does this PR introduce any user-facing change?

Yes. asof can no longer be an unquoted table alias, for example FROM t asof. That worked in Spark 4.2.0. In ANSI keyword mode, asof is reserved. Use `asof` instead.

How was this patch tested?

New tests in PlanParserSuite (both keyword modes) and join-asof-grammar.sql. Regenerated the golden files.

Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Code

…f a silent plain join

ASOF JOIN requires MATCH_CONDITION. Without it, and with no alias on the left
table, the parser read ASOF as that table's alias and built a plain INNER JOIN,
so the query ran and returned wrong results with no error.

Move ASOF into strictNonReserved so it can no longer be a table alias. The query
now fails at parse time. ASOF stays usable as a column and table name.
Copilot AI lite review requested due to automatic review settings September 23, 2026 15:50

Copilot AI left a comment

Copy link
Copy Markdown

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.

@LukaZdravic
LukaZdravic marked this pull request as draft September 24, 2026 09:03
With spark.sql.ansi.enforceReservedKeywords=true, the parser still read
ASOF as the alias of the left table, because ASOF was in
ansiNonReserved. So a missing MATCH_CONDITION still ran as a plain join,
and MATCH_CONDITION (flag) parsed as a join with no condition. Remove
ASOF from ansiNonReserved, so it is reserved in that mode.

Run the parser test in both keyword modes and pin the plans. Use a
USING golden case, which ran as a plain join before this change.

Co-authored-by: Isaac <no-reply@databricks.com>
@LukaZdravic
LukaZdravic marked this pull request as ready for review September 24, 2026 11:34
SELECT * FROM trades t ASOF JOIN quotes q ON t.symbol = q.symbol;

-- FVT-ASOF-1-020: missing MATCH_CONDITION with no left alias is rejected, not a plain join
SELECT * FROM trades ASOF JOIN quotes USING (symbol);

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. what about AS ASOF ?
  2. what about AS ASOF with MATCH_CONDITION?

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.

When ANSI is off, AS gets treated as an alias to the table because it's a non-reserved word, and that means
FROM trades AS ASOF JOIN quotes USING (symbol) this fails because it's an asof join

FROM trades AS ASOF JOIN quotes MATCH_CONDITION (trades.trade_time >= quotes.quote_time) USING(symbol) this fails because it expects the AS to be an alias, so no trades.trade_time allowed

When ANSI is on, AS is a reserved word so every attempt at writing AS ASOF is failing.

I should add goldens so we solidify this behaviour for someone else who may come in contact with this, what do you think?

|ASC|non-reserved|non-reserved|non-reserved|
|ASENSITIVE|non-reserved|non-reserved|non-reserved|
|ASOF|non-reserved|non-reserved|non-reserved|
|ASOF|reserved|strict-non-reserved|non-reserved|

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.

ANSI mode should probably match SQL-2016, like in ANTI case

@LukaZdravic LukaZdravic Sep 24, 2026 •

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.

ASOF cannot be non-reserved, because it can then be read as a table alias, same as the AS we talked about above. However ANTI that you mention has the same problem SELECT * FROM t ANTI JOIN u USING (k) this return the inner join rows not the anti join rows. Definitely worth a follow up, what do you think? Also probably an investigation as to what else can be written normally and have different behaviours

This branch has not been deployed

No deployments
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.

3 participants