Skip to content

[feature](function) Add array_except_all scalar function - #67132

Open
Mryange wants to merge 2 commits into
apache:masterfrom
Mryange:add-array-except-all
Open

[feature](function) Add array_except_all scalar function#67132
Mryange wants to merge 2 commits into
apache:masterfrom
Mryange:add-array-except-all

Conversation

@Mryange

@Mryange Mryange commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Problem Summary: Add array_except_all for ARRAY arguments. The function applies multiset difference semantics, preserves unmatched left-side duplicates and order, handles nullable elements and constant columns through ColumnArrayView, and rejects complex element types. Add BE unit coverage and regression coverage for scalar types, nulls, constants, duplicates, and unsupported nested types.

doc apache/doris-website#4089

### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Add array_except_all for ARRAY<scalar> arguments. The function applies multiset difference semantics, preserves unmatched left-side duplicates and order, handles nullable elements and constant columns through ColumnArrayView, and rejects complex element types. Add BE unit coverage and regression coverage for scalar types, nulls, constants, duplicates, and unsupported nested types.

### Release note

Add array_except_all for arrays with scalar element types.

### Check List (For Author)

- Test: Regression test
    - Built BE with ASAN
    - Built FE
    - Ran test_array_except_all regression suite
- Behavior changed: Yes, adds the array_except_all scalar function
- Does this need documentation: No
@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?

@Mryange

Mryange commented Aug 25, 2026

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.

Request changes: the core loop implements saturating multiset subtraction in left-input order, and the registration/include wiring is coherent, but five distinct issues remain.

Goal and scope: the PR is focused on array_except_all; the generic supported-type path, per-row count reset, NULL multiplicity, result offsets, and string-key lifetime are otherwise sound.

Correctness and compatibility: FE admits ARRAY<VARBINARY> even though BE cannot dispatch it, and FLOAT/DOUBLE NaNs use equality semantics inconsistent with Doris. There is no storage-format, protocol, rolling-upgrade, transaction, persistence, or data-write change.

Concurrency, lifecycle, and configuration: the implementation is function-local and introduces no shared mutable state, special lifecycle, or new configuration. The existing constant-fold control exposes the all-constant execution path described inline.

Parallel/special paths: FE registration, scalar visitor fallback, BE factory registration, default outer-NULL handling, common-type coercion, one-constant execution, source discovery, and unity/direct-include hygiene were traced. The both-constant path unnecessarily bypasses expression/framework caching and expands the result once per block row.

Performance and memory: the user-proportional count map bypasses Doris query-memory accounting, and the all-constant override can multiply CPU and result memory by the block row count.

Tests and results: the added expected values cover duplicates, NULLs, row reset, constants, coercion, and several scalar families, but they do not prove unsorted left-order preservation, NaN cancellation, the VARBINARY boundary, or multi-row all-constant result shape. No build or test binary was run because the review bundle prohibits builds; the repository's pure-text build-hygiene check passed.

Observability: no additional logging or metrics are required once the execution failures and resource-accounting defects are fixed. User focus: no additional focus point was supplied.

Review completion: three independent review rounds converged with no new finding in the final round. All surviving candidates are represented by the four inline comments below (the two line-40 defects share one anchor).

struct ArrayExceptAllCountMap {
using ElementType = typename ColumnElementView<PType>::ElementType;
using KeyType = typename NativeType<ElementType>::Type;
using Type = phmap::flat_hash_map<KeyType, size_t>;

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.

Please replace both direct phmap aliases with Doris's allocator-aware map and use float hash/equality consistent with Doris. There are two independent defects here: (1) FLOAT/DOUBLE use ordinary equality, so a right-side NaN cannot be found by the left lookup and array_except_all(array(cast('NaN' as double)), array(cast('NaN' as double))) incorrectly retains it; use the normalized hash plus doris::EqualTo. (2) The default std::allocator leaves this user-sized scratch table, and the string specialization below, outside query MemTracker accounting; doris::flat_hash_map routes allocation through tracking Allocator::alloc. Please fix both aliases and add NaN/accounting coverage.

String get_name() const override { return name; }
bool is_variadic() const override { return false; }
size_t get_number_of_arguments() const override { return 2; }
bool use_default_implementation_for_constants() const override { return false; }

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.

Please keep the framework's default all-constant execution path. This opt-out also makes VectorizedFnCall::is_constant() false, disabling open-time constant-result caching. When FE folding is skipped, two ColumnConst arrays retain the live block's logical row count in ColumnArrayView, so the loop rebuilds the same map and appends the same potentially large result for every row instead of executing once and returning a ColumnConst. Removing the override still leaves one-constant calls on the normal implementation because the framework unwraps only when all arguments are constant; add a multi-row, both-constant BE test that checks the nontrivial value and constant result shape.

continue;
}
DataType itemType = ((ArrayType) argumentType).getItemType();
if (itemType.isComplexType() || itemType.isVariantType() || itemType.isJsonType()) {

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.

Please keep the FE-accepted element families aligned with the BE implementation. VarBinaryType is primitive, so array_except_all(array(X'AB'), array(X'AB')) binds through this check and the ARRAY<AnyDataType(0)> signature, but BE's dispatch_switch_all has no TYPE_VARBINARY case and returns InvalidArgument only at execution. Either reject unsupported primitive/object families here during analysis or implement the missing BE family, and add a regression for the boundary.


suite("test_array_except_all") {
order_qt_partial_cancel """
select array_sort(array_except_all(['a', 'a', 'b'], ['a']))

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.

Please add one non-monotonic assertion without array_sort to cover the advertised left-order guarantee, for example array_except_all([3, 1, 2, 1], [1]) = [3, 2, 1]. As written, every nontrivial result is sorted (and the BE unit inputs are already ordered), so an implementation that emits hash/sorted order would pass the whole new suite.

@Mryange

Mryange commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17598	3027	2993	2993
q2	2161	270	222	222
q3	10214	879	497	497
q4	4683	245	201	201
q5	7674	578	385	385
q6	136	115	96	96
q7	536	489	381	381
q8	9234	893	993	893
q9	3480	2428	2426	2426
q10	6520	836	713	713
q11	398	195	177	177
q12	616	249	190	190
q13	18161	1517	1167	1167
q14	159	153	136	136
q15	q16	435	390	364	364
q17	1325	881	848	848
q18	3105	2271	2272	2271
q19	1115	914	804	804
q20	377	282	195	195
q21	4836	1608	1957	1608
q22	331	261	230	230
Total cold run time: 93094 ms
Total hot run time: 16797 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3371	3333	3291	3291
q2	505	379	372	372
q3	2208	2354	2174	2174
q4	1194	1147	879	879
q5	2194	2117	2097	2097
q6	174	119	88	88
q7	1039	925	824	824
q8	1587	1391	1384	1384
q9	3138	3120	3097	3097
q10	1846	1777	1614	1614
q11	362	274	256	256
q12	455	430	338	338
q13	1496	1538	1133	1133
q14	175	161	165	161
q15	q16	391	397	362	362
q17	3609	3316	3342	3316
q18	4832	4450	4742	4450
q19	874	808	924	808
q20	1018	973	820	820
q21	3858	3108	3314	3108
q22	393	336	316	316
Total cold run time: 34719 ms
Total hot run time: 30888 ms

@hello-stephen

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

query5	4256	403	332	332
query6	383	143	121	121
query7	4982	419	237	237
query8	286	125	135	125
query9	8682	2883	2896	2883
query10	374	220	186	186
query11	5380	1028	904	904
query12	119	71	69	69
query13	1184	453	323	323
query14	6069	2183	2068	2068
query14_1	1951	1946	1933	1933
query15	177	121	116	116
query16	930	376	340	340
query17	792	453	362	362
query18	2340	332	240	240
query19	172	142	115	115
query20	72	69	69	69
query21	202	102	85	85
query22	5343	5319	5243	5243
query23	6731	6200	6010	6010
query23_1	5962	6216	6133	6133
query24	7283	1075	798	798
query24_1	784	797	763	763
query25	412	281	233	233
query26	1230	236	122	122
query27	2780	412	252	252
query28	4703	1489	1475	1475
query29	906	434	338	338
query30	247	158	127	127
query31	823	397	332	332
query32	136	85	81	81
query33	464	218	178	178
query34	993	839	474	474
query35	404	381	324	324
query36	569	562	518	518
query37	119	80	72	72
query38	982	845	833	833
query39	506	490	484	484
query39_1	466	476	469	469
query40	204	88	77	77
query41	55	53	55	53
query42	73	71	74	71
query43	237	241	210	210
query44	1017	555	552	552
query45	110	109	109	109
query46	782	811	525	525
query47	773	755	695	695
query48	296	305	228	228
query49	533	238	191	191
query50	711	273	190	190
query51	8087	7954	8119	7954
query52	75	67	62	62
query53	201	199	150	150
query54	233	178	175	175
query55	71	58	62	58
query56	204	158	175	158
query57	654	682	651	651
query58	200	165	157	157
query59	1309	1201	1096	1096
query60	229	179	184	179
query61	122	152	128	128
query62	345	208	175	175
query63	187	148	145	145
query64	2983	810	647	647
query65	1644	1671	1657	1657
query66	1816	264	203	203
query67	9904	9673	12136	9673
query68	2942	1215	763	763
query69	347	227	194	194
query70	677	598	619	598
query71	253	177	152	152
query72	2338	1709	1526	1526
query73	650	615	341	341
query74	1985	1196	1131	1131
query75	1179	1095	970	970
query76	2313	731	557	557
query77	259	264	210	210
query78	4046	3634	3194	3194
query79	2719	818	600	600
query80	1576	337	287	287
query81	478	153	133	133
query82	620	138	95	95
query83	282	205	192	192
query84	300	105	86	86
query85	838	364	302	302
query86	406	180	170	170
query87	1011	974	887	887
query88	2784	2127	2102	2102
query89	293	197	170	170
query90	1966	125	127	125
query91	130	119	98	98
query92	81	73	81	73
query93	1480	1063	686	686
query94	617	248	228	228
query95	520	325	227	227
query96	773	573	279	279
query97	1066	1050	1024	1024
query98	176	134	141	134
query99	423	341	307	307
Total cold run time: 178197 ms
Total hot run time: 81939 ms

@hello-stephen

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

query1	0.00	0.01	0.01
query2	0.07	0.04	0.04
query3	0.25	0.11	0.11
query4	1.60	0.10	0.10
query5	0.17	0.16	0.16
query6	1.26	0.70	0.70
query7	0.04	0.01	0.00
query8	0.05	0.03	0.04
query9	0.28	0.21	0.21
query10	0.35	0.34	0.33
query11	0.17	0.13	0.12
query12	0.16	0.12	0.12
query13	0.32	0.30	0.31
query14	0.45	0.44	0.44
query15	0.36	0.33	0.36
query16	0.23	0.22	0.22
query17	0.69	0.68	0.63
query18	0.19	0.17	0.17
query19	1.16	1.17	1.16
query20	0.02	0.01	0.01
query21	15.43	0.17	0.11
query22	5.03	0.05	0.04
query23	16.18	0.26	0.10
query24	3.14	0.35	0.26
query25	0.12	0.03	0.04
query26	0.72	0.17	0.13
query27	0.03	0.02	0.03
query28	3.61	0.55	0.30
query29	12.43	3.23	2.56
query30	0.25	0.10	0.12
query31	2.75	0.39	0.16
query32	3.52	0.32	0.23
query33	1.49	1.38	1.38
query34	15.36	2.14	1.75
query35	1.74	1.74	1.75
query36	0.46	0.29	0.29
query37	0.06	0.03	0.04
query38	0.06	0.03	0.03
query39	0.04	0.02	0.02
query40	0.11	0.08	0.07
query41	0.08	0.03	0.02
query42	0.03	0.03	0.03
query43	0.03	0.02	0.03
Total cold run time: 90.49 s
Total hot run time: 14.61 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100% (0/0) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 65.33% (29549/45230)
Line Coverage 50.15% (318562/635277)
Region Coverage 45.86% (260895/568849)
Branch Coverage 47.09% (120460/255826)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 82.61% (19/23) 🎉
Increment coverage report
Complete coverage report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants