Skip to content

feat(stats): derive content_stats types and stats field IDs - #1128

Open
xndai wants to merge 1 commit into
apache:mainfrom
xndai:xndai/iceberg-cpp/S1
Open

xndai wants to merge 1 commit into
apache:mainfrom
xndai:xndai/iceberg-cpp/S1

Conversation

@xndai

@xndai xndai commented Nov 15, 2026 •

Copy link
Copy Markdown

Closes #1127. Part of #1107.

Adds ContentStatsUtil (src/iceberg/stats/content_stats_util.{h,cc}) for the v4 content_stats struct, following the spec's Content Stats section (apache/iceberg#14234; total_bytes from apache/iceberg#18308). BaseId, FieldId and StatOffset map between table field IDs and stats IDs (10_000 + 200 * field-id, and 9000/9200 for _last_updated_sequence_number/_row_id). StatsWriteType(schema, MetricsConfig) builds the struct a writer puts in manifests: bounds for truncate/full (geo_lower/geo_upper structs for geo types), the counts, nan_value_count for float/double, total_bytes for variable-length types, and no stats for fields in lists or maps. StatsReadType(schema, field_ids) builds the projection a reader asks for.

This ports Java's StatsUtil with one difference. The spec includes null_value_count for optional fields only, but Java writes it for every field since apache/iceberg#17413, and a required field inside an optional struct can still be null. The write type follows the spec, and the read type also projects the count for required fields, so stats written by Java are read.

Testing

New content_stats_util_test.cc (13 tests, in schema_test):

  • ID math: BaseId, FieldId and StatOffset round-trip every offset for several field IDs, including the last storable ID (999_949), plus the reserved _last_updated_sequence_number/_row_id ranges, the unassigned 9_400-9_999 (NotSupported) and out-of-range IDs (InvalidArgument).
  • Spec examples: the id/data and geometry location examples from the spec, compared field by field (IDs, names, types, docs), with geo_lower/geo_upper for geometry and geography.
  • Types and modes: every primitive type plus variant, required and optional (which stats are present and the bound types); none/counts/truncate/full and per-column overrides through MetricsConfig.
  • Schema shapes: nested structs (_-joined names), fields in lists and maps skipped, field IDs too large to store.
  • Read type: mode full, deduplicated and sorted, unknown IDs ignored, covers the write type; projected with Project() against Java-shaped stats (required null_value_count read) and spec-shaped stats (read as null).

cmake --build, the full ctest suite, pre-commit and clang-tidy 22.1.8 pass locally.

Add ContentStatsUtil for the v4 content_stats struct (spec section
"Content Stats"):

- stats ID math: base-id = 10_000 + 200 * field-id, the reserved
  9000/9200 bases for _last_updated_sequence_number and _row_id, and
  the reverse mapping from a stats ID to its field ID and offset;
- per-field stats structs with lower/upper bounds (geo_lower and
  geo_upper for geometry and geography), value_count,
  null_value_count, nan_value_count and total_bytes;
- StatsWriteType from a schema and MetricsConfig, and StatsReadType
  for a set of field IDs. Fields in lists or maps have no stats.

As the spec says, null_value_count is written for optional fields
only. The read type also projects it for required fields, because
Java writes it for every field (apache/iceberg#17413).

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.

Derive the v4 content_stats type and stats field IDs

1 participant