Skip to content

Navigation Menu

Sign in
Sign up

feat(datafusion): support ANALYZE TABLE on catalog-managed format tables - #815

Draft
sundapeng wants to merge 7 commits into
apache:main from
sundapeng:feat/format-table-analyze
Draft

feat(datafusion): support ANALYZE TABLE on catalog-managed format tables #815
sundapeng wants to merge 7 commits into
apache:main from
sundapeng:feat/format-table-analyze

Conversation

@sundapeng

@sundapeng sundapeng commented Sep 11, 2026
edited
Loading

Copy link
Copy Markdown
Member

Purpose

Child of #591, which stays open as a draft that shows the full feature. A Format Table loaded from a REST catalog with metastore.partitioned-table=true has its partitions registered in the catalog, and the catalog can hold statistics for each of them: record count, file count, size and last file creation time. The Rust client had no way to measure a partition and report them. This adds ANALYZE TABLE for such tables, following the Java PaimonAnalyzeFormatTablePartitionsCommand and FormatTablePartitionStatsCollector.

Depends on #817 , which depends on #816. Until they merge, this branch includes their five commits; please review only the last two: feat(datafusion): support ANALYZE TABLE on catalog-managed format tables and docs(datafusion): document ANALYZE TABLE for format tables.

This PR was rebased onto the restructured stack. ANALYZE itself is unchanged: its tests now use the shared helpers of #816, and a value in PARTITION (...) is read by the shared parse_format_partition_value that #816 introduces for partition literals.

Brief change log

  • ANALYZE TABLE t [PARTITION (...)] COMPUTE STATISTICS [NOSCAN] on a Format Table with catalog-managed partitions measures the registered partitions from storage and reports them through Catalog::create_partitions_with_statistics with replaceStatistics=true, so each measured field replaces what the catalog holds. It never adds or removes a partition.
    • NOSCAN reports the file count, total size and latest file modification time, and leaves the row count unknown (-1).
    • A full ANALYZE also reads Parquet and ORC footers for row counts. A footer that cannot be read leaves the row count of its partition unknown rather than short, a partition without files holds exactly zero rows, and other formats report an unknown row count.
    • PARTITION (...) selects a leading run of partition values; a column named without a value means every value of it. A prefix with no registered partition is an error.
    • Refused: FOR COLUMNS, CACHE METADATA, a missing COMPUTE STATISTICS, tables whose partitions the catalog does not manage, an empty or whitespace-only string in PARTITION (...) for a string column (it would name the default partition, as ADD and DROP in feat(datafusion): add SHOW, ADD and DROP PARTITION for catalog-managed format tables #816 refuse), and any selected partition at a custom location.
    • format-table.statistics.parallelism (a session setting, SET 'paimon.<key>', as in Java; default 8, at least 1) bounds the listings and footer reads in flight.
  • FormatTablePartitionStatsCollector, public in paimon::table, does the measuring. A listing failure fails the whole collection, since a truncated listing cannot be told apart from a partition that lost files.
  • The Format Table scan's file listing is factored into list_format_table_data_files, which the collector shares, so a measurement counts exactly the files a scan of that partition reads and leaves committer staging trees out (the rule from fix(table): skip staging files and list format table partitions concurrently #813 ). The scan's behaviour is unchanged.
  • read_file_row_count reads the row count of a Parquet or ORC file from its footer without decoding rows.

Follow-up: using the reported row counts in DataFusion scan statistics, as apache/paimon#9351 does for Spark.

Tests

  • crates/integrations/datafusion/tests/rest_format_partition_sql.rs:
    • NOSCAN and full measurement replacing what the catalog holds, and a later NOSCAN keeping the known row counts;
    • PARTITION selecting a leading run of values, and its errors;
    • a partition value read as its column type;
    • staging trees, markers, hidden files and non-data files left out by both ANALYZE and a scan;
    • an unreadable footer leaving the row count unknown;
    • refusals: FOR COLUMNS, a blank string partition value, a partition at a custom location, a non-positive parallelism read as one, and a table without catalog-managed partitions.
  • format_table_scan::tests::test_data_file_listing_returns_what_a_partition_scan_reads for the shared listing.
  • The compile-time check that the future of SQLContext::sql stays Send now covers the new statement too.

Commands run on the top commit:

cargo fmt --all -- --check
cargo clippy --locked --all-targets --workspace --features fulltext,vortex -- -D warnings
cargo test --locked -p paimon --all-targets --features fulltext,vortex
cargo test --locked --no-fail-fast -p paimon-datafusion --all-targets
cargo test --locked -p paimon-rest-server --all-targets

fmt and clippy pass. paimon: 2967 tests pass. paimon-rest-server: 10 tests pass. paimon-datafusion: 780 pass, including the 15 tests in rest_format_partition_sql.rs, and 39 fail. The failures are the 39 tests that also fail by name on main in this environment, because they read fixture tables provisioned by make docker-up (two also need the lumina native library).

Additions without a direct Java counterpart

  • read_file_row_count reads a Parquet or ORC file's row count from its footer. Java counts rows with the format's SimpleStatsExtractor, which the Rust client does not have for Format Tables.
  • A full ANALYZE measures in process with bounded concurrency, where Spark spreads the footer reads over executors (PaimonAnalyzeFormatTablePartitionsCommand.measureOnExecutors).

API and Format

  • New public paimon::table::FormatTablePartitionStatsCollector.
  • New option format-table.statistics.parallelism, read by SQLContext.

The storage format is unchanged.

Documentation

docs/src/sql.md gains an ANALYZE TABLE subsection under Format Table Partitions and lists ANALYZE TABLE in the SQL support scope.

...d format tables
A Format Table whose partitions a REST catalog manages is read from its
registrations, but DataFusion could not change them. This adds the
statements that do:
- SHOW PARTITIONS [PARTITION (...)] lists the registrations in escaped
 key=value form, read with the column types and optionally filtered by
 any subset of partition values.
- ALTER TABLE ... ADD [IF NOT EXISTS] PARTITION (...) registers complete
 specs, then creates their directories.
- ALTER TABLE ... DROP [IF EXISTS] PARTITION (...) takes several
 specifications, expands a partial one to every registered partition it
 matches and compares values as the catalog holds them, so `month = 1`
 does not select `month=01`, as Java resolveFormatTablePartitionsForDrop
 does. Complete specifications are looked up by name; a partial one reads
 the registry once. Registrations go first and directories after; a
 partition at a custom location is only unregistered.
Partition literals are read with the column type through the parser that
reads registrations and directories, the way Java casts partition strings,
so a BOOLEAN value accepts t/true/y/yes/1 and their false counterparts.
Catalog gains drop_partitions. RESTCatalog refuses it for a table without
catalog-managed partitions, since the endpoint only removes metadata and
would leave the data of any other table in place while reporting success.
...bles
Describe catalog-managed Format Table partitions in the SQL guide: the
metastore.partitioned-table prerequisite, SHOW / ADD / DROP PARTITION, how
partition values are read, what each statement does to registrations and
directories, and how a partition at a custom location is treated.
... PARTITION
A blank string for a string partition column is formatted as the default
partition name, so DROP PARTITION (dt = '') unregistered the NULL
partition and deleted its directory, and ADD PARTITION (dt = '')
registered it.
Refuse an empty or whitespace-only string for a string partition column in
ADD and DROP PARTITION, as Java
PaimonFormatTable.requireNameablePartitionValues does. SHOW PARTITIONS
still accepts it as a filter.
...bles
A catalog-managed Format Table reads only the partitions its catalog
registers, so a partition directory written without a registration stays
invisible, and a registration whose directory is gone points at nothing.
MSCK REPAIR TABLE [{ADD|DROP|SYNC} PARTITIONS] reconciles the two:
- ADD, the default, registers every discovered partition the catalog does
 not hold; DROP unregisters every registration without a directory; SYNC
 does both. Nothing on storage is created or deleted.
- Directory discovery and the catalog listing both finish before any
 change, and DROP and SYNC fail on a table directory that cannot be
 listed rather than reading it as empty.
- Partitions are matched by escaped name and registered with the values
 as their directories spell them, so month=01 stays month=01.
- A partition registered at a custom location is never unregistered,
 since its data does not live under the table directory.
FormatTablePartitionPaths gains discover, which skips hidden directories
and segments outside the configured layout, and fails on a matching
segment that is not canonically escaped. unescape_path_name moves from
the scan to spec::partition_utils, beside escape_path_name, so discovery
and the scan share it.
Describe the ADD, DROP and SYNC modes, that repair only changes
registrations, that discovery and the catalog listing finish before any
change, and that a partition at a custom location is never unregistered.
Split out of apache#591. A Format Table whose partitions the REST catalog
manages had no way to report what those partitions hold.
ANALYZE TABLE t [PARTITION (...)] COMPUTE STATISTICS [NOSCAN] measures the
registered partitions from storage and reports the result through
create_partitions_with_statistics with replaceStatistics, as Java
PaimonAnalyzeFormatTablePartitionsCommand does:
- NOSCAN stops at the listing: file count, byte size and the latest file
 modification time. A full ANALYZE also reads Parquet and ORC footers
 for row counts; a footer that cannot be read leaves the partition's row
 count unknown rather than short, and an empty partition holds exactly
 zero rows.
- PARTITION (...) selects a leading run of partition values. A prefix
 with no registered partition, a partition at a custom location, FOR
 COLUMNS and CACHE METADATA are refused, as is a table whose partitions
 the catalog does not manage.
- format-table.statistics.parallelism (default 8) bounds the listings and
 footer reads in flight.
The collector lists each partition through the same helper the scan now
uses, so a measurement counts exactly the files a scan of that partition
reads and leaves committer staging trees out. Its streams own their items
so that the future of every SQLContext statement stays Send.
Describe what NOSCAN and a full ANALYZE measure, how PARTITION selects
partitions, what is refused, and format-table.statistics.parallelism.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /