Skip to content

fix(spark): throw proper ParseExceptions in the six extended SQL AST... - #19460

Merged
voonhous merged 3 commits into
apache:masterfrom
voonhous:fix-extended-ast-builder-parse-exception
Aug 3, 2026
Merged

fix(spark): throw proper ParseExceptions in the six extended SQL AST...#19460
voonhous merged 3 commits into
apache:masterfrom
voonhous:fix-extended-ast-builder-parse-exception

Conversation

@voonhous

@voonhous voonhous commented Aug 2, 2026

Copy link
Copy Markdown
Member

Describe the issue this Pull Request addresses

Closes #19450

The six HoodieSpark*ExtendedSqlAstBuilders throw parse errors as new ParseException(<message>, ctx). On Spark 3.3 that constructor's String is the human message, but on Spark 3.4+ it is the error class, so every such site surfaces at runtime as SparkException: [INTERNAL_ERROR] Cannot find main error class '<msg>' instead of a clean ParseException. Messages containing 2+ dots (e.g. interpolated e.getMessage, or numeric ranges like -3.4028234663852886E+38) are even worse: they fail an assertion inside Spark's ErrorClassesJsonReader and throw a bare AssertionError with no diagnostic text at all. The three construct-then-setStackTrace sites also lost the original exception's stack trace on 3.4+, because the constructor threw before setStackTrace could run (a cause cannot be attached at all: ParseException never passes one to AnalysisException, and initCause is blocked once the null cause is committed).

Summary and Changelog

Extended-parser errors (BLOB/VECTOR DDL, index statements) now surface as a clean ParseException carrying the intended message on every supported Spark version.

Production changes:

  • Add a private parseException(message, ctx) helper to each of the six builders (3.3, 3.4, 3.5, 4.0, 4.1, 4.2) and route all 28 raw throw sites per builder through it, keeping call-site text identical across the six files to preserve diffability:
    • 3.3: the 2-arg constructor, which already takes the human message (behavior unchanged).
    • 3.4/3.5: the public message-based primary constructor (errorClass stays None), the same pattern HoodieSpark3_4/3_5ExtendedSqlParser already use.
    • 4.x: the (errorClass, messageParameters, ctx) constructor with _LEGACY_ERROR_TEMP_0035, the same class ParserUtils.operationNotAllowed uses, because the message-based constructor is private on Spark 4.x. A trailing period is stripped from the message so the template does not render a double period.
  • The three construct-then-throw sites keep their setStackTrace of the original cause's stack trace, which previously never ran on 3.4+.
  • Fix the unbraced interpolation in the transform error message: $nonRef.describe becomes ${nonRef.describe} (it printed the literal text .describe).
  • Mark the grammar-unreachable at least one time unit interval arm with a comment for the next pruning pass (a bare INTERVAL keyword binds to transformArgument's qualifiedName alternative first).

Test changes:

  • Add a shared interceptParse(sql)(expected) helper to ExtendedParserTestHelpers that asserts a clean ParseException plus a message substring. The previous checkExceptionContain-based assertions could not detect this bug class: they catch any Throwable and the pre-fix [INTERNAL_ERROR] text embeds the original message, so substring matching passed while every site was broken.
  • Convert the negative parser assertions in TestBlobDataType (invalid partition transforms, interval endpoints) and TestCreateTable (VECTOR without dimension, invalid VECTOR type, duplicated table paths, reserved table properties) to interceptParse.
  • Extend the invalid-partition-transform test with four new cases covering the previously untested visitor arms: typed literals (DATE 'nope'), invalid INTERVAL literals (the setStackTrace arm), out-of-range fractional literals (the pre-fix AssertionError mode), and mixed year-month/day-time interval fields. Coverage of the 28 converted sites goes from 3 to 15.

No code was copied; the builders themselves remain the pre-existing fork of Spark's AstBuilder.

Impact

No public API changes. User-facing error behavior on Spark 3.4+ profiles: extended-parser failures now raise ParseException with the intended message instead of SparkException [INTERNAL_ERROR] or AssertionError. On Spark 4.x profiles the message is rendered through the legacy error class as Operation not allowed: <message>.; on 3.3-3.5 messages are byte-identical to before. Known divergence from stock Spark: for the same SQL error, stock Spark answers with named error classes (e.g. INVALID_SQL_SYNTAX.INVALID_COLUMN_REFERENCE); those QueryParsingErrors helpers take Spark's own parser-context types, so they cannot be reused from Hudi's forked grammar contexts. These statements are only reachable through Hudi's extended parser. No performance impact (error paths only).

Risk Level

low. The change is confined to error-raising paths in the extended parsers. All six Spark profiles were compile-gated locally, hudi-spark was test-compiled under spark3.5 (Scala 2.12) and spark4.2 (Scala 2.13), and the modified suites were executed against the Spark 4.2 classpath (the riskiest variant, exercising the _LEGACY_ERROR_TEMP_0035 rendering end to end). If a future Spark release drops that legacy class, the interceptParse assertions fail loudly on the 4.x CI profiles rather than silently regressing.

Documentation Update

none

Contributor's checklist

  • Read through contributor's guide
  • Enough context is provided in the sections above
  • Adequate tests were added if applicable

…builders

On Spark 3.4+ the (String, ParserRuleContext) ParseException constructor
treats the string as an error class, so the builders' 28 raw throw sites
surfaced at runtime as SparkException [INTERNAL_ERROR] (or a bare
AssertionError for messages containing dots), and the three
construct-then-setStackTrace sites discarded the original cause. Route
all sites through a per-version parseException helper (message-based
constructor on 3.3-3.5, _LEGACY_ERROR_TEMP_0035 on 4.x where the message
constructor is private), fix the unbraced ${nonRef.describe}
interpolation, and tighten the invalid-partition-transform test to
intercept[ParseException].

Fixes apache#19450
@github-actions github-actions Bot added the size:L PR with lines of changes in (300, 1000] label Aug 2, 2026
@voonhous voonhous changed the title fix(spark): throw proper ParseExceptions in the six extended SQL AST … fix(spark): throw proper ParseExceptions in the six extended SQL AST builders Aug 2, 2026
@voonhous voonhous changed the title fix(spark): throw proper ParseExceptions in the six extended SQL AST builders fix(spark): throw proper ParseExceptions in the six extended SQL AST... Aug 2, 2026
@codecov-commenter

codecov-commenter commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.48276% with 27 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.91%. Comparing base (64afac0) to head (2cbbd13).

Files with missing lines Patch % Lines
...l/parser/HoodieSpark4_0ExtendedSqlAstBuilder.scala 68.96% 9 Missing ⚠️
...l/parser/HoodieSpark3_4ExtendedSqlAstBuilder.scala 86.20% 4 Missing ⚠️
...l/parser/HoodieSpark4_1ExtendedSqlAstBuilder.scala 86.20% 4 Missing ⚠️
...l/parser/HoodieSpark4_2ExtendedSqlAstBuilder.scala 86.20% 4 Missing ⚠️
...l/parser/HoodieSpark3_3ExtendedSqlAstBuilder.scala 89.65% 3 Missing ⚠️
...l/parser/HoodieSpark3_5ExtendedSqlAstBuilder.scala 89.65% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19460      +/-   ##
============================================
+ Coverage     76.81%   76.91%   +0.09%     
- Complexity    33770    33842      +72     
============================================
  Files          2576     2576              
  Lines        143457   143463       +6     
  Branches      17589    17589              
============================================
+ Hits         110201   110338     +137     
+ Misses        25010    24851     -159     
- Partials       8246     8274      +28     
Components Coverage Δ
hudi-common 82.27% <ø> (+<0.01%) ⬆️
hudi-client 81.82% <ø> (-0.03%) ⬇️
hudi-flink 84.03% <ø> (+0.06%) ⬆️
hudi-spark-datasource 74.76% <84.48%> (+0.53%) ⬆️
hudi-utilities 73.63% <ø> (-0.04%) ⬇️
hudi-cli 15.32% <ø> (ø)
hudi-hadoop 63.49% <ø> (ø)
hudi-sync 70.87% <ø> (ø)
hudi-io 79.60% <ø> (ø)
hudi-timeline-service 83.44% <ø> (-0.79%) ⬇️
hudi-cloud 64.00% <ø> (ø)
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 49.50% <0.00%> (-0.01%) ⬇️
flink-integration-tests 48.83% <ø> (+0.04%) ⬆️
hadoop-mr-java-client 43.41% <ø> (-0.01%) ⬇️
integration-tests 13.57% <0.00%> (-0.01%) ⬇️
spark-client-hadoop-common 48.67% <ø> (-0.01%) ⬇️
spark-java-tests 51.30% <3.44%> (-0.04%) ⬇️
spark-scala-tests 47.21% <84.48%> (+<0.01%) ⬆️
utilities 36.56% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...l/parser/HoodieSpark3_3ExtendedSqlAstBuilder.scala 69.26% <89.65%> (+4.81%) ⬆️
...l/parser/HoodieSpark3_5ExtendedSqlAstBuilder.scala 70.96% <89.65%> (+6.17%) ⬆️
...l/parser/HoodieSpark3_4ExtendedSqlAstBuilder.scala 68.25% <86.20%> (+6.17%) ⬆️
...l/parser/HoodieSpark4_1ExtendedSqlAstBuilder.scala 66.55% <86.20%> (+5.66%) ⬆️
...l/parser/HoodieSpark4_2ExtendedSqlAstBuilder.scala 66.55% <86.20%> (+5.66%) ⬆️
...l/parser/HoodieSpark4_0ExtendedSqlAstBuilder.scala 59.76% <68.96%> (-1.13%) ⬇️

... and 21 files with indirect coverage changes

🚀 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.

@hudi-agent hudi-agent 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.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for the contribution! This PR fixes the six HoodieSpark*ExtendedSqlAstBuilders so extended-parser errors (BLOB/VECTOR DDL, index statements) surface as a clean ParseException across every supported Spark version (3.3–4.2), by routing all raw throw sites through a per-version parseException helper and correcting an unbraced ${nonRef.describe} interpolation. I verified against the actual PR head that all six builders route every throw site through the helper (no un-converted two-arg new ParseException(message, ctx) sites remain), that the 3.4/3.5 helper matches the pattern already used by the existing extended parsers, and that the 4.x helper mirrors Spark's own operationNotAllowedError (correct legacy error class and message parameter). No issues flagged from this automated pass — a Hudi committer or PMC member can take it from here for a final review.

cc @yihua

…4.x helper

Add a shared interceptParse helper that asserts a clean ParseException plus
a message substring; the previous checkExceptionContain assertions catch any
Throwable, and the pre-fix [INTERNAL_ERROR] text embeds the original message,
so they passed while every site was broken. Convert the negative parser
assertions in TestBlobDataType and TestCreateTable, add four cases for the
previously untested visitor arms (typed literals, invalid INTERVAL, the
out-of-range AssertionError mode, mixed interval fields), pin the
${nonRef.describe} rendering, strip the trailing period in the 4.x helper
to avoid double periods, document the cross-profile prefix and the
_LEGACY_ERROR_TEMP_0035 coupling, and mark the grammar-unreachable interval
arm for the next pruning pass.
@voonhous

voonhous commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review, let me address the comments and fix the coverage issues before merging.

…view asserts

Address review comments: pin the interpolation fix by asserting the
absence of ".describe" in the column-reference message, and assert the
restored setStackTrace by checking for an IntervalUtils frame on the
invalid-INTERVAL case. Address the codecov patch-coverage gap with a new
negative test covering seven more converted throw sites (unsupported
typed literal, hex IllegalArgumentException fallback, both
single-from-to-unit arms, non-numeric unit value, non-string from-to
value, unsupported from-to pair); interceptParse now returns the
ParseException for follow-on assertions. The remaining uncovered sites
re-wrap Spark-utility exceptions whose types changed across 3.3-4.2
(e.g. over-precision decimals throw SparkArithmeticException on 4.x,
bypassing the AnalysisException catch), are grammar-unreachable, or need
ANTLR error-recovery trees.
@voonhous

voonhous commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Re the codecov patch-coverage gap: added Test parse CREATE TABLE with BLOB column and invalid literal transform arguments, covering seven more converted throw sites:

  • unsupported typed literal (FOO 'bar')
  • hex literal IllegalArgumentException fallback (X'zz')
  • both Can only have a single from-to unit arms
  • non-numeric unit value (INTERVAL 'x' DAY)
  • non-string from-to value (INTERVAL 1 DAY TO HOUR)
  • unsupported from-to pair (INTERVAL '1' MONTH TO HOUR)

The remaining uncovered converted lines are intentionally left:

  • the NumberFormatException/AnalysisException re-wrap arms: their triggering exceptions changed type across Spark versions (e.g. an over-precision decimal literal now throws SparkArithmeticException on 4.x, bypassing the AnalysisException catch entirely), so no portable SQL reaches them on every profile
  • the bare-INTERVAL arm: grammar-unreachable through Hudi's pruned grammar (now commented in the builders)
  • Invalid transform argument: requires an ANTLR error-recovery tree, not producible from well-formed SQL text

@voonhous

voonhous commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review and approval @wombatu-kun! Both comments are addressed in 2cbbd13:

  • the column-reference case now also asserts the absence of .describe (the positive substring alone could not discriminate, as you pointed out)
  • the restored setStackTrace is pinned on blob_e5 via an IntervalUtils stack-frame assert

The same commit closes most of the codecov patch gap (7 more throw sites covered, details in the comment above). Verified green locally on Spark 3.5 and 4.2; CI should confirm the rest of the matrix.

@hudi-bot

hudi-bot commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands @hudi-bot supports the following commands:
  • @hudi-bot run azure re-run the last Azure build

@voonhous

voonhous commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Let's merge this in first given that the coverage is > 80%

@voonhous
voonhous merged commit 365d0e4 into apache:master Aug 3, 2026
74 of 75 checks passed
@voonhous
voonhous deleted the fix-extended-ast-builder-parse-exception branch August 9, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L PR with lines of changes in (300, 1000]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(spark): extended AST builders pass messages as error classes to ParseException, surfacing INTERNAL_ERROR on Spark 3.4+

5 participants