Skip to content

[refactor](connector) give every connector the same property layout, and make each key have one reader - #66507

Merged
morningman merged 25 commits into
apache:masterfrom
morningman:connector-properties-convention
Aug 7, 2026
Merged

[refactor](connector) give every connector the same property layout, and make each key have one reader#66507
morningman merged 25 commits into
apache:masterfrom
morningman:connector-properties-convention

Conversation

@morningman

@morningman morningman commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue: #65185

Problem Summary:

Every connector had grown its own way of dealing with catalog properties. Some kept a
XxxConnectorProperties constant class and parsed values at each read site; some had no property
class at all and inlined the key names; iceberg and paimon had two readers for the same keys — a
typed holder used for CREATE-time validation, and a separate raw-map scan with its own copy of the
alias arrays used to actually build the catalog. Nothing kept those two in agreement, so an alias
priority or a blank-value rule could be validated one way and assembled another.

This PR gives every connector the same four-way split, and makes each key have exactly one reader.
No new SPI: ConnectorPluginSurfaceTest is untouched throughout.

Where things live now

A — <Xxx>CatalogProperties: what a user writes in CREATE CATALOG.
One class per connector, in the connector module. Fields carry @ConnectorProperty with the alias
list, so a key name and its aliases are declared exactly once, and ConnectorPropertiesUtils binds
them. This is what the connector, the metadata layer and the scan/write planners read — none of them
touch the raw map for a key this class declares.

The two entry points are deliberately not interchangeable, and this is the part most worth reviewing:

  • of(Map) binds and derives, and never throws. It runs at CREATE, at ALTER validation, and on
    every connector build — including the lazy rebuild after an FE restart. A rule placed here that a
    live catalog violates does not fail at review time; it fails months later, as a catalog that stops
    coming back after a restart.
  • checkCreateTimeOnlyRules() carries everything that judges, and only the provider calls it, from
    validateProperties — one line in most connectors. Unknown keys are never rejected: the same map
    carries engine keys and storage keys, and ALTER CATALOG can only overwrite a key, never remove
    one, so a rejected unknown key could not be repaired.

B — <Xxx>Conf: deployment-level settings. The keys of the plugin's own <name>.conf, each
falling back to the fe.conf key it used to live under. Static accessors (driversDir(context),
metastoreClientTimeoutSecond(context), …) rather than a bound object, because these belong to the
deployment and not to any one catalog. Only the connectors that actually have such settings have one.

Per-flavor *MetaStoreProperties (iceberg / paimon): the keys of one metastore backend.
These already existed in fe-connector-metastore-{iceberg,paimon} but described themselves as
"validation only" while the connector re-scanned the same keys to build the catalog. They are now the
single declaration: they gained the fields the assembly needed and the getters it reads, and the
factories consume the bound holder instead of firstNonBlank(props, ALIASES). The alias arrays that
duplicated them are deleted. The metastore modules stay SDK-free — they expose neutral getters and
maps; engine-SDK option assembly stays in the connector factory.

Splitting by flavor also splits the "annotation count == key count" invariant: the connector-level
holder declares the flavor-independent keys, each backend holder declares its own.

F/G — literals the assembly emits, and mode enums. Option keys and values that a connector
writes (rather than a user setting them) are private to the class that writes them —
IcebergCatalogFactory for the iceberg SDK dialect, and so on. Cache key names sit next to the cache
they configure. In several places these had drifted into two spellings of the same string in two
files; folding them removed those duplicates.

Responsibilities, end to end

Layer Reads Writes
<Xxx>ConnectorProvider validatePropertiesof(props).checkCreateTimeOnlyRules()
<Xxx>CatalogProperties the raw map, once typed getters; the derived flavor
*MetaStoreProperties the raw map, once, for its own backend typed getters; neutral conf maps
<Xxx>CatalogFactory the bound holders (+ raw map only for copy-all / prefix passthroughs) the engine-SDK option map
<Xxx>Conf ConnectorContext conf + environment
connector / metadata / scan / write the bound holders

The raw map is still read in three legitimate shapes, each commented where it happens: copy-all
passthrough into the SDK options, whole-namespace forwarding (fs. / dfs. / hadoop., paimon.,
jdbc.), and alias sets that span namespaces and so belong to no single flavor (the S3 region
aliases, the AWS credentials-provider mode).

Verification

Unit tests only — this is a refactor with no intended behavior change, and the guard against
unintended change is a set of whole-map snapshot tests added before each rework: paimon 8 cases and
iceberg 20 cases assert the ENTIRE catalog option map, one per flavor and per emission branch. Both
SDKs silently ignore an option they do not recognize, so a dropped or misspelled key does not throw —
it produces a catalog that connects with different settings than the operator asked for. Those tests
stay in the tree afterwards as the permanent guard that the holder and the assembly agree.

Every touched module, run together at the final commit with
-Dmaven.build.cache.enabled=false (the build cache otherwise reports a stale green):

module tests module tests
iceberg 1215 (5 skip) hudi 203
paimon 532 (1 skip) adbc 200
hive 411 maxcompute 147 (1 skip)
jdbc 222 es 118
connector-spi 140 trino 55
hms shared lib 107 metastore-{iceberg,paimon,spi,api} 77
foundation 161 cache framework 33

3780 tests, 0 failures, 0 errors, 7 skips — every skip is a pre-existing live-connectivity test
gated on environment variables, and each was already skipped before this PR. checkstyle clean;
ConnectorPluginSurfaceTest green.

Behavior changes

Small, and all on inputs that are already degenerate. Full per-connector tables are in the commit
messages; the classes of change are:

  1. Values are trimmed. The property binder trims and the old hand-written scans did not, so a
    uri written with a trailing space was already validated trimmed while the catalog was built
    from the untrimmed string. The two now agree. For paimon HMS this removed an existing internal
    inconsistency (HiveConf got the trimmed value, the paimon Options got the raw one).
  2. Blank now means unset, where old code used containsKey / getOrDefault. Affects e.g.
    iceberg.rest.view-enabled = "" (was false, now its default true) and
    external_catalog.name = "" (was an empty namespace level, now absent).
  3. Values the FE interprets are now sent downstream interpreted. This fixed three real bugs where
    the FE parsed a value and then forwarded the unparsed original — the ES http_ssl_enabled payload
    to BE, the hive uri shorthand, and the trino connector.name.

Numeric keys were audited per connector and deliberately left as Strings wherever the value is
forwarded verbatim to an engine SDK, so that a catalog created with a value the SDK tolerates keeps
building; the JDBC connection-pool knobs are the one place where the strict/lenient choice is made
per key, with the reasoning in that commit.

Release note

None

Check List (For Author)

  • Test

    • Unit Test
    • Regression test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
  • Behavior changed:

    • Yes. Whitespace around property values is now trimmed; an explicitly blank value now reads
      as unset rather than as an empty string; and three keys the FE interprets are now forwarded in
      their interpreted form. Details per connector are in the individual commit messages.
  • Does this need documentation?

    • No.

@morningman
morningman requested a review from CalvinKirs as a code owner August 5, 2026 14:23
@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 Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

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

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

