Skip to content

[feature](fe) Support iceberg REST catalog labels as the $labels table - #69135

Merged
morningman merged 2 commits into
apache:masterfrom
laskoviymishka:iceberg-1.12-bump
Oct 27, 2026
Merged

morningman merged 2 commits into
apache:masterfrom
laskoviymishka:iceberg-1.12-bump

Conversation

@laskoviymishka

@laskoviymishka laskoviymishka commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

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:

    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, Regression 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: test_iceberg_rest_labels (external_table_p0/iceberg). The docker REST fixture returns no labels (the upstream fixture option is OpenAPI: emit labels from the REST server test fixture iceberg#18047), so the suite starts a small HTTP proxy inside the regression runner, in front of that fixture, which adds labels to load-table responses; the iceberg REST client, FE planning and the BE JNI scanner run unmodified, and no docker change is needed. It covers object, field and nested-field labels, column order and subsets, filters and COUNT, a table with no snapshot, RENAME/DROP COLUMN, FOR VERSION AS OF, the table cache and REFRESH TABLE, a vended-credentials catalog, and the rejection on a non-REST catalog.
  • Behavior changed: No (adds a new system table for REST catalogs only)
  • Does this need documentation: Yes (doc PR to follow)

What problem does this PR solve?

Issue Number: close #xxx

Related PR: #xxx

Problem Summary:

Release note

None

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes.
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@morningman

Copy link
Copy Markdown
Contributor

run buildall

@morningman

Copy link
Copy Markdown
Contributor

/review

@github-actions github-actions Bot 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.

Reviewed all 11 changed paths at 41f37f514bee86fb25738207246bf45e197a4751, including the REST label load, table wrappers, system-table binding, scan planning, serialized split and BE JNI consumer. No actionable issue was found. There were no existing inline findings to carry forward and no additional user focus points.

The reviewed execution chain is:

REST table load + labels -> statement/cache read table -> fixed labels schema and handles
  -> ordered StaticDataTask rows -> existing serialized_split/JNI range
  -> BE positional reader -> query predicates and aggregation
Required checkpoint Conclusion
Goal and evidence REST catalogs expose object and field labels, including null names for dropped fields. Added unit assertions cover row content, projection, serialization, discovery and wrapper retention.
Scope and clarity Changes are focused; shared projection and base-table resolution preserve the existing metadata-table logic.
Concurrency and locking No new threads or locks. Existing statement memoization and atomic leases protect the same owners; label references are immutable.
Lifecycle and initialization Labels survive all three read BaseTable rebuilds; existing resource cleanup and metadata freezing remain intact. Other rebuilds are write/procedure paths. No new resource cycle or static-order dependency.
Configuration No new setting. Existing REST flavor and table-cache TTL/refresh policy govern availability and freshness.
Compatibility No SPI, persisted-format or wire-field change. The split contains existing SDK classes and row values; the existing mixed-SDK-version restriction remains.
Thrift/protobuf IDs Not applicable: no gensrc changes.
Parallel paths Cached, direct, statement and authenticated read wrappers preserve labels; metadata schema/column overloads, existing metadata tables and native position-deletes routing were checked.
Conditions and errors REST-only exposure, labels dispatch and the empty-snapshot exemption match the feature's semantics. Server-side scan planning remains explicitly rejected; residual predicates remain on BE.
Test coverage New tests cover object/field/dropped-field/empty labels, reordered projection, serialization, REST-only metadata and cache/scope preservation. No direct labels planScan or SQL integration case is added.
Changed expected results No regression result files changed. Unit expectations match the inspected schema and producer.
Observability Existing load/planning error boundaries remain. No new persistent or distributed operation requires additional logging or metrics.
Transactions and persistence Read-only catalog enrichment; no EditLog, replay, commit or failover state changes.
Writes and crash handling No new data write. Existing lease and FileIO ownership remains unchanged.
FE-BE variables Existing serialized_split and JNI format carry the task; no new protocol field or consumer class is required.
Performance Linear label materialization into one split. The synthetic metadata file avoids a storage HEAD; ordered projection matches BE's positional reads. No new locking or table-data scan.
Additional edge cases COUNT retains a readable slot and avoids base-table count pushdown; empty tables may expose labels; unknown statistics cannot become base-table cardinality. No unresolved static candidate remains.

The SDK checks used the pinned Iceberg 1.12.0 sources: StaticDataTask projects in-memory rows without reading its synthetic file, and BaseTable keeps labels outside table metadata and serialization.

Validation limits: this was a static-only review. No builds or tests were run, and the author's reported results were not independently reproduced. The added tests do not exercise a REST-to-BE SQL query. All changed paths and identified risks received static coverage; there are no unresolved findings or uncovered changed paths.

Review converged after 1 round(s).

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28508 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 41f37f514bee86fb25738207246bf45e197a4751, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17606	5186	5029	5029
q2	2114	345	256	256
q3	10200	1399	809	809
q4	4677	460	340	340
q5	7424	786	532	532
q6	186	185	153	153
q7	743	790	595	595
q8	9303	1343	1095	1095
q9	5510	4221	4209	4209
q10	6843	1296	1027	1027
q11	430	244	229	229
q12	635	396	289	289
q13	18097	3618	3022	3022
q14	286	268	245	245
q15	q16	742	699	654	654
q17	1205	763	595	595
q18	7102	6182	6121	6121
q19	1104	949	596	596
q20	383	341	228	228
q21	5370	2237	2288	2237
q22	343	289	247	247
Total cold run time: 100303 ms
Total hot run time: 28508 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5607	5584	5569	5569
q2	665	576	536	536
q3	5202	5397	4905	4905
q4	1993	2138	1539	1539
q5	5262	5124	5678	5124
q6	273	197	156	156
q7	2369	2107	1902	1902
q8	3097	2922	2619	2619
q9	8040	8011	8044	8011
q10	3752	3643	3220	3220
q11	618	430	425	425
q12	677	706	514	514
q13	3488	3872	3213	3213
q14	303	303	283	283
q15	q16	710	712	644	644
q17	8358	7431	7376	7376
q18	13035	12187	12921	12187
q19	949	854	855	854
q20	2140	2133	1949	1949
q21	5714	4583	4815	4583
q22	487	444	401	401
Total cold run time: 72739 ms
Total hot run time: 66010 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 150765 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 41f37f514bee86fb25738207246bf45e197a4751, data reload: false

query5	4318	567	444	444
query6	441	205	184	184
query7	4829	445	223	223
query8	320	166	151	151
query9	8782	3139	3109	3109
query10	492	303	254	254
query11	5963	3827	3568	3568
query12	141	89	86	86
query13	1246	423	306	306
query14	6072	3880	3621	3621
query14_1	3387	3413	3382	3382
query15	204	196	188	188
query16	952	443	393	393
query17	829	637	519	519
query18	2411	421	309	309
query19	186	160	129	129
query20	82	93	80	80
query21	215	129	105	105
query22	13020	12981	12778	12778
query23	13503	12908	12396	12396
query23_1	12592	12597	12645	12597
query24	7389	829	463	463
query24_1	481	465	469	465
query25	563	400	336	336
query26	1248	271	134	134
query27	2764	431	267	267
query28	4640	1940	1958	1940
query29	1542	573	427	427
query30	297	204	176	176
query31	1010	921	819	819
query32	144	89	91	89
query33	515	305	232	232
query34	899	836	494	494
query35	727	760	658	658
query36	804	805	725	725
query37	122	97	76	76
query38	1764	1754	1674	1674
query39	773	809	737	737
query39_1	721	755	734	734
query40	219	115	95	95
query41	65	63	63	63
query42	86	84	82	82
query43	344	367	306	306
query44	1292	683	687	683
query45	184	186	162	162
query46	832	971	558	558
query47	2811	2998	2925	2925
query48	300	310	213	213
query49	579	412	293	293
query50	678	268	202	202
query51	10278	10417	10220	10220
query52	79	82	67	67
query53	183	211	149	149
query54	250	211	218	211
query55	71	67	63	63
query56	223	211	209	209
query57	1668	1622	1570	1570
query58	284	271	261	261
query59	2069	2157	1987	1987
query60	306	249	236	236
query61	167	164	166	164
query62	396	348	301	301
query63	185	155	161	155
query64	2838	1046	852	852
query65	3318	3260	3274	3260
query66	1816	428	306	306
query67	20265	20449	20036	20036
query68	3006	997	622	622
query69	392	312	244	244
query70	843	757	760	757
query71	309	236	207	207
query72	2868	2557	2316	2316
query73	511	540	293	293
query74	4635	4483	4319	4319
query75	2328	2302	1915	1915
query76	2321	1003	588	588
query77	348	368	297	297
query78	9840	9905	9220	9220
query79	981	867	504	504
query80	1434	436	341	341
query81	595	318	262	262
query82	612	130	101	101
query83	281	202	180	180
query84	311	122	105	105
query85	1090	435	385	385
query86	397	243	232	232
query87	1981	1935	1777	1777
query88	3684	2686	2649	2649
query89	339	283	247	247
query90	1813	162	154	154
query91	162	162	152	152
query92	94	90	90	90
query93	1004	979	580	580
query94	645	311	275	275
query95	564	330	366	330
query96	673	475	234	234
query97	2448	2423	2305	2305
query98	160	152	149	149
query99	771	762	653	653
Total cold run time: 235329 ms
Total hot run time: 150765 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.33 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 41f37f514bee86fb25738207246bf45e197a4751, data reload: false

query1	0.00	0.00	0.01
query2	0.09	0.06	0.05
query3	0.26	0.09	0.09
query4	1.61	0.10	0.11
query5	0.24	0.25	0.25
query6	1.17	0.69	0.67
query7	0.04	0.01	0.01
query8	0.05	0.03	0.04
query9	0.40	0.33	0.36
query10	0.57	0.61	0.62
query11	0.20	0.15	0.14
query12	0.20	0.15	0.15
query13	0.50	0.50	0.48
query14	1.03	1.02	1.02
query15	0.63	0.59	0.59
query16	0.36	0.35	0.37
query17	1.19	1.16	1.12
query18	0.20	0.18	0.18
query19	2.12	2.01	2.01
query20	0.02	0.02	0.01
query21	15.42	0.19	0.14
query22	4.92	0.05	0.06
query23	16.28	0.32	0.13
query24	2.72	0.55	0.32
query25	0.10	0.05	0.07
query26	1.24	0.21	0.14
query27	0.05	0.05	0.05
query28	4.04	0.81	0.35
query29	12.51	4.19	3.31
query30	0.28	0.13	0.10
query31	2.78	0.55	0.32
query32	3.22	0.61	0.50
query33	3.10	3.17	3.17
query34	15.50	3.99	3.39
query35	3.39	3.32	3.35
query36	0.56	0.43	0.43
query37	0.09	0.06	0.06
query38	0.05	0.04	0.04
query39	0.04	0.04	0.04
query40	0.17	0.15	0.14
query41	0.08	0.03	0.03
query42	0.03	0.03	0.03
query43	0.05	0.03	0.03
Total cold run time: 97.5 s
Total hot run time: 24.33 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage `` 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 100% (0/0) 🎉
Increment coverage report
Complete coverage report

@laskoviymishka
laskoviymishka marked this pull request as ready for review October 20, 2026 09:55
@morningman

Copy link
Copy Markdown
Contributor

Hi @laskoviymishka , LGTM, I will help to add some e2e tests later

@laskoviymishka

Copy link
Copy Markdown
Contributor Author

thanks @morningman !

morningman added a commit to laskoviymishka/doris that referenced this pull request Oct 22, 2026
…m table

### What problem does this PR solve?

Issue Number: apache#68916

Related PR: apache#69135

Problem Summary: t$labels was covered by unit tests only: nothing ran a
REST table load that carries labels through FE planning and the BE JNI
system-table scanner. No REST fixture returns labels yet (the upstream
fixture option is apache/iceberg#18047), so the new suite
test_iceberg_rest_labels starts a small HTTP proxy inside the regression
runner, in front of the docker iceberg REST fixture. The proxy forwards
every request unchanged and adds the suite's labels to load-table
responses; the iceberg REST client, FE and BE run unmodified. It needs
no docker change and no new image.

The suite checks:
- object labels, field labels and a nested field named by its full path
  (address.city), including a non-ASCII value;
- another column order, a column subset, filters, and COUNT(*), which
  counts the labels (7) rather than the table's records (2);
- a table with no snapshot still shows its labels; a table without
  labels, and the labelled table read without the proxy, show none;
- a catalog with vended credentials, which has no table cache, shows the
  labels too;
- after RENAME COLUMN a label follows the new name, after DROP COLUMN
  its field_name is NULL; FOR VERSION AS OF is ignored, because labels
  are not versioned, and names still come from the current schema;
- the cached catalog keeps the loaded labels until REFRESH TABLE, while a
  catalog with meta.cache.iceberg.table.ttl-second = 0 sees new labels at
  once;
- a hadoop catalog rejects t$labels with "Unknown sys table".

### Release note

None

### Check List (For Author)

- Test: Regression test
    - test_iceberg_rest_labels passes locally against the iceberg docker
      fixture, with FE built from this PR merged into master.
    - Mutation check: dropping the labels in
      IcebergTableCache.TableLease#snapshotReadTable, building the labels
      split without the requested projection, or offering t$labels on
      every catalog type each makes the suite fail.
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@morningman

Copy link
Copy Markdown
Contributor

@laskoviymishka As mentioned, I added the e2e test. I pushed three commits on top of 41f37f5, without a force push, so your commit is untouched:

  1. Merge of apache/master. [fix](iceberg) Align scan schema carrier names by field id #69382, merged today, renamed IcebergScanPlanProvider#requestedLowerNames to requestedColumnNames. CI builds the PR merged into master, and there the new doPlanLabelsSystemTableScan no longer compiled: cannot find symbol: method requestedLowerNames.
  2. [fix](fe) Project $labels with requestedColumnNames after the master rename: the one-line follow-up. The labels schema is all lower case, so the projected schema is unchanged.
  3. [test](regression) Add an e2e test for the iceberg REST $labels system table: the new suite test_iceberg_rest_labels.

How the test gets labels. No REST fixture returns labels yet (apache/iceberg#18047 adds an option to the upstream one), so the suite starts a small HTTP proxy inside the regression runner, in front of the docker REST fixture. The proxy forwards every request unchanged and adds the suite's labels to load-table responses. Everything after the HTTP response is the real path: the iceberg 1.12 REST client parses the labels, FE plans the split, and BE reads it through IcebergSysTableJniScanner. No docker change and no new image.

What it checks.

  • object, field and nested-field labels (address.city), including a non-ASCII value
  • another column order, a column subset, filters, and COUNT(*), which returns 7 labels on a 2-row table
  • a table with no snapshot shows its labels; a table without labels, and the labelled table read without the proxy, show none
  • a vended-credentials catalog, which has no table cache, shows the labels
  • after RENAME COLUMN a label follows the new name, after DROP COLUMN its field_name is NULL; FOR VERSION AS OF is ignored and names still come from the current schema
  • the cached catalog keeps the loaded labels until REFRESH TABLE; with meta.cache.iceberg.table.ttl-second = 0 new labels show up at once
  • a hadoop catalog rejects t$labels with Unknown sys table

Mutation check, one change at a time: dropping the labels in IcebergTableCache.TableLease#snapshotReadTable, building the split without the requested projection, or offering $labels on every catalog type each makes the suite fail. Two parts of the PR cannot be reached from SQL today, so only the unit tests cover them:

  • IcebergStatementScope#snapshotReadTable. Inside a statement the table comes from a cache lease (cache on) or the tracked raw table (cache off, e.g. vended credentials); this rebuild only runs outside a statement scope, or without a resource tracker as in the unit test.
  • The $labels exemption from the resolved-empty-snapshot fence. A system-table scan never gets a query-begin snapshot pin, and an explicit FOR VERSION/TIME AS OF resolves to a real snapshot, so the fence never applies to $labels.

For the doc PR: key is a reserved word in Doris, so the column has to be quoted: SELECT `key`, value FROM tbl$labels.

Local verification: the suite passes against the iceberg docker fixture with FE built from this PR merged into master (BE from a recent master build, since the PR has no BE change), and all 1646 iceberg connector FE unit tests pass on the same tree.

@morningman

Copy link
Copy Markdown
Contributor

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28647 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 253e13a7c2f4339b576183cea2498c531fae95a7, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17593	5174	5054	5054
q2	2135	336	261	261
q3	10190	1423	822	822
q4	4679	464	334	334
q5	7416	794	531	531
q6	180	183	153	153
q7	734	791	593	593
q8	9256	1334	1066	1066
q9	5477	4181	4157	4157
q10	6818	1287	1017	1017
q11	424	247	226	226
q12	630	395	275	275
q13	18097	3655	3011	3011
q14	272	275	248	248
q15	q16	727	721	673	673
q17	1199	744	576	576
q18	7000	6186	6125	6125
q19	1105	956	602	602
q20	419	338	228	228
q21	5913	2491	2421	2421
q22	368	347	274	274
Total cold run time: 100632 ms
Total hot run time: 28647 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5938	5921	6131	5921
q2	746	601	593	593
q3	5601	5766	5376	5376
q4	2026	2194	1547	1547
q5	5864	5760	5686	5686
q6	234	186	144	144
q7	2267	1998	1890	1890
q8	2989	2622	2678	2622
q9	8104	7906	7586	7586
q10	3623	3609	3127	3127
q11	578	407	374	374
q12	662	666	507	507
q13	3252	3534	3041	3041
q14	301	299	277	277
q15	q16	649	697	602	602
q17	7798	7065	6884	6884
q18	13006	12165	12756	12165
q19	908	791	811	791
q20	2167	2170	1937	1937
q21	5556	4592	4694	4592
q22	485	483	440	440
Total cold run time: 72754 ms
Total hot run time: 66102 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 150440 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 253e13a7c2f4339b576183cea2498c531fae95a7, data reload: false

query5	4335	577	437	437
query6	421	205	198	198
query7	4810	438	226	226
query8	326	164	152	152
query9	8788	3071	3079	3071
query10	480	296	248	248
query11	5988	3839	3569	3569
query12	145	89	86	86
query13	1243	454	320	320
query14	6144	3912	3599	3599
query14_1	3382	3405	3428	3405
query15	205	198	184	184
query16	936	437	397	397
query17	860	660	537	537
query18	2421	420	306	306
query19	193	179	139	139
query20	85	91	85	85
query21	203	126	109	109
query22	12920	13096	12849	12849
query23	13555	12785	12335	12335
query23_1	12465	12537	12563	12537
query24	7374	810	467	467
query24_1	477	470	479	470
query25	544	423	356	356
query26	1255	258	143	143
query27	2734	442	270	270
query28	4608	1973	1952	1952
query29	1577	599	454	454
query30	306	221	189	189
query31	1015	942	817	817
query32	155	95	92	92
query33	534	305	248	248
query34	884	843	490	490
query35	727	787	646	646
query36	812	814	705	705
query37	126	103	89	89
query38	1764	1749	1651	1651
query39	797	807	735	735
query39_1	730	723	744	723
query40	223	114	99	99
query41	72	68	68	68
query42	92	83	86	83
query43	341	366	312	312
query44	1292	691	705	691
query45	180	182	162	162
query46	831	966	595	595
query47	2942	2968	2776	2776
query48	288	297	214	214
query49	587	400	286	286
query50	656	265	206	206
query51	10513	10372	10336	10336
query52	87	79	70	70
query53	182	211	147	147
query54	234	188	175	175
query55	67	69	61	61
query56	220	210	219	210
query57	1637	1614	1546	1546
query58	288	266	251	251
query59	2102	2183	1946	1946
query60	273	237	221	221
query61	165	144	140	140
query62	382	332	281	281
query63	189	152	149	149
query64	2776	944	765	765
query65	3324	3266	3268	3266
query66	1791	417	295	295
query67	20296	20099	20017	20017
query68	3248	984	622	622
query69	409	295	240	240
query70	833	778	750	750
query71	296	244	213	213
query72	2732	2649	2317	2317
query73	530	529	288	288
query74	4680	4546	4340	4340
query75	2354	2281	1937	1937
query76	2403	1008	585	585
query77	370	374	273	273
query78	10017	9699	9172	9172
query79	959	906	507	507
query80	741	448	327	327
query81	571	310	263	263
query82	592	128	103	103
query83	298	196	181	181
query84	317	122	102	102
query85	842	424	369	369
query86	349	237	232	232
query87	2013	1947	1804	1804
query88	3638	2662	2645	2645
query89	328	280	243	243
query90	1789	165	165	165
query91	162	154	131	131
query92	99	84	89	84
query93	986	982	568	568
query94	493	293	266	266
query95	569	422	298	298
query96	669	488	233	233
query97	2479	2407	2319	2319
query98	162	160	148	148
query99	770	757	661	661
Total cold run time: 234682 ms
Total hot run time: 150440 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.21 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 253e13a7c2f4339b576183cea2498c531fae95a7, data reload: false

query1	0.01	0.00	0.00
query2	0.09	0.05	0.05
query3	0.27	0.09	0.09
query4	1.61	0.10	0.10
query5	0.24	0.25	0.24
query6	1.17	0.69	0.68
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.41	0.36	0.35
query10	0.60	0.58	0.59
query11	0.20	0.14	0.13
query12	0.18	0.14	0.15
query13	0.47	0.48	0.49
query14	1.02	1.01	1.01
query15	0.63	0.60	0.60
query16	0.32	0.35	0.31
query17	1.17	1.14	1.14
query18	0.21	0.19	0.19
query19	2.09	2.02	2.01
query20	0.02	0.02	0.01
query21	15.50	0.19	0.13
query22	4.89	0.05	0.06
query23	16.25	0.33	0.13
query24	3.06	0.53	0.35
query25	0.11	0.06	0.07
query26	0.79	0.22	0.16
query27	0.04	0.03	0.05
query28	3.31	0.78	0.34
query29	12.52	4.10	3.25
query30	0.28	0.12	0.09
query31	2.77	0.56	0.32
query32	3.22	0.59	0.49
query33	3.26	3.18	3.22
query34	15.43	3.98	3.36
query35	3.33	3.33	3.34
query36	0.54	0.42	0.44
query37	0.10	0.06	0.07
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.15	0.14
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.05	0.03	0.03
Total cold run time: 96.65 s
Total hot run time: 24.21 s

@laskoviymishka

Copy link
Copy Markdown
Contributor Author

@morningman awesome! thanks!

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 100% (0/0) 🎉
Increment coverage report
Complete coverage report

@morningman

Copy link
Copy Markdown
Contributor

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Review context preparation failed before Codex ran; inspect the 'Prepare authoritative PR context and required AGENTS guides' step.
Workflow run: https://github.com/apache/doris/actions/runs/39896958224

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

laskoviymishka and others added 2 commits October 27, 2026 10:49
…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 <no-reply@databricks.com>
…m table

### What problem does this PR solve?

Issue Number: apache#68916

Related PR: apache#69135

Problem Summary: t$labels was covered by unit tests only: nothing ran a
REST table load that carries labels through FE planning and the BE JNI
system-table scanner. No REST fixture returns labels yet (the upstream
fixture option is apache/iceberg#18047), so the new suite
test_iceberg_rest_labels starts a small HTTP proxy inside the regression
runner, in front of the docker iceberg REST fixture. The proxy forwards
every request unchanged and adds the suite's labels to load-table
responses; the iceberg REST client, FE and BE run unmodified. It needs
no docker change and no new image.

The suite checks:
- object labels, field labels and a nested field named by its full path
  (address.city), including a non-ASCII value;
- another column order, a column subset, filters, and COUNT(*), which
  counts the labels (7) rather than the table's records (2);
- a table with no snapshot still shows its labels; a table without
  labels, and the labelled table read without the proxy, show none;
- a catalog with vended credentials, which has no table cache, shows the
  labels too;
- after RENAME COLUMN a label follows the new name, after DROP COLUMN
  its field_name is NULL; FOR VERSION AS OF is ignored, because labels
  are not versioned, and names still come from the current schema;
- the cached catalog keeps the loaded labels until REFRESH TABLE, while a
  catalog with meta.cache.iceberg.table.ttl-second = 0 sees new labels at
  once;
- a hadoop catalog rejects t$labels with "Unknown sys table".

### Release note

None

### Check List (For Author)

- Test: Regression test
    - test_iceberg_rest_labels passes locally against the iceberg docker
      fixture, with FE built from this PR merged into master.
    - Mutation check: dropping the labels in
      IcebergTableCache.TableLease#snapshotReadTable, building the labels
      split without the requested projection, or offering t$labels on
      every catalog type each makes the suite fail.
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@morningman

Copy link
Copy Markdown
Contributor

/review

@github-actions github-actions Bot 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.

Reviewed all 13 changed files and the upstream/downstream read path at cdfb6c4. No actionable defect was found. There are no existing inline findings to retain, and no additional user focus was supplied.

Critical checkpoint conclusions:

  1. Goal and evidence: the REST-only t$labels view follows base-handle resolution, fixed schema/column binding, the statement's loaded table, materialized label rows, and the existing JNI metadata reader. Unit and regression cases exercise the intended semantics in source; their execution was not verified in this review.
  2. Scope and clarity: changes stay within the Iceberg connector, its tests, and a narrow ImportControl exception. The extracted ordered projection retains the existing metadata-table dependency handling and avoids a second implementation.
  3. Concurrency: production code adds no worker, lock, or mutable shared container. Labels are read from the statement/cache table generation. The regression proxy uses a concurrent map and shuts down its executor in finally; existing production lock order is unchanged.
  4. Lifecycle: cached, direct, and authenticated read-table rebuilds preserve transient labels. Existing statement leases and resource cleanup remain in control; the serialized task holds materialized scalar rows rather than a table, FileIO, or authenticator reference.
  5. Configuration: no configuration item is added. Existing table-cache TTL, zero-TTL reads, and REFRESH invalidation govern label freshness as described.
  6. Compatibility: no shared SPI or storage format changes. Both connector and JNI scanner use Iceberg 1.12.0, and the task consists of existing SDK classes. The SDK constructor and row projection match this use (StaticDataTask source). Existing restrictions when FE and BE carry different Iceberg serialization versions remain; rolling-upgrade execution was not tested.
  7. Thrift/Protobuf IDs: neither directory is changed; no field IDs or enum values are introduced.
  8. Parallel paths: cache-backed and direct/vended statement reads preserve labels, as does the authenticated scan wrapper. Other BaseTable wrappers serve writes or procedures and do not feed labels reads. Existing metadata and native position-delete paths retain their dispatch.
  9. Conditional checks: REST-only discovery, fixed-schema dispatch, and the labels-specific empty-snapshot exception are consistent. Ordinary reads retain their snapshot fence. Labels intentionally ignore the selected snapshot and resolve field names against the loaded current schema. Server-side scan-planning rejection remains enforced.
  10. Test coverage: inspected unit cases for discovery, schema/handles, row construction, projection, dropped fields, empty labels, serialization and table-wrapper preservation. The regression suite covers filters, COUNT, nested names, Unicode, an empty base table, schema evolution, time travel, cache/REFRESH, vended reads, and non-REST rejection. Builds, tests, formatting checks and runtime validation were not run under the static-only contract.
  11. Expected results: all 17 result queries have matching output blocks; row counts, column widths/order, null values and cache transitions are internally consistent with the fixture. Output-generation provenance and actual execution cannot be established from static inspection.
  12. Observability: existing query/JNI exception and profile paths cover this metadata view. No additional critical metric or log requirement was identified.
  13. Persistence and failover: labels remain transient catalog response data; no edit-log, replay, or persistent state transition is introduced.
  14. Writes and atomicity: production changes are read-only. No transaction, commit, data-write, or crash-recovery behavior is modified.
  15. FE-to-BE values: the existing serialized-split route carries ordered projected rows. The synthetic zero-size metadata file is internal to the task; the JNI reader iterates rows without opening that file. System handles bypass base-table metadata COUNT and streaming, and predicates remain for BE evaluation.
  16. Performance: label materialization and serialization are linear in returned labels, use one split, and avoid a storage HEAD on the dummy file. No new heavy work under a production lock or obvious performance regression was found.
  17. Core invariants and other checks: no committed-data visibility, visible-version, delete-bitmap, or BE allocation path is changed. Unsupported label sources and missing requested fields fail explicitly. System-table scans are excluded from SQL result caching, avoiding reuse based on unrelated data-table metadata versions. Connector boundaries and the narrow style suppression remain consistent with the repository's architecture.

The review converged after two rounds, including a separate risk challenge and a final pass over the unchanged payload. No unresolved candidate or static coverage gap remains. Validation is limited to source inspection and review-artifact consistency checks; no build, test, benchmark, or FE/BE runtime result is claimed.

Review converged after 2 round(s).

@morningman

Copy link
Copy Markdown
Contributor

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28416 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit cdfb6c4019d876dba56fd6c8819d8d7354b9e125, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17641	5168	5029	5029
q2	2157	305	247	247
q3	10244	1356	819	819
q4	4673	459	333	333
q5	7448	786	527	527
q6	184	192	158	158
q7	757	807	612	612
q8	9257	1308	1163	1163
q9	5409	4165	4330	4165
q10	6834	1280	1036	1036
q11	430	247	239	239
q12	641	401	294	294
q13	18055	3556	2985	2985
q14	268	270	244	244
q15	q16	719	719	681	681
q17	1166	753	576	576
q18	6985	6167	6086	6086
q19	1099	958	599	599
q20	376	340	217	217
q21	4898	2156	2290	2156
q22	354	296	250	250
Total cold run time: 99595 ms
Total hot run time: 28416 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5598	5578	5608	5578
q2	678	576	544	544
q3	5190	5404	4976	4976
q4	2011	2126	1534	1534
q5	5212	5116	5503	5116
q6	273	203	162	162
q7	2310	2068	1877	1877
q8	3009	2680	2657	2657
q9	8012	7910	7908	7908
q10	3712	3814	3231	3231
q11	633	456	423	423
q12	683	702	574	574
q13	3572	3866	3317	3317
q14	326	321	289	289
q15	q16	708	720	649	649
q17	8389	7013	6836	6836
q18	13016	12220	12798	12220
q19	941	817	848	817
q20	2120	2169	1936	1936
q21	5558	4586	4664	4586
q22	498	469	407	407
Total cold run time: 72449 ms
Total hot run time: 65637 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 151295 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit cdfb6c4019d876dba56fd6c8819d8d7354b9e125, data reload: false

query5	4336	597	465	465
query6	445	226	211	211
query7	4833	457	238	238
query8	335	186	168	168
query9	8817	3194	3201	3194
query10	479	330	279	279
query11	6051	3880	3567	3567
query12	147	99	95	95
query13	1319	460	347	347
query14	6153	3933	3686	3686
query14_1	3470	3485	3417	3417
query15	208	201	198	198
query16	973	455	424	424
query17	914	698	595	595
query18	2447	452	366	366
query19	214	189	154	154
query20	89	90	87	87
query21	215	130	112	112
query22	12915	12966	12771	12771
query23	13494	12829	12441	12441
query23_1	12640	12644	12736	12644
query24	7374	851	488	488
query24_1	495	490	495	490
query25	585	473	416	416
query26	1256	270	157	157
query27	2738	454	296	296
query28	4574	1944	1945	1944
query29	1575	603	445	445
query30	302	229	181	181
query31	992	924	819	819
query32	139	99	88	88
query33	526	302	246	246
query34	884	845	506	506
query35	725	754	641	641
query36	812	816	721	721
query37	121	94	86	86
query38	1751	1718	1631	1631
query39	790	774	749	749
query39_1	732	722	735	722
query40	224	118	107	107
query41	72	67	68	67
query42	90	87	84	84
query43	350	374	317	317
query44	1308	680	695	680
query45	182	176	166	166
query46	826	973	556	556
query47	3087	2699	2687	2687
query48	286	299	217	217
query49	588	400	294	294
query50	687	270	203	203
query51	10620	10502	10482	10482
query52	74	83	69	69
query53	181	209	153	153
query54	234	185	178	178
query55	73	68	63	63
query56	239	223	216	216
query57	1652	1596	1540	1540
query58	283	261	255	255
query59	2189	2241	2033	2033
query60	281	237	225	225
query61	144	140	146	140
query62	393	327	282	282
query63	192	152	155	152
query64	2803	933	741	741
query65	3301	3257	3257	3257
query66	1778	416	306	306
query67	20165	20130	19895	19895
query68	2911	1012	622	622
query69	372	285	252	252
query70	856	768	789	768
query71	302	235	208	208
query72	2720	2550	2294	2294
query73	508	516	297	297
query74	4584	4492	4334	4334
query75	2316	2282	1886	1886
query76	2310	1017	603	603
query77	330	368	267	267
query78	9703	9679	9198	9198
query79	982	846	515	515
query80	917	422	343	343
query81	576	309	270	270
query82	580	131	102	102
query83	303	196	179	179
query84	315	119	94	94
query85	847	431	373	373
query86	362	242	231	231
query87	1952	1932	1772	1772
query88	3610	2674	2673	2673
query89	329	281	241	241
query90	1799	171	170	170
query91	156	152	124	124
query92	96	87	92	87
query93	991	995	584	584
query94	511	297	265	265
query95	567	323	317	317
query96	676	483	229	229
query97	2453	2416	2319	2319
query98	160	150	154	150
query99	762	760	642	642
Total cold run time: 234839 ms
Total hot run time: 151295 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.36 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit cdfb6c4019d876dba56fd6c8819d8d7354b9e125, data reload: false

query1	0.01	0.01	0.00
query2	0.09	0.05	0.04
query3	0.26	0.09	0.08
query4	1.61	0.09	0.09
query5	0.23	0.24	0.24
query6	1.17	0.69	0.68
query7	0.03	0.01	0.00
query8	0.05	0.04	0.03
query9	0.39	0.35	0.35
query10	0.56	0.60	0.57
query11	0.20	0.14	0.14
query12	0.19	0.16	0.16
query13	0.49	0.48	0.48
query14	1.03	1.00	1.01
query15	0.62	0.61	0.61
query16	0.35	0.34	0.35
query17	1.13	1.15	1.08
query18	0.19	0.19	0.19
query19	2.05	1.97	2.00
query20	0.02	0.01	0.02
query21	15.45	0.19	0.14
query22	4.94	0.06	0.05
query23	16.26	0.33	0.12
query24	3.11	0.56	0.52
query25	0.10	0.07	0.06
query26	1.07	0.21	0.15
query27	0.04	0.03	0.03
query28	3.73	0.74	0.41
query29	12.49	4.17	3.25
query30	0.27	0.13	0.09
query31	2.78	0.56	0.32
query32	3.22	0.59	0.50
query33	3.20	3.21	3.17
query34	15.39	3.98	3.39
query35	3.33	3.32	3.34
query36	0.54	0.43	0.43
query37	0.09	0.06	0.06
query38	0.05	0.04	0.04
query39	0.03	0.02	0.02
query40	0.18	0.16	0.15
query41	0.10	0.03	0.02
query42	0.04	0.03	0.04
query43	0.04	0.03	0.04
Total cold run time: 97.12 s
Total hot run time: 24.36 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage `` 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 0.00% (0/36) 🎉
Increment coverage report
Complete coverage report

@morningman
morningman merged commit cf2988b into apache:master Oct 27, 2026
37 checks passed
morningman added a commit to apache/doris-website that referenced this pull request Oct 27, 2026
Document the Iceberg REST Catalog `$labels` system table introduced by
apache/doris#69135, available since Doris 5.0.0.

Update the English and Chinese current-version Iceberg Catalog guides
with the field definitions, query examples and expected results, cache
refresh instructions, and restrictions. Cover REST-only availability,
nested fields, schema changes, empty results, snapshot-independent
labels, and the server-side scan planning restriction. Add
support-matrix and system-table version notes.

## Validation

- Docusaurus MDX compilation, front matter, and section-link targets
passed for both pages.
- Internal-link checks passed for both pages; existing external links
are report-only.
- All five SQL and expected-output blocks match between English and
Chinese.
- `git diff --check` passed.
- SEO checking reported existing duplicate-title warnings across
versions and locales; front matter is unchanged.
- AI self-review found no additional issues. Page paths, navigation, and
site code are unchanged. Older versions are not updated because this
feature starts in 5.0.0.

## Versions

- [x] dev
- [ ] 4.x
- [ ] 3.x
- [ ] 2.1 or older (not covered by version/language sync gate)

## Languages

- [x] Chinese
- [x] English

## Docs Checklist

- [x] Checked by AI
- [ ] Test Cases Built (documentation only; no runtime test cases added
or run)
- [x] Updated required version and language counterparts, or explained
why not
- [x] If only one language changed, confirmed whether source/translation
counterparts need sync (not applicable: both languages updated)

Co-authored-by: morningman <moringman@apache.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants