Skip to content

[refactor](fs) Migrate fe-core storage consumers onto the fe-filesystem SPI facade - #66004

Merged
CalvinKirs merged 1 commit into
apache:masterfrom
CalvinKirs:fs-spi-a5-parity
Jul 27, 2026
Merged

[refactor](fs) Migrate fe-core storage consumers onto the fe-filesystem SPI facade#66004
CalvinKirs merged 1 commit into
apache:masterfrom
CalvinKirs:fs-spi-a5-parity

Conversation

@CalvinKirs

@CalvinKirs CalvinKirs commented Jul 24, 2026

Copy link
Copy Markdown
Member

What problem does this PR solve?

Issue Number: close #xxx

Related PR: #65834

Problem Summary:

fe-core carried its own typed storage-properties hierarchy (datasource/property/storage/*, fs/SchemaTypeMapper, fs/StoragePropertiesConverter) in parallel with the fe-filesystem SPI plugins. Every storage consumer in fe-core was coupled to those typed classes, keeping storage knowledge split across two stacks.

This PR migrates every fe-core consumer onto the fe-filesystem SPI behind a single fe-core facade, and deletes the legacy hierarchy entirely (21 main classes + SchemaTypeMapper + StoragePropertiesConverter + 34 legacy-only tests). Gate: grep -rE "datasource\.property\.storage\.|fs\.SchemaTypeMapper|StoragePropertiesConverter" fe/fe-core/src --include=*.java returns empty over ALL of fe-core sources, main and test alike.

Approach

  • SPI readiness: HDFS-family typed properties implement the SPI interfaces; auth is unified on a foundation-level ExecutionAuthenticator (doAs abstraction, no Hadoop types) with UGI holders staying in impl modules; all 14 providers implement supportsExplicit/supportsGuess and self-declare their metadata (storageFamilyName/legacyCacheSchemes/sensitivePropertyKeys); bindPrimary/bindAll mirror the legacy routing exactly (priority order, explicit fs.<x>.support flags disabling guesses, OSS-HDFS/OSS and JFS/HDFS exclusivity, default-HDFS fallback at index 0), and providers absent from the built-in registry are consulted after it in registration order — an out-of-tree S3-compatible plugin needs zero fe-core changes.
  • fe-core facade (datasource/storage): StorageAdapter wraps one SPI binding with the legacy consumer surface (backend map, Hadoop conf, storage name, schemas, type id, URI validation, broker params, AWS credentials providers); StorageRegistry is the single fe-core-side registry (bind order + type ids + uri-scheme table); S3ThriftAdapter (TS3StorageParam) and CloudObjectStoreAdapter (ObjectStoreInfoPB) are verbatim moves of the wire glue; S3ResourceCompat is the verbatim quarantine for the image/DDL-bearing S3 constants and map utilities so S3Resource/AzureResource logic stays untouched.
  • Consumers: CatalogProperty, LocationPath, BrokerDesc/StorageDesc, all TVFs, backup/restore, resources/vaults/policy, connectivity testers, vended credentials, iceberg/paimon/hive integrations and cloud copy-into are re-typed mechanically. Behavior was frozen during the migration by golden parity tests comparing the new path against the legacy classes; with the legacy package now deleted, the surviving tests assert the same expectations as fixed golden values (routing table, wire structs field-by-field, persisted S3Resource maps, guess batteries).
  • Deliberate, test-locked behavior deltas: S3-family no longer silently defaults region to us-east-1 (same error fe-core raised), OSS non-standard endpoints are rewritten like fe-core did, multi-provider backend/Hadoop map merge order is pinned to binding order, and validateAndNormalizeUri failures surface as unchecked StoragePropertiesException with unchanged messages. Deployments must ship the fe-filesystem plugin zips (FE plugin dir), including the broker plugin.

Gson/image shapes, EditLog payloads, BE code and thrift IDL are unchanged (verified field-by-field for every @SerializedName bearer in the diff).

Release note

None

Check List (For Author)

  • Test
    • Unit test: golden tests covering bind routing, wire structs (thrift/PB field-by-field), persisted S3Resource maps, URI normalization matrices, guess batteries and out-of-tree plugin extensibility; full buildall regression matrix green (P0/NonConcurrent/cloud_p0/vault_p0/External/performance).
  • Behavior changed:
    • No.
  • Does this need documentation?
    • No.

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?

@CalvinKirs
CalvinKirs force-pushed the fs-spi-a5-parity branch 2 times, most recently from 9400f70 to 7ce1594 Compare July 24, 2026 10:21
@CalvinKirs

Copy link
Copy Markdown
Member Author

/run buildall

@CalvinKirs

Copy link
Copy Markdown
Member Author

/review

@CalvinKirs
CalvinKirs force-pushed the fs-spi-a5-parity branch 2 times, most recently from bb45242 to a80a7b2 Compare July 24, 2026 10:53
@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Jul 30th, 2026 7:58 AM.
Workflow run: https://github.com/apache/doris/actions/runs/30086219672

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

@CalvinKirs

Copy link
Copy Markdown
Member Author

/review

@CalvinKirs

Copy link
Copy Markdown
Member Author

run buildall

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Jul 30th, 2026 7:58 AM.
Workflow run: https://github.com/apache/doris/actions/runs/30088492903

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

@CalvinKirs

Copy link
Copy Markdown
Member Author

run feut

@CalvinKirs

Copy link
Copy Markdown
Member Author

run external

@CalvinKirs

Copy link
Copy Markdown
Member Author

run performance

@CalvinKirs

Copy link
Copy Markdown
Member Author

run vault_p0

@CalvinKirs

Copy link
Copy Markdown
Member Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17723	3542	3519	3519
q2	1996	300	190	190
q3	10336	1211	729	729
q4	4717	319	230	230
q5	7958	729	476	476
q6	257	147	109	109
q7	731	685	509	509
q8	10570	1441	1585	1441
q9	4600	3098	3032	3032
q10	6737	1209	1018	1018
q11	512	342	308	308
q12	727	525	426	426
q13	17982	2445	1945	1945
q14	231	189	177	177
q15	q16	555	511	459	459
q17	2622	1126	843	843
q18	4844	3558	3416	3416
q19	2076	1223	999	999
q20	790	641	559	559
q21	5850	2565	2268	2268
q22	434	358	297	297
Total cold run time: 102248 ms
Total hot run time: 22950 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3969	3807	3800	3800
q2	262	258	194	194
q3	2667	2775	2575	2575
q4	1371	1340	1054	1054
q5	2667	2541	2536	2536
q6	230	156	103	103
q7	1335	1155	1086	1086
q8	2009	1745	1712	1712
q9	3710	3692	3643	3643
q10	2423	2411	2128	2128
q11	419	333	313	313
q12	572	547	404	404
q13	2568	2441	2006	2006
q14	223	226	203	203
q15	q16	509	527	455	455
q17	1263	1218	1201	1201
q18	5066	4901	4697	4697
q19	1215	1150	1209	1150
q20	1118	1107	977	977
q21	4330	3793	3933	3793
q22	481	455	417	417
Total cold run time: 38407 ms
Total hot run time: 34447 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 117683 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 c5e1adfd01cc44431e33fd6ef5790931826b8132, data reload: false

query5	4329	548	480	480
query6	450	222	204	204
query7	4853	590	322	322
query8	333	185	168	168
query9	8758	3939	3887	3887
query10	483	324	280	280
query11	5553	1342	1235	1235
query12	146	108	94	94
query13	1259	597	421	421
query14	6115	2814	2629	2629
query14_1	2307	2306	2327	2306
query15	194	140	137	137
query16	1044	542	504	504
query17	855	570	457	457
query18	2397	451	299	299
query19	203	184	150	150
query20	96	92	93	92
query21	232	146	127	127
query22	7564	7457	7458	7457
query23	9648	9148	9018	9018
query23_1	9126	9069	9170	9069
query24	7418	1414	1009	1009
query24_1	1017	1016	996	996
query25	539	423	379	379
query26	1305	330	208	208
query27	2633	546	387	387
query28	4437	2169	2147	2147
query29	1060	609	506	506
query30	328	234	221	221
query31	937	635	546	546
query32	112	67	69	67
query33	527	340	243	243
query34	1100	1103	650	650
query35	526	527	453	453
query36	1063	1011	957	957
query37	165	106	93	93
query38	1288	1026	968	968
query39	645	673	615	615
query39_1	651	625	590	590
query40	264	167	150	150
query41	71	71	69	69
query42	94	96	95	95
query43	286	292	252	252
query44	1356	783	794	783
query45	146	132	122	122
query46	1047	1125	709	709
query47	1415	1419	1336	1336
query48	425	474	301	301
query49	594	341	267	267
query50	1099	431	332	332
query51	10827	10979	11258	10979
query52	91	93	77	77
query53	255	270	208	208
query54	293	260	238	238
query55	85	76	71	71
query56	306	300	286	286
query57	887	907	838	838
query58	288	249	270	249
query59	1320	1364	1233	1233
query60	312	274	261	261
query61	180	173	174	173
query62	407	299	273	273
query63	253	203	214	203
query64	2792	850	749	749
query65	2106	2467	2035	2035
query66	1822	395	316	316
query67	16270	16275	16164	16164
query68	3307	1551	975	975
query69	429	308	265	265
query70	1159	1018	977	977
query71	387	345	340	340
query72	2859	2407	2239	2239
query73	798	775	440	440
query74	1729	1542	1456	1456
query75	1594	1495	1282	1282
query76	2322	1000	704	704
query77	331	351	273	273
query78	6054	5896	5485	5485
query79	2438	1008	744	744
query80	1689	576	445	445
query81	511	248	234	234
query82	605	145	114	114
query83	411	336	297	297
query84	338	152	136	136
query85	933	580	495	495
query86	440	328	320	320
query87	1154	1082	1035	1035
query88	3794	2944	2910	2910
query89	413	315	273	273
query90	1917	186	181	181
query91	198	199	161	161
query92	62	58	55	55
query93	1698	1465	1016	1016
query94	751	361	322	322
query95	758	541	447	447
query96	979	751	368	368
query97	1370	1328	1223	1223
query98	179	171	167	167
query99	594	568	507	507
Total cold run time: 208857 ms
Total hot run time: 117683 ms

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.11	0.05	0.04
query3	0.24	0.13	0.12
query4	1.60	0.12	0.12
query5	0.19	0.18	0.17
query6	1.21	0.93	0.99
query7	0.03	0.01	0.00
query8	0.05	0.03	0.04
query9	0.32	0.26	0.26
query10	0.42	0.46	0.43
query11	0.17	0.12	0.13
query12	0.17	0.13	0.13
query13	0.38	0.36	0.38
query14	0.63	0.60	0.59
query15	0.47	0.45	0.46
query16	0.24	0.27	0.25
query17	0.89	0.92	0.92
query18	0.24	0.22	0.22
query19	1.55	1.46	1.49
query20	0.02	0.01	0.01
query21	15.42	0.27	0.15
query22	4.65	0.05	0.05
query23	16.13	0.31	0.13
query24	2.92	0.41	0.31
query25	0.11	0.06	0.05
query26	0.73	0.22	0.14
query27	0.05	0.03	0.04
query28	3.57	0.80	0.47
query29	12.47	4.32	3.45
query30	0.26	0.14	0.15
query31	2.76	0.49	0.22
query32	3.32	0.41	0.28
query33	1.69	1.64	1.67
query34	15.36	2.72	2.14
query35	2.14	2.12	2.12
query36	0.48	0.34	0.32
query37	0.07	0.06	0.05
query38	0.06	0.03	0.04
query39	0.04	0.02	0.02
query40	0.13	0.11	0.10
query41	0.09	0.03	0.03
query42	0.03	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 91.45 s
Total hot run time: 18.54 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 60.91% (787/1292) 🎉
Increment coverage report
Complete coverage report

@CalvinKirs

Copy link
Copy Markdown
Member Author

run feut

@CalvinKirs

Copy link
Copy Markdown
Member Author

run p0

@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR approved by at least one committer and no changes requested.

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 51.02% (674/1321) 🎉
Increment coverage report
Complete coverage report

@CalvinKirs
CalvinKirs merged commit f499c78 into apache:master Jul 27, 2026
36 of 37 checks passed
@CalvinKirs
CalvinKirs deleted the fs-spi-a5-parity branch July 27, 2026 10:27
morningman added a commit that referenced this pull request Jul 27, 2026
…filesystem storage facade

Upstream #66004 ("Migrate fe-core storage consumers onto the fe-filesystem SPI facade")
deleted fe-core's entire typed storage hierarchy (`datasource/property/storage/*`,
`fs/SchemaTypeMapper`, `fs/StoragePropertiesConverter`) and replaced it with the
`datasource/storage` facade (StorageAdapter / StorageTypeId / StorageRegistry).

Our connector-SPI bridge consumed the deleted types but was never touched by #66004, so
git reported no conflict on these files — they simply stopped compiling. Re-typed onto
the facade:

- DefaultConnectorContext: storage supplier + vended-credentials map keyed by
  StorageTypeId/StorageAdapter; StorageProperties.createAll -> StorageAdapter.ofAll;
  LocationPath.of(uri, map) -> LocationPath.ofAdapters(uri, map).
- PluginDrivenExternalCatalog: wires getStorageAdaptersMap() into the context.
- 6 fe-core tests re-typed to match; `HdfsProperties.FS_HDFS_SUPPORT` inlined as the
  "fs.hdfs.support" literal, following upstream (the constant died with the hierarchy).

Two further reconciliations that had no compile signal either:

- fe-kerberos now depends on fe-foundation. #66004 made fe-common's ExecutionAuthenticator
  extend the foundation-level (Hadoop-free) doAs abstraction; P3b had moved that class into
  fe-kerberos, so the moved copy inherited a dependency its pom did not declare.
- Three test fakes (FileSystemPluginManagerTest, DefaultConnectorContextStoragePropsTest,
  FileSystemFactoryBindAllTest) now declare supportsGuess(). #66004 tightened
  FileSystemPluginManager.bindAll so out-of-tree providers are selected by
  supportsExplicit/supportsGuess; a provider overriding only supports() is now logged and
  skipped. The fakes asserted binding, so they had to adopt the new contract — which is
  exactly what a real out-of-tree plugin must now do.

Also repairs IOCallable/SimpleAuthenticationConfig, which git cross-contaminated by pairing
them as a rename during the P3b replay (8-character conflict markers).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 27, 2026
…66004 storage facade)

Also records an upstream-owned regression found while validating: #66004 itself breaks
ForwardToMasterTest by double-nesting the /rest/v2/manager/node/* JSON payload
(its parent 0dde273 is green, and so is this branch before the rebase).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 28, 2026
…filesystem storage facade

Upstream #66004 ("Migrate fe-core storage consumers onto the fe-filesystem SPI facade")
deleted fe-core's entire typed storage hierarchy (`datasource/property/storage/*`,
`fs/SchemaTypeMapper`, `fs/StoragePropertiesConverter`) and replaced it with the
`datasource/storage` facade (StorageAdapter / StorageTypeId / StorageRegistry).

Our connector-SPI bridge consumed the deleted types but was never touched by #66004, so
git reported no conflict on these files — they simply stopped compiling. Re-typed onto
the facade:

- DefaultConnectorContext: storage supplier + vended-credentials map keyed by
  StorageTypeId/StorageAdapter; StorageProperties.createAll -> StorageAdapter.ofAll;
  LocationPath.of(uri, map) -> LocationPath.ofAdapters(uri, map).
- PluginDrivenExternalCatalog: wires getStorageAdaptersMap() into the context.
- 6 fe-core tests re-typed to match; `HdfsProperties.FS_HDFS_SUPPORT` inlined as the
  "fs.hdfs.support" literal, following upstream (the constant died with the hierarchy).

Two further reconciliations that had no compile signal either:

- fe-kerberos now depends on fe-foundation. #66004 made fe-common's ExecutionAuthenticator
  extend the foundation-level (Hadoop-free) doAs abstraction; P3b had moved that class into
  fe-kerberos, so the moved copy inherited a dependency its pom did not declare.
- Three test fakes (FileSystemPluginManagerTest, DefaultConnectorContextStoragePropsTest,
  FileSystemFactoryBindAllTest) now declare supportsGuess(). #66004 tightened
  FileSystemPluginManager.bindAll so out-of-tree providers are selected by
  supportsExplicit/supportsGuess; a provider overriding only supports() is now logged and
  skipped. The fakes asserted binding, so they had to adopt the new contract — which is
  exactly what a real out-of-tree plugin must now do.

Also repairs IOCallable/SimpleAuthenticationConfig, which git cross-contaminated by pairing
them as a rename during the P3b replay (8-character conflict markers).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 28, 2026
…66004 storage facade)

Also records an upstream-owned regression found while validating: #66004 itself breaks
ForwardToMasterTest by double-nesting the /rest/v2/manager/node/* JSON payload
(its parent 0dde273 is green, and so is this branch before the rebase).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 28, 2026
…onnector-paimon

Upstream #65984 adds a relation-scoped `@options('scan.snapshot-id'='1', ...)` clause for
paimon. Its fe-core half is catalog-neutral and merged as-is; its paimon half landed in the
fe-core paimon subsystem this branch deleted (P5-T29), so it moves into the connector.

`@options` is just a sixth time-travel selector, so it rides the existing SPI pipeline rather
than a new one: ConnectorTimeTravelSpec gains Kind.OPTIONS carrying the raw map, paimon's
resolveTimeTravel validates the keys and freezes a mutable selector (scan.mode=latest, a tag,
a wall clock) into an immutable pin, applySnapshot threads it onto the handle, and
resolveScanTable applies it via PaimonScanParams.applyOptions. fe-core never inspects an
option key. BindRelation rejects the clause up front through a new
SUPPORTS_SCAN_PARAM_OPTIONS capability: @options only reaches a connector through the MVCC pin
path, so a table that never enters it would silently answer a historical query with latest data.

Two upstream changes needed no port: initialSelectedPartitions -> NOT_PRUNED and
computeOutput's getFullSchema(scanParams) arm are already covered, because LogicalFileScan
resolves partitions and schema against THIS reference's own version and an explicit pin yields
empty (scan-all) partition maps. Upstream's per-relation resolve-once cache is likewise
provided by StatementContext's version key, which includes the option map.

#65984 also widens paimon system tables from "no scan params at all" to a per-view capability
matrix. The generic pipeline for that already exists (resolveSysTableSnapshotPin +
applyMvccSnapshotPin); it only needed a per-system-table question, so
ConnectorScanPlanProvider gains supportsSystemTableIncrementalRead/Options and the
PluginDrivenScanNode guard now names the missing capability instead of refusing wholesale.
paimon's applySnapshot stops returning system handles unchanged so the pin reaches the view.

Three independent hardenings ride along: the @incr null-reset widens from two keys to paimon's
whole inherited read-state family; a $binlog @incr read vetoes COUNT(*) pushdown (its reader
packs an UPDATE_BEFORE/AFTER pair into one logical row); and the JNI-forced system-table set
picks up row_tracking, now sourced from the same capability table.

Deliberately NOT ported, with the reasons recorded in plan-doc/HANDOFF.md so a later rebase
does not re-add them: upstream re-adds jni.enable_file_reader_async to the backend option
list, but no BE/JNI consumer for it exists anywhere, including upstream master itself (#65955
deleted it) -- this branch already guards that with a test; and the selector-free @options
preload exemption, which would need the connector's option vocabulary before binding and only
affects warmup latency.

Tests: fe-connector-paimon 421/421, fe-connector-api 110/110, fe-connector-iceberg 1151/1151,
fe-core 8335 tests / 0 failures (2 pre-existing errors: upstream #66004's NodeInfo JSON
regression and HFUtilsTest needing network), full FE install + checkstyle green. Upstream's
PaimonScanParamsTest is ported to the connector; its resolution cases now run against real
local paimon tables because no connector module carries mockito. Three mutations verified RED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 28, 2026
…filesystem storage facade

Upstream #66004 ("Migrate fe-core storage consumers onto the fe-filesystem SPI facade")
deleted fe-core's entire typed storage hierarchy (`datasource/property/storage/*`,
`fs/SchemaTypeMapper`, `fs/StoragePropertiesConverter`) and replaced it with the
`datasource/storage` facade (StorageAdapter / StorageTypeId / StorageRegistry).

Our connector-SPI bridge consumed the deleted types but was never touched by #66004, so
git reported no conflict on these files — they simply stopped compiling. Re-typed onto
the facade:

- DefaultConnectorContext: storage supplier + vended-credentials map keyed by
  StorageTypeId/StorageAdapter; StorageProperties.createAll -> StorageAdapter.ofAll;
  LocationPath.of(uri, map) -> LocationPath.ofAdapters(uri, map).
- PluginDrivenExternalCatalog: wires getStorageAdaptersMap() into the context.
- 6 fe-core tests re-typed to match; `HdfsProperties.FS_HDFS_SUPPORT` inlined as the
  "fs.hdfs.support" literal, following upstream (the constant died with the hierarchy).

Two further reconciliations that had no compile signal either:

- fe-kerberos now depends on fe-foundation. #66004 made fe-common's ExecutionAuthenticator
  extend the foundation-level (Hadoop-free) doAs abstraction; P3b had moved that class into
  fe-kerberos, so the moved copy inherited a dependency its pom did not declare.
- Three test fakes (FileSystemPluginManagerTest, DefaultConnectorContextStoragePropsTest,
  FileSystemFactoryBindAllTest) now declare supportsGuess(). #66004 tightened
  FileSystemPluginManager.bindAll so out-of-tree providers are selected by
  supportsExplicit/supportsGuess; a provider overriding only supports() is now logged and
  skipped. The fakes asserted binding, so they had to adopt the new contract — which is
  exactly what a real out-of-tree plugin must now do.

Also repairs IOCallable/SimpleAuthenticationConfig, which git cross-contaminated by pairing
them as a rename during the P3b replay (8-character conflict markers).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 28, 2026
…66004 storage facade)

Also records an upstream-owned regression found while validating: #66004 itself breaks
ForwardToMasterTest by double-nesting the /rest/v2/manager/node/* JSON payload
(its parent 0dde273 is green, and so is this branch before the rebase).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 28, 2026
…onnector-paimon

Upstream #65984 adds a relation-scoped `@options('scan.snapshot-id'='1', ...)` clause for
paimon. Its fe-core half is catalog-neutral and merged as-is; its paimon half landed in the
fe-core paimon subsystem this branch deleted (P5-T29), so it moves into the connector.

`@options` is just a sixth time-travel selector, so it rides the existing SPI pipeline rather
than a new one: ConnectorTimeTravelSpec gains Kind.OPTIONS carrying the raw map, paimon's
resolveTimeTravel validates the keys and freezes a mutable selector (scan.mode=latest, a tag,
a wall clock) into an immutable pin, applySnapshot threads it onto the handle, and
resolveScanTable applies it via PaimonScanParams.applyOptions. fe-core never inspects an
option key. BindRelation rejects the clause up front through a new
SUPPORTS_SCAN_PARAM_OPTIONS capability: @options only reaches a connector through the MVCC pin
path, so a table that never enters it would silently answer a historical query with latest data.

Two upstream changes needed no port: initialSelectedPartitions -> NOT_PRUNED and
computeOutput's getFullSchema(scanParams) arm are already covered, because LogicalFileScan
resolves partitions and schema against THIS reference's own version and an explicit pin yields
empty (scan-all) partition maps. Upstream's per-relation resolve-once cache is likewise
provided by StatementContext's version key, which includes the option map.

#65984 also widens paimon system tables from "no scan params at all" to a per-view capability
matrix. The generic pipeline for that already exists (resolveSysTableSnapshotPin +
applyMvccSnapshotPin); it only needed a per-system-table question, so
ConnectorScanPlanProvider gains supportsSystemTableIncrementalRead/Options and the
PluginDrivenScanNode guard now names the missing capability instead of refusing wholesale.
paimon's applySnapshot stops returning system handles unchanged so the pin reaches the view.

Three independent hardenings ride along: the @incr null-reset widens from two keys to paimon's
whole inherited read-state family; a $binlog @incr read vetoes COUNT(*) pushdown (its reader
packs an UPDATE_BEFORE/AFTER pair into one logical row); and the JNI-forced system-table set
picks up row_tracking, now sourced from the same capability table.

Deliberately NOT ported, with the reasons recorded in plan-doc/HANDOFF.md so a later rebase
does not re-add them: upstream re-adds jni.enable_file_reader_async to the backend option
list, but no BE/JNI consumer for it exists anywhere, including upstream master itself (#65955
deleted it) -- this branch already guards that with a test; and the selector-free @options
preload exemption, which would need the connector's option vocabulary before binding and only
affects warmup latency.

Tests: fe-connector-paimon 421/421, fe-connector-api 110/110, fe-connector-iceberg 1151/1151,
fe-core 8335 tests / 0 failures (2 pre-existing errors: upstream #66004's NodeInfo JSON
regression and HFUtilsTest needing network), full FE install + checkstyle green. Upstream's
PaimonScanParamsTest is ported to the connector; its resolution cases now run against real
local paimon tables because no connector module carries mockito. Three mutations verified RED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…filesystem storage facade

Upstream #66004 ("Migrate fe-core storage consumers onto the fe-filesystem SPI facade")
deleted fe-core's entire typed storage hierarchy (`datasource/property/storage/*`,
`fs/SchemaTypeMapper`, `fs/StoragePropertiesConverter`) and replaced it with the
`datasource/storage` facade (StorageAdapter / StorageTypeId / StorageRegistry).

Our connector-SPI bridge consumed the deleted types but was never touched by #66004, so
git reported no conflict on these files — they simply stopped compiling. Re-typed onto
the facade:

- DefaultConnectorContext: storage supplier + vended-credentials map keyed by
  StorageTypeId/StorageAdapter; StorageProperties.createAll -> StorageAdapter.ofAll;
  LocationPath.of(uri, map) -> LocationPath.ofAdapters(uri, map).
- PluginDrivenExternalCatalog: wires getStorageAdaptersMap() into the context.
- 6 fe-core tests re-typed to match; `HdfsProperties.FS_HDFS_SUPPORT` inlined as the
  "fs.hdfs.support" literal, following upstream (the constant died with the hierarchy).

Two further reconciliations that had no compile signal either:

- fe-kerberos now depends on fe-foundation. #66004 made fe-common's ExecutionAuthenticator
  extend the foundation-level (Hadoop-free) doAs abstraction; P3b had moved that class into
  fe-kerberos, so the moved copy inherited a dependency its pom did not declare.
- Three test fakes (FileSystemPluginManagerTest, DefaultConnectorContextStoragePropsTest,
  FileSystemFactoryBindAllTest) now declare supportsGuess(). #66004 tightened
  FileSystemPluginManager.bindAll so out-of-tree providers are selected by
  supportsExplicit/supportsGuess; a provider overriding only supports() is now logged and
  skipped. The fakes asserted binding, so they had to adopt the new contract — which is
  exactly what a real out-of-tree plugin must now do.

Also repairs IOCallable/SimpleAuthenticationConfig, which git cross-contaminated by pairing
them as a rename during the P3b replay (8-character conflict markers).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…66004 storage facade)

Also records an upstream-owned regression found while validating: #66004 itself breaks
ForwardToMasterTest by double-nesting the /rest/v2/manager/node/* JSON payload
(its parent 0dde273 is green, and so is this branch before the rebase).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…onnector-paimon

Upstream #65984 adds a relation-scoped `@options('scan.snapshot-id'='1', ...)` clause for
paimon. Its fe-core half is catalog-neutral and merged as-is; its paimon half landed in the
fe-core paimon subsystem this branch deleted (P5-T29), so it moves into the connector.

`@options` is just a sixth time-travel selector, so it rides the existing SPI pipeline rather
than a new one: ConnectorTimeTravelSpec gains Kind.OPTIONS carrying the raw map, paimon's
resolveTimeTravel validates the keys and freezes a mutable selector (scan.mode=latest, a tag,
a wall clock) into an immutable pin, applySnapshot threads it onto the handle, and
resolveScanTable applies it via PaimonScanParams.applyOptions. fe-core never inspects an
option key. BindRelation rejects the clause up front through a new
SUPPORTS_SCAN_PARAM_OPTIONS capability: @options only reaches a connector through the MVCC pin
path, so a table that never enters it would silently answer a historical query with latest data.

Two upstream changes needed no port: initialSelectedPartitions -> NOT_PRUNED and
computeOutput's getFullSchema(scanParams) arm are already covered, because LogicalFileScan
resolves partitions and schema against THIS reference's own version and an explicit pin yields
empty (scan-all) partition maps. Upstream's per-relation resolve-once cache is likewise
provided by StatementContext's version key, which includes the option map.

#65984 also widens paimon system tables from "no scan params at all" to a per-view capability
matrix. The generic pipeline for that already exists (resolveSysTableSnapshotPin +
applyMvccSnapshotPin); it only needed a per-system-table question, so
ConnectorScanPlanProvider gains supportsSystemTableIncrementalRead/Options and the
PluginDrivenScanNode guard now names the missing capability instead of refusing wholesale.
paimon's applySnapshot stops returning system handles unchanged so the pin reaches the view.

Three independent hardenings ride along: the @incr null-reset widens from two keys to paimon's
whole inherited read-state family; a $binlog @incr read vetoes COUNT(*) pushdown (its reader
packs an UPDATE_BEFORE/AFTER pair into one logical row); and the JNI-forced system-table set
picks up row_tracking, now sourced from the same capability table.

Deliberately NOT ported, with the reasons recorded in plan-doc/HANDOFF.md so a later rebase
does not re-add them: upstream re-adds jni.enable_file_reader_async to the backend option
list, but no BE/JNI consumer for it exists anywhere, including upstream master itself (#65955
deleted it) -- this branch already guards that with a test; and the selector-free @options
preload exemption, which would need the connector's option vocabulary before binding and only
affects warmup latency.

Tests: fe-connector-paimon 421/421, fe-connector-api 110/110, fe-connector-iceberg 1151/1151,
fe-core 8335 tests / 0 failures (2 pre-existing errors: upstream #66004's NodeInfo JSON
regression and HFUtilsTest needing network), full FE install + checkstyle green. Upstream's
PaimonScanParamsTest is ported to the connector; its resolution cases now run against real
local paimon tables because no connector module carries mockito. Three mutations verified RED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…filesystem storage facade

Upstream #66004 ("Migrate fe-core storage consumers onto the fe-filesystem SPI facade")
deleted fe-core's entire typed storage hierarchy (`datasource/property/storage/*`,
`fs/SchemaTypeMapper`, `fs/StoragePropertiesConverter`) and replaced it with the
`datasource/storage` facade (StorageAdapter / StorageTypeId / StorageRegistry).

Our connector-SPI bridge consumed the deleted types but was never touched by #66004, so
git reported no conflict on these files — they simply stopped compiling. Re-typed onto
the facade:

- DefaultConnectorContext: storage supplier + vended-credentials map keyed by
  StorageTypeId/StorageAdapter; StorageProperties.createAll -> StorageAdapter.ofAll;
  LocationPath.of(uri, map) -> LocationPath.ofAdapters(uri, map).
- PluginDrivenExternalCatalog: wires getStorageAdaptersMap() into the context.
- 6 fe-core tests re-typed to match; `HdfsProperties.FS_HDFS_SUPPORT` inlined as the
  "fs.hdfs.support" literal, following upstream (the constant died with the hierarchy).

Two further reconciliations that had no compile signal either:

- fe-kerberos now depends on fe-foundation. #66004 made fe-common's ExecutionAuthenticator
  extend the foundation-level (Hadoop-free) doAs abstraction; P3b had moved that class into
  fe-kerberos, so the moved copy inherited a dependency its pom did not declare.
- Three test fakes (FileSystemPluginManagerTest, DefaultConnectorContextStoragePropsTest,
  FileSystemFactoryBindAllTest) now declare supportsGuess(). #66004 tightened
  FileSystemPluginManager.bindAll so out-of-tree providers are selected by
  supportsExplicit/supportsGuess; a provider overriding only supports() is now logged and
  skipped. The fakes asserted binding, so they had to adopt the new contract — which is
  exactly what a real out-of-tree plugin must now do.

Also repairs IOCallable/SimpleAuthenticationConfig, which git cross-contaminated by pairing
them as a rename during the P3b replay (8-character conflict markers).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…66004 storage facade)

Also records an upstream-owned regression found while validating: #66004 itself breaks
ForwardToMasterTest by double-nesting the /rest/v2/manager/node/* JSON payload
(its parent 0dde273 is green, and so is this branch before the rebase).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 29, 2026
…onnector-paimon

Upstream #65984 adds a relation-scoped `@options('scan.snapshot-id'='1', ...)` clause for
paimon. Its fe-core half is catalog-neutral and merged as-is; its paimon half landed in the
fe-core paimon subsystem this branch deleted (P5-T29), so it moves into the connector.

`@options` is just a sixth time-travel selector, so it rides the existing SPI pipeline rather
than a new one: ConnectorTimeTravelSpec gains Kind.OPTIONS carrying the raw map, paimon's
resolveTimeTravel validates the keys and freezes a mutable selector (scan.mode=latest, a tag,
a wall clock) into an immutable pin, applySnapshot threads it onto the handle, and
resolveScanTable applies it via PaimonScanParams.applyOptions. fe-core never inspects an
option key. BindRelation rejects the clause up front through a new
SUPPORTS_SCAN_PARAM_OPTIONS capability: @options only reaches a connector through the MVCC pin
path, so a table that never enters it would silently answer a historical query with latest data.

Two upstream changes needed no port: initialSelectedPartitions -> NOT_PRUNED and
computeOutput's getFullSchema(scanParams) arm are already covered, because LogicalFileScan
resolves partitions and schema against THIS reference's own version and an explicit pin yields
empty (scan-all) partition maps. Upstream's per-relation resolve-once cache is likewise
provided by StatementContext's version key, which includes the option map.

#65984 also widens paimon system tables from "no scan params at all" to a per-view capability
matrix. The generic pipeline for that already exists (resolveSysTableSnapshotPin +
applyMvccSnapshotPin); it only needed a per-system-table question, so
ConnectorScanPlanProvider gains supportsSystemTableIncrementalRead/Options and the
PluginDrivenScanNode guard now names the missing capability instead of refusing wholesale.
paimon's applySnapshot stops returning system handles unchanged so the pin reaches the view.

Three independent hardenings ride along: the @incr null-reset widens from two keys to paimon's
whole inherited read-state family; a $binlog @incr read vetoes COUNT(*) pushdown (its reader
packs an UPDATE_BEFORE/AFTER pair into one logical row); and the JNI-forced system-table set
picks up row_tracking, now sourced from the same capability table.

Deliberately NOT ported, with the reasons recorded in plan-doc/HANDOFF.md so a later rebase
does not re-add them: upstream re-adds jni.enable_file_reader_async to the backend option
list, but no BE/JNI consumer for it exists anywhere, including upstream master itself (#65955
deleted it) -- this branch already guards that with a test; and the selector-free @options
preload exemption, which would need the connector's option vocabulary before binding and only
affects warmup latency.

Tests: fe-connector-paimon 421/421, fe-connector-api 110/110, fe-connector-iceberg 1151/1151,
fe-core 8335 tests / 0 failures (2 pre-existing errors: upstream #66004's NodeInfo JSON
regression and HFUtilsTest needing network), full FE install + checkstyle green. Upstream's
PaimonScanParamsTest is ported to the connector; its resolution cases now run against real
local paimon tables because no connector module carries mockito. Three mutations verified RED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit that referenced this pull request Jul 31, 2026
…y the harness (#66195)

### What problem does this PR solve?

FE unit tests dominate CI wall time, and most of that time is not spent
running test logic.

Full attribution of one `fe-core` surefire window (4092s x 12 forks =
**49,104 fork-seconds**, 86% of an 79.7-min build):

| component | fork-seconds | share | nature |
|---|---|---|---|
| JVM lifecycle *between* classes (1219x) | 17,317 | 35.3% | fixed
overhead, proportional to class count |
| non-FE classes: real test execution | 13,259 | 27.0% | **real work** |
| FE startup + teardown (304x) | 7,066 | 14.4% | fixed overhead,
proportional to FE-starting classes |
| FE classes: class loading before FE start | 4,463 | 9.1% | fixed
overhead |
| FE classes: real test execution | 3,797 | 7.7% | **real work** |
| non-FE classes: class-level setup | 3,172 | 6.5% | fixed overhead |

**Real work 17,055s (34.7%) vs fixed overhead 32,019s (65.2%).**
Unattributed: 30s.

The overhead is per *class*, so the only lever with real magnitude is
**reducing class count by merging test classes**. Eliminating one class
is worth **55.2 fork-seconds if it starts an FE** (304 such classes)
versus **16.0 if it does not** (915 classes) — FE-starting classes are
3.5x more valuable to merge.

### What is changed?

Six commits, in dependency order:

1. **Avoid a 10s heartbeat wait in every FE unit test class.**
`Daemon.run()` runs one cycle before sleeping, so `HeartbeatMgr`'s first
cycle completes before `createDorisCluster()` registers a backend;
`checkBEHeartbeat()` then waits a full `heartbeat_interval_second`
(default 10). Sets the interval to 1 before the `Env` singleton is
created, and makes `checkBEHeartbeatStatus()` check-then-sleep at 20ms
granularity instead of sleeping 1s first. Timeout budget unchanged.

2. **Merge 12 tiny nereids test classes into 3 suites.**

3. **Give `DistributeHintTest` assertions and cut its runtime by 95%.**
It enumerated a large parameter space while asserting almost nothing;
now it asserts the distribution actually chosen and covers the space at
a sane size.

4. **Restore spied `Env` fields after every test method**, to stop
unbounded spy nesting leaking across methods.

5. **Merge six command privilege tests into one suite.**

6. **Migrate 25 legacy `UtFrameUtils` test classes to
`TestWithFeService`.** This one saves no time by itself — it is the
precondition for merging, since only classes on the same harness can
share an FE.

### Measurements (CI, build 1009445 unless noted)

| change | measured | note |
|---|---|---|
| heartbeat fix | -3.7s/class, **within noise** | **not the lever** —
see caveat below |
| merge 12 -> 3 classes | 614.4s -> 168.5s | -72.6% |
| `DistributeHintTest` | 458.7s -> 64.2s | -86% |
| merge 6 -> 1 class | 302.8s -> 63.9s | -78.9%, 13 test cases unchanged
|
| migrate 25 classes | `DropTableTest` 4.483s -> 4.566s | no gain by
design; enables merging |

The three verified items save roughly **840 test-seconds** in total.

**Caveat, stated plainly:** at ~7x effective parallelism, and with
per-build noise of 2 sigma = 151s, that lands as only about **2 minutes
of wall clock and is partly masked by noise**. Do not expect the
total-seconds number in the build summary to move cleanly. The
measurement method that does work is per-class comparison against an
unaffected control group, not the build total. The heartbeat fix in
commit 1 in particular looked large in local single-fork measurement and
turned out to be noise-level on CI — it is kept because it is correct
and harmless, not because it is a win.

Full-suite result on build 1009445: **SUCCESS, 8773 passed, 0 failed, 1
muted.** The muted one is `ForwardToMasterTest.testAddBeDropBe`
(ClassCastException), which is known upstream issue #66004 and unrelated
to this branch.

### Rebase note

Rebased onto `af6dcff9051` today. Two files conflicted with upstream,
both against commit 6:

- `FrontendServiceImplTest` — #64304 moved `MCTransaction` /
`MaxComputeExternalCatalog` out of `fe-core`; upstream's
`WriteBlockAllocatingTransaction` mock kept verbatim.
- `DynamicPartitionTableTest` — #65219 added 1478 lines of TIMESTAMPTZ
tests. Resolved by taking upstream's file whole and replaying the
harness migration onto it, then diffing against upstream to confirm only
harness lines moved. All 241 `Assert.*` calls left untouched.

Post-rebase verification: `test-compile` clean, `checkstyle:check`
clean, and the two conflicted classes run **69/69 passing, 0 skipped** —
counts matching upstream's annotation counts exactly.

### A note for reviewers on how this was verified

The JUnit4 -> JUnit5 migration in commit 6 has two failure modes that
**pass silently** rather than erroring:

- `@Rule ExpectedException` is ignored outright by JUnit5, turning six
exception tests into tests that assert nothing. Converted to
`ExceptionChecker.expectThrowsWithMsg`, which matches JUnit4 semantics
(`isInstance` for subclasses, `contains` for messages).
- JUnit4 `Assert.assertEquals(msg, expected, actual)` versus JUnit5
`Assertions.assertEquals(expected, actual, msg)` have **reversed
argument order**. When all three arguments are Strings this compiles
fine and silently compares the wrong things. 36 such call sites exist
across the migrated classes; message-first overloads were identified by
argument *count*, not by whether the first argument looks like a string.

Because of this, `BUILD SUCCESS` is not sufficient evidence for these
commits. Every migrated class was accepted only after its `Tests run:`
count matched the pre-migration count exactly. Reviewers checking this
PR should apply the same standard.

### Release note

None

### Check List

- [x] Test
- [x] Regression test — not applicable, this changes only the FE unit
test harness and test classes
- [x] Unit Test — full FE UT suite green on CI (build 1009445: 8773
passed, 0 failed); post-rebase spot check 69/69 on the two conflicted
classes
- [x] Behavior changed: No (test-only, no production code paths altered)
- [x] Does this need documentation? No
morningman added a commit that referenced this pull request Aug 1, 2026
…mmon (#66324)

### What problem does this PR solve?

Issue Number: #65185

Related PR: #66004

Problem Summary:

Now that catalogs go through the connector/filesystem SPIs, fe-core and
fe-common no
longer have a reason to compile against hadoop, but a handful of source
imports were
still left over from the pre-SPI code. This removes them.

Scope is deliberately narrow: **source imports only**. The hadoop pom
dependencies stay
exactly as they are, because they remain reachable at runtime —
`ranger-plugins-common`
inherits from `org.apache.hadoop.conf.Configuration`, `hive-exec`
supplies the UDF base
class that `CREATE FUNCTION` resolves, and `fe-kerberos` uses
`UserGroupInformation`.
Dropping the jars is a separate, larger question and is not attempted
here.

After this PR, `grep -rn "^import org.apache.hadoop" fe/fe-core/src
fe/fe-common/src`
returns exactly one line: `RangerHiveAuditHandler`, which is a
sanctioned exception. The
type is imposed by Ranger's own API, and `ranger-plugins-common` puts
hadoop on the
classpath regardless, so removing that import would remove no
dependency. It is
documented as such in the code rather than worked around.

The six commits are independent steps and are easiest to review one at a
time:

| Commit | What moves |
|---|---|
| `4314ad2` | Dead and trivial imports: delete `CatalogConfigFileUtils`
(no caller; `fe-filesystem-hdfs*` already carries the port) and
`LocationPath.getTempWritePath` (no caller); inline two hadoop constants
that were plain strings; document the Ranger exception |
| `29649e2` | `hadoop-huaweicloud` moves from fe-core to the
`fe-filesystem-obs` plugin, together with the `huawei-obs-sdk`
repository declaration. The only FE reference is the `Class.forName`
probe in `ObsFileSystemProperties`, which must resolve against that
plugin's classloader to report the truth |
| `90339cd` | `FileSplitter` takes a new fe-core `FileBlockLocation`
instead of hadoop's `BlockLocation`. Only
`getOffset`/`getLength`/`getHosts` were ever read |
| `c55800a` | `LocationPath.toStorageLocation()` returns the existing
`org.apache.doris.filesystem.Location` instead of a hadoop `Path`;
`getPath()` is deleted in favour of the `fsIdentifier` the class already
computes. **See the behaviour-change note below** |
| `592e8aa` | The Azure OAuth2 backend map moves from `StorageAdapter`
into `fe-filesystem-azure`, which is where the legacy `AzureProperties`
owned it. This deletes ~120 lines of fe-core code that turned out to be
unreachable |
| `464efa4` | `StageUtil`'s `GlobExpander`/`GlobFilter` use is ported to
a package-private `GlobPatterns`, compiled with re2j — the same engine
hadoop's `GlobPattern` uses, and already a declared fe-core dependency |

Two notes that are easy to miss on review:

- `hadoop.fs.GlobFilter` reads like a wildcard predicate but its
constructor is also a
validator: it rejects `a[b`, `a{b`, a trailing backslash and `[z-a]`,
and `analyzeGlob`
surfaces that as a `DdlException`. Porting only the predicate would have
accepted those
globs and quietly listed an unintended object-store prefix instead of
failing the
  statement, so the validation is ported with it.
- `StorageAdapter.getHadoopStorageConfig()` had no caller anywhere in
the tree; the only
consumer was the adapter's own Azure OAuth2 arm. That map is still
load-bearing (BE
routes Microsoft Fabric OneLake locations to `FILE_HDFS`, and
`hdfs_builder` feeds every
entry into its JNI hadoop builder), so it is preserved key-for-key
rather than dropped —
  it just lives in the Azure plugin now.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by one committer.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants