Skip to content

Push Iceberg residual predicates on decimal columns into the native scan #6854

Description

@andygrove

What is the problem the feature request solves?

Comet's native Iceberg scan never passes iceberg-rust a predicate on a decimal column. IcebergReflection.pageIndexUnsupportedColumns lists every decimal, uuid, fixed and binary column. CometIcebergNativeScan.icebergExprToProto then drops any residual predicate that references one, including the IS NOT NULL that Iceberg adds for every filtered column.

Results stay correct. Iceberg's planning still prunes files on manifest statistics, and the filter runs after the scan. But the native reader never prunes row groups or pages on a decimal column. A selective decimal filter therefore reads every row group and page of every file that survives planning.

#4982 added the gate because iceberg-rust, at the pins before #6649, could not handle these columns in the page index:

None of these holds at the current pins:

The Scaladoc on pageIndexUnsupportedColumns still describes the behavior from before #6649.

Describe the potential solution

Push decimal predicates through to iceberg-rust:

  • CometIcebergNativeScan.predicateLiteralToProto:
    • Build IcebergLiteral.decimal_val for a DecimalType column, at the column's precision and scale.
    • If a literal can't be represented exactly at that scale, drop that predicate on its own. A literal that fails to bind would make iceberg-rust drop the whole residual.
  • iceberg_literal_to_datum in planner.rs: turn DecimalVal into a Datum with Datum::decimal_with_precision. iceberg-rust's Decimal is now fastnum::D128, so all 38 digits fit.
  • IcebergReflection.pageIndexUnsupportedColumns: stop listing decimals.
  • Update three comments that say decimal pushdown is deferred: the Scaladoc on pageIndexUnsupportedColumns, the doc comment on iceberg_literal_to_datum, and the comment on IcebergDecimal in operator.proto.

What works at which pin:

The other gated types:

  • uuid and fixed should stay in the list. iceberg-rust still skips their page indexes, and a uuid predicate's string literal doesn't bind to a uuid field.
  • binary could come out too, but there is no binary predicate literal, so only IS [NOT] NULL would gain anything.

Tests should compare against Spark with checkIcebergNativeScan. The data should be decimal columns at precisions 9, 18 and 38, written in order over many small pages (write.parquet.page-row-limit). Cover:

  • values on both sides of zero;
  • range, equality and IN predicates;
  • a literal at a different scale from the column.

A pruned page that held a match loses rows silently, so these comparisons are the main guard. A check that the scan actually prunes, for example on the scan's bytes_scanned metric, would show the change has an effect.

Additional context

Found while reviewing #6851. A probe on that branch wrote a sorted decimal(38, 2) column over 200 pages, from -10100.00 to 10098.99, and ran nine predicates through the native scan. All nine matched Spark. But they only exercised the filter that runs after the scan, because the gate kept the predicates away from iceberg-rust.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions