danielcweeks commented on code in PR #14234:
URL: https://github.com/apache/iceberg/pull/14234#discussion_r3237588423
##########
format/spec.md:
##########
@@ -704,11 +727,133 @@ Examples of valid field paths using normalized JSON path
format are:
* `$['tags']` -- the `tags` array
* `$['addresses']['zip']` -- the `zip` field in an `addresses` array that
contains objects
-For `geometry` and `geography` types, `lower_bounds` and `upper_bounds` are
both points of the following coordinates X, Y, Z, and M (see Appendix G) which
are the lower / upper bound of all objects in the file.
+##### Content Stats
-For `geography` only, xmin (X value of `lower_bounds`) may be greater than
xmax (X value of `upper_bounds`), in which case an object in this bounding box
may match if it contains an X such that x >= xmin OR x <= xmax. In geographic
terminology, the concepts of xmin, xmax, ymin, and ymax are also known as
westernmost, easternmost, southernmost and northernmost, respectively. These
points are further restricted to the canonical ranges of [-180..180] for X and
[-90..90] for Y.
+In Iceberg v4, statistics are stored in typed fields grouped in a struct that
corresponds to the table field. These stats structs are nested within the
`content_stats` struct in manifest files.
-When calculating upper and lower bounds for `geometry` and `geography`, null
or NaN values in a coordinate dimension are skipped; for example, POINT (1 NaN)
contributes a value to X but no values to Y, Z, or M dimension bounds. If a
dimension has only null or NaN values, that dimension is omitted from the
bounding box. If either the X or Y dimension is missing then the bounding box
itself is not produced.
+###### Field Statistics
+
+Field-level structs in `content_stats` are based on the corresponding table
field's type, requirement, and ID (`field-id`).
+
+Field stats structs are assigned a range of 200 IDs, starting at `10_000 + 200
* field-id`. The first ID in the range (`base-id`) is the ID of the struct
field in `content_stats`. Fields within the stats struct are assigned IDs from
the range by adding an offset to the `base-id`. For example, the stats struct
for table field 2 uses IDs in the range `[10_400, 10_599]`, the field within
`content_stats` uses the `base-id`, ID `10_400`, and its `lower_bound` field
(offset 1) uses ID `10_401`.
+
+Content stats must be resolved by ID; field names used for stats structs are
informational. The recommended name for each field is the full name of the
field in the table schema.
+
+IDs in the range `10_000` (inclusive) to `200_000_000` (exclusive) are
reserved for column stats structs in `content_stats`. Stats for table fields
with stats IDs outside that range cannot be stored in `content_stats`.
+
+[Reserved metadata fields](#reserved-field-ids) must use the stats ID ranges
from the following table. Stats for metadata fields not in the table are not
tracked.
+
+| Reserved field | ID | `base-id` | Range end |
+|---------------------------------|------------|-----------|-----------|
+| `_last_updated_sequence_number` | 2147483539 | 9000 | 9199 |
+| `_row_id` | 2147483540 | 9200 | 9399 |
+
+Each stats struct holds statistics for one table field. It may contain the
following metrics:
+
+| Requirement | Offset | Name | Type
| Included for | Description |
+|-------------|--------|---------------------------|---------------------------|-----------------------------------------------|-------------|
+| _optional_ | 1 | `lower_bound` | Field type or `geo_lower`
| all primitives or `variant` | Lower bound stored as the
field's type, or `geo_lower` for geo types |
+| _optional_ | 2 | `upper_bound` | Field type or `geo_upper`
| all primitives or `variant` | Upper bound stored as the
field's type, or `geo_upper` for geo types |
+| _optional_ | 3 | `tight_bounds` | `boolean`
| all except `geometry`, `geography`, `variant` | When true, `lower_bound` and
`upper_bound` must be equal to the min and max values |
+| _optional_ | 4 | `value_count` | `long`
| all | Number of values in the
column (including null and NaN values) |
+| _optional_ | 5 | `null_value_count` | `long`
| optional fields | Number of null values in the
column |
+| _optional_ | 6 | `nan_value_count` | `long`
| `float`, `double` | Number of NaN values in the
column |
+| _optional_ | 7 | `avg_value_size_in_bytes` | `int`
| `string`, `binary`, `variant` | Avg value size (uncompressed)
in bytes to estimate memory consumption |
+
+For example, stats for a `required` `int` field named `id` with field-id `2`
are stored using:
+
+```
+10_400: optional struct id (default null) {
+ 10_401: optional int lower_bound; // type matches the field type (int)
+ 10_402: optional int upper_bound; // type matches the field type (int)
+ 10_403: optional boolean tight_bounds;
+ 10_404: optional long value_count;
+
+ // null_value_count is only used for optional fields
+ // nan_value_count is only used for float and double
+ // avg_value_size_in_bytes is only used for variable length types
+}
+```
+
+If any field is missing from the struct, readers must assume that it is
unknown.
+
+Lower and upper bounds for `geometry` and `geography` columns are XYZM points
that define a bounding box, stored in `geo_lower` and `geo_upper` structs (see
[Bounds for Geometry and Geography](#bounds-for-geometry-and-geography)). IDs
used by geo structs are assigned using offsets in the table field's stats ID
range.
+
+The `geo_lower` struct is defined as:
+
+| Requirement | Offset | Name | Type | Description |
+|-------------|--------|------|----------|-------------|
+| _required_ | 10 | `x` | `double` | Bounding box westernmost/xmin;
[-180..180] |
+| _required_ | 11 | `y` | `double` | Bounding box southernmost/ymin;
[-90..90] |
+| _optional_ | 12 | `z` | `double` | Bounding box zmin |
+| _optional_ | 13 | `m` | `double` | Bounding box mmin |
+
+The `geo_upper` struct is defined as:
+
+| Requirement | Offset | Name | Type | Description |
+|-------------|--------|------|----------|-------------|
+| _required_ | 14 | `x` | `double` | Bounding box easternmost/xmax;
[-180..180] |
+| _required_ | 15 | `y` | `double` | Bounding box northernmost/ymax;
[-90..90] |
+| _optional_ | 16 | `z` | `double` | Bounding box zmax |
+| _optional_ | 17 | `m` | `double` | Bounding box mmax |
+
+For example, stats for an optional `geometry` field named `location` with
field-id `4` are stored as:
+
+```
+10_800: optional struct location (default null) {
+ 10_801: optional struct lower_bound (default null) { // geo_lower
+ 10_810: required double x;
+ 10_811: required double y;
+ 10_812: optional double z;
+ 10_813: optional double m;
+ }
+ 10_802: optional struct upper_bound (default null) { // geo_upper
+ 10_814: required double x;
+ 10_815: required double y;
+ 10_816: optional double z;
+ 10_817: optional double m;
+ }
+ 10_804: optional long value_count;
+ 10_805: optional long null_value_count;
+ // tight_bounds / nan_value_count / avg_value_size_in_bytes are not stored
for geo types
+}
+```
+
+For `variant`, both bounds are unshredded `variant` that store variant field
bounds by normalized JSON paths as field names. See [Bounds for
Variant](#bounds-for-variant) for details on producing these bounds.
+
+###### Content Stats in Manifests
+
+Manifest files are written using a specific `content_stats` struct type,
determined by the writer and incorporated into the manifest schema. All
field-level structs are optional fields in the `content_stats` struct.
Review Comment:
```suggestion
Manifest files are written using a specific `content_stats` struct type per
field, determined by the writer and incorporated into the manifest schema. All
field-level structs are optional fields in the `content_stats` struct.
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]