Skip to content

REST Spec: add labels field for catalog metadata enrichment - #15750

Merged
laskoviymishka merged 1 commit into
apache:mainfrom
laskoviymishka:labels-in-load-table-response
Sep 9, 2026
Merged

laskoviymishka merged 1 commit into
apache:mainfrom
laskoviymishka:labels-in-load-table-response

Conversation

@laskoviymishka

@laskoviymishka laskoviymishka commented Mar 24, 2026 •

Copy link
Copy Markdown
Contributor

Adds an optional labels field to the REST catalog spec for catalog-owned metadata enrichment.

Motivation

A catalog maintains operational context about the objects it serves — ownership, classification, cost attribution, semantic hints — but the spec gives it no standard place to return that context. Today catalogs work around this by extending load responses with vendor-specific fields that OSS engines can't consume. Labels provide one standard channel instead.

Labels are catalog-owned and ephemeral: generated per request, never written to metadata.json, no commits or snapshots, no versioning with table history. They are not table state, and different catalogs serving the same object may legitimately return different labels. Catalogs may populate them; engines may use them or ignore them entirely.

This is deliberately kept out of properties. Properties are table state — writer-owned, persisted, and they travel with the table across catalogs. Catalog enrichment is the opposite, so putting it in properties would conflate authority and force it to travel where it shouldn't. The boundary is the table spec (properties) vs. the IRC spec (labels).

What changed

New Labels (table flat key-values + columns keyed by field-id for stability across schema evolution) and ColumnLabels schemas; labels field added to LoadTableResult and LoadViewResult.

Read-only and backward compatible (labels is optional; older clients ignore it). Namespace labels and the write path (UpdateLabels) are follow-ups.

Proposal and design doc: #15521.

@laskoviymishka
laskoviymishka force-pushed the labels-in-load-table-response branch from acdb80a to a80fc1a Compare March 24, 2026 09:14
@github-actions github-actions Bot added the stale label Apr 24, 2026
@laskoviymishka
laskoviymishka force-pushed the labels-in-load-table-response branch from 7daf93c to cfcd777 Compare April 27, 2026 13:21
@github-actions github-actions Bot removed the stale label Apr 28, 2026
Comment thread open-api/rest-catalog-open-api.yaml Resolved
@github-actions github-actions Bot added the stale label Jun 13, 2026
@github-actions github-actions Bot closed this Jun 21, 2026
@apache apache deleted a comment from github-actions Bot Jun 24, 2026
@apache apache deleted a comment from github-actions Bot Jun 24, 2026
@apache apache deleted a comment from github-actions Bot Jun 24, 2026
@laskoviymishka
laskoviymishka force-pushed the labels-in-load-table-response branch 2 times, most recently from a4dd7be to e8c9e33 Compare June 24, 2026 22:49
@laskoviymishka laskoviymishka changed the title REST Spec: add labels field to LoadTableResult REST Spec: add labels field for catalog metadata enrichment Jun 24, 2026
Comment thread open-api/rest-catalog-open-api.py Resolved
@laskoviymishka
laskoviymishka force-pushed the labels-in-load-table-response branch from e8c9e33 to b44678b Compare July 7, 2026 08:45
@laskoviymishka
laskoviymishka marked this pull request as ready for review July 7, 2026 21:12
Comment thread open-api/rest-catalog-open-api.yaml Resolved
Comment thread open-api/rest-catalog-open-api.yaml Resolved
Comment thread open-api/rest-catalog-open-api.yaml Resolved
Comment thread open-api/rest-catalog-open-api.yaml Resolved
Comment thread open-api/rest-catalog-open-api.yaml Resolved
Comment thread open-api/rest-catalog-open-api.yaml Resolved
Comment thread open-api/rest-catalog-open-api.yaml Resolved
laskoviymishka added a commit to laskoviymishka/iceberg that referenced this pull request Jul 23, 2026
Reference client implementation for the IRC labels read-path spec change
(apache#15750). Without it, RESTObjectMapper (which sets
FAIL_ON_UNKNOWN_PROPERTIES = false) silently drops the labels field on
deserialization, so labels returned by a catalog are invisible to the Java
client.

- New rest.labels package (mirroring rest.credentials): Labels (object +
  fields sub-scopes) and FieldLabels (per-field, keyed by field-id) value
  types as immutables interfaces, each with a JSON parser. FieldLabels
  validates field-id >= 1 and a non-empty labels map, matching Credential.
- Wire an optional labels field into LoadTableResponse / LoadViewResponse
  and their parsers. Labels are omitted from the wire when absent (or empty),
  so the change is additive and backward compatible.
- Accept the LoadViewResponse::labels() interface addition in
  .palantir/revapi.yml (the interface is only implemented by the generated
  ImmutableLoadViewResponse).
- Tests: parser round-trips (object-only, fields-only, both, empty),
  optional-when-absent, field-id validation from the wire, and a full
  RESTObjectMapper (Jackson) round-trip for table labels.
laskoviymishka added a commit to laskoviymishka/iceberg that referenced this pull request Jul 24, 2026
Reference client implementation for the IRC labels read-path spec change
(apache#15750). Without it, RESTObjectMapper (which sets
FAIL_ON_UNKNOWN_PROPERTIES = false) silently drops the labels field on
deserialization, so labels returned by a catalog are invisible to the Java
client.

- New rest.labels package (mirroring rest.credentials): Labels (object +
  fields sub-scopes) and FieldLabels (per-field, keyed by field-id) value
  types as immutables interfaces, each with a JSON parser. FieldLabels
  validates field-id >= 1 and a non-empty labels map, matching Credential.
- Wire an optional labels field into LoadTableResponse / LoadViewResponse
  and their parsers. On LoadViewResponse it is a @Value.Default default
  method so no interface API break is introduced. Labels are omitted from
  the wire when absent (or empty), so the change is additive and backward
  compatible.
- Tests: parser round-trips (object-only, fields-only, both, empty),
  optional-when-absent, field-id validation from the wire, and a full
  RESTObjectMapper (Jackson) round-trip for table labels.
laskoviymishka added a commit to laskoviymishka/iceberg that referenced this pull request Jul 24, 2026
Reference client implementation for the IRC labels read-path spec change
(apache#15750). Without it, RESTObjectMapper (which sets
FAIL_ON_UNKNOWN_PROPERTIES = false) silently drops the labels field on
deserialization, so labels returned by a catalog are invisible to the Java
client.

- New rest.labels package (mirroring rest.credentials): Labels (object +
  fields sub-scopes) and FieldLabels (per-field, keyed by field-id) value
  types as immutables interfaces, each with a JSON parser. FieldLabels
  validates field-id >= 1 and a non-empty labels map, matching Credential.
- Wire an optional labels field into LoadTableResponse / LoadViewResponse
  and their parsers. On LoadViewResponse it is a @Value.Default default
  method so no interface API break is introduced. Labels are omitted from
  the wire when absent (or empty), so the change is additive and backward
  compatible.
- Tests: parser round-trips (object-only, fields-only, both, empty),
  optional-when-absent, field-id validation from the wire, and a full
  RESTObjectMapper (Jackson) round-trip for table labels.

@singhpk234 singhpk234 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work @laskoviymishka !

Posted couple of feedbacks on spec, which i believe we should resolve !

On an orthogonal note: I have some doubts on the POC that was linked to the VOTE thread, specially this section
Governance: Trusted Engine Architecture

labels travel through the standard IRC protocol, they enable a trusted engine architecture: the catalog carries classification, each engine implements and enforces its own policies based on the same labels

This assumes classification is stored in catalog and policies are stored in engine ? i have personally not seen systems like this generally policies are stored along with the catalog (which can mounted with a policy store like RANGER / OPA),

can you please elaborate which catalog + engine pair is this,
in the POC an explicit view is created here

Also why can't the policy store be behind catalog and why can't one use ReadRestrictions spec directly to returning these restriction rather than calling the policy store again from engine and creating a governed view locally ?

Comment on lines +4077 to +4078
labels:
$ref: '#/components/schemas/Labels'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please elaborate the use case of when does one has labels on view ?

we had similar debate on read restriction on this and we decided to not have this for views

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Views can carry inherited labels (from the underlying tables / namespace), and surfacing that on the view has real value: a consumer reading the view sees the classification/context without re-deriving it from the sources.

The view use case came up multiple times during the sync discussions and in the design doc.

It's a different call than ReadRestrictions because labels are informational exposure, not an access decision that has to be enforced consistently, so the reason to drop views there doesn't carry over one-to-one. The most important use case is informational exchange for the reader: for example, added AI semantic context.

Comment thread open-api/rest-catalog-open-api.yaml Resolved
Comment thread open-api/rest-catalog-open-api.yaml Resolved
description: Field ID from the current schema of the table or view
labels:
type: object
description: Flat key-value labels for this field

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fairly concerned on how feasible / practical it for platforms to rely on this,
since these are sometimes well defined object which have their own inheritance mechanism.
I checked some of the catalogs which actually do supports tags (unfortunately Apache Polaris is not a good example which is used in POC as it neither supports ABAC nor does it support TAGs (there is proposal just published)) and it feels pretty standard to have such kind of info carried for example :

i believe we should think this through more on how are we gonna converge this behaviour for example consider a label attached to a namespace and gets inherited to the table, it flows to another catalog which mounts this as an external obj... how will the external catalog know this was inherited ? and if in the external catalog platform if some one attaches direct label to the obj it would override the namespace one ?
These things make me nervous in having this in the protocol without we agreeing on the attachment experience.

I understand the proposal with exposing things that catalog tracks and should not be part of table props but this is getting more involved if we include governance.

not dictating this behaviour can also be possible but i believe folks who exchange this info will be forced to stuff more in this kv pair and end up making custom adapters around it and i feel it defeats the purpose of having this in the interoperable spec in the first place

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree here, that inheritance and attachment are real, well-defined concepts in the catalogs that have a tag/entity model, and that's exactly why I'd keep them out of this field.

Baking an attachment/inheritance model into the read path would force every catalog to reason about it, including the simpler catalogs and engines that have no such concept today. They'd end up with a more complicated story than they need just to produce or consume a flat value.

So the intent here is the lowest common denominator: the catalog resolves whatever it resolves (inheritance applied or not, its own way) and returns the resolved values. A catalog with a rich tag model can still populate it; one without doesn't have to grow one.

The structured side you're describing (attachment, inheritance provenance, cross-catalog convergence) is important, but it's a heavier layer that should build on top of this. I'm planning to expand into that as a follow-up (the structured classification layer, see in original doc: here).

I plan to continue pushing this once simple read-path landed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @laskoviymishka's points here and we should be careful to not try to infer how the labels are applied (i.e. direct vs. inheritance) as we want to be flexible to support any model catalogs implement. Importantly, the protocol is only reflecting what is applied to the catalog object at the point of access. How the catalog arrived at that is immaterial.

Comment thread open-api/rest-catalog-open-api.yaml Outdated
fields:
type: array
description: >-
Field-level labels. Each entry identifies its field by field-id.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens in case of nested fields ? can the outer struct say {'sensitivity': high} and the inner struct say {'sensistivity': low } .... which is correct then ?
how would one treat this info, if we are saying this is info what one should use for governing access to the enitities, we should be explicit on the interpretation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every field-id is its own entry: the outer struct and the nested field are two separate entries keyed by their own field-ids, so on the wire there's no conflict to resolve. Both values are just carried as-is.

The question "which one wins" only comes up if you read a label as an enforced access decision, and that's the part the spec deliberately doesn't claim. Labels are informational; they don't carry precedence semantics. If a catalog does derive an effective value across nested fields, that resolution rule lives in the catalog and it can return the resolved value on the field-id it applies to.

Defining precedence/interpretation in the spec would mean specifying a tag model, which is the heavier structured layer I'd rather build on top as a follow-up (design doc here) than bake into the read path here.

Comment on lines +3697 to +3700
type: object
description: Flat key-value labels for this field
additionalProperties:
type: string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if labels names can be renamed then key name is not sufficient for uniqueness in case of cross platform syncing, as a user might have taken a lot of action based on these synced label for example attaching a PII policy to the label.
How do we expect the system to be reconcile in this scenario if we have things like uuid missing from these ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree here, but I don't think we could solve this on the read path.

Stable identity across rename (so a consumer that bound a PII policy to a synced label can reconcile when the value or name changes) is an entity property. It needs a first-class tag with its own id and lifecycle, which is the structured write layer, not a flat resolved read value.

Adding a uuid to this field would pull that entity model onto the read path, which is exactly the flat-vs-structured line I'm trying to keep clean. So rather than half-modeling it here, I'd carry it as a first-class part of the structured classification layer (design doc here), which calls out stable identity and rename/delete lifecycle explicitly.

This is the case we went deep on offline and it's the main driver for the structured follow up track.

laskoviymishka added a commit to laskoviymishka/iceberg that referenced this pull request Jul 27, 2026
Reference client implementation for the IRC labels read-path spec change
(apache#15750). Without it, RESTObjectMapper (which sets
FAIL_ON_UNKNOWN_PROPERTIES = false) silently drops the labels field on
deserialization, so labels returned by a catalog are invisible to the Java
client.

- New rest.labels package (mirroring rest.credentials): Labels (object +
  fields sub-scopes) and FieldLabels (per-field, keyed by field-id) value
  types as immutables interfaces, each with a JSON parser. FieldLabels
  validates field-id >= 1 and a non-empty labels map, matching Credential.
- Wire an optional labels field into LoadTableResponse / LoadViewResponse
  and their parsers. On LoadViewResponse it is a @Value.Default default
  method so no interface API break is introduced. Labels are omitted from
  the wire when absent (or empty), so the change is additive and backward
  compatible.
- Tests: parser round-trips (object-only, fields-only, both, empty),
  optional-when-absent, field-id validation from the wire, and a full
  RESTObjectMapper (Jackson) round-trip for table labels.
@laskoviymishka
laskoviymishka force-pushed the labels-in-load-table-response branch from eed5f8c to e701a0d Compare July 27, 2026 12:56
@laskoviymishka

Copy link
Copy Markdown
Contributor Author

Thanks @singhpk234 for review!

Let me unpack the POC, i think that section is oversold and it's dragging the wrong framing onto the thread.

Disclaimer: this isn't a governance proposal and labels don't mandate any enforcement model. the POC is one thing a consumer can do with labels, not the way to govern iceberg.

The pair is an IRC catalog → ClickHouse. The explicit view you found is there because ClickHouse enforces access control natively: roles, row policies, column grants -- all evaluated inside the server. It has no Ranger plugin and no per-query external-authorizer hook the way Trino/Hive do, so it can't consume a catalog-side policy decision at query time. That's why the POC looks like "classification in catalog, policy in engine", it's not a pattern i'm pushing, it's just the shape ClickHouse already has.

Engine-enforcement isn't specific to ClickHouse: Postgres (RLS + column grants) and MySQL (grants, view-based row filtering) enforce the same way and have no per-query external-authorizer path either. Any of them consuming a catalog would act on classification with its own primitives rather than a catalog-side decision.

Which is also the honest answer to "why not ReadRestrictions directly".

I don't see these as competing:

ReadRestrictions returns a decision, catalog is the authority. Labels return classification and leave the decision to the consumer. ReadRestrictions also can't represent every policy scenario, so it's not a full substitute either way. Conceptually labels are aligned with a policy-coordination system like Ranger/Immuta: the difference is the context arrives from a REST catalog instead of an internal one, but the net result is the same.

A catalog can do both. Where the policy store sits behind the catalog and identity resolves cleanly catalog-side (Trino+Ranger, Spark+catalog), ReadRestrictions is the right tool and labels aren't trying to replace it.

ClickHouse just isn't that case - its SQL identities don't map 1:1 to catalog principals so the catalog can't resolve whose decision to return, and it queries well beyond iceberg so it wants one policy layer across all its sources, not a per-catalog decision for the iceberg slice. so for that engine, catalog-provides-classification + engine-enforces is the thing that actually fits, not a claim that it's better in general.

Stepping back: governance is one consumer of labels, next to discovery, cost attribution, semantic/AI context.

laskoviymishka added a commit to laskoviymishka/iceberg that referenced this pull request Jul 28, 2026
Reference client implementation for the IRC labels read-path spec change
(apache#15750). Without it, RESTObjectMapper (which sets
FAIL_ON_UNKNOWN_PROPERTIES = false) silently drops the labels field on
deserialization, so labels returned by a catalog are invisible to the Java
client.

- New rest.labels package (mirroring rest.credentials): Labels (object +
  fields sub-scopes) and FieldLabels (per-field, keyed by field-id) value
  types as immutables interfaces, each with a JSON parser. FieldLabels
  validates field-id >= 1 and a non-empty labels map, matching Credential.
- Wire an optional labels field into LoadTableResponse / LoadViewResponse
  and their parsers. On LoadViewResponse it is a @Value.Default default
  method so no interface API break is introduced. Labels are omitted from
  the wire when absent (or empty), so the change is additive and backward
  compatible.
- Tests: parser round-trips (object-only, fields-only, both, empty),
  optional-when-absent, field-id validation from the wire, and a full
  RESTObjectMapper (Jackson) round-trip for table labels.
laskoviymishka added a commit to laskoviymishka/iceberg that referenced this pull request Jul 28, 2026
Reference client implementation for the IRC labels read-path spec change
(apache#15750). Without it, RESTObjectMapper (which sets
FAIL_ON_UNKNOWN_PROPERTIES = false) silently drops the labels field on
deserialization, so labels returned by a catalog are invisible to the Java
client.

- New rest.labels package (mirroring rest.credentials): Labels (object +
  fields sub-scopes) and FieldLabels (per-field, keyed by field-id) value
  types as immutables interfaces, each with a JSON parser. FieldLabels
  validates field-id >= 1 and a non-empty labels map, matching Credential.
- Wire an optional labels field into LoadTableResponse / LoadViewResponse
  and their parsers. On LoadViewResponse it is a @Value.Default default
  method so no interface API break is introduced. Labels are omitted from
  the wire when absent (or empty), so the change is additive and backward
  compatible.
- Expose labels on the loaded table via a SupportsLabels mixin implemented
  by BaseTable, populated from the load response in RESTSessionCatalog.
  Labels are ephemeral catalog enrichment, not table state, and are not
  preserved across table serialization.
- Tests: parser round-trips, optional-when-absent, field-id validation, a
  full RESTObjectMapper (Jackson) round-trip, and BaseTable label exposure.
@laskoviymishka
laskoviymishka force-pushed the labels-in-load-table-response branch from a135461 to d36ffd0 Compare September 9, 2026 08:52

@kevinjqliu kevinjqliu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@laskoviymishka
laskoviymishka merged commit de8cba1 into apache:main Sep 9, 2026
25 checks passed
laskoviymishka added a commit that referenced this pull request Sep 17, 2026
Implement the client read path for catalog labels (added to the REST
spec in #15750): parse the labels a catalog returns on
load table/view responses so they are visible to the Java client. Until
now RESTObjectMapper (FAIL_ON_UNKNOWN_PROPERTIES = false) silently
dropped the field, so any labels a catalog returned were lost.

Add Labels and FieldLabel value types with JSON parsers, and wire an
optional labels field into LoadTableResponse and LoadViewResponse.
Labels default to absent and are omitted from the wire when empty, so
the change is additive and backward compatible.
laskoviymishka added a commit to laskoviymishka/doris that referenced this pull request Oct 13, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Upgrade the Iceberg Java SDK from 1.11.0 to 1.12.0.
1.12.0 brings the client side of REST catalog labels (`Labels`,
`SupportsLabels`, labels on `LoadTableResponse`, see apache/iceberg#15750,
apache#18045, apache#18046), which Doris needs before it can expose catalog-provided
labels for iceberg tables. This change only upgrades the dependency and
adapts Doris to it.

- Bump `iceberg.version` 1.11.0 -> 1.12.0, and `avro.version`
  1.12.1 -> 1.12.2 to stay consistent with what iceberg 1.12.0 is built
  against (the pom requires the two to move together).
- Bump the explicit caffeine pin in fe-connector-iceberg 2.9.3 -> 3.2.4.
  Iceberg 1.12.0 moved to caffeine 3. The pin is a direct dependency, so
  it wins Maven mediation; leaving it at 2.9.3 would package an older
  caffeine into the iceberg plugin than iceberg-core is built against.
- Re-vendor `org.apache.iceberg.DeleteFileIndex` from 1.12.0 and re-apply
  the Doris changes (public class/builder methods, schemasById comment,
  equality-field-id sets). The vendored class shadows the SDK class in
  the same package, and 1.12.0 changed it (an extra `specsById` constructor
  argument and delete/data file partition-match validation), so keeping
  the 1.11.0 copy would break the SDK's own callers at runtime.
  Since Doris passes `specsById` to the builder, the cached scan planning
  path now also rejects delete files whose partition spec or tuple does
  not match the data file, the same as upstream iceberg.
- Pass `table.specs()` to `ManifestFiles.read` in `IcebergManifestCache`.
  Iceberg 1.12.0 removed the deprecated `read(ManifestFile, FileIO)`
  overload, which read the partition specs from the manifest file's own
  metadata (only possible for Avro manifests). This matches the delete
  manifest path next to it (`readDeleteManifest(..., table.specs())`)
  and `IcebergRemoveOrphanFilesAction`.
- Keep the AWS SDK at 2.29.52 and default iceberg-aws's HTTP client to
  `urlconnection`. In 1.12.0, iceberg-aws's default `http-client.type`
  (`apache`) builds an `Apache5HttpClient` from
  `software.amazon.awssdk:apache5-client`, which first shipped in AWS SDK
  2.32. Doris pins 2.29.52 on purpose (apache#58566: newer SDKs add default S3
  integrity checksums that break S3-compatible services), so with the
  default every S3FileIO, Glue and STS client iceberg builds failed with
  `NoClassDefFoundError: software/amazon/awssdk/http/apache5/Apache5HttpClient$Builder`.
  `IcebergCatalogFactory.buildBaseCatalogProperties`, the base of every
  catalog flavor, now sets `http-client.type=urlconnection` unless the
  catalog sets it. S3FileIO keeps the setting when it is serialized, so
  the BE iceberg metadata scanner, which rebuilds the FileIO from it,
  uses the same client; `url-connection-client` is added to that scanner,
  which only bundled `apache-client` and `netty-nio-client`. An explicit
  `http-client.type=apache` is rejected at CREATE/ALTER CATALOG instead of
  failing on the first S3 or Glue call.

`SchemaAwareDataTableScan` (the other vendored class) needs no change:
`DataTableScan`, `BaseTableScan`, `SnapshotScan` and `TableScanContext`
are identical between 1.11.0 and 1.12.0.

### Release note

Upgrade the Iceberg Java SDK to 1.12.0. Iceberg's AWS clients (S3FileIO,
Glue, STS) now use the URLConnection HTTP client by default, and the
`http-client.type=apache` catalog property is no longer accepted.

### Check List (For Author)

- Test: Unit Test
    - Added IcebergCatalogFactoryTest#buildBaseDefaultsIcebergAwsHttpClientToUrlConnection
      (builds a real S3 client builder with the default) and
      #buildBaseKeepsAnExplicitHttpClientType, and
      IcebergCatalogPropertiesTest#createTimeRulesRejectTheApacheHttpClientType;
      IcebergCatalogOptionsSnapshotTest snapshots now include the default.
    - `build.sh --fe` passes. Ran every iceberg FE unit test with
      run-fe-ut.sh (fe-connector-iceberg, fe-connector-metastore-iceberg,
      be-java-extensions/iceberg-metadata-scanner): 1630 tests, 0 failures,
      5 skipped.
    - Iceberg regression suites not run.
- Behavior changed: Yes.
    - Iceberg's AWS clients use the URLConnection HTTP client by default,
      so `http-client.apache.*` properties no longer apply, and
      `http-client.type=apache` is rejected at CREATE/ALTER CATALOG.
    - Iceberg scans whose delete files have a partition spec or partition
      tuple that does not match the data file now fail with a
      ValidationException (iceberg 1.12.0 behavior) instead of applying
      the deletes.
- Does this need documentation: No
laskoviymishka added a commit to laskoviymishka/doris that referenced this pull request Oct 13, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Upgrade the Iceberg Java SDK from 1.11.0 to 1.12.0.
1.12.0 brings the client side of REST catalog labels (`Labels`,
`SupportsLabels`, labels on `LoadTableResponse`, see apache/iceberg#15750,
apache#18045, apache#18046), which Doris needs before it can expose catalog-provided
labels for iceberg tables. This change only upgrades the dependency and
adapts Doris to it.

- Bump `iceberg.version` 1.11.0 -> 1.12.0, and `avro.version`
  1.12.1 -> 1.12.2 to stay consistent with what iceberg 1.12.0 is built
  against (the pom requires the two to move together).
- Bump the explicit caffeine pin in fe-connector-iceberg 2.9.3 -> 3.2.4.
  Iceberg 1.12.0 moved to caffeine 3. The pin is a direct dependency, so
  it wins Maven mediation; leaving it at 2.9.3 would package an older
  caffeine into the iceberg plugin than iceberg-core is built against.
- Re-vendor `org.apache.iceberg.DeleteFileIndex` from 1.12.0 and re-apply
  the Doris changes (public class/builder methods, schemasById comment,
  equality-field-id sets). The vendored class shadows the SDK class in
  the same package, and 1.12.0 changed it (an extra `specsById` constructor
  argument and delete/data file partition-match validation), so keeping
  the 1.11.0 copy would break the SDK's own callers at runtime.
  Since Doris passes `specsById` to the builder, the cached scan planning
  path now also rejects delete files whose partition spec or tuple does
  not match the data file, the same as upstream iceberg.
- Pass `table.specs()` to `ManifestFiles.read` in `IcebergManifestCache`.
  Iceberg 1.12.0 removed the deprecated `read(ManifestFile, FileIO)`
  overload, which read the partition specs from the manifest file's own
  metadata (only possible for Avro manifests). This matches the delete
  manifest path next to it (`readDeleteManifest(..., table.specs())`)
  and `IcebergRemoveOrphanFilesAction`.
- Keep the AWS SDK at 2.29.52 and default iceberg-aws's HTTP client to
  `urlconnection`. In 1.12.0, iceberg-aws's default `http-client.type`
  (`apache`) builds an `Apache5HttpClient` from
  `software.amazon.awssdk:apache5-client`, which first shipped in AWS SDK
  2.32. Doris pins 2.29.52 on purpose (apache#58566: newer SDKs add default S3
  integrity checksums that break S3-compatible services), so with the
  default every S3FileIO, Glue and STS client iceberg builds failed with
  `NoClassDefFoundError: software/amazon/awssdk/http/apache5/Apache5HttpClient$Builder`.
  `IcebergCatalogFactory.buildBaseCatalogProperties`, the base of every
  catalog flavor, now sets `http-client.type=urlconnection` unless the
  catalog sets it. S3FileIO keeps the setting when it is serialized, so
  the BE iceberg metadata scanner, which rebuilds the FileIO from it,
  uses the same client; `url-connection-client` is added to that scanner,
  which only bundled `apache-client` and `netty-nio-client`. An explicit
  `http-client.type=apache` is rejected at CREATE/ALTER CATALOG instead of
  failing on the first S3 or Glue call.

- Report the mixed-version case clearly. FE sends iceberg system-table
  splits to the BE as Java-serialized iceberg objects, and iceberg 1.12.0
  changed the serialized form of classes every split carries (`BaseFile`,
  `PartitionData`, their anonymous inner classes; 24 serializable classes
  changed `serialVersionUID`). Unlike 1.11.0, where only `Schema` changed
  and is shimmed, this cannot be bridged by remapping UIDs, so while FE and
  BE run different iceberg versions (a rolling upgrade) iceberg system
  tables cannot be read. `IcebergSerializationCompat` now turns the bare
  `InvalidClassException` / `ClassNotFoundException` into an error naming
  the cause and the BE's iceberg version, and the shim comments no longer
  claim a shared wire across versions.

`SchemaAwareDataTableScan` (the other vendored class) needs no change:
`DataTableScan`, `BaseTableScan`, `SnapshotScan` and `TableScanContext`
are identical between 1.11.0 and 1.12.0.

### Release note

Upgrade the Iceberg Java SDK to 1.12.0. Iceberg's AWS clients (S3FileIO,
Glue, STS) now use the URLConnection HTTP client by default, and the
`http-client.type=apache` catalog property is no longer accepted. During a
rolling upgrade from a release on Iceberg 1.11, Iceberg system tables
(`tbl$snapshots`, `tbl$files`, ...) are unavailable until FE and BE are
both upgraded.

### Check List (For Author)

- Test: Unit Test
    - Added IcebergCatalogFactoryTest#buildBaseDefaultsIcebergAwsHttpClientToUrlConnection
      (builds a real S3 client builder with the default) and
      #buildBaseKeepsAnExplicitHttpClientType, and
      IcebergCatalogPropertiesTest#createTimeRulesRejectTheApacheHttpClientType;
      IcebergCatalogOptionsSnapshotTest snapshots now include the default.
    - Added IcebergSerializationCompatTest#reportsATaskFromAnotherIcebergVersion
      (a DataFile carrying Iceberg 1.11's BaseFile UID) and
      #reportsAClassThisIcebergDoesNotHave.
    - Verified the mixed-version break outside the build: a DataFile written
      with iceberg 1.11.0 cannot be read with 1.12.0 and vice versa, even
      after rewriting every changed class UID in the stream.
    - `build.sh --fe` passes. Ran every iceberg FE unit test with
      run-fe-ut.sh (fe-connector-iceberg, fe-connector-metastore-iceberg,
      be-java-extensions/iceberg-metadata-scanner): 1650 tests, 0 failures,
      5 skipped.
    - Iceberg regression suites not run.
- Behavior changed: Yes.
    - Iceberg's AWS clients use the URLConnection HTTP client by default,
      so `http-client.apache.*` properties no longer apply, and
      `http-client.type=apache` is rejected at CREATE/ALTER CATALOG.
    - Iceberg scans whose delete files have a partition spec or partition
      tuple that does not match the data file now fail with a
      ValidationException (iceberg 1.12.0 behavior) instead of applying
      the deletes.
    - While FE and BE run different iceberg versions (rolling upgrade from
      iceberg 1.11), iceberg system tables fail with an explicit
      "different Iceberg version" error until both sides are upgraded.
- Does this need documentation: No
laskoviymishka added a commit to laskoviymishka/doris that referenced this pull request Oct 15, 2026
…em table

### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: The Iceberg REST catalog spec now lets a catalog return
`labels` with each table load (apache/iceberg#15750): catalog-owned,
per-request metadata such as ownership, classification or cost
attribution, at the table level (`object-labels`) and per field (keyed by
`field-id`). Labels are not part of the table metadata, have no
snapshots and are never written to `metadata.json`. Iceberg 1.12.0
exposes them on the loaded table via `SupportsLabels#labels()`. Doris had
no way to show them.

Add a `t$labels` system table for iceberg tables in a REST catalog:

    SELECT * FROM rest_ctl.db.tbl$labels;
    scope  | field_id | field_name | key            | value
    object | NULL     | NULL       | owner          | team-a
    field  | 2        | email      | classification | pii

One row per label. `field_name` is resolved against the current table
schema and is NULL for a field id that is no longer in it (a dropped
column). The schema mirrors iceberg's proposed `LabelsTable`.

Implementation:

- Keep labels when Doris rebuilds a loaded table. iceberg's REST client
  attaches them to the loaded `BaseTable` as a transient field; they are
  not table metadata, so a `BaseTable` rebuilt from its `TableOperations`
  must pass them explicitly. `IcebergTableCache.TableLease#snapshotReadTable`
  (cached reads), `IcebergStatementScope#snapshotReadTable` (statement
  reads) and `IcebergScanPlanProvider#wrapTableForScan` (Kerberos FileIO
  wrap) used constructors that reset labels to `Labels.EMPTY`, so every
  read saw no labels; they now pass `baseTable.labels()`.
- `labels` is not a `MetadataTableType`, so it is handled next to the
  SDK metadata tables rather than through `MetadataTableUtils`.
  `IcebergConnectorMetadata` lists and resolves it only when the catalog
  flavor is `rest` (no other catalog returns labels), and serves its fixed
  schema and column handles without loading the base table.
- `IcebergScanPlanProvider` plans it as one split. A new split-package
  helper `org.apache.iceberg.IcebergLabelsDataTask` builds an iceberg
  `StaticDataTask` (package-private in the SDK) holding the label rows,
  projected to the requested columns in BE's required-field order; its
  metadata `DataFile` is synthesized, so no storage request is made. The
  task is serialized like every other static metadata table, so BE's
  `IcebergSysTableJniScanner` reads it with no BE change.
- The ordered system-table projection is extracted into
  `projectSystemTableSchema` and shared with the existing path, and the
  base-table load is split out of `resolveSysTable` as
  `resolveSysBaseTable`.
- Labels are not versioned: like `$snapshots`/`$history`, the snapshot pin
  of the handle is ignored, and `$labels` is exempt from the
  resolved-empty-snapshot fence, so a table with no commit still shows its
  labels.

Labels reflect the table load the statement used, so they follow the
iceberg table cache (`meta.cache.iceberg.table.ttl-second`); REFRESH
TABLE picks up new labels. Like the other system tables, a REST catalog
that requires server-side scan planning rejects `$labels`.

### Release note

Support querying REST catalog labels of an iceberg table through the
`tbl$labels` system table.

### Check List (For Author)

- Test: Unit Test
    - Added IcebergLabelsDataTaskTest, the $labels cases in
      IcebergConnectorMetadataSysTableTest,
      IcebergTableCacheTest#snapshotReadTableKeepsCatalogLabels and
      IcebergStatementScopeTest#sharedTableKeepsCatalogLabels.
    - `build.sh --fe` (Maven build cache disabled) passes on top of current
      master. Ran every iceberg and hudi FE unit test with run-fe-ut.sh:
      1990 tests, 0 failures, 5 skipped.
    - Regression test pending a REST fixture that returns labels
      (apache/iceberg#18047).
- Behavior changed: No (adds a new system table for REST catalogs only)
- Does this need documentation: Yes (doc PR to follow)

Co-authored-by: Isaac <[email protected]>
morningman pushed a commit to laskoviymishka/doris that referenced this pull request Oct 27, 2026
…em table

### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: The Iceberg REST catalog spec now lets a catalog return
`labels` with each table load (apache/iceberg#15750): catalog-owned,
per-request metadata such as ownership, classification or cost
attribution, at the table level (`object-labels`) and per field (keyed by
`field-id`). Labels are not part of the table metadata, have no
snapshots and are never written to `metadata.json`. Iceberg 1.12.0
exposes them on the loaded table via `SupportsLabels#labels()`. Doris had
no way to show them.

Add a `t$labels` system table for iceberg tables in a REST catalog:

    SELECT * FROM rest_ctl.db.tbl$labels;
    scope  | field_id | field_name | key            | value
    object | NULL     | NULL       | owner          | team-a
    field  | 2        | email      | classification | pii

One row per label. `field_name` is resolved against the current table
schema and is NULL for a field id that is no longer in it (a dropped
column). The schema mirrors iceberg's proposed `LabelsTable`.

Implementation:

- Keep labels when Doris rebuilds a loaded table. iceberg's REST client
  attaches them to the loaded `BaseTable` as a transient field; they are
  not table metadata, so a `BaseTable` rebuilt from its `TableOperations`
  must pass them explicitly. `IcebergTableCache.TableLease#snapshotReadTable`
  (cached reads), `IcebergStatementScope#snapshotReadTable` (statement
  reads) and `IcebergScanPlanProvider#wrapTableForScan` (Kerberos FileIO
  wrap) used constructors that reset labels to `Labels.EMPTY`, so every
  read saw no labels; they now pass `baseTable.labels()`.
- `labels` is not a `MetadataTableType`, so it is handled next to the
  SDK metadata tables rather than through `MetadataTableUtils`.
  `IcebergConnectorMetadata` lists and resolves it only when the catalog
  flavor is `rest` (no other catalog returns labels), and serves its fixed
  schema and column handles without loading the base table.
- `IcebergScanPlanProvider` plans it as one split. A new split-package
  helper `org.apache.iceberg.IcebergLabelsDataTask` builds an iceberg
  `StaticDataTask` (package-private in the SDK) holding the label rows,
  projected to the requested columns in BE's required-field order; its
  metadata `DataFile` is synthesized, so no storage request is made. The
  task is serialized like every other static metadata table, so BE's
  `IcebergSysTableJniScanner` reads it with no BE change.
- The ordered system-table projection is extracted into
  `projectSystemTableSchema` and shared with the existing path, and the
  base-table load is split out of `resolveSysTable` as
  `resolveSysBaseTable`.
- Labels are not versioned: like `$snapshots`/`$history`, the snapshot pin
  of the handle is ignored, and `$labels` is exempt from the
  resolved-empty-snapshot fence, so a table with no commit still shows its
  labels.

Labels reflect the table load the statement used, so they follow the
iceberg table cache (`meta.cache.iceberg.table.ttl-second`); REFRESH
TABLE picks up new labels. Like the other system tables, a REST catalog
that requires server-side scan planning rejects `$labels`.

### Release note

Support querying REST catalog labels of an iceberg table through the
`tbl$labels` system table.

### Check List (For Author)

- Test: Unit Test
    - Added IcebergLabelsDataTaskTest, the $labels cases in
      IcebergConnectorMetadataSysTableTest,
      IcebergTableCacheTest#snapshotReadTableKeepsCatalogLabels and
      IcebergStatementScopeTest#sharedTableKeepsCatalogLabels.
    - `build.sh --fe` (Maven build cache disabled) passes on top of current
      master. Ran every iceberg and hudi FE unit test with run-fe-ut.sh:
      1990 tests, 0 failures, 5 skipped.
    - Regression test pending a REST fixture that returns labels
      (apache/iceberg#18047).
- Behavior changed: No (adds a new system table for REST catalogs only)
- Does this need documentation: Yes (doc PR to follow)

Co-authored-by: Isaac <[email protected]>
morningman added a commit to apache/doris that referenced this pull request Oct 27, 2026
#69135)

### What problem does this PR solve?

Issue Number: #68916 

Related PR: #69031

Problem Summary: The Iceberg REST catalog spec now lets a catalog return
`labels` with each table load (apache/iceberg#15750): catalog-owned,
per-request metadata such as ownership, classification or cost
attribution, at the table level (`object-labels`) and per field (keyed
by `field-id`). Labels are not part of the table metadata, have no
snapshots and are never written to `metadata.json`. Iceberg 1.12.0
exposes them on the loaded table via `SupportsLabels#labels()`. Doris
had no way to show them.

Add a `t$labels` system table for iceberg tables in a REST catalog:

```sql
    SELECT * FROM rest_ctl.db.tbl$labels;
    scope  | field_id | field_name | key            | value
    object | NULL     | NULL       | owner          | team-a
    field  | 2        | email      | classification | pii
```

One row per label. `field_name` is resolved against the current table
schema and is NULL for a field id that is no longer in it (a dropped
column). The schema mirrors iceberg's proposed `LabelsTable`.

Implementation:

- Keep labels when Doris rebuilds a loaded table. iceberg's REST client
attaches them to the loaded `BaseTable` as a transient field; they are
not table metadata, so a `BaseTable` rebuilt from its `TableOperations`
must pass them explicitly.
`IcebergTableCache.TableLease#snapshotReadTable` (cached reads),
`IcebergStatementScope#snapshotReadTable` (statement reads) and
`IcebergScanPlanProvider#wrapTableForScan` (Kerberos FileIO wrap) used
constructors that reset labels to `Labels.EMPTY`, so every read saw no
labels; they now pass `baseTable.labels()`.
- `labels` is not a `MetadataTableType`, so it is handled next to the
SDK metadata tables rather than through `MetadataTableUtils`.
`IcebergConnectorMetadata` lists and resolves it only when the catalog
flavor is `rest` (no other catalog returns labels), and serves its fixed
schema and column handles without loading the base table.
- `IcebergScanPlanProvider` plans it as one split. A new split-package
helper `org.apache.iceberg.IcebergLabelsDataTask` builds an iceberg
`StaticDataTask` (package-private in the SDK) holding the label rows,
projected to the requested columns in BE's required-field order; its
metadata `DataFile` is synthesized, so no storage request is made. The
task is serialized like every other static metadata table, so BE's
`IcebergSysTableJniScanner` reads it with no BE change.
- The ordered system-table projection is extracted into
`projectSystemTableSchema` and shared with the existing path, and the
base-table load is split out of `resolveSysTable` as
`resolveSysBaseTable`.
- Labels are not versioned: like `$snapshots`/`$history`, the snapshot
pin of the handle is ignored, and `$labels` is exempt from the
resolved-empty-snapshot fence, so a table with no commit still shows its
labels.

Labels reflect the table load the statement used, so they follow the
iceberg table cache (`meta.cache.iceberg.table.ttl-second`); REFRESH
TABLE picks up new labels. Like the other system tables, a REST catalog
that requires server-side scan planning rejects `$labels`.

Co-authored-by: Isaac <[email protected]>
Co-authored-by: morningman <[email protected]>
laskoviymishka added a commit to laskoviymishka/lakekeeper that referenced this pull request Oct 28, 2026
Add the Iceberg REST Catalog `labels` wire types from
apache/iceberg#15750 and an optional `labels` member on
`LoadTableResult` and `LoadViewResult`, ahead of exposing Governance
Tags as labels (lakekeeper#2078).

- `Labels { object-labels, fields }` and `FieldLabels { field-id,
  labels }` in `iceberg_ext::catalog::rest`, matching the
  `Labels` / `FieldLabels` / `CatalogObjectLabels` schemas in the
  vendored spec. `field-id` and `labels` are required on a field entry;
  both `Labels` members are optional.
- `Labels::from_maps` builds the canonical form: keys ordered, fields
  sorted by field id with one entry each, empty entries dropped, and
  `None` when nothing is left. Equal label sets built this way serialize
  to the same bytes, which a label-aware `loadTable` ETag will rely on.
- Empty members and an absent `labels` are omitted on serialize and
  default on deserialize, so responses without labels still parse.
- Every producer sets `labels: None`, so no response body changes and
  the `loadTable` ETag needs no new input yet.

Downstream: `LoadTableResult` and `LoadViewResult` gain a public field.
Code building them with struct-literal syntax must add `labels: None`.

Co-authored-by: Isaac <[email protected]>
laskoviymishka added a commit to laskoviymishka/lakekeeper that referenced this pull request Oct 28, 2026
`loadTable` and `loadView` now return Governance Tags as Iceberg REST
Catalog labels (apache/iceberg#15750), so query engines receive them
without the Management API (lakekeeper#2078).

- Object labels are the object's effective tags, the set the
  Management API lists with `effective=true`: inherited from namespaces
  and warehouse, nearest attachment winning. Read with the same
  `list_effective_tag_candidates` + `resolve_effective_tags` path, so
  there is no second inheritance algorithm.
- Field labels (tables only) are the direct column tags, keyed by
  field id, so they follow a renamed column. A label is returned while
  some schema in the table metadata has its field id.
- Tag names are label keys unchanged. Markers map to "true";
  free-text and enumerated tags keep their value. No tags, no `labels`.
- Visibility follows `effective=true`: whoever may load the object,
  including through a view that reads the table on the caller's
  behalf, sees its labels. A failed tag read fails the load.
- Create and register responses carry no labels, and neither do their
  idempotent replays: `load_table_with_flags` takes a `TableLabels`
  mode, and replays pass `Omit`, reading nothing, so a replay keeps the
  original body and tag. Commit responses have no `labels` member.

Reads: two queries per load (effective tags, column tags; one for a
view), run concurrently on the read pool before the metadata
transaction opens, so no second connection is held alongside it. The
count does not depend on the number of ancestors, tags or columns.
They also run on a load answered with 304, which needs them.

Conditional `loadTable`: a tag change on the table, a column or an
ancestor changes the body while the metadata location stays, so the
labels are read before the 304 check and their digest is a
`TableResponseShape` input. Absent labels hash nothing: a table without
labels keeps its existing `lk3` tag and body, so the prefix stays and
an upgrade forces no fleet-wide reload. The digest is computed from the
canonical `Labels::from_maps` value before the schema filter, which
depends only on the metadata the tag already covers.

`load_table_with_flags` stays under its pre-change length: the
warehouse refetch and the storage-config generation move into helpers.

Tests: projection and schema-filter unit tests; ETag tests pinning the
unlabelled tag, a labelled tag, and digest separation; a 304 test for
label changes; Postgres integration tests for inheritance, override,
value kinds, column grouping, rename and drop of a tagged column,
views, a `createTable` replay, and the ETag across namespace, table and
column tag changes and their removal.

Co-authored-by: Isaac <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants