{"record":{"id":"2b8fc70fe615abae","repo":"pola-rs/polars","slug":"sink-to-iceberg-table-with-partition-field-field","errorCode":null,"errorMessage":"sink to Iceberg table with partition field '{field.name}' on source column '{source_name}' with '{metrics_mode}' metrics; partition value inference requires lower and upper bounds","messagePattern":"sink to Iceberg table with partition field '(.+?)' on source column '(.+?)' with '(.+?)' metrics; partition value inference requires lower and upper bounds","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/io/iceberg/_sink.py","lineNumber":290,"sourceCode":"    exprs: list[pl.Expr] = []\n\n    for field in spec.fields:\n        source_field = schema.find_field(field.source_id)\n        statistics = statistics_plan.get(field.source_id)\n        if field.source_id not in nested_source_ids and (\n            statistics is None or statistics.mode.type not in bounds_metrics_modes\n        ):\n            source_name = schema.find_column_name(field.source_id)\n            metrics_mode = (\n                statistics.mode.type.value if statistics is not None else \"unavailable\"\n            )\n            msg = (\n                \"sink to Iceberg table with partition field \"\n                f\"'{field.name}' on source column '{source_name}' with \"\n                f\"'{metrics_mode}' metrics; partition value inference requires \"\n                \"lower and upper bounds\"\n            )\n            raise NotImplementedError(msg)\n\n        source_type = source_field.field_type\n        transform = field.transform\n        expr = _partition_source_expr(schema, field.source_id)\n\n        if isinstance(transform, IdentityTransform):\n            pass\n        elif isinstance(\n            transform, (YearTransform, MonthTransform, DayTransform, HourTransform)\n        ):\n            if type(source_type).__name__ in {\n                \"TimestamptzType\",\n                \"TimestamptzNanoType\",\n            }:\n                expr = expr.dt.convert_time_zone(\"UTC\")\n\n            if isinstance(transform, YearTransform):\n                expr = expr.dt.year() - 1970","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/pola-rs/polars/blob/fc24390824009e8fc5b7870e256273c1b140a019/py-polars/src/polars/io/iceberg/_sink.py#L272-L308","documentation":"The Iceberg sink derives partition values from Parquet lower/upper bound statistics. This error means the partition field's source column was written with a metrics mode (e.g. `none` or truncated metrics) that omits the lower/upper bounds needed to infer the partition value. It is raised as NotImplementedError because the sink does not support that configuration combination.","triggerScenarios":"Sinking to a partitioned Iceberg table when the table's metrics config (`write.metrics.default` / per-column `write.metrics.<column>`) for the partition source column is set to a mode without min/max bounds (e.g. `none`), while partition value inference is required.","commonSituations":"Tables created with `write.metrics.default=none` for performance; per-column metrics tuned down for wide tables accidentally applied to a partition key column; inheriting a table created by another engine with restrictive metrics settings.","solutions":["Change the table property so the partition source column collects full metrics: set `write.metrics.<column>=full` (or `write.metrics.default=truncate/full`) on the Iceberg table","Recreate the table with default metrics (`full`/`truncate` at sufficient length) if you can control the schema/metadata","Sink to a non-partitioned table, or pre-write the partition key as a top-level column with metrics enabled","Check `table.properties` via pyiceberg before sinking to confirm the metric mode for the source column"],"exampleFix":"// before: metrics disabled for the partition source column\ntable.properties['write.metrics.category'] = 'none'\n// after: require full metrics for the partition key column\ntable.properties['write.metrics.category'] = 'full'\nlf.sink_iceberg(table)","handlingStrategy":"validation","validationCode":"for field in table.spec().fields:\n    col = table.metadata.current_schema.find_field(field.source_id).name\n    mode = table.properties.get(f'write.metrics.{col}', table.properties.get('write.metrics.default', 'truncate'))\n    if mode == 'none':\n        raise ValueError(f\"metrics mode '{mode}' for partition source column '{col}' prevents partition value inference\")","typeGuard":"def metrics_allow_bounds(table, source_name: str) -> bool:\n    mode = table.properties.get(f'write.metrics.{source_name}', table.properties.get('write.metrics.default'))\n    return mode != 'none'","tryCatchPattern":"try:\n    lf.sink_iceberg(table)\nexcept NotImplementedError as e:\n    if 'partition value inference requires lower and upper bounds' in str(e):\n        raise RuntimeError(\"set write.metrics.<partition_col>=full on the table before sinking\") from e\n    raise","preventionTips":["Audit `table.properties` for write.metrics.* settings, especially per-column overrides on partition key columns","Avoid `write.metrics.default=none` on tables you intend to sink to with polars","Set `write.metrics.<column>=full` for every partition source column"],"tags":["iceberg","partitioning","metrics-mode","configuration","polars"],"backgroundTag":"missing-parquet-statistics","analyzedSha":"fc24390824009e8fc5b7870e256273c1b140a019","analyzedAt":"2026-09-02T20:08:03.494Z","contentChangedAt":"2026-09-02T20:08:03.494Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}