Skip to content

[rust] Add V1 batch statistics collection - #4011

Open
leekeiabstraction wants to merge 4 commits into
apache:mainfrom
leekeiabstraction:rust-v1-statistics-standalone
Open

[rust] Add V1 batch statistics collection#4011
leekeiabstraction wants to merge 4 commits into
apache:mainfrom
leekeiabstraction:rust-v1-statistics-standalone

Conversation

@leekeiabstraction

Copy link
Copy Markdown
Contributor

Summary

  • Closes [rust] Batch statistics implementation #4008.
  • Adds the pieces a writer needs to produce V1 log record batch statistics: AlignedRowWriter for the min and max rows, record::statistics to reduce an Arrow batch to per-column bounds and null counts, and the table.statistics.columns plumbing that selects the columns.
  • Statistics are derived from the finished Arrow batch rather than row by row as Java does, so the row-append and append_arrow_batch paths are covered by one implementation.
  • Nothing emits them yet, so batches stay in the V0 format and behaviour is unchanged.

Test Plan

  • cargo test -p fluss-rs --lib: 646 passed, including 16 tests asserting AlignedRowWriter output byte by byte against Java's layout and 5 covering the statistics block.
  • cargo fmt --all --check and cargo clippy -p fluss-rs --all-targets clean.

🤖 AI-assisted changes - reviewed by human developer

@leekeiabstraction
leekeiabstraction force-pushed the rust-v1-statistics-standalone branch 2 times, most recently from 7595c4d to 4692a22 Compare August 16, 2026 18:12
Adds the pieces a writer needs to produce V1 log record batch statistics:
`AlignedRowWriter` for the min and max rows, `record::statistics` to reduce an
Arrow batch to per-column bounds and null counts, and the
`table.statistics.columns` plumbing that selects the columns. Nothing emits
them yet, so batches stay in the V0 format.
@leekeiabstraction
leekeiabstraction force-pushed the rust-v1-statistics-standalone branch from 4692a22 to 4bbfbd0 Compare August 16, 2026 18:36
@leekeiabstraction

Copy link
Copy Markdown
Contributor Author

@fresh-borzoni @charlesdong1991 Appreciate review here 🙏

@fresh-borzoni fresh-borzoni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leekeiabstraction Thank you for the PR, left a couple of comments, PTAL

let mut null_counts = Vec::with_capacity(mapping.len());
let mut bounds = Vec::with_capacity(mapping.len());
for &column_index in mapping {
let column = batch.column(column_index);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapping is checked against the table schema but not against the batch, and batch.column(i) panics out of range.

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.

Well spotted, added mapped index range checks. Note that check against table schema is done in column_bounds

///
/// # Errors
/// Returns an error if a named column is absent from the table schema.
pub fn get_stats_index_mapping(&self) -> Result<Vec<usize>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java rejects unsupported statistics columns at table creation, not at write time. Why do we prefer to differ here?

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.

Well spotted, I've made an error in the documentation. The implementation is already mirroring Java's TableInfo.getStatsIndexMapping() which is called at write time.

fn write_decimal(&mut self, value: &Decimal, precision: u32) {
let pos = self.current_pos;
if Decimal::is_compact_precision(precision) {
let unscaled = value.to_unscaled_long().unwrap_or(0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unwrap_or(0) turns a decimal that doesn't fit in i64 into a silent 0, which then ships as a min or max. Java throws here. Shall we propagate it or there is a reason for clamping?

@leekeiabstraction leekeiabstraction Aug 17, 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.

Well spotted, I've updated to mirror Java side client. Using panic instead of Result::Error due to existing signature of writers. Changing the signature or the reducing writer visibility to pub(crate) probably requires some discussion and is out of scope of this PR.

Bounds-check the statistics mapping against the batch rather than panicking on
a mismatch, and reject a decimal built at a precision other than the column's
instead of writing a silent zero bound.
The Arrow array holds seconds for TIME(0) while the statistics format is
always millis of day, so the bounds were a thousand times too small.
Adds tests for the Time64 micro and nano, timestamp second and nano, and non
compact decimal arms, which were the conversions still going unchecked.
@leekeiabstraction
leekeiabstraction force-pushed the rust-v1-statistics-standalone branch from 9962e3a to 0d7b031 Compare August 17, 2026 07:31
@leekeiabstraction

Copy link
Copy Markdown
Contributor Author

@fresh-borzoni TY for your review, I've addressed your comments and also push a separate fix to a bug I found. PTAL.

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.

[rust] Batch statistics implementation

2 participants