1 similar comment
@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman
morningman force-pushed the connector-properties-convention branch from c38c6f5 to 068b106 Compare August 6, 2026 08:53
@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman
morningman force-pushed the connector-properties-convention branch from 068b106 to 7e780e9 Compare August 6, 2026 09:44
@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman
morningman force-pushed the connector-properties-convention branch from 7e780e9 to d5e33ae Compare August 6, 2026 11:35
@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

CalvinKirs
CalvinKirs previously approved these changes Aug 6, 2026
@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

PR approved by anyone and no changes requested.

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 100% (0/0) 🎉
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

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17668	3971	3939	3939
q2	2027	323	196	196
q3	10257	1358	795	795
q4	4685	471	332	332
q5	7498	819	540	540
q6	178	166	133	133
q7	730	807	594	594
q8	9840	1627	1636	1627
q9	5741	4084	4082	4082
q10	6809	1591	1368	1368
q11	503	345	317	317
q12	747	593	450	450
q13	18079	3226	2721	2721
q14	269	253	242	242
q15	q16	741	716	661	661
q17	1275	991	963	963
q18	6521	5621	5551	5551
q19	1786	1161	1051	1051
q20	776	650	574	574
q21	5904	2520	2329	2329
q22	429	355	295	295
Total cold run time: 102463 ms
Total hot run time: 28760 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4290	4193	4161	4161
q2	272	321	204	204
q3	4513	4886	4340	4340
q4	2178	2279	1407	1407
q5	4250	4146	4125	4125
q6	228	172	126	126
q7	1680	1598	1411	1411
q8	2631	2144	2065	2065
q9	7239	7313	7334	7313
q10	4283	4255	3843	3843
q11	549	388	352	352
q12	699	728	509	509
q13	3166	3620	2889	2889
q14	290	306	284	284
q15	q16	700	745	651	651
q17	1336	1304	1288	1288
q18	12109	11049	11870	11049
q19	1186	1114	1141	1114
q20	2241	2217	1944	1944
q21	5661	4909	4624	4624
q22	523	450	441	441
Total cold run time: 60024 ms
Total hot run time: 54140 ms

@hello-stephen

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

query5	4337	605	447	447
query6	461	228	201	201
query7	4894	555	333	333
query8	324	160	148	148
query9	8745	4069	4050	4050
query10	452	357	316	316
query11	5817	2174	2003	2003
query12	153	96	94	94
query13	1239	582	435	435
query14	6073	4283	3960	3960
query14_1	3765	3733	3761	3733
query15	202	193	177	177
query16	992	462	500	462
query17	917	688	564	564
query18	2423	465	349	349
query19	213	186	149	149
query20	101	100	103	100
query21	235	160	135	135
query22	13040	12989	12812	12812
query23	15824	14907	14593	14593
query23_1	14647	14631	14706	14631
query24	7553	1730	1226	1226
query24_1	1253	1227	1223	1223
query25	538	457	343	343
query26	1299	343	218	218
query27	2641	577	376	376
query28	4529	2039	2017	2017
query29	1071	624	464	464
query30	351	259	228	228
query31	1174	1115	1035	1035
query32	110	61	60	60
query33	507	296	241	241
query34	1207	1171	654	654
query35	730	736	639	639
query36	787	786	686	686
query37	166	109	91	91
query38	1831	1763	1707	1707
query39	819	820	785	785
query39_1	781	777	775	775
query40	251	165	145	145
query41	65	67	67	67
query42	100	96	92	92
query43	319	336	279	279
query44	1433	782	795	782
query45	189	180	178	178
query46	1009	1223	728	728
query47	1558	1543	1560	1543
query48	439	430	315	315
query49	597	408	302	302
query50	1047	440	355	355
query51	10543	10564	10597	10564
query52	91	90	78	78
query53	264	285	204	204
query54	298	247	238	238
query55	80	75	69	69
query56	313	318	328	318
query57	1017	1021	923	923
query58	305	265	287	265
query59	1551	1615	1399	1399
query60	329	279	278	278
query61	181	175	170	170
query62	404	324	273	273
query63	244	195	204	195
query64	3011	1164	973	973
query65	3855	3811	3808	3808
query66	1855	506	377	377
query67	28151	28144	28025	28025
query68	3195	1527	1022	1022
query69	406	302	270	270
query70	890	811	779	779
query71	368	349	299	299
query72	2990	2790	2287	2287
query73	817	763	438	438
query74	4636	4505	4282	4282
query75	2373	2330	1997	1997
query76	2326	1136	756	756
query77	357	362	274	274
query78	11162	11080	10538	10538
query79	1457	1116	724	724
query80	1243	537	466	466
query81	525	335	285	285
query82	639	169	131	131
query83	376	327	290	290
query84	319	156	132	132
query85	964	601	521	521
query86	410	229	228	228
query87	2022	1969	1839	1839
query88	3714	2782	2833	2782
query89	384	309	282	282
query90	1947	201	197	197
query91	205	191	163	163
query92	65	57	56	56
query93	1725	1650	951	951
query94	724	344	322	322
query95	815	589	488	488
query96	1039	779	349	349
query97	2457	2464	2344	2344
query98	196	185	185	185
query99	738	743	629	629
Total cold run time: 253327 ms
Total hot run time: 166476 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.09	0.05	0.05
query3	0.26	0.13	0.13
query4	1.60	0.14	0.14
query5	0.24	0.21	0.22
query6	1.16	0.81	0.82
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.37	0.32	0.30
query10	0.55	0.54	0.58
query11	0.19	0.13	0.13
query12	0.18	0.14	0.14
query13	0.46	0.45	0.46
query14	1.01	0.99	0.99
query15	0.59	0.59	0.59
query16	0.34	0.34	0.32
query17	1.12	1.09	1.11
query18	0.22	0.20	0.20
query19	2.09	1.96	1.95
query20	0.01	0.02	0.01
query21	15.45	0.18	0.14
query22	4.99	0.05	0.05
query23	16.13	0.30	0.12
query24	2.92	0.44	0.34
query25	0.12	0.05	0.05
query26	0.72	0.20	0.16
query27	0.05	0.04	0.04
query28	3.51	0.76	0.34
query29	12.48	4.03	3.16
query30	0.27	0.16	0.15
query31	2.77	0.54	0.32
query32	3.23	0.58	0.49
query33	3.23	3.16	3.27
query34	15.71	3.91	3.27
query35	3.23	3.24	3.22
query36	0.54	0.43	0.42
query37	0.09	0.07	0.06
query38	0.06	0.03	0.03
query39	0.04	0.03	0.03
query40	0.17	0.15	0.15
query41	0.08	0.04	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 96.46 s
Total hot run time: 23.89 s

morningman and others added 8 commits August 6, 2026 22:46
…vention A-class)

Everything a user writes in CREATE CATALOG for an adbc catalog becomes a typed
holder: @ConnectorProperty fields bound by ConnectorPropertiesUtils, the derived
partitioned-read mode and driver-option map, and the key-name constants.

of(map) binds, derives and validates in one step, so an instance that exists has
valid properties -- which is what lets every reader downstream use a getter
instead of re-parsing the map. It stays free of I/O and of arrow-adbc types
because it runs at CREATE, again on the merged candidate when ALTER validates,
and once more on every connector rebuild, including on an FE replaying the edit
log.

Unknown keys are accepted by design: the same map carries engine keys
(type, meta.cache.*) and storage keys (s3.*), and ALTER CATALOG merges
properties -- it can overwrite a key but never remove one -- so a key refused
here would leave a catalog no statement could repair.

fe-foundation is now declared directly rather than relied on transitively
through fe-connector-spi, so the binding annotations this class needs cannot
disappear with someone else's dependency change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… convention B-class)

The keys of this plugin's adbc.conf, their defaults, and the two reads that
resolve them now live in one class, separate from the per-catalog properties
they used to sit beside. The reading logic moves verbatim out of AdbcConnector.

The conf test gains real teeth in the process. It used to call ConnectorConf.get
with a hand-written null legacy key, so its "no fe.conf key is consulted"
assertion only proved that the test passed null -- it would have stayed green if
a reader started naming one. Going through AdbcConf's own readers makes that
assertion about production code, and the DORIS_HOME default is now exercised
rather than restated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s.of

The hand-written require/parse sequence and the meta-cache check are gone: both
now live in of(), so the provider's door and every other construction of the
holder check exactly the same things. A check that existed only here would have
been one that ALTER validation and connector rebuild did not run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…es/AdbcConf, drop the constants class

AdbcConnectorProperties is gone. The catalog map now has exactly one reader --
AdbcCatalogProperties -- and every consumer takes a getter instead of parsing
the map again: AdbcConnector builds the holder in its constructor and passes it
to the scan planner, AdbcDialectSelector takes the dialect name as a value, and
the driver-path resolver's adbc.conf keys come from AdbcConf.

That single reader is the point. Blank-means-unset, trimming and the
partitioned-read spelling were each expressed at more than one call site before,
which is how two of them start disagreeing without anything failing.

The null-to-empty-string change in the defaults is payload-neutral: both
AdbcScanRange.Builder.putIfPresent and AdbcClient.buildParameters already treat
"" as absent, so nothing new reaches BE or the driver.

The old test's cases that the holder test did not already cover are carried
over, including the provider-door ones -- validateProperties is one line now,
and a body that stopped calling of() would otherwise leave every other test
green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AdbcConnectorMetadata handed the catalog's whole property map -- password
included -- to every ConnectorTableSchema it built, where it was cached and
carried around for the rest of the catalog's life.

Nothing reads those entries for an adbc table today, and the one rendering that
would print them, SHOW CREATE TABLE (Env.getDdlStmt), prints table properties
unmasked but is fenced off by the SUPPORTS_SHOW_CREATE_DDL capability this
connector does not declare. So this leaks nothing now -- but one capability
declaration is the entire distance between a stored credential and a string a
user can read, and that is too thin a margin to leave for data nobody wants.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two named classes per connector and nothing else: <Xxx>CatalogProperties for
what a user writes in CREATE CATALOG, <Xxx>Conf for the plugin's own settings
file. The rules that are easy to get wrong are the point of writing this down:
of() must be pure and idempotent because it runs on paths no validator does,
it must refuse bad values but never unknown keys because ALTER cannot remove
one, and the raw map holds credentials so it must not reach anything a user
can read.

Also records why the binding has to happen inside the plugin -- foundation is
child-first for connectors, so fe-core reflecting on a plugin object finds no
annotations and says nothing about it -- and what to audit when migrating a
released connector, where the binder throwing on a malformed number replaces a
getInt helper that silently used the default.

Fixes the duplicated list number 15 while in the file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… holder

HudiConnectorProperties was a constants class plus a getInt helper, read from
two places: HudiConnector parsed the metastore URI (with its "uri" short-form
fallback) and the client-pool size, and HudiConnectorMetadata separately parsed
use_hive_sync_partition off the raw map. Replace it with HudiCatalogProperties,
which binds and validates in one step, so the connector and its metadata read
getters instead of the map.

None of these keys belong to hudi. There is no type=hudi catalog: the connector
is always an embedded sibling of an HMS gateway and receives that gateway
catalog's whole property map verbatim, so what it reads are hive's keys. The
class says so, and keeps the copied literals next to a comment naming their
owner, which is the only way to reference another plugin's key. For the same
reason no validateProperties is added to the provider: sibling creation does not
pass a validation door, so of() runs from the connector constructor only.

hadoop.security.authentication stays a private constant next to
buildPluginAuthenticator: it is a raw storage key that buildHadoopConf hands to
the Configuration wholesale along with every other passthrough key, so reading
it there is a peek, not this connector interpreting a property of its own.

Behaviour change, one key: hive.metastore.client.pool.size was parsed by a
helper that swallowed NumberFormatException and fell back to 8; it is now bound
as an int and a malformed value is refused. Note the asymmetry this leaves while
the hive connector still parses the same key leniently -- on a catalog whose
pool size is misspelled, hive tables keep working and hudi tables do not, until
an ALTER CATALOG overwrites the value.

Also drops hoodie.datasource.write.table.type, which had no reader anywhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MCConnectorProperties was 25 key constants with no parsing of its own, so the
values were re-read at six call sites -- the provider's validateProperties, the
connector's doInit and buildSettings, the scan and write plan providers, the
endpoint resolver and the client factory -- each with its own getOrDefault plus
parseInt pair, and each free to disagree with the others about a default.
Replace it with MCCatalogProperties: one bind-derive-validate step, then getters
everywhere. validateProperties collapses to building one.

The three value vocabularies become enums next to the property they belong to
(SplitStrategy, AccountFormat, AuthType), which is what lets the client factory
drop its unsupported-type arm: an invalid auth type can no longer reach it.
Note the enums keep their pre-existing matching: auth type case-insensitive, the
other two exact, since aligning them would change which spellings an existing
catalog accepts.

mc.endpoint is required of a new catalog but not of a stored one. Catalogs
written before it existed carry only mc.region / mc.odps_endpoint /
mc.tunnel_endpoint, and of() runs on every FE restart, so requiring the current
spelling there would take those catalogs away from their owners with no
statement able to repair them. of() therefore requires a *resolvable* endpoint
and the CREATE/ALTER-only rule lives in checkCreateTimeOnlyRules(), which only
the provider calls -- preserving both doors exactly as they behave today.

Numeric key audit (D7). This connector already parsed strictly at CREATE, so no
key flips from lenient to strict:

  key                       before                        after
  mc.connect_timeout        parseInt, threw               bound int
  mc.read_timeout           parseInt, threw               bound int
  mc.retry_count            parseInt, threw               bound int
  mc.split_byte_size        parsed only under byte_size   bound long, always
  mc.split_row_count        parsed only under row_count   bound long, always
  mc.max_field_size_bytes   parsed in the write path only bound long, at CREATE

Two user-visible consequences, both in the direction of failing earlier:

- A malformed number is now refused by the binder, so the message reads
  "Failed to set property 'mc.read_timeout' on MCCatalogProperties: For input
  string: ..." instead of "property mc.read_timeout must be an integer". It
  still names the key and the bad value.
- The split size of the *unselected* strategy is validated too. A catalog with
  mc.split_strategy=byte_size and a malformed mc.split_row_count is accepted
  today and refused after this change; ALTER CATALOG overwriting the value
  repairs it.

The binder also treats a blank value as unset, where resolveEndpoint and the
auth check used containsKey. So "mc.access_key" = "" now fails at CREATE naming
the missing credential rather than at the first request with whatever the
service says.

Also drops mc.session_token and mc.max_write_batch_rows, which no code read.
Neither is removed from the property map that goes to BE -- that is still passed
whole -- so a catalog setting them is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman and others added 16 commits August 6, 2026 22:46
…a conf class

JdbcConnectorProperties mixed three unrelated things: the per-catalog property
keys, the per-FE deployment settings read from jdbc.conf, and a lenient getInt
helper. Its values were then re-read at five call sites -- the provider's
validateProperties, the connector, the scan and write plan providers, and the
metadata -- each with its own default. Split it into JdbcCatalogProperties (the
CREATE CATALOG map, bound and checked) and JdbcConf (the deployment settings),
and give every reader a getter.

The split that matters here is not between the two new classes but inside the
first one. This connector has always validated far more at CREATE/ALTER than at
run time: the pool sizes have bounds no reader enforces, the boolean properties
must be spelled true/false where every reader takes anything, the database-list
consistency rule is checked once and never again, and lower_case_table_names is
rejected by name. None of those is an invariant of a working catalog -- a stored
catalog breaking any of them runs today -- so putting them in of(), which runs
on every rebuild including on an FE replaying the edit log, would take such
catalogs away from their owners with no statement able to repair them. of()
therefore holds only the required jdbc_url and the framework's type conversion;
everything else moved to checkCreateTimeOnlyRules(), which only the interactive
doors call. The test file pins each rule on its side of that line.

URL normalization stays outside the holder. It depends on both halves of the
configuration at once -- the URL is per-catalog, but whether a SQL Server URL
gets encrypt=false appended is per-FE -- so a holder of per-catalog properties
has no business doing it. of() takes the normalization as a function: the
connector passes the real one, the validation doors pass identity.

The jdbc. prefix is stripped on the way in, as it was in both places that used
to resolve these keys, and the short spelling still wins over the prefixed one.

Numeric key audit (D7): the five connection-pool keys.

  key                             CREATE door        runtime          after
  connection_pool_min_size        parseInt, threw    getInt, swallowed  bound int
  connection_pool_max_size        parseInt, threw    getInt, swallowed  bound int
  connection_pool_max_wait_time   parseInt, threw    getInt, swallowed  bound int
  connection_pool_max_life_time   parseInt, threw    getInt, swallowed  bound int
  connection_pool_keep_alive      true/false only    parseBoolean       unchanged

So CREATE and ALTER already refused a malformed number and still do; what
changes is the stored catalog that somehow holds one -- only possible from an
image written before this validation existed. It is silently read as the default
today and refuses to build after this change, until ALTER CATALOG overwrites the
value. The bounds deliberately do NOT follow: they were never enforced at run
time, so they stay create-time only, per the paragraph above.

The message for a malformed number now comes from the binder, so it reads
"Failed to set property 'connection_pool_min_size' ..." rather than "Property
'connection_pool_min_size' must be a valid integer, got: ...". It still names
the key and the bad value.

Also drops the "type" constant, which nothing read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…a conf class

Applies the connector property convention to the hive (hms) connector: the
per-catalog keys become HiveCatalogProperties, a typed holder whose of() binds,
derives and validates in one step, and the two deployment-level settings become
HmsConf. The 150-line HiveConnectorProperties constants class is gone; every
other constant it carried moved next to its single reader (CREATE TABLE keys and
session variable names to HiveConnectorMetadata, the remote table-parameter keys
to HiveTableFormatDetector).

Six of its constants were dead and are simply removed: the metastore type and the
four Kerberos keys are owned by HmsClientConfig and by the shared
AbstractHmsMetaStoreProperties holder, and copying them here gave those keys two
sources of truth. FLINK_CONNECTOR was both dead and wrong -- it spelled the key
"connector" while the detector reads "flink.connector".

Fixes the "uri" short form. HiveConnector read it, but only to decide whether the
user had named a metastore at all: the map then went to HmsConfHelper, which
copies it verbatim into a HiveConf that knows only hive.metastore.uris, so a
catalog written with the short form was created successfully and then connected
nowhere. The holder binds both spellings (canonical wins) and restates the
resolved value under the canonical key for the HMS client. The hudi connector and
the iceberg sibling already honoured the short form, so this also removes an
asymmetry within one catalog.

Behavior changes, all user-visible:

- A catalog naming no metastore URI is now rejected by CREATE CATALOG instead of
  failing at first access. Such a catalog could never run: createClient() has
  always required the property outright.
- hive.metastore.client.pool.size and hive.hms_events_batch_size_per_rpc are
  strict. The removed getInt swallowed a malformed value and used the default, so
  a stored catalog carrying one now fails to build; ALTER CATALOG repairs it. The
  pool size also stops being strict-for-hudi / lenient-for-hive on one catalog.
- On the lazy build path, a catalog naming a removed metastore type or no URI now
  raises IllegalArgumentException rather than DorisConnectorException. The
  messages are unchanged.

The two meta-cache TTL checks stay create-time only, in checkCreateTimeOnlyRules:
they were added after the hms cutover and have only ever guarded the interactive
doors, so a stored catalog that breaks one runs today and must keep running.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a conf class

Applies the connector property convention to the trino-connector bridge, the last
connector in this series that had no property class at all: its two per-catalog
keys were read inline in the provider and in the connector's lazy init, and the
two deployment-level keys lived as constants on the provider. They become
TrinoCatalogProperties and TrinoConf, and validateProperties is one line.

TrinoBootstrap.resolvePluginDir stops taking a property map. It now takes the
catalog's already-bound trino.plugin.dir override, so the holder is the only
thing in the module that reads the catalog map.

Fixes the deprecated dashed connector name. Trino renamed its connector names to
underscores and its ConnectorName constructor now rejects anything outside
[a-z][a-z0-9_]*, so the bridge translates the old spelling -- but it did so into
a local variable, leaving the map serialized into the BE scan payload with the
dashed name still in it. BE feeds that value straight into its own ConnectorName,
so a catalog written as e.g. "delta-lake" served metadata correctly on FE and
failed every SELECT on BE with "Invalid connector name", which reads as unrelated
to the spelling. The correction now happens once, in the holder, and both the FE
factory lookup and the BE payload see the corrected value.

One further user-visible change: the binder treats a blank value as unset, where
the hand-written check only tested isEmpty(), so a whitespace-only
trino.connector.name is now refused at CREATE. Such a catalog could never work.

No numeric-key audit table for this connector: both of its catalog keys are
strings and it had no hand-written number parsing, so the strictness flip that
governs the other migrations has no target here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… convergence rests on

Every alias-bearing paimon catalog property has two readers today: the per-flavor
*MetaStoreProperties holder binds it through @ConnectorProperty(names = ...) for
validation, and PaimonCatalogFactory re-scans the raw map with its own firstNonBlank
helper and a parallel String[] alias array for assembly. Retiring the second reader is
only safe if both resolve the same value, and nothing in the compiler enforces that: a
divergence would silently let a catalog validate against one value and connect with
another.

Two guards, no production change:

- fe-foundation ConnectorPropertiesUtilsTest gains the three rules the convergence rests
  on -- names() resolves to the first NON-BLANK alias (not the first present one),
  declaration order is priority order, all-blank leaves the field at its initial value --
  plus the fact that bound values are trimmed.
- fe-connector-paimon PaimonAliasResolutionParityTest drives the same map through both
  readers for every converging alias pair (hms uri, rest uri, the five jdbc keys) and
  asserts they agree on all four alias shapes.

The readers agree on selection. The single divergence is trim: the binder trims, the
helper returns the value verbatim. That split is already live for the hms flavor, where
the HiveConf is built from the bound holder ("thrift://nn:9083") while the paimon Options
come from the raw scan ("thrift://nn:9083 ") -- one catalog, two values. The convergence
normalizes them instead of preserving the split, so the divergence is pinned explicitly
rather than left to be discovered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S73AWq9AHxYfKkzWJfTcKs
…re the assembly rework

PaimonCatalogFactoryTest asserts that the keys it names are right; nothing there notices a
key that appears, disappears, or is spelled differently. That matters more than usual here:
paimon silently ignores an option it does not recognize, so a dropped or misspelled key
does not throw -- it produces a catalog that connects with different settings than the
operator asked for.

Folding the per-flavor assembly onto the bound *MetaStoreProperties holders removes the raw
alias scan it is currently compared against, so the reference has to be captured before the
change and kept afterwards. These eight snapshots assert the ENTIRE Options map for
filesystem (explicit and defaulted), hms (defaults and the uri alias with both defaults
overridden), rest (both forms) and jdbc (full and minimal). Every input also carries the
three namespaces the paimon.* passthrough must exclude -- storage, per-table options, and
the BE jni knobs -- so an exclusion that stops working is a diff rather than a silence.

Writing them out surfaced one thing the per-key tests never showed: paimon.catalog.type is
a Doris-side key, but it matches the generic paimon. passthrough like any other, so every
catalog emits a catalog.type option paimon does not define. It is inert and removing it
would change what a live catalog is built with, so it is pinned as-is; cleaning it up is a
separate, deliberate change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S73AWq9AHxYfKkzWJfTcKs
…rties, not a second raw scan

The hms flavor had two readers for the same keys: PaimonHmsMetaStoreProperties bound the
metastore uri through @ConnectorProperty for validation and for the HiveConf, while
PaimonCatalogFactory re-scanned the raw map with firstNonBlank and a parallel alias array
to build the paimon Options. Nothing kept the two in step.

They were already out of step. The binder trims, firstNonBlank does not, so a catalog
created with "hive.metastore.uris" = "thrift://nn:9083 " talks to the metastore as
"thrift://nn:9083" (HiveConf, built from the bound value) while its paimon Options claim
"thrift://nn:9083 ". buildCatalogOptions now binds the flavor's typed facts once and
assembles from those, so both come from the same value. That is a user-visible change: a
padded value now reaches the paimon SDK trimmed.

Ownership follows the same rule:

- hive.conf.resources was a bare string literal in the connector (and is one in the iceberg
  connector too). It is an HMS-backend fact like every other field, so it is declared once
  on AbstractHmsMetaStoreProperties and read back through a getter. It still rides the
  verbatim hive.* passthrough into the HiveConf, unchanged.
- client-pool-cache.eviction-interval-ms and location-in-properties are paimon's own hms
  options, so they move to PaimonHmsMetaStoreProperties, which now emits the flavor's
  option keys as a neutral map (the metastore modules stay free of the paimon SDK). They
  stay Strings: paimon parses them itself, and binding them to a number would turn a value
  paimon tolerates today into a catalog that cannot be created.
- warehouse is declared for all flavors on AbstractMetaStoreProperties, so the common
  appender reads it from there instead of the raw map.

The metastore identifier is resolved before binding so an unknown paimon.catalog.type keeps
failing with this factory's own message rather than the dispatcher's.

Verified: fe-connector-paimon 525 tests 0 failures (1 skip is the pre-existing
env-gated PaimonLiveConnectivityTest); fe-connector-iceberg, fe-connector-hive and
fe-connector-metastore-hms green against the shared base change; ConnectorPluginSurfaceTest
untouched; checkstyle clean.

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

PaimonRestMetaStoreProperties.toRestOptions() was written for exactly this cutover and then
never wired up: the rest flavor kept assembling its options from a second raw scan while the
holder that validated them sat unused. This connects it.

One ordering change inside toRestOptions(). Both the "paimon.rest." prefix strip and the
alias-resolved uri write to the same "uri" key, and the strip used to run last, so a padded
"paimon.rest.uri" would connect with a value validate() never saw. The bound value now wins.
Everything else the prefix strip forwards stays verbatim -- it is a wildcard passthrough of
keys this connector does not interpret, not alias resolution.

Verified: fe-connector-paimon 525 tests 0 failures (1 pre-existing env-gated skip);
checkstyle clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S73AWq9AHxYfKkzWJfTcKs
…ies everywhere

The jdbc flavor resolved its five aliases in four places: the catalog Options, the CREATE-time
driver-path check, the FE driver registration, and the BE-bound scan options. All four scanned
the raw map with firstNonBlank while PaimonJdbcMetaStoreProperties bound the same aliases for
validation. All four now read the bound values.

The BE-bound options are the ones that matter most. jdbc.driver_url is turned into a real URL
and jdbc.driver_class into a real Class.forName on the backend, so a padded value used to reach
BE as "file:///opt/drivers/  mysql.jar  " and fail at load time with an error naming neither the
property nor the padding. Covered by a new test.

Alias priority is unchanged -- PaimonJdbcMetaStoreProperties declares the same names in the same
order, which the parity test pins -- and the raw jdbc.* passthrough stays a raw read: it forwards
keys the holder does not model, and it still runs after the bound keys so an alias-resolved
user or password wins over a bare jdbc.* copy.

Verified: fe-connector-paimon 527 tests 0 failures (1 pre-existing env-gated skip); checkstyle
clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S73AWq9AHxYfKkzWJfTcKs
…d a conf class

PaimonConnectorProperties was a 135-line constants class mixing four unrelated things: the
plugin's deployment settings and their readers, the connector-level catalog keys, the flavor
literals, and a set of per-flavor alias arrays. The alias arrays went in the preceding
commits, when the assembly started reading the bound metastore properties. This splits what
is left the way every other connector in this tree is now split, and deletes the class.

- PaimonCatalogProperties: the three catalog keys that are not specific to a backend --
  paimon.catalog.type and the two enable.mapping.* switches -- as @ConnectorProperty fields,
  plus the derived lower-cased flavor and the flavor literals. Everything else a user writes
  belongs to the bound *MetaStoreProperties, so three keys is the whole surface.
- PaimonConf: the two paimon.conf settings, their fe.conf fallback keys, and the readers.
  The metastore client timeout was an inlined three-argument ConnectorConf.get at the call
  site; it is a named reader now, and its test drives that reader instead of re-spelling it.

of() and checkCreateTimeOnlyRules() are deliberately not one method. of() runs on every
connector build, including the lazy rebuild after an FE restart, so it only binds and
derives -- it cannot throw. The meta-cache checks, the dead-knob warning, the paimon
table-option extraction and the backend's own validate() all ran solely against a
CREATE/ALTER statement before, and they still do: a catalog created before one of those
rules existed has to be able to come back. That is why validateProperties is one line and
the connector constructor calls only of().

The dead-knob warning moves with them. The design had it staying on the provider so a lazy
rebuild could not reprint it every time; the create-time-only method turns out to be a third
place that is off the rebuild path, which keeps the warning in its original position
relative to the meta-cache check. It now logs under PaimonCatalogProperties.

Also drops MetaStoreParseUtils.firstNonBlank, which had no production caller in any
connector (both factories carried their own copy), and its test.

Mechanical fallout: PaimonScanPlanProvider and PaimonConnectorMetadata take the holder
instead of the raw map, which is 91 construction sites across 18 test files, rewritten by
splitting each argument list on bracket balance rather than by pattern -- the arguments
contain nested calls. Both keep getRaw() for the namespaces they forward wholesale
(paimon.jni.*, jdbc.*, fs./dfs./hadoop.), which are copy-all passthroughs, not properties.

Verified: fe-connector-paimon 532 tests 0 failures (1 skip is the pre-existing env-gated
PaimonLiveConnectivityTest); iceberg, hive, metastore-hms, metastore-spi and connector-spi
1189 tests 0 failures (5 skips are pre-existing iceberg live-connectivity tests);
ConnectorPluginSurfaceTest green; checkstyle clean; @ConnectorProperty count is 3; the
freshly built plugin zip carries lib/fe-foundation-1.2-SNAPSHOT.jar and paimon.conf.template.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S73AWq9AHxYfKkzWJfTcKs
…ore the assembly rework

The per-flavor assembly is about to be folded onto the bound
Iceberg*MetaStoreProperties holders, retiring the parallel raw-map alias scan
that IcebergCatalogFactory performs today. Once that scan is gone there is
nothing left to compare the new path against, so capture the reference now.

IcebergCatalogFactoryTest asserts that the keys it names are right; nothing
there notices a key that appears, disappears, or is spelled differently. The
iceberg SDK ignores options it does not recognize, so such a drift does not
throw -- it produces a catalog that connects with different settings than the
operator asked for. These 19 cases assert the ENTIRE option map, one per
flavor and per emission branch, so any drift renders as a diff.

They stay after the rework as the permanent guard that the holder and the
assembly agree.

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

The rest flavor had two readers of the same keys: IcebergRestMetaStoreProperties
declared them with @ConnectorProperty for CREATE-time validation, while
IcebergCatalogFactory scanned the raw map again with its own copy of the alias
names to build the catalog options. Nothing kept the two in agreement, so an
alias or a blank-value rule could be validated one way and assembled another.

The holder is now the single declaration. It gains the eight keys the assembly
needed and the holder lacked (uri, prefix, vended-credentials-enabled, the two
client timeouts, the oauth2 server-uri and token-refresh flag, session-token),
plus the three the connector was reading as raw literals
(nested-namespace-enabled, view-enabled, session-timeout) -- all three are
rest-only, and IcebergCatalogOps already gates the first two on the flavor, so
a non-rest catalog reads exactly the defaults it read before. The connector
binds it once and reads session mode, delegated-token-mode and the listing
flags off it; the scan and write providers read the vended-credentials flag off
it too. The now-dead alias constants are deleted rather than left behind.

The one visible change: values are trimmed, because the property binder trims
and the raw scan did not. A uri written with a trailing space was already
VALIDATED trimmed while the catalog was BUILT from the untrimmed string; the
two now agree. This is the same trade accepted for paimon, and it reaches the
oauth2 token and the rest access keys as well -- pinned as its own snapshot
case. A second, narrower change: iceberg.rest.view-enabled set to the empty
string now reads as its default (true) rather than false, because the framework
treats blank as unset.

The whole-map snapshots are unchanged except for that trim case, which is the
evidence that the assembly still emits byte-identical options.

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

Same convergence as the rest flavor: IcebergGlueMetaStoreProperties declared
the glue keys for CREATE-time validation while IcebergCatalogFactory kept its
own copy of the alias arrays to scan the raw map with at assembly time.

Auditing the two copies against each other first (the alias sets, in order) --
they agreed, so nothing about which alias wins changes here. What the holder
lacked was three keys the assembly reads and validation does not: the region
(and its two aliases), the session token, and the assume-role external id.
Those are added, the seven alias arrays in IcebergConnectorProperties are
deleted, and the region-resolution helper now takes the bound region instead of
re-scanning for it -- the endpoint-regex and us-east-1 fallbacks stay where
they are, since they derive a value rather than read one.

Values are trimmed now, as in the rest flavor. The whole-map snapshots are
unchanged.

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

Last of the three flavors, and with it the raw alias scan goes away.

IcebergJdbcMetaStoreProperties declared only uri and catalog_name; the seven
keys the assembly reads (user, password, init-catalog-tables, schema-version,
strict-mode, and the two driver keys) were scanned out of the raw map by the
factory and the connector. The holder now declares all of them, and the four
readers -- the options appender, the positional catalog-name resolution, the
CREATE-time driver-url gate and the driver registration -- go through it.

Auditing the alias sets first, as with glue: the uri alias order here is the
REVERSE of the rest flavor's (plain uri wins over iceberg.jdbc.uri) and the
holder already had it right, so nothing changes. That order is now pinned by a
whole-map snapshot, since getting it backwards would silently point a live
catalog at a different database.

Two loose ends in the same sweep, both of which the retirement depends on:
the hms path reads hive.conf.resources off the shared HMS holder (which has
declared it since the paimon convergence) instead of as a bare string literal,
and IcebergCatalogFactory.firstNonBlank -- with no callers left outside the S3
region scan, whose alias set is a storage concern rather than any one metastore
flavor's -- becomes private, its two wrappers deleted.

Values are trimmed now, as in the other two flavors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S73AWq9AHxYfKkzWJfTcKs
…nd a conf class

Last step of the iceberg property rework, and the one that retires
IcebergConnectorProperties. That class had become a bag of five unrelated
things: deployment settings, connector-level catalog keys, cache key names,
option keys the assembly emits, and a few names only test fixtures used. Each
goes where its reader is.

IcebergCatalogProperties holds what a user writes for the catalog itself and no
metastore backend in particular -- the backend type, the two type-mapping
switches, and the extra namespace level. Four keys, because everything else
belongs to a flavor and moved onto the *MetaStoreProperties in the two previous
commits. Its two entry points are deliberately unequal: of(Map) binds and
derives and never throws, because it runs on every connector build including
the lazy rebuild after an FE restart, while checkCreateTimeOnlyRules() carries
the meta-cache validation and the backend dispatch that only a CREATE/ALTER
statement reaches. A rule in the wrong one of those two does not fail visibly;
it makes a catalog created last year stop coming back after a restart. The
provider's validateProperties is one line now.

IcebergConf holds the two deployment settings, resolved from the plugin's own
iceberg.conf with the fe.conf key as fallback -- the same shape PaimonConf has,
including the accessors the connector calls instead of spelling the lookup out.

The six meta-cache key names move next to the caches they configure on
IcebergConnector, which was already spelling two of them out a second time (as
was IcebergScanPlanProvider, a third). external_catalog.name is worth a note:
it reads like a REST knob, but IcebergCatalogOps honors it for every flavor, so
it is connector-level -- and it binds to null rather than "", or every catalog
that never set it would acquire an empty namespace level.

The metadata, scan and write providers take the bound holder as their first
argument, so all four consumers read one bound object rather than four
re-derivations of the same map.

Behavior changes, all on inputs that are already pathological: an all-blank
iceberg.catalog.type now reports "Missing" instead of "Unknown" (both throw);
external_catalog.name set to the empty string now means unset; and the flavor
comparison sees trimmed values, as in the two previous commits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S73AWq9AHxYfKkzWJfTcKs
…ess hms catalog

Folding the hms catalog properties into HiveCatalogProperties moved the "HMS URI
is required" check off the lazy createClient path and into of(), which runs at
CREATE CATALOG. Two suites build an hms catalog that names no metastore at all
and never query it, so until now they created one that could only ever fail on
first access.

external_table_p0/tvf/test_catalogs_tvf.groovy reads the catalog's properties
back through catalogs(), covering the *XXX masking of credential keys and the
GRANT/REVOKE visibility filtering. It lost its address when apache#64304 removed DLF
1.0 and commented out "hive.metastore.type" = "dlf", which had been what made a
metastore URI unnecessary. The address added here is never dialled.

auth_call/test_hive_base_case_auth.groovy uses the catalog purely as something to
grant a privilege on. Only the statement root runs would have failed -- the
privilege check in CreateCatalogCommand.validate() precedes property validation,
so the neighbouring statement expecting "denied" still gets "denied" -- but the
two are deliberately identical, and the sibling test_ddl_catalog_auth.groovy
already names a metastore on all four of its hms catalogs, the denied one
included. Both are updated so they stay identical.

Every property the assertions read is untouched, so no .out changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S73AWq9AHxYfKkzWJfTcKs
@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17651	3957	3940	3940
q2	1987	308	204	204
q3	10322	1387	788	788
q4	4680	463	331	331
q5	7474	844	550	550
q6	184	165	133	133
q7	744	792	590	590
q8	9773	1546	1504	1504
q9	5804	4007	3992	3992
q10	6864	1615	1341	1341
q11	501	350	316	316
q12	724	577	452	452
q13	18104	3224	2730	2730
q14	263	252	235	235
q15	q16	731	730	655	655
q17	966	1000	955	955
q18	6552	5562	5541	5541
q19	1169	1151	1060	1060
q20	799	663	544	544
q21	5549	2550	2335	2335
q22	418	341	300	300
Total cold run time: 101259 ms
Total hot run time: 28496 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4310	4160	4241	4160
q2	284	329	210	210
q3	4592	4999	4534	4534
q4	2192	2289	1420	1420
q5	4226	4086	4049	4049
q6	234	172	124	124
q7	1670	1575	1390	1390
q8	2544	2377	2010	2010
q9	7325	7213	7216	7213
q10	4255	4258	3808	3808
q11	546	397	358	358
q12	716	704	496	496
q13	3180	3589	3084	3084
q14	294	311	264	264
q15	q16	689	722	618	618
q17	1312	1266	1291	1266
q18	12159	10991	11737	10991
q19	1183	1153	1140	1140
q20	2211	2230	1983	1983
q21	5507	4856	4667	4667
q22	504	451	414	414
Total cold run time: 59933 ms
Total hot run time: 54199 ms

@hello-stephen

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

query5	4318	589	451	451
query6	463	219	200	200
query7	4835	583	348	348
query8	325	159	148	148
query9	8804	3946	3955	3946
query10	501	364	292	292
query11	5630	2147	1983	1983
query12	152	107	93	93
query13	1286	593	430	430
query14	6063	4214	3951	3951
query14_1	3769	3735	3722	3722
query15	197	189	171	171
query16	990	472	360	360
query17	901	657	533	533
query18	2402	460	329	329
query19	207	189	144	144
query20	102	100	102	100
query21	228	157	140	140
query22	12875	12961	12760	12760
query23	15554	14947	14604	14604
query23_1	14820	14770	14750	14750
query24	7488	1655	1231	1231
query24_1	1249	1243	1233	1233
query25	581	447	394	394
query26	1335	388	209	209
query27	2535	619	380	380
query28	4553	2038	2000	2000
query29	1061	620	485	485
query30	338	259	221	221
query31	1170	1117	1050	1050
query32	108	62	61	61
query33	535	310	247	247
query34	1166	1124	620	620
query35	741	754	651	651
query36	787	780	676	676
query37	156	107	92	92
query38	1835	1783	1658	1658
query39	816	818	800	800
query39_1	790	785	791	785
query40	250	173	148	148
query41	72	69	72	69
query42	96	95	93	93
query43	312	320	275	275
query44	1429	785	773	773
query45	185	179	172	172
query46	1059	1136	727	727
query47	1574	1601	1456	1456
query48	418	439	299	299
query49	593	414	307	307
query50	1118	427	339	339
query51	10372	10791	10559	10559
query52	86	89	78	78
query53	262	277	193	193
query54	294	242	235	235
query55	75	75	70	70
query56	311	305	300	300
query57	1031	1006	955	955
query58	287	278	268	268
query59	1509	1589	1425	1425
query60	325	283	259	259
query61	174	174	174	174
query62	400	323	272	272
query63	270	193	199	193
query64	2823	1042	831	831
query65	3847	3807	3853	3807
query66	1827	483	348	348
query67	27954	27390	27909	27390
query68	3343	1533	1018	1018
query69	421	306	251	251
query70	861	793	760	760
query71	374	340	316	316
query72	3008	2615	2308	2308
query73	847	781	434	434
query74	4598	4477	4269	4269
query75	2341	2346	1971	1971
query76	2353	1106	745	745
query77	335	365	273	273
query78	11013	11084	10608	10608
query79	1479	1112	766	766
query80	1245	526	461	461
query81	520	321	276	276
query82	625	168	135	135
query83	376	316	300	300
query84	318	164	134	134
query85	945	615	505	505
query86	405	234	228	228
query87	1976	1954	1822	1822
query88	3755	2803	2777	2777
query89	386	316	281	281
query90	1905	197	197	197
query91	198	187	155	155
query92	62	60	53	53
query93	1618	1431	990	990
query94	706	349	306	306
query95	799	575	469	469
query96	1043	794	353	353
query97	2471	2465	2393	2393
query98	198	188	180	180
query99	736	731	602	602
Total cold run time: 252079 ms
Total hot run time: 165581 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.09	0.05	0.05
query3	0.26	0.13	0.13
query4	1.60	0.13	0.14
query5	0.24	0.21	0.22
query6	1.16	0.82	0.78
query7	0.04	0.01	0.00
query8	0.06	0.04	0.04
query9	0.37	0.31	0.31
query10	0.58	0.54	0.54
query11	0.18	0.15	0.13
query12	0.18	0.14	0.14
query13	0.47	0.46	0.45
query14	1.01	1.00	0.98
query15	0.62	0.57	0.59
query16	0.30	0.31	0.31
query17	1.10	1.08	1.11
query18	0.20	0.20	0.20
query19	2.00	1.84	1.94
query20	0.01	0.02	0.01
query21	15.42	0.19	0.15
query22	4.97	0.05	0.06
query23	16.16	0.29	0.12
query24	2.91	0.44	0.32
query25	0.11	0.05	0.04
query26	0.74	0.21	0.15
query27	0.04	0.03	0.04
query28	3.61	0.78	0.33
query29	12.47	4.01	3.19
query30	0.27	0.15	0.16
query31	2.77	0.57	0.31
query32	3.23	0.58	0.50
query33	3.14	3.15	3.20
query34	15.64	3.91	3.28
query35	3.23	3.21	3.19
query36	0.56	0.43	0.42
query37	0.08	0.07	0.06
query38	0.05	0.04	0.03
query39	0.04	0.03	0.03
query40	0.16	0.15	0.14
query41	0.08	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.04
Total cold run time: 96.24 s
Total hot run time: 23.64 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 100% (0/0) 🎉
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

@morningman morningman closed this Aug 6, 2026
@morningman morningman reopened this Aug 6, 2026
@morningman
morningman merged commit 95098e3 into apache:master Aug 7, 2026
53 of 59 checks passed
morningman added a commit to morningman/doris that referenced this pull request Aug 7, 2026
…every other connector

Upstream apache#66507 gave every connector one `<Xxx>CatalogProperties`: @ConnectorProperty
fields bound by ConnectorPropertiesUtils, derived read-only values, and one `of(map)`
that binds, derives and validates so each key has exactly one reader. The fluss
connector landed on this branch before that and kept the old shape -- a constant class
of static readers, each re-parsing the raw map at every call site. It was the only
connector left outside the convention.

FlussCatalogProperties replaces FlussConnectorProperties. Five bound keys
(fluss.bootstrap.servers, fluss.union_read.mode, fluss.union_read.max_tail_rows and the
two engine-wide enable.mapping.* switches) and three derived values (the union-read
mode, the type-mapping options, the prefix-stripped fluss client config). The union-read
mode was being parsed four separate times per scan and the client config twice; both are
now derived once. FlussConnector and FlussScanPlanProvider hold the bound object instead
of the map, and the provider's validateProperties is one line -- which, through the SPI
default validatePropertiesForUpdate, guards ALTER CATALOG with the same line as CREATE.

No FlussConf: that class is for a plugin's own <name>.conf, and fluss ships no template
and reads no ConnectorConf key. PaimonSiblingProperties stays as it is -- it holds
another plugin's key literals copied across the classloader split, which is the
IcebergSiblingProperties precedent, not a properties class of this connector.

Every rule stayed in of(). The convention's test is "could an existing catalog violate
this and still run?", and here nothing can: FlussConnector's constructor already
validated, so all three rules were on the rebuild path before this change. There is no
create-time-only set to split out.

Two behavior differences, both from adopting the shared binder:

- A blank value now counts as absent for every key, so
  `fluss.union_read.max_tail_rows = ""` reads as the default instead of being rejected.
  That is the binder's framework-wide rule; a connector that made blank mean something
  else here would be the one place a user has to remember a local exception. It is also
  what keeps `fluss.bootstrap.servers = "   "` reporting "is missing" as it did before.
- The client config now gets the bound (trimmed) bootstrap servers rather than the raw
  string. They are the same key seen twice -- bound, and forwarded with the prefix
  stripped -- and letting the raw entry win handed the fluss client an address that
  differed from the one this connector validated and quotes in its errors.

Verified: 194 fluss tests, 0 failures, 0 skipped; checkstyle 0; the plugin zip carries
fe-foundation (the binder has to run inside the plugin, since org.apache.doris.foundation
is child-first for connectors) and still excludes the parent-first modules;
FlussConnectorProperties has zero hits repo-wide. Both new invariants were checked red by
mutation: dropping the bound-value-last write, and degrading an unknown union-read mode
to auto instead of failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
morningman added a commit to morningman/doris that referenced this pull request Aug 8, 2026
…every other connector

Upstream apache#66507 gave every connector one `<Xxx>CatalogProperties`: @ConnectorProperty
fields bound by ConnectorPropertiesUtils, derived read-only values, and one `of(map)`
that binds, derives and validates so each key has exactly one reader. The fluss
connector landed on this branch before that and kept the old shape -- a constant class
of static readers, each re-parsing the raw map at every call site. It was the only
connector left outside the convention.

FlussCatalogProperties replaces FlussConnectorProperties. Five bound keys
(fluss.bootstrap.servers, fluss.union_read.mode, fluss.union_read.max_tail_rows and the
two engine-wide enable.mapping.* switches) and three derived values (the union-read
mode, the type-mapping options, the prefix-stripped fluss client config). The union-read
mode was being parsed four separate times per scan and the client config twice; both are
now derived once. FlussConnector and FlussScanPlanProvider hold the bound object instead
of the map, and the provider's validateProperties is one line -- which, through the SPI
default validatePropertiesForUpdate, guards ALTER CATALOG with the same line as CREATE.

No FlussConf: that class is for a plugin's own <name>.conf, and fluss ships no template
and reads no ConnectorConf key. PaimonSiblingProperties stays as it is -- it holds
another plugin's key literals copied across the classloader split, which is the
IcebergSiblingProperties precedent, not a properties class of this connector.

Every rule stayed in of(). The convention's test is "could an existing catalog violate
this and still run?", and here nothing can: FlussConnector's constructor already
validated, so all three rules were on the rebuild path before this change. There is no
create-time-only set to split out.

Two behavior differences, both from adopting the shared binder:

- A blank value now counts as absent for every key, so
  `fluss.union_read.max_tail_rows = ""` reads as the default instead of being rejected.
  That is the binder's framework-wide rule; a connector that made blank mean something
  else here would be the one place a user has to remember a local exception. It is also
  what keeps `fluss.bootstrap.servers = "   "` reporting "is missing" as it did before.
- The client config now gets the bound (trimmed) bootstrap servers rather than the raw
  string. They are the same key seen twice -- bound, and forwarded with the prefix
  stripped -- and letting the raw entry win handed the fluss client an address that
  differed from the one this connector validated and quotes in its errors.

Verified: 194 fluss tests, 0 failures, 0 skipped; checkstyle 0; the plugin zip carries
fe-foundation (the binder has to run inside the plugin, since org.apache.doris.foundation
is child-first for connectors) and still excludes the parent-first modules;
FlussConnectorProperties has zero hits repo-wide. Both new invariants were checked red by
mutation: dropping the bound-value-last write, and degrading an unknown union-read mode
to auto instead of failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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. reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants