Skip to content

docs: track LakeCat catalog & Iceberg changes on the lakecat branch - #3

Draft
alexy wants to merge 43 commits into
mainfrom
lakecat
Draft

alexy wants to merge 43 commits into
mainfrom
lakecat

Conversation

@alexy

@alexy alexy commented Jun 26, 2026

Copy link
Copy Markdown
Member

LakeCat ↔ Sail: catalog & Iceberg changes (tracking)

This PR tracks the Sail-side changes that LakeCat (an Iceberg REST catalog
sitting close to the engine) depends on, integrated on this fork's lakecat
branch, with LAKECAT-SAIL.md as the running change log.

This is the development base for LakeCat catalog work — querygraph/sail
lakecat → main. It is held here (rather than on lakehq/sail) to avoid
churning against the parallel upstream provider redesign
(lakehq/sail#2141) and the Iceberg catalog-support decisions
(lakehq/sail#982). Correctness fixes are offered upstream separately; the
provider-surface changes stay on-branch pending design coordination.

What's in the branch (atop the merged lakehq/sail#2134)

Change Kind Orig. PR Status
Round-trip manifest lower/upper bounds through Avro bug fix lakehq/sail#2139 consolidated here
Skip type-mismatched bound stats in pruning bug fix lakehq/sail#2139 consolidated here
apply_table_updates — evolve TableMetadata server-side enablement lakehq/sail#2135 consolidated here (held)
Expose planning helpers + commit-table provider seam enablement lakehq/sail#2140 consolidated here
docs(lakecat): add LAKECAT-SAIL.md, lakecat/AGENTS.md, fork notice docs — this branch

Correctness fixes

  • Manifest lower_bounds/upper_bounds were silently written as null because
    serde encoded the bytes-typed value as an Avro array — bounds were lost on
    read, defeating pruning. Forced byte-string (de)serialization, tolerant of the
    legacy array form.
  • Once bounds exist, pruning skips stats whose Arrow type doesn't match the
    logical column type, avoiding DataFusion's comparability assertion on
    partition-transform reads (no false pruning, no wrong results).

Enablement (coordinated with upstream)

  • apply_table_updates(&mut TableMetadata, &[TableUpdate], now_ms) materializes
    new metadata server-side (properties, schema, spec, sort-order, statistics, …),
    returning NotImplemented for updates needing deeper machinery. Held pending
    Iceberg catalog decisions (lakehq/sail#982); originally lakehq/sail#2135.
  • Additive, default-safe provider seam: public models, planning-result helpers,
    and default CatalogProvider::{commit_table, get_table_commits} →
    NotSupported.

⚠️ Design-pending: commit_table overlaps existing commit_lakehouse_table

The seam adds CatalogProvider::commit_table, but commit_lakehouse_table
(Xiaolong's) already covers that path and is implemented by the Iceberg
provider. The REST commit flow should reconcile with / route through the
existing method rather than add a parallel one; get_table_commits (history
listing) may be genuinely new. Upstream has since pivoted
(lakehq/sail#2138), so this seam needs reconciling against the new provider
shape regardless. No merge of the seam until a light design discussion settles
the direction.

Note: the bug fixes are independently mergeable

The two correctness fixes are independent of the provider redesign
(lakehq/sail#2141) and the Iceberg decisions (lakehq/sail#982) — they should
not be blocked behind the enablement work, and can be re-spun as a focused
upstream fixes-only PR.

Superseded PRs (for additional context)

Consolidated here; their original descriptions, diffs, and review threads carry
extra detail:

  • lakehq/sail#2139 — manifest-bounds round-trip + pruning type-safety (closed).
  • lakehq/sail#2140 — planning helpers + commit-table provider seam (closed).
  • lakehq/sail#2135 — apply_table_updates server-side metadata (closed, held).
  • lakehq/sail#2134 — TableUpdate/ViewUpdate discriminated enums (merged base).

See LAKECAT-SAIL.md for the full write-up and lakecat/AGENTS.md for
agent working discipline on this fork.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RJGCWiVM3DW1Keu4Pwuqjs

alexy and others added 6 commits June 25, 2026 21:38
…updates

Add a reusable apply_table_updates(&mut TableMetadata, &[TableUpdate], now_ms)
that applies Iceberg REST metadata updates (properties, location, uuid,
format-version, schema, current-schema, default-spec, sort-order, removals,
statistics) to produce new table metadata server-side. Updates needing deeper
machinery (add-spec binding, add-snapshot sequencing, snapshot-ref state) return
NotImplemented rather than producing incorrect metadata.

Lets a catalog (LakeCat) materialize new metadata on commit instead of relying
on the client to author it.
IntBytesMapEntry.value is a Vec<u8> typed as Avro `bytes` in the manifest
schema, but serde's default Vec<u8> serialization emits an Avro array. That
array fails to resolve against the `bytes` schema, so the nullable lower_bounds
/upper_bounds maps were silently written as null and every column bound was lost
on read. Force byte-string (de)serialization on the entry value (tolerating the
legacy array form on read for backward compatibility) so bounds survive the
write/parse round-trip.

sail-iceberg tests: 79 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UbNyd333y17hMC55Pk4CRk
Once manifest lower/upper bounds round-trip correctly, IcebergPruningStats feeds
them to DataFusion's PruningPredicate. An Iceberg bound can decode to a primitive
whose Arrow scalar type differs from how the predicate treats the column (the
to_scalar fallback yields e.g. an Int32 for a value the predicate compares as
Timestamp/Date), which trips DataFusion's "same data type are comparable"
assertion on partition-transform reads.

Wire up the previously-unused arrow_schema: min_values/max_values now skip stats
whose array type does not match the logical column type, so pruning is only
applied with well-typed bounds (no false pruning, no wrong results) and falls
back to a full scan otherwise.

sail-iceberg: 79 tests pass; clippy + nightly fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UbNyd333y17hMC55Pk4CRk
…r seam

Surface the symbols a thin REST-catalog bridge (LakeCat) needs without forking
the generated models:
- sail-catalog-iceberg: make `models` public; add planning result helpers
  (planning.rs: completed_planning[_with_id]_result_from_values,
  fetch_scan_tasks_result_from_values); re-export LoadTableResult/TableMetadata;
  expose load_table_result_to_status as a standalone `pub fn` (the existing
  method now delegates to it).
- sail-catalog: add CommitTableOptions/GetTableCommitsOptions/TableCommitInfo/
  GetTableCommitsResponse and default-impl CatalogProvider::{commit_table,
  get_table_commits} (returning NotSupported), so existing providers are
  unaffected.

sail-catalog + sail-catalog-iceberg build clean; existing providers compile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UbNyd333y17hMC55Pk4CRk
Add LAKECAT-SAIL.md recording the bug fixes and enabling changes made to
Sail for LakeCat: manifest-bounds round-trip (#2139), pruning type-safety
(#2139), apply_table_updates server-side metadata (#2135, held),
planning/commit-table provider seam (#2140), atop the merged TableUpdate/
ViewUpdate discriminated enums (#2134). Documents why the work is held on
the lakecat branch (parallel provider redesign #2141, Iceberg decisions
in #982) and how LakeCat consumes it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RJGCWiVM3DW1Keu4Pwuqjs
Establish this as a LakeCat fork of Sail: add lakecat/AGENTS.md with agent
working discipline (PR intention/coordination, no-scope Conventional-Commits
titles, design-before-code on shared trait surfaces, BDD .feature tests for
gaps), pointing back to .github/copilot-instructions.md as authoritative.
Add a README fork notice linking to it, and un-ignore lakecat/AGENTS.md
(AGENTS.md is otherwise gitignored repo-wide).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RJGCWiVM3DW1Keu4Pwuqjs
alexy and others added 23 commits June 26, 2026 20:40
Add an opt-in per-worker object-store read cache to sail-object-store, modeled on
lancedb/ocra (Apache-2.0) with Moka swapped for Foyer, mirroring the existing
RuntimeAwareObjectStore layer. Caches fixed-size pages keyed by (location, page_id)
plus ObjectMeta, fetching misses through the inner store and coalescing concurrent
misses via Foyer's get_or_fetch.

object_store 0.13.2 note: get/get_range/head live in the non-overridable
ObjectStoreExt blanket trait, so caching is intercepted at get_opts + get_ranges;
invalidation in put/copy/rename/delete. Conditional/versioned reads bypass the cache.

Opt-in and default-unchanged: enable with SAIL_OBJECT_STORE_CACHE (+ _PAGE_SIZE,
_MEMORY, _METADATA). Defaults: 1 MiB pages, 1 GiB memory, 64 MiB metadata.
In-memory tier only; Foyer HybridCache disk tiering is a follow-up.

ocra is attributed in a header block and per-site `// Adapted from ocra ...`
comments. foyer 0.22.3. 8 sail-object-store tests pass; clippy/fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UbNyd333y17hMC55Pk4CRk
…_updates

A catalog receiving an Iceberg `updateTable` commit from a stock client
(pyiceberg/Spark append) applies an `add-snapshot` followed by a
`set-snapshot-ref` for `main`; both previously returned NotImplemented, so a real
snapshot append was rejected with HTTP 400.

Implement them per the Iceberg spec in `apply_table_updates`:
- add-snapshot: append the snapshot to the snapshot set, advance
  last-sequence-number, and advance the v3 next-row-id when the snapshot carries
  added-rows. It does not move the current ref (that is set-snapshot-ref's job).
- set-snapshot-ref: insert/move the branch or tag ref; moving `main` advances
  current-snapshot-id and records a snapshot-log entry (timestamped with the
  commit time threaded through apply_one). Rejects a ref to an unknown snapshot-id.

`add-spec` remains the only unsupported update (partition-spec binding).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UbNyd333y17hMC55Pk4CRk
Unit tests over a minimal v2 metadata fixture:
- a stock append (add-snapshot + set-snapshot-ref main) adds the snapshot, moves
  current-snapshot-id + the main ref, logs a snapshot-log entry at the commit
  timestamp, and bumps last-sequence-number / last-updated-ms;
- add-snapshot alone adds the snapshot but does not move the current ref or log;
- set-snapshot-ref to an unknown snapshot-id is rejected;
- a second append accumulates both snapshots and advances current + sequence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UbNyd333y17hMC55Pk4CRk
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.

1 participant