Skip to content

HIVE-29785: SkippingTextInputFormat: ClassCastException on skip.header.line.count files with lone-CR (\r) line endings - #6680

Open
srbiswal wants to merge 1 commit into
apache:masterfrom
srbiswal:HIVE-29785
Open

HIVE-29785: SkippingTextInputFormat: ClassCastException on skip.header.line.count files with lone-CR (\r) line endings#6680
srbiswal wants to merge 1 commit into
apache:masterfrom
srbiswal:HIVE-29785

Conversation

@srbiswal

@srbiswal srbiswal commented Aug 6, 2026

Copy link
Copy Markdown

For tables with skip.header.line.count/skip.footer.line.count, SkippingTextInputFormat called readLine() then getPos() on the same FSDataInputStream (in getCachedStartIndex and getCachedEndIndex). When the data contains a lone \r (not followed by \n), DataInputStream.readLine() pushes back its look-ahead byte by wrapping the stream in a non-Seekable PushbackInputStream. The following getPos() then throws ClassCastException during Tez split generation. LF, CRLF, and \r-at-EOF were unaffected.

Replace readLine()+getPos() in both methods with a ByteCountingLineReader that handles \n, \r\n, and lone \r, computing offsets from bytes consumed, so getPos() is never called on a mutated stream. Offset behavior is unchanged. As a safety net, makeSplitInternal now turns any unexpected error from header/footer detection into a clear, file-specific message instead of a raw ClassCastException

Does this PR introduce any user-facing change?

No

How was this patch tested?

Added unit tests in TestSkippingTextInputFormat:

  • testSkipFileSplitsLoneCR — reproduces the ClassCastException on a lone-CR file with skip.header.line.count=1 (fails on master, passes with this fix).
  • testSkipHeaderSplitOffsetsAcrossLineEndings — writes the same content with LF, lone-CR, and CRLF terminators and asserts exact split getStart()/getLength(), proving lone-CR matches LF and there is no boundary regression.
  • testSkipFileSplitsLoneCRHeaderFooter — exercises the footer path (getCachedEndIndex) with lone-CR and skip header/footer, verifying the header and footer rows are skipped.

…r.line.count files with lone-CR (\r) line endings

SkippingTextInputFormat.getCachedStartIndex (header path) and
getCachedEndIndex (footer path) called FSDataInputStream.readLine() and
then getPos() on the same stream. On a lone '\r' not followed by '\n',
DataInputStream.readLine() pushes its look-ahead byte back by replacing
the stream's inner input with a non-Seekable PushbackInputStream, so the
subsequent getPos() throws ClassCastException during Tez split generation.
LF, CRLF and '\r'-at-EOF were unaffected.

Replace readLine()+getPos() in both methods with a ByteCountingLineReader
that recognizes '\n', '\r\n' and lone '\r' and derives offsets from bytes
consumed, so getPos() is never called on a readLine()-mutated stream.
Offset semantics are preserved exactly. As a safety net, makeSplitInternal
now converts any unexpected RuntimeException from header/footer detection
into a clear, file-contextual error instead of a cryptic cast.

Adds tests: a lone-CR repro, exact split-offset assertions across LF/CR/CRLF
proving no boundary regression, and a footer-path lone-CR case.
@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants