Skip to content

feat(catalog-rest): wire TableScan to REST scan planning - #3012

Open
daviddallakyan2005 wants to merge 6 commits into
apache:mainfrom
daviddallakyan2005:rest-scan-tablescan
Open

feat(catalog-rest): wire TableScan to REST scan planning#3012
daviddallakyan2005 wants to merge 6 commits into
apache:mainfrom
daviddallakyan2005:rest-scan-tablescan

Conversation

@daviddallakyan2005

@daviddallakyan2005 daviddallakyan2005 commented Aug 16, 2026

Copy link
Copy Markdown

Which issue does this PR close?

Stacked on #3011 (slices 1 to 3: REST plan / fetch / wait client). This is slice 4: wire TableScan::plan_files and decode REST content-file JSON.

What changes are included in this PR?

ScanPlanner and ScanPlanningMode::{Auto, Local, Remote} live in the iceberg crate. The Catalog trait is unchanged. Default mode is Local (Go ScanPlanningLocal, Java CLIENT) so existing table.scan().plan_files() does not POST /plan just because the server advertised endpoints.

RestCatalog implements ScanPlanner and injects the already initialized catalog (shared HTTP client) onto tables it loads. clone_uninitialized stays only on PlanAbandonGuard.

REST ContentFile.content is decoded as OpenAPI strings (data, position-deletes, equality-deletes) and as Avro ordinals 0/1/2. Data files may omit content. Delete files require a valid delete content type.

delete-file-references are 0-based into that response's delete-files. Each ScanTasks payload is decoded on its own, then FileScanTasks are appended.

key-metadata accepts OpenAPI uppercase hex text (and JSON byte arrays without truncating as u8).

Auto remotes only when supports_remote_scan_planning() is true and table LoadTable/config (or metadata) scan-planning-mode is server (case-insensitive). Missing key is client. Explicit client plus Remote is FeatureUnsupported. Capability probe supports_remote_scan_planning stays true when all four endpoints are advertised.

Remote plan POST sets use-snapshot-schema=true whenever a snapshot id is sent, so time travel plans against the snapshot schema. OpenAPI defaults the flag to false (current table schema).

Out of scope (as agreed on #1690): DataFusion (#2671), plan-scoped FileIO / vended credentials (#2651 / #2932). Filter JSON on the wire is omitted (no ExpressionParser yet). The scan's bound predicate is stamped onto each task.

Remote Auto/Remote materializes the full FileScanTask list (plan-task handles expanded sequentially) before the stream yields, unlike local concurrent manifest streaming, and does not send the scan filter so the server cannot prune files. The planner reuses the live catalog client; typed content-file decode does not clone a JSON DOM per file.

Are these changes tested?

Unit tests with mockito (no docker / iceberg-rest-fixture). "content": "data" and "content": "position-deletes" decode; integer 0/1 still work. Hex "key-metadata": "00000000000000000000000000000000" becomes 16 zero bytes. A delete file missing content is DataInvalid. Plan-task page h1 with delete-file-references: [0] and its own delete-files resolves to that page's file, not a decoy from the parent payload. Default plan_files with four endpoints advertised does not POST /plan. scan-planning-mode=client plus Auto does not POST /plan. scan-planning-mode=server plus Auto does POST /plan. After load_table plus Auto with no plan endpoints, GET /v1/config is expected once. Remote plan POST matches use-snapshot-schema: true and the table's current snapshot id.

How to test

cargo test -p iceberg --lib
cargo test -p iceberg-catalog-rest --lib
cargo clippy -p iceberg -p iceberg-catalog-rest --all-targets --all-features -- -D warnings
cargo fmt -p iceberg -p iceberg-catalog-rest -- --check

Local results on 6233b7c: iceberg lib 1555 passed; iceberg-catalog-rest lib 131 passed; clippy -D warnings clean; rustfmt --check clean. cargo public-api -p iceberg-catalog-rest --all-features -ss updated public-api.txt for typed RestContentFile. cargo build -p iceberg-datafusion compiles (no DataFusion source changes). --all-targets for that crate hit disk full while linking its test binary.

AI Disclosure

https://iceberg.apache.org/contribute/#guidelines-for-ai-assisted-contributions

This change was drafted with an AI coding assistant. I reviewed the Go PlanFiles / ScanPlanner design, Java scan-planning-mode and use-snapshot-schema, OpenAPI ContentFile, the maintainer review on this PR, and the resulting Rust diff.

Port plan / fetch-result / cancel / fetch-tasks onto RestCatalog with a
WaitForPlan poller. Task decoding and TableScan routing stay follow-ups,
so supports_remote_scan_planning remains false.

Part of apache#1690.
A huge Retry-After used to panic Duration::from_secs. Ignore overflow,
await cancel when the poller times out, and DELETE the plan if the wait
future is dropped.
RFC 9110 allows both delta-seconds and IMF-fixdate. Ignoring the date
form made wait_for_plan retry immediately against a server that asked
us to wait. Tests also cover cancelled/failed/expired poll outcomes
and the namespace 404 split.
Decode REST content-file JSON into FileScanTask and auto-route
TableScan::plan_files through a ScanPlanner with local fallback.
…lanning default

REST content-file JSON uses kebab-case string discriminators and hex key-metadata.
Decode each ScanTasks payload against its own delete-files. Default ScanPlanningMode
to Local so existing scans do not POST /plan. Auto remotes only for scan-planning-mode=server.
Share the live catalog client when injecting the planner.
OpenAPI defaults the flag to false (current table schema). TableScan
always binds the snapshot schema, so time travel would plan against
the wrong schema without this flag.
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.

Client side for REST scan planning

1 participant