Skip to content

[python] Keep reading an Avro file after a fully-filtered batch - #10128

Open
jackylee-ch wants to merge 2 commits into
apache:masterfrom
jackylee-ch:py-avro-filter-rows
Open

jackylee-ch wants to merge 2 commits into
apache:masterfrom
jackylee-ch:py-avro-filter-rows

Conversation

@jackylee-ch

Copy link
Copy Markdown
Contributor

Purpose

FormatAvroReader.read_arrow_batch reads the fastavro generator batch_size (default 1024) rows at a time and applies the pushed-down predicate in Python. When a whole batch matched nothing it returned None — but a RecordBatchReader returns None only at end of input, and ConcatBatchReader treats None as "reader exhausted" and advances to the next file.

So a filtered read of an Avro file with a full non-matching batch silently dropped every remaining row: an append-only file.format = avro table of 3000 rows read with user_id >= 2000 returns 0 rows instead of 1000 (the leading 1024-row block fails the predicate → None → EOF).

The reader now loops to the next batch on an empty filtered batch, returning None only when the generator is exhausted. A loop (not recursion) avoids a RecursionError on long filtered runs; the no-predicate path is unchanged. format_row_reader already handles this the same way.

Tests

reader_append_only_test.test_avro_ao_reader_filter_keeps_rows_past_first_batch: a 3000-row Avro table read with user_id >= 2000 asserts the 1000 matching rows. Fails on master ([]); passes here.


pb = table.new_read_builder().new_predicate_builder()
read_builder = table.new_read_builder().with_filter(
pb.greater_or_equal('user_id', 2000))

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.

Nit: With the default batch size of 1024, this threshold makes only the first batch fully filtered; the second batch already contains matching rows. Since the implementation intentionally uses a loop to support arbitrarily many consecutive empty batches, could we use  2500  here instead? With 3000 input rows, that would skip two complete batches before returning the final 500 rows and would distinguish the loop from an implementation that retries only once.

I think, the assertion should become:

 read_builder = table.new_read_builder().with_filter(
     pb.greater_or_equal('user_id', 2500))
 result = self._read_test_table(read_builder)
 
 self.assertEqual(
     sorted(result.column('user_id').to_pylist()), list(range(2500, n)))

FormatAvroReader.read_arrow_batch reads the fastavro generator batch_size rows
at a time and applies the pushed-down predicate to each batch in Python. When a
whole batch matched nothing it returned None, but a RecordBatchReader returns
None only at end of input, and ConcatBatchReader treats None as "reader
exhausted" and moves to the next file. So a filtered read of an Avro file that
had a full batch (1024 rows by default) of non-matching rows silently dropped
every remaining row.

Loop to the next batch when a filtered batch is empty instead of returning None;
return None only when the generator is exhausted. A loop rather than recursion
avoids a RecursionError on long runs of filtered-out rows. The no-predicate path
is unchanged.
Raise the filter threshold so a 3000-row file fully filters its first two
1024-row batches before matching, distinguishing the read loop from an
implementation that only retries a single empty batch.
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.

2 participants