Skip to content

[fix](paimon) Handle special characters in partition values#65904

Merged
Gabriel39 merged 2 commits into
apache:masterfrom
zhangstar333:master2
Jul 23, 2026
Merged

[fix](paimon) Handle special characters in partition values#65904
Gabriel39 merged 2 commits into
apache:masterfrom
zhangstar333:master2

Conversation

@zhangstar333

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Problem Summary:

Paimon Partition.spec() returns logical partition values. Doris previously concatenated them into a Hive-style partition path and parsed it with HiveUtil.toPartitionValues().

When a partition value contains /, =, or other path-related characters, it can be parsed incorrectly, causing invalid partition metadata, repeated warning logs, and potentially
increased planning latency.

Release note

None

Check List (For Author)

  • Test

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

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

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

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

@hello-stephen

Copy link
Copy Markdown
Contributor

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

Please clearly describe your PR:

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

@zhangstar333

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39 Gabriel39 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Make partitionName a collision-free canonical key

Although this change correctly builds PartitionItem from the logical values, partitionName is still produced by concatenating the unescaped values as col=value/col=value and is then used as the unique key of both maps.

This encoding is not injective. For example, these two different partitions:

P1: a="x/b=y", b="z"
P2: a="x",     b="y/b=z"

both produce a=x/b=y/b=z. The second partition silently overwrites the first one in both nameToPartition and nameToPartitionItem. Since both maps lose the same entry, PaimonPartitionInfo.isPartitionInvalid() still returns false, so partition pruning/MTMV partition tracking can consume incomplete metadata without detecting the collision.

Please use an escaped/canonical representation (for example Paimon PartitionPathUtils.generatePartitionPath, or another collision-free encoding) for the map key, and fail explicitly if put() replaces an existing entry. The regression test should contain both colliding partitions and assert that both maps retain two entries. Existing special-character tests only contain one partition, so they cannot detect this case.

Static review only; no tests were executed.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review completed with no additional unique inline findings. A blocking partition-identity issue remains, but it is already fully covered by review 4753864901 on the current head, so this review does not duplicate it.

  • Goal and tests: Directly converting Paimon logical values fixes the single-partition Hive-path parsing failure and the added tests cover value preservation, map-order independence, and legacy DATE conversion. The existing review correctly identifies the missing two-partition collision case.
  • Scope and mechanisms: The patch is otherwise small and focused. The direct value/type alignment is correct, and all supported Paimon partition-info creation paths share this loader.
  • Concurrency and lifecycle: The changed maps/lists are constructed locally during snapshot-cache loading; no new thread, lock, publication, static-init, or resource-lifecycle issue was found.
  • Compatibility and parallel paths: No FE-BE, RPC, EditLog, storage-format, transaction, data-write, or configuration contract changes. Partition-order changes can trigger a safe MTMV refresh. Paimon scans still receive predicates separately, but pruning metadata, partition-use collection, SHOW PARTITIONS, update-time aggregation, and MTMV mappings/snapshots consume these maps.
  • Error handling and observability: Conversion failures still warn, but the already-reported collision is silent because both conversions succeed and the map-size validity check cannot detect equal overwrites.
  • Performance: The new loop remains O(partitions * partition columns), avoids redundant Hive parsing, and adds no separate CPU/memory concern.
  • Validation: Static review only as required by the review environment. CheckStyle is green on the live head; no FE unit-test result is present in the visible PR checks.
  • User focus: No additional focus points were supplied.

@zhangstar333

Copy link
Copy Markdown
Contributor Author

run buildall

1 similar comment
@zhangstar333

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17604	4102	4087	4087
q2	2024	316	196	196
q3	10330	1362	819	819
q4	4678	471	339	339
q5	7526	844	560	560
q6	178	167	137	137
q7	735	817	604	604
q8	9574	1547	1546	1546
q9	6093	4325	4277	4277
q10	6804	1724	1466	1466
q11	511	357	325	325
q12	742	576	450	450
q13	18070	3209	2696	2696
q14	269	262	241	241
q15	q16	786	774	695	695
q17	1031	1105	989	989
q18	6798	5745	5371	5371
q19	1444	1212	1033	1033
q20	837	667	568	568
q21	5710	2532	2318	2318
q22	421	349	293	293
Total cold run time: 102165 ms
Total hot run time: 29010 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4413	4305	4299	4299
q2	280	316	212	212
q3	4534	4875	4417	4417
q4	2036	2158	1335	1335
q5	4403	4272	4281	4272
q6	233	179	123	123
q7	1735	1622	1917	1622
q8	2534	2191	2101	2101
q9	7754	7859	7587	7587
q10	4649	4690	4191	4191
q11	575	411	393	393
q12	747	750	542	542
q13	3351	3534	2958	2958
q14	293	310	288	288
q15	q16	735	726	639	639
q17	1365	1324	1345	1324
q18	8092	7246	6901	6901
q19	1111	1088	1051	1051
q20	2211	2222	1941	1941
q21	5204	4542	4357	4357
q22	523	445	396	396
Total cold run time: 56778 ms
Total hot run time: 50949 ms

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17672	4217	4209	4209
q2	2038	336	199	199
q3	10278	1434	822	822
q4	4712	480	342	342
q5	7719	861	568	568
q6	214	174	136	136
q7	772	819	604	604
q8	10079	1598	1684	1598
q9	5910	4387	4371	4371
q10	6836	1762	1477	1477
q11	532	383	335	335
q12	736	577	464	464
q13	18090	3987	2821	2821
q14	269	263	247	247
q15	q16	793	784	714	714
q17	972	1050	1014	1014
q18	6855	5737	5649	5649
q19	1160	1262	1115	1115
q20	834	696	598	598
q21	5738	2614	2595	2595
q22	446	370	300	300
Total cold run time: 102655 ms
Total hot run time: 30178 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4456	4391	4365	4365
q2	297	316	233	233
q3	4581	5049	4371	4371
q4	2092	2158	1373	1373
q5	4625	4297	4299	4297
q6	232	181	125	125
q7	1776	1620	1448	1448
q8	2355	2055	1955	1955
q9	7280	7235	7214	7214
q10	4696	4593	4174	4174
q11	556	429	365	365
q12	757	750	532	532
q13	2993	3322	2818	2818
q14	288	286	265	265
q15	q16	694	713	626	626
q17	1319	1302	1281	1281
q18	7347	6933	6875	6875
q19	1093	1124	1092	1092
q20	2234	2225	1960	1960
q21	5328	4631	4524	4524
q22	539	472	419	419
Total cold run time: 55538 ms
Total hot run time: 50312 ms

@hello-stephen

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

query5	4255	624	494	494
query6	471	227	202	202
query7	4821	575	343	343
query8	323	186	168	168
query9	8698	4003	3992	3992
query10	480	362	317	317
query11	5876	2333	2136	2136
query12	163	103	103	103
query13	1274	597	432	432
query14	5800	5187	4841	4841
query14_1	4227	4202	4184	4184
query15	214	199	173	173
query16	947	476	448	448
query17	1047	675	534	534
query18	604	448	339	339
query19	208	182	148	148
query20	107	105	104	104
query21	177	163	135	135
query22	13561	13516	13286	13286
query23	17421	16542	16064	16064
query23_1	16361	16206	16274	16206
query24	7690	1743	1280	1280
query24_1	1283	1281	1270	1270
query25	534	433	371	371
query26	1187	327	201	201
query27	2606	601	388	388
query28	4465	1964	1973	1964
query29	1076	618	490	490
query30	353	264	229	229
query31	1126	1092	993	993
query32	110	63	63	63
query33	538	325	302	302
query34	1171	1149	635	635
query35	754	774	650	650
query36	1202	1192	1112	1112
query37	143	113	90	90
query38	1882	1702	1642	1642
query39	897	890	853	853
query39_1	842	822	822	822
query40	248	160	145	145
query41	64	60	61	60
query42	91	91	94	91
query43	325	315	277	277
query44	1418	746	748	746
query45	193	186	172	172
query46	1053	1175	733	733
query47	2196	2155	2060	2060
query48	404	401	292	292
query49	588	432	302	302
query50	1071	437	347	347
query51	10818	10746	10622	10622
query52	86	92	75	75
query53	253	272	199	199
query54	279	227	207	207
query55	77	70	65	65
query56	294	301	293	293
query57	1333	1321	1231	1231
query58	288	271	256	256
query59	1566	1704	1461	1461
query60	295	274	260	260
query61	150	153	149	149
query62	540	496	428	428
query63	249	199	203	199
query64	2794	1026	828	828
query65	4714	4635	4611	4611
query66	1823	496	377	377
query67	29312	29225	29039	29039
query68	3097	1518	1000	1000
query69	418	307	257	257
query70	1047	944	937	937
query71	371	333	314	314
query72	3014	2693	2451	2451
query73	838	785	427	427
query74	5086	4916	4737	4737
query75	2535	2496	2134	2134
query76	2347	1192	774	774
query77	360	389	288	288
query78	11914	11798	11366	11366
query79	1229	1119	759	759
query80	644	591	510	510
query81	459	343	294	294
query82	242	163	123	123
query83	421	332	302	302
query84	287	169	137	137
query85	1047	593	512	512
query86	323	298	285	285
query87	1828	1815	1732	1732
query88	3719	2780	2760	2760
query89	409	363	337	337
query90	2050	196	191	191
query91	203	184	159	159
query92	60	60	55	55
query93	1590	1595	1022	1022
query94	536	350	281	281
query95	774	501	543	501
query96	1029	826	355	355
query97	2676	2631	2469	2469
query98	218	204	197	197
query99	1097	1108	975	975
Total cold run time: 255889 ms
Total hot run time: 177107 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.09	0.05	0.05
query3	0.25	0.14	0.14
query4	1.60	0.14	0.14
query5	0.24	0.23	0.22
query6	1.24	1.08	1.03
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.38	0.30	0.31
query10	0.59	0.55	0.54
query11	0.19	0.14	0.14
query12	0.18	0.14	0.14
query13	0.47	0.48	0.47
query14	1.02	1.01	0.99
query15	0.61	0.60	0.59
query16	0.34	0.31	0.33
query17	1.08	1.10	1.14
query18	0.23	0.21	0.21
query19	2.12	1.94	1.92
query20	0.02	0.02	0.02
query21	15.45	0.20	0.14
query22	5.01	0.05	0.06
query23	16.13	0.31	0.12
query24	2.99	0.39	0.32
query25	0.11	0.05	0.04
query26	0.73	0.21	0.15
query27	0.04	0.03	0.03
query28	3.52	0.95	0.54
query29	12.47	4.10	3.29
query30	0.27	0.15	0.15
query31	2.76	0.60	0.31
query32	3.24	0.58	0.49
query33	3.16	3.25	3.20
query34	15.55	4.24	3.46
query35	3.48	3.50	3.57
query36	0.55	0.44	0.41
query37	0.09	0.06	0.07
query38	0.05	0.05	0.03
query39	0.04	0.03	0.03
query40	0.17	0.17	0.15
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.04
Total cold run time: 96.73 s
Total hot run time: 24.93 s

@zhangstar333

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review completed with one P2 inline finding: the new escaped partition name still derives its component order from Doris's schema-field-ordered partitionColumns, not Paimon's declared partition-key order.

  • Goal and tests: Direct logical-value conversion and Paimon's escaping fix special-character parsing and partition-name collisions. The five focused unit tests cover the intended local cases, but not the production field-order/key-order divergence in the inline finding.
  • Scope and mechanisms: This is a small read-side metadata change. Filesystem/JDBC, normal Hive, and REST/fallback partition-loading paths converge on this helper.
  • Concurrency and lifecycle: The change builds fresh local maps and lists before cache publication. It adds no thread, lock, shared mutable state, static initialization, or resource-lifecycle path.
  • Compatibility and parallel paths: There is no FE-BE/RPC, EditLog, storage-format, configuration, transaction, or data-write compatibility change. Percent-escaped keys are otherwise consumed opaquely, but the declared-order mismatch changes the user-visible SHOW PARTITIONS name and persisted MTMV partition identities.
  • Conditions, errors, and observability: Legacy DATE conversion and conversion-failure fallback remain intact. Duplicate identities now fail explicitly, loader failures do not publish partial maps, and no additional logging or metric issue was found.
  • Persistence and refresh behavior: Old special-character names conservatively cause a one-time MTMV refresh. The inline issue is distinct because a valid field/key-order mismatch is noncanonical immediately and can change again after field-position evolution without changing the Paimon partition declaration.
  • Performance: Complexity remains O(partitions * partition columns), and removing Hive-path reparsing introduces no separate CPU, memory, I/O, or blocking regression.
  • Validation: On the live head, FE UT, External Regression, compile, and CheckStyle passed. check_coverage_fe failed during JaCoCo report generation because avro-scanner.jar was missing, not because a test or coverage threshold failed. Static review only; no local builds or tests were run under the review-environment contract.
  • User focus: No additional focus points were supplied.

// Build partition values directly instead of parsing them as a Hive partition path.
List<String> partitionValues = Lists.newArrayListWithExpectedSize(partitionColumns.size());
LinkedHashMap<String, String> orderedPartitionSpec = new LinkedHashMap<>();
for (Column partitionColumn : partitionColumns) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Preserve Paimon's declared partition-key order

This loop uses partitionColumns as the canonical name order, but the production list is built by filtering TableSchema.fields() in PaimonExternalTable.initSchema; Paimon keeps partitionKeys() as an independent ordered list and emits Partition.spec() in that declared order. For fields [dt, region] with PARTITIONED BY (region, dt), this changes the identity from region=us/dt=2024-01-15 to dt=2024-01-15/region=us. Typed pruning values happen to remain aligned with Doris's field-ordered list, but SHOW PARTITIONS now exposes a non-Paimon name and persisted MTMV partition keys change whenever field order differs or is reordered.

Please build/cache the Doris partition columns in tableSchema.partitionKeys() order (or pass that order explicitly) and use it for the types, values, and escaped name. The test should exercise a schema whose field order differs from its declared partition-key order; the current test only permutes spec.entrySet() while manually passing the expected column order.

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

Copy link
Copy Markdown
Contributor

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

@Gabriel39
Gabriel39 merged commit 4016780 into apache:master Jul 23, 2026
32 of 33 checks passed
github-actions Bot pushed a commit that referenced this pull request Jul 23, 2026
Paimon `Partition.spec()` returns logical partition values. Doris
previously concatenated them into a Hive-style partition path and parsed
it with `HiveUtil.toPartitionValues()`.

When a partition value contains `/`, `=`, or other path-related
characters, it can be parsed incorrectly, causing invalid partition
metadata, repeated warning logs, and potentially
  increased planning latency.
morningman added a commit that referenced this pull request Jul 23, 2026
…e-connector

Upstream #65904 hardened PaimonUtil.generatePartitionInfo in fe-core to
handle partition values containing path-special chars (/, =, [, ], *).
That file was already deleted on this branch (P5 paimon migration, #64653),
so the rebase surfaced only a modify/delete conflict; the equivalent logic
lives in PaimonConnectorMetadata.collectPartitions.

The core bug (re-parsing the concatenated Hive-style name back into values
via HiveUtil.toPartitionValues) cannot occur here: the connector already
supplies parsed values directly and fe-core never re-parses the name. This
ports the remaining hardening:
  - drive value order from the partition columns, not Paimon's spec-map
    iteration order, so value i aligns with type i in fe-core;
  - render the partition NAME via PartitionPathUtils.generatePartitionPath
    so path-special chars are escaped exactly like the Paimon SDK, keeping
    names byte-identical to fe-core and collision-free;
  - fail loud on genuinely-duplicate partition names.

Mirrors #65904's four new PaimonUtilTest cases at the connector level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T2pc9qUhHnjcvLtzzuHaRE
morningman added a commit that referenced this pull request Jul 23, 2026
…e-connector

Upstream #65904 hardened PaimonUtil.generatePartitionInfo in fe-core to
handle partition values containing path-special chars (/, =, [, ], *).
That file was already deleted on this branch (P5 paimon migration, #64653),
so the rebase surfaced only a modify/delete conflict; the equivalent logic
lives in PaimonConnectorMetadata.collectPartitions.

The core bug (re-parsing the concatenated Hive-style name back into values
via HiveUtil.toPartitionValues) cannot occur here: the connector already
supplies parsed values directly and fe-core never re-parses the name. This
ports the remaining hardening:
  - drive value order from the partition columns, not Paimon's spec-map
    iteration order, so value i aligns with type i in fe-core;
  - render the partition NAME via PartitionPathUtils.generatePartitionPath
    so path-special chars are escaped exactly like the Paimon SDK, keeping
    names byte-identical to fe-core and collision-free;
  - fail loud on genuinely-duplicate partition names.

Mirrors #65904's four new PaimonUtilTest cases at the connector level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T2pc9qUhHnjcvLtzzuHaRE
yiguolei pushed a commit that referenced this pull request Jul 23, 2026
#65904 (#65938)

Cherry-picked from #65904

Co-authored-by: zhangstar333 <zhangsida@selectdb.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. dev/4.0.x dev/4.0.x-conflict dev/4.1.4-merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants