Skip to content

[master][pick] Forward-port Parquet nullable selection and Iceberg Variant reads - #66413

Draft
Gabriel39 wants to merge 9 commits into
apache:masterfrom
Gabriel39:dev/forward-port-66397-66302-master
Draft

[master][pick] Forward-port Parquet nullable selection and Iceberg Variant reads#66413
Gabriel39 wants to merge 9 commits into
apache:masterfrom
Gabriel39:dev/forward-port-66397-66302-master

Conversation

@Gabriel39

@Gabriel39 Gabriel39 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Forward-port the Parquet nullable selection optimization from branch-4.1 to the latest master.
  • Forward-port Iceberg Variant reads to master's plugin-driven connector implementation.
  • Preserve File Scanner V2 safety checks, mixed-version backend gating, typed-leaf projection, and delete-only MERGE behavior.
  • Resolve the latest-master benchmark and deferred-column layout conflicts while retaining coverage from both sides.
  • Adapt the catalog Hadoop-property concurrency fix to master's storage adapter architecture by atomically publishing an immutable shared snapshot.
  • Expand Iceberg Variant reader coverage for primitive types, nested containers, multi-file scans, row groups, delete vectors, equality/position deletes, and lazy materialization.
  • Preserve append atomicity for nullable, STRUCT, ARRAY, and MAP destinations when lazy Variant fallback discovers corrupt input.
  • Cache Variant schema presence so ordinary Parquet scans avoid Variant-specific planning and statistics work.
  • Preserve projected shredded Variant states across exchange gathers, including batches with different physical leaf types.
  • Adapt the forwarded shredded-state traversal to master's immutable column callback interface.

Original pull requests

Verification

  • FE reactor build, Checkstyle, and targeted tests: 277 tests passed for the Parquet and Iceberg forward ports.
  • CatalogPropertyTest: 2 tests passed for atomic publication and snapshot immutability.
  • FE Checkstyle passed with 0 violations after the catalog forward port.
  • BE ASAN targeted tests: 408 tests from 29 suites passed.
  • Focused BE ASAN Variant/Parquet tests after the latest forward ports: 119 tests from 6 suites passed.
  • Clang-format 16 dry run passed for all changed C/C++ files.
  • git diff --check passed.

@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?

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39
Gabriel39 marked this pull request as ready for review August 4, 2026 04:04
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Codex completed, but no new pull request review was submitted for the current head SHA.
Workflow run: https://github.com/apache/doris/actions/runs/30875467070

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

@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: four blocking issues remain.

  • Variant footer/page-index pruning can suppress an earlier error-preserving conjunct.
  • Late runtime-filter refresh can accept shifted deferred Variant output slots and index outside the active file block.
  • Delete-only Variant MERGE is not safe for new-FE/old-BE rolling upgrades.
  • One deterministic regression result bypasses the required generated golden file.

Checkpoint conclusions:

  • Goal and data correctness: The forward-port covers native Parquet Variant reads, nullable selection, planner/access-path plumbing, metadata COUNT, and delete-only MERGE, with broad unit/regression coverage; the two scan correctness defects above mean the goal is not yet safely achieved.
  • Scope and parallel paths: The change is cohesive but large. Footer/page pruning, eager/deferred projection, native/legacy scanner gates, and read/write paths were traced end to end. The mirrored page-index defect is covered by the first inline comment.
  • Concurrency and lifecycle: Catalog storage bindings and shredded-state ownership/COW were checked without another defect. Late request activation at row-group boundaries is not safe because deferred positions are not preserved (inline comment).
  • Compatibility and protocol: New Thrift plumbing defaults correctly for old-FE/new-BE, but new-FE/old-BE delete-only Variant MERGE lacks a query-wide capability fence (inline comment).
  • Tests and observability: The PR reports targeted FE, connector, BE ASAN, and format checks, and adds useful profiles. I did not rerun builds/tests because the authoritative review bundle forbids it. Missing coverage includes unsafe-conjunct metadata pruning, two-root late-RF refresh, mixed-version writer omission, and the signed-selector golden result.
  • Transactions/persistence/configuration: No new persistence or dynamic-configuration defect was found; delete-file lifecycle otherwise remains fenced and errors propagate.

User focus: review_focus.txt contains no additional guidance, so the entire PR was reviewed.

Review completion: Three rounds converged on this frozen four-comment set; all other candidates were either disproved by upstream invariants or dismissed with concrete code evidence.

const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
const format::FileScanRequest& request, const cctz::time_zone* timezone) {
for (const auto& conjunct : request.conjuncts) {
const auto predicate = extract_variant_shredded_predicate(conjunct);

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] Fence Variant metadata pruning at the first unsafe conjunct

This loop can skip an earlier non-extractable, error-preserving conjunct and prune from a later Variant comparison before any rows are evaluated. For example, with assert_true(id != 0, 'boom') followed by CAST(v['n'] AS INT) > 3000, a row group containing id = 0 but with shredded n.max <= 3000 is discarded here, so the required error never occurs. TableReader::_is_safe_to_pre_execute() stops split pruning at this barrier, and build_predicate_conjunct_schedule() disables staged compaction for the same reason, but both this footer loop and the page-index loop at 1712 ignore it. Please carry the original safe-prefix boundary into the localized request (slotless barriers may otherwise disappear), apply it to both loops, and add an error-preservation differential test.

// Signed integer selectors are array indexes, even when a shredded object has a key with the
// same serialized token. The ambiguous scanner path must retain enough state for both results.
List<List<Object>> signedSelectorRows = sql """
SELECT CAST(v[-1] AS INT), CAST(v['-1'] AS INT)

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] Record this deterministic result through the golden file

This stable one-row result is currently hardcoded with assertEquals, so it bypasses the suite's generated .out contract. The repository testing rules require determined regression results to use qt/order_qt rather than assertion-style checks. Please make this a named golden query (with deterministic ordering if the fixture can grow) and regenerate test_iceberg_variant_read.out so the signed-selector behavior is reviewed and maintained like the neighboring cases.

file_request->local_positions = *fixed_local_positions;
}
file_request->non_predicate_positions.clear();
file_request->conjuncts.clear();

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] Preserve deferred output slots across late-filter refresh

refresh_conjuncts() seeds only local_positions, while this clears and later reallocates non_predicate_positions. With two independent Variant roots, the initial layout can be v1 predicate/output at 0/1 and v2 at 2/3; rebuilding from retained locals {0,2} assigns the outputs to 3/4. same_physical_scan_layout() compares only the local map and each root's predicate projection, so it accepts that shifted request even though the active file-block template still has four slots. At the next row group the Parquet scheduler uses the refreshed output positions, overwrites slot 3 and then indexes slot 4 out of range. Please preserve the active deferred-position map and compare both deferred positions and output projection trees before queueing a refresh; add a multi-row-group late-RF test with two deferred roots.

const auto& merge_sink = _t_sink.iceberg_merge_sink;
// An old FE cannot produce delete-only plans, so an unset flag retains its data-writer path.
_writes_data_files = !merge_sink.__isset.writes_data_files || merge_sink.writes_data_files;
// Missing means an old FE plan, which predates SQL MERGE cardinality validation.

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] Fence writer omission for old BEs during rolling upgrade

A new FE can now allow a delete-only MERGE on a Variant table and send writes_data_files=false, but an old BE skips this unknown Thrift field and still constructs VIcebergTableWriter. Its init_properties() parses the full Iceberg schema_json, and the old parser has no variant primitive, so a fragment placed on that BE fails while the same fragment succeeds on a new BE. The adjacent cardinality capability is disabled through the query-wide execution version for exactly this rolling-upgrade reason; please add an equivalent capability fence here (or reject this plan in FE until all participating BEs support writer omission) and cover the mixed-version case.

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 37.04% (30/81) 🎉
Increment coverage report
Complete coverage report

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

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

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

)

- Fuse nullable definition-level runs with the row filter in one
traversal.
- Produce physical decode ranges, the selected NULL map, and selected
value counts without first materializing and rescanning a row-wise
selection map.
- Reuse the existing selected-decoder strategies and nullable in-place
expansion.
- Restrict fusion to batches with at least 1,024 rows, at least 10%
NULLs, and materially fragmented definition-level runs. No-NULL,
low-NULL, clustered, nested, and non-expandable shapes keep the legacy
path.

The full benchmark matrix includes no-NULL, low-NULL, and clustered
level plans as negative controls. Those shapes do not remove enough
legacy work to guarantee a win, so this change deliberately leaves them
unchanged. Decoder selection and encoding-specific materialization are
not modified.

- ASAN: `NativeNullableSelectionTest.*` and benchmark scenario tests:
16/16 passed.
- ASAN: `ParquetV2NativeDecoderTest.*`: 118/118 passed.

| Coverage | Legacy/fused pairs | Correctness | Regressions | Mean CPU
change | Least improvement |
|---|---:|---|---:|---:|---:|
| Full scenario matrix | 100 | Identical ranges and NULL maps | N/A
(includes negative controls) | N/A | N/A |
| Production-eligible scenarios | 30 | Identical ranges and NULL maps |
0 | -42.68% | 8.08% |

| Scenario | Repetitions | Legacy median CPU | Fused median CPU | CPU
change | Legacy CV | Fused CV |
|---|---:|---:|---:|---:|---:|---:|
| 10% selectivity / 50% NULL, fragmented | 10 | 400,220 ns | 177,905 ns
| -55.55% | 0.58% | 1.28% |
| 99% selectivity / 50% NULL, high-selectivity boundary | 10 | 687,385
ns | 460,657 ns | -32.98% | 0.60% | 1.32% |

The microbenchmark isolates nullable selection planning; it is not
presented as an end-to-end query speedup.
…he#66302)

Forward-port the Iceberg Variant Parquet reader to the plugin-driven connector on master while preserving mixed-version scan safety and delete-only merge behavior.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#66392

Problem Summary: Master replaced the legacy Hadoop property cache with a shared storage adapter snapshot. Preserve the original fix invariant by publishing an immutable type-keyed map so connector consumers cannot mutate catalog-wide state after publication. Add deterministic coverage for atomic publication and snapshot immutability.

### Release note

Prevent connector consumers from modifying shared catalog storage adapter snapshots.

### Check List (For Author)

- Test: Unit Test (`CatalogPropertyTest`)
- Behavior changed: No. This hardens the existing snapshot contract.
- Does this need documentation: No
@Gabriel39
Gabriel39 force-pushed the dev/forward-port-66397-66302-master branch from 154db63 to 6adba50 Compare August 4, 2026 07:57
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot removed the approved Indicates a PR has been approved by one committer. label Aug 4, 2026

@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.

Requesting changes: three additional P1 blockers remain. I deduplicated against the four existing live threads, which continue to cover unsafe Variant metadata-pruning order, late-filter deferred-slot refresh, the regression golden contract, and old-BE merge-writer omission.

Checkpoint conclusions:

  • Goal and proof: The PR aims to forward-port native Iceberg Variant reads/writes, physical-leaf pruning, and fused nullable selection. Broad unit and regression coverage is present, but the connector API, rolling-upgrade scan admission, and metadata-COUNT snapshot gaps mean the goal is not safely complete.
  • Scope and focus: The change is large but cohesive around external Variant execution. review_focus.txt adds no extra guidance, so the whole PR was reviewed.
  • Concurrency: No new thread is introduced. The private shredded-state materialization-cache mutex, its append/reset/read ordering, catalog publication, scanner scheduling, and late runtime-filter activation were traced; no new concurrency issue survived beyond the existing deferred-slot thread.
  • Lifecycle: Connector class loading, scan initialization, statement snapshot pinning, file-local projection, block reuse, and merge sink open/close were traced. The metadata-COUNT escape hatch is incorrectly decided before the handle used for planning is pinned (inline).
  • Configuration: No new configuration item or dynamic-reload contract is introduced.
  • Compatibility: Two public connector SPI methods were added without the required API-major/baseline update (inline). Read-side Variant admission also mistakes a cloud-only smooth-upgrade marker for a general old-BE capability fence (inline).
  • Parallel paths: Cloud and community upgrades, root and leaf projections, native and legacy scanner gates, metadata and real-range COUNT, and all merge clause shapes were compared. Delete-only merge propagation is complete for new participants; its old-BE failure remains covered by the existing live thread.
  • Conditional logic: The metadata-only COUNT and backend-marker conditions are not sufficient for the states they claim to prove (inline). Other new projection, fallback, and selection gates were checked against their upstream invariants.
  • Error handling and memory safety: Status/exception propagation, footer corruption checks, recursive column exclusivity, direct-leaf ownership, nullable alignment, and conversion-failure remapping were checked without another distinct defect.
  • Data correctness: Existing live threads cover unsafe metadata pruning and shifted scan coordinates. The new snapshot and mixed-version findings can also route unsupported Variant decoding and are blocking.
  • Tests: Coverage is broad, but it lacks connector-major enforcement for the reachable handle/provider surface, a non-cloud old-BE scan case, and a pinned snapshot whose COUNT summary must fall back to files. The existing live P2 covers the deterministic result that bypasses the generated golden file.
  • Test results: I did not run builds or tests because the authoritative review bundle requires a static-only review; reported PR results were therefore not independently verified.
  • Observability: New scan profiles cover the important reader paths, and no distinct logging or metrics blocker was found. The upgrade and snapshot mismatches need admission-time correctness rather than post-failure observability.
  • Transactions and persistence: No Doris EditLog or transaction-state change is introduced. Iceberg snapshot selection and write lifecycle were reviewed; snapshot consistency is the blocking read-side issue.
  • Writes and FE-BE variables: The new write flag survives planner clones and new-version sink lifecycles, and its old-FE/new-BE default is conservative. New-FE/old-BE writer omission remains the existing live blocker; the new read carrier has the separate ordinary-upgrade blocker inline.
  • Performance and other risks: Physical projection, page/footer pruning, allocation/COW, and fused nullable hot paths were examined. No additional substantiated performance or correctness issue remained after the final candidate audit.

Review status: static review converged on this frozen three-comment addition plus the four existing live threads.

* Whether this write can emit data files. A delete-only MERGE returns false so a connector may
* allow position-delete output even when the table has read-only column types.
*/
default boolean isWritesDataFiles() {

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] Bump the connector plugin API for these new SPI methods

This method and ConnectorScanPlanProvider.canServeMetadataOnlyCount() extend the public connector SPI, but the PR leaves the kernel/plugin API at 3.0. A new Iceberg plugin is therefore admitted by an old 3.0 FE; because connector SPI classes are parent-first, planWrite() then invokes isWritesDataFiles() on the old kernel interface and fails with NoSuchMethodError. Please apply the repository's required major bump (including the test pin/baseline), and include these reachable provider/handle types in the frozen surface so this cannot evade the guard.

ConnectorScanPlanProvider scanProvider = resolveScanProvider();
if (isTableLevelCountStarPushdown() && conjuncts.isEmpty() && scanProvider != null) {
metadataCountProven = onPluginClassLoader(scanProvider,
() -> scanProvider.canServeMetadataOnlyCount(

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] Prove metadata COUNT on the same pinned handle that is planned

This capability check runs before pinMvccSnapshot() updates currentHandle, while planScan() later uses the pinned handle and recomputes whether the summary can serve the count. For a time-travel/reference query (or a snapshot change between phases), the early latest snapshot can return true, disabling the old-BE fence, but the selected snapshot can contain deletes or missing counters and fall back to real Variant file ranges. Please apply the statement pin before using this escape hatch, or base the bypass on the actual planned ranges; add a test where latest is countable but the pinned snapshot is not.

return;
}
for (Backend backend : backends) {
if (backend.isSmoothUpgradeSrc()) {

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] Fence ordinary rolling-upgrade backends too

isSmoothUpgradeSrc() identifies only the cloud colocated smooth-upgrade source: its production setter is in CloudSystemInfoService, while ordinary backends default this bit to false. During a normal mixed-version cluster upgrade, an old BE therefore passes this loop and can be assigned an Iceberg Variant scan even though it lacks the new carrier/native reader. Please gate on an explicit BE capability or execution version that covers every eligible backend, and test an old non-cloud backend rather than manually setting the cloud-only flag.

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 71.26% (2120/2975) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 59.40% (25861/43535)
Line Coverage 43.52% (260624/598842)
Region Coverage 39.24% (206818/527073)
Branch Coverage 40.59% (94725/233369)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 37.04% (30/81) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 73.10% (2174/2974) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 74.56% (31653/42454)
Line Coverage 58.77% (349529/594779)
Region Coverage 54.94% (290517/528811)
Branch Coverage 55.98% (130645/233383)

### What problem does this PR solve?

Issue Number: None

Related PR: apache#66413

Problem Summary: Review follow-ups found that Variant metadata pruning could bypass an earlier error-producing predicate, late runtime-filter refresh could shift deferred Variant output slots, and mixed-version clusters could schedule unsupported Variant scans or delete-only MERGE plans. The connector SPI version also did not reflect its expanded public surface. In addition, debug Boolean validation filtered and copied large nullable complex columns even when they contained no Boolean values, which could exhaust query memory. This change preserves the safe pruning prefix and scan layout, introduces execution-version compatibility gates, bumps and freezes the connector SPI surface, pins metadata-count checks to the selected snapshot, and skips allocation-heavy Boolean filtering when no Boolean subcolumn exists.

### Release note

Iceberg Variant scans and delete-only MERGE now enforce rolling-upgrade compatibility, and debug column validation avoids copying non-Boolean complex payloads.

### Check List (For Author)

- Test: Unit Test and Regression Test
    - Focused ASAN BE unit tests for Boolean validation, Variant scan refresh, metadata pruning, page filtering, and MERGE compatibility
    - FE compatibility and connector SPI surface unit tests
    - Generated Iceberg Variant regression golden output
    - FE Checkstyle and BE clang-format
- Behavior changed: Yes. Unsafe metadata pruning and mixed-version Variant execution are rejected or conservatively evaluated, and non-Boolean nullable complex columns are validated without copying their payload.
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 17.82% (18/101) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.57% (1912/2465)
Line Coverage 64.55% (34226/53024)
Region Coverage 64.46% (17280/26809)
Branch Coverage 53.95% (9252/17148)

@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.

I found two P1 blockers, detailed inline: incomplete shredded Variant state crosses a join shuffle and cannot be accumulated safely, and the rolling-upgrade fence rejects valid non-Variant delete-only Iceberg MERGE.

Checkpoint conclusions:

  • Goal and correctness: the native Parquet Variant read/write integration is broadly implemented, but the two production paths above violate query correctness/availability. The existing tests do not cover a two-block join shuffle, conflicting cross-file shredded leaf types, or non-Variant delete-only MERGE at execution version 11.
  • Scope and parallel paths: I reviewed all 117 authoritative changed paths across BE, FE, connector SPI, Thrift, unit tests, and regression assets. The change is large but cohesive. I traced scan projection/predicate/deferred paths, metadata COUNT, join/exchange buffering, Iceberg merge writers, and mixed-format paths; no additional blocker survived.
  • Concurrency and lifecycle: scanner split/late-filter state, immutable/COW shredded ownership, snapshot pinning, exchange buffering, and merge-writer open/write/close cleanup were checked. No additional lock, race, deadlock, static-initialization, or lifetime issue was found; the first inline is a cross-batch lifecycle defect.
  • Compatibility, protocol, and configuration: connector API 4.0, the new Thrift field, query-wide BE execution version, and old/new backend behavior were traced. The second inline is the remaining schema-blind compatibility regression. No other FE-BE propagation or dynamic-configuration issue survived.
  • Writes, transactions, and persistence: Iceberg data/delete writer routing, close/commit-data collection, and cleanup paths were reviewed. This change adds no EditLog state, and no additional atomicity, failover, or resource-leak issue was found.
  • Performance and observability: fused nullable decoding, direct-leaf projection, pruning, counters, and profiles were reviewed. No separate substantiated performance or observability blocker survived.
  • Tests: static review only; no builds or tests were run, as required by the review environment.
  • User focus: review_focus.txt contained no additional user-provided focus, so the whole PR was reviewed without a narrower focus.

Review completion is incomplete at the three-round cap: both final-round normal reviewers returned NO_NEW_VALUABLE_FINDINGS, but the final risk-focused sweep added the accepted cross-file schema refinement now incorporated into the first inline. A fourth same-state convergence round would exceed the prompt's maximum. Every candidate nevertheless has an explicit accepted, duplicate, or evidence-backed dismissed disposition.

public void bindDataSink(Optional<InsertCommandContext> insertCtx)
throws AnalysisException {
if (writeOperation == WriteOperation.MERGE && !writesDataFiles
&& Config.be_exec_version < SUPPORT_ICEBERG_VARIANT_EXEC_VERSION) {

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] Keep non-Variant delete-only MERGE available during rolling upgrades

writesDataFiles is false for every delete-only MERGE, so this rejects even tables whose schema has no Variant whenever be_exec_version is 11. Those queries were supported before this change: an old BE opens the table writer, but with no insert rows it creates no partition writers or data files; only a Variant schema makes the old parser fail. The matching BE check is equally schema-blind, and the new test uses an empty/non-Variant column list, so it pins this regression instead of the intended Variant-only fence. Please apply the version fence only when the omitted writer schema contains Variant, and retain coverage that ordinary delete-only MERGE remains allowed at version 11.

return;
}

if (_shredded) {

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] Do not buffer incomplete Variant states across the exchange

The first indexed selection into an empty destination adopts source._shredded, but the next batch reaches this branch and calls ensure_encoded(); a leaf-only Parquet projection cannot materialize and throws. This is reachable when v['n'] is projected above a partitioned hash join: nested pruning sends the raw partial v through the child shuffle, where one channel buffers selections from consecutive scan blocks. Adding the insert_range_from()-style try_append() only fixes identical schemas: Parquet permits different files to shred the same path with incompatible types, while try_append() requires exact schema equality and the incomplete fallback still cannot encode. Please consume the leaf into a common logical representation before exchange, or use an accumulator that preserves heterogeneous partial states; cover both two same-schema blocks and two files with conflicting shredded leaf types.

@Gabriel39
Gabriel39 marked this pull request as draft August 4, 2026 12:04
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Aug 8th, 2026 3:37 AM.
Workflow run: https://github.com/apache/doris/actions/runs/30908497884

The selected account is excluded until 2026-08-08T03:37:00Z. Please trigger /review again; another configured account may be available.

### What problem does this PR solve?

Issue Number: None

Related PR: apache#66446

Problem Summary: Master exposes immutable subcolumn traversal through IColumn::ColumnCallback, while branch-4.1 still uses ImutableColumnCallback. Adapt the forward-port implementation to master’s callback contract so the composite shredded state overrides the interface and compiles.

### Release note

None

### Check List (For Author)

- Test: Unit Test
- Behavior changed: No
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.57% (1912/2465)
Line Coverage 64.51% (34207/53024)
Region Coverage 64.39% (17262/26809)
Branch Coverage 53.93% (9248/17148)

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17593	3932	3913	3913
q2	1996	309	201	201
q3	10303	1409	830	830
q4	4684	473	341	341
q5	7558	838	551	551
q6	177	171	136	136
q7	725	801	604	604
q8	9318	1649	1537	1537
q9	5277	4090	4079	4079
q10	6746	1647	1354	1354
q11	510	347	325	325
q12	730	591	451	451
q13	18101	3318	2756	2756
q14	261	256	236	236
q15	q16	745	729	669	669
q17	961	1030	1000	1000
q18	6640	5637	5516	5516
q19	1234	1222	1072	1072
q20	813	701	561	561
q21	6022	2865	2827	2827
q22	456	382	329	329
Total cold run time: 100850 ms
Total hot run time: 29288 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4946	4669	4596	4596
q2	275	332	204	204
q3	4903	5211	4645	4645
q4	2186	2275	1447	1447
q5	4540	4635	4339	4339
q6	230	178	128	128
q7	1831	1779	1520	1520
q8	2326	2025	2024	2024
q9	7135	7031	6683	6683
q10	4235	4163	3773	3773
q11	503	392	336	336
q12	700	710	498	498
q13	2967	3305	2761	2761
q14	281	286	262	262
q15	q16	661	678	598	598
q17	1227	1207	1188	1188
q18	12120	10999	11825	10999
q19	1063	1075	1081	1075
q20	2190	2197	1888	1888
q21	5270	4507	4548	4507
q22	505	455	410	410
Total cold run time: 60094 ms
Total hot run time: 53881 ms

@hello-stephen

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

query5	4286	584	458	458
query6	476	218	201	201
query7	4854	561	339	339
query8	316	163	149	149
query9	8757	4025	3993	3993
query10	480	356	319	319
query11	5806	2164	2031	2031
query12	163	100	98	98
query13	1262	630	448	448
query14	6089	4254	4023	4023
query14_1	3791	3776	3811	3776
query15	203	195	178	178
query16	1027	512	469	469
query17	925	699	557	557
query18	2438	487	344	344
query19	211	198	149	149
query20	101	100	104	100
query21	234	162	135	135
query22	12998	13004	12862	12862
query23	15850	14944	14507	14507
query23_1	14671	14728	14625	14625
query24	7633	1728	1218	1218
query24_1	1253	1239	1229	1229
query25	557	449	383	383
query26	1317	349	212	212
query27	2610	611	372	372
query28	4554	2053	2013	2013
query29	1096	620	492	492
query30	347	262	228	228
query31	1179	1125	1053	1053
query32	119	63	64	63
query33	538	316	260	260
query34	1181	1116	681	681
query35	737	741	641	641
query36	775	780	683	683
query37	158	112	91	91
query38	1840	1787	1683	1683
query39	825	842	803	803
query39_1	769	781	780	780
query40	266	167	150	150
query41	72	67	71	67
query42	98	95	96	95
query43	321	337	276	276
query44	1426	815	769	769
query45	182	176	173	173
query46	1050	1169	696	696
query47	1552	1624	1393	1393
query48	402	394	310	310
query49	571	401	294	294
query50	1043	418	359	359
query51	10571	10567	10519	10519
query52	84	87	70	70
query53	258	266	204	204
query54	284	235	217	217
query55	75	68	68	68
query56	292	299	281	281
query57	1014	1011	926	926
query58	304	265	251	251
query59	1525	1578	1391	1391
query60	307	270	249	249
query61	151	146	144	144
query62	399	316	265	265
query63	228	193	205	193
query64	2868	1007	880	880
query65	3892	3865	3796	3796
query66	1844	465	358	358
query67	28096	28045	27984	27984
query68	3414	1515	986	986
query69	416	303	265	265
query70	844	807	797	797
query71	383	342	328	328
query72	3011	2706	2330	2330
query73	836	779	394	394
query74	4666	4487	4279	4279
query75	2362	2357	1994	1994
query76	2384	1136	760	760
query77	339	365	286	286
query78	11245	11250	10575	10575
query79	1291	1091	739	739
query80	656	537	453	453
query81	450	320	287	287
query82	626	168	142	142
query83	403	317	296	296
query84	322	165	134	134
query85	923	629	569	569
query86	314	230	217	217
query87	1975	1960	1835	1835
query88	3705	2829	2839	2829
query89	394	328	280	280
query90	1955	193	192	192
query91	197	184	165	165
query92	63	57	54	54
query93	1555	1473	984	984
query94	529	331	290	290
query95	777	480	473	473
query96	1051	788	352	352
query97	2455	2444	2332	2332
query98	194	200	182	182
query99	736	741	616	616
Total cold run time: 252101 ms
Total hot run time: 166056 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.09	0.05	0.05
query3	0.26	0.14	0.14
query4	1.61	0.13	0.14
query5	0.25	0.23	0.22
query6	1.16	0.84	0.83
query7	0.04	0.01	0.01
query8	0.07	0.04	0.04
query9	0.38	0.32	0.31
query10	0.58	0.54	0.57
query11	0.20	0.14	0.14
query12	0.19	0.14	0.18
query13	0.48	0.48	0.48
query14	0.99	0.99	1.00
query15	0.61	0.59	0.58
query16	0.33	0.32	0.33
query17	1.06	1.06	1.12
query18	0.21	0.20	0.21
query19	2.06	1.96	1.92
query20	0.02	0.02	0.01
query21	15.47	0.23	0.14
query22	4.74	0.05	0.06
query23	16.15	0.33	0.13
query24	2.90	0.42	0.34
query25	0.10	0.05	0.04
query26	0.74	0.23	0.14
query27	0.03	0.04	0.03
query28	3.52	0.82	0.33
query29	12.49	4.02	3.18
query30	0.27	0.15	0.14
query31	2.78	0.56	0.33
query32	3.22	0.59	0.49
query33	3.28	3.25	3.22
query34	15.66	3.95	3.28
query35	3.26	3.24	3.23
query36	0.55	0.43	0.41
query37	0.09	0.06	0.06
query38	0.06	0.04	0.03
query39	0.04	0.04	0.03
query40	0.17	0.16	0.14
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.04
Total cold run time: 96.28 s
Total hot run time: 23.94 s

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.57% (1912/2465)
Line Coverage 64.53% (34217/53024)
Region Coverage 64.44% (17275/26809)
Branch Coverage 53.91% (9245/17148)

@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.

Requesting changes: one additional P1 blocker remains.

  • Complete Parquet Variant roots stay in shredded state, but the VariantV2 Block SerDe treats every non-typed column as encoded and writes the empty encoded buffers. A complete root or nested Parquet Variant that reaches exchange or spill serialization while still shredded therefore saves a nonzero row count but reconstructs zero rows, then deterministically fails the saved-row-count corruption check. The inline comment identifies the construction boundary and the missing round-trip coverage.

I deduplicated this review against all nine live inline threads. Those threads continue to cover unsafe metadata pruning, the missing golden result, late-runtime-filter output positions, rolling-upgrade writer/scan fences, connector SPI versioning, pinned metadata COUNT, non-Variant delete-only MERGE, and incomplete heterogeneous exchange accumulation. The finding in this review is distinct: it affects a materializable complete root whenever it reaches a serialization boundary before an operation has encoded it.

User focus: review_focus.txt adds no extra guidance, so I reviewed the entire PR without narrowing scope.

Critical checkpoint conclusions:

  • Goal and proof: The PR implements Iceberg/Parquet Variant v1 reading, nested projection/pruning, scanner-v2 integration, metadata/page pruning, and guarded external writes. It adds broad BE/FE unit and regression coverage, but it does not prove a full-root Variant Block serialization round trip; the accepted P1 means the end-to-end goal is not yet met.
  • Scope and clarity: The change is large but cohesive across the FE planner, connector SPI, Thrift, BE scanner/column stack, benchmark, and tests. I found no unrelated source change.
  • Concurrency: The new shredded materialization caches are protected by local mutexes, and the reviewed scanner/reader state remains operator-local. Lock ordering and heavy work around the materialization cache did not expose a distinct deadlock or race.
  • Lifecycle and static initialization: Shredded/composite state uses immutable shared ownership plus explicit COW detachment; scanner and materialization lifetimes terminate through their existing owners. No new cross-TU static-initialization dependency was found.
  • Configuration: The FE/BE execution version is bumped to 12; this is a compatibility selector rather than a newly dynamic feature switch. Existing live threads already cover the remaining ordinary rolling-upgrade gating gaps.
  • Compatibility: FE/BE execution-version checks, connector surfaces, and the new Thrift write flag were traced through their consumers. Outstanding old-BE, SPI, omitted-field, and non-Variant MERGE issues are already represented by live threads; no new duplicate was submitted.
  • Parallel paths: Scanner V1/V2, native Parquet, eager predicate versus deferred output readers, metadata COUNT, mixed file formats, and external MERGE paths were compared. Variant support is deliberately confined to the native V2 path; known divergences are already covered by existing threads.
  • Conditional checks: Projection-completeness, residual fallback, metadata-count proof, and write-data classification branches were checked against their callers. Their remaining substantiated gaps are existing-thread issues, apart from the new serialization dispatch gap.
  • Test coverage: The PR adds extensive schema, reader, mapper, selector, access-path, merge-sink, connector, and regression cases, including negative schemas and heterogeneous leaf types. A complete-root/nested-Variant Block::serialize/deserialize test is missing and is required by the inline finding.
  • Test results: Expected-output changes are ordered and consistent with the reviewed SQL. Per the review-runner contract, this was a static review and no builds or tests were run.
  • Observability: Variant projection, direct-leaf, fallback, reconstruction, and fused-selection counters provide useful path-level visibility; no additional logging/metric issue survived review.
  • Transactions and persistence: No Doris EditLog or table-version persistence path is changed. Iceberg snapshot pinning and metadata-only planning were traced; the pinned-count issue is already a live thread.
  • Data writes, atomicity, and crash behavior: External MERGE classification and writer omission were traced from FE planning through the BE sink. Existing threads cover the remaining version/schema fence defects; no additional atomicity, cleanup, or crash leak was found.
  • FE/BE variables and protocol: writes_data_files and execution-version changes are sent and consumed on the reviewed planner/sink paths. Compatibility defaults and older consumers are already covered by the live review context.
  • Error handling: New Status and exception boundaries generally propagate failures and use invariant checks for impossible shapes. The P1 is a data-correctness/serialization dispatch failure, not a silently ignored status.
  • Memory and ownership safety: Recursive COW, nullable/typed/shredded ownership, filter/cut/gather, and composite-state transitions were checked. The suspected filter-side shared-child mutation was dismissed because these paths detach buffers or replace immutable child state.
  • Null and nullable handling: Definition-level null runs, selected-null expansion, outer Variant nulls, nullable wrappers, and nested array/map/struct carriers were traced. No distinct nullability or const-column defect was found.
  • Performance: Direct leaf reads, independent projection, page pruning, fused nullable selection, and benchmark coverage target the hot paths without a substantiated new CPU/memory regression. The required serialization fix should preserve lazy shredding until an actual serialization boundary.
  • Other issues: No additional non-duplicate finding remains after three normal full reviews, one adversarial risk review, and the main-agent final sweep.


const auto* outer_nullable = check_and_get_column<ColumnNullable>(*physical);
MutableColumnPtr variants =
ColumnVariantV2::create_shredded(std::make_shared<ParquetVariantShreddedState>(

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] Materialize complete shredded Variants before block serialization

This returns a shredded ColumnVariantV2 even when complete is true. That state reports the physical row count from _shredded, but DataTypeVariantV2SerDe::{get_uncompressed_serialized_bytes,serialize} only distinguishes typed from encoded and serializes _metadatas/_meta_ids/_values; those buffers are empty for every shredded state. A complete Parquet Variant that reaches fragment-exchange or spill serialization while still shredded therefore saves N rows but encodes zero; the receiver or spill reader reconstructs zero rows and fails the saved-row-count corruption check. This is separate from the existing partial-leaf accumulator thread: it also affects a complete root projection from a single block. Please make the block SerDe materialize complete shredded states (and fail explicitly for incomplete ones), or encode complete rows before they can reach a serialization boundary, with a full-root Variant block round-trip test.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17682	3936	4055	3936
q2	2017	306	191	191
q3	10773	1411	803	803
q4	4750	472	340	340
q5	8348	842	553	553
q6	316	171	140	140
q7	817	818	601	601
q8	10427	1678	1653	1653
q9	5529	4059	4077	4059
q10	6846	1663	1370	1370
q11	520	351	331	331
q12	749	574	466	466
q13	18542	3355	2740	2740
q14	262	259	247	247
q15	q16	734	731	659	659
q17	1031	1023	1007	1007
q18	6558	5629	5546	5546
q19	1169	1217	1037	1037
q20	804	655	610	610
q21	5512	2644	2261	2261
q22	426	361	298	298
Total cold run time: 103812 ms
Total hot run time: 28848 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4273	4163	4157	4157
q2	276	317	212	212
q3	4496	4931	4328	4328
q4	2133	2265	1392	1392
q5	4237	4113	4116	4113
q6	227	178	124	124
q7	1734	1843	1685	1685
q8	2344	2049	2023	2023
q9	7323	7316	7213	7213
q10	4305	4269	3879	3879
q11	555	405	368	368
q12	702	721	529	529
q13	3155	3762	2921	2921
q14	307	310	281	281
q15	q16	682	753	643	643
q17	1320	1274	1266	1266
q18	12200	11088	11879	11088
q19	1181	1172	1150	1150
q20	2221	2211	1986	1986
q21	5646	4963	4745	4745
q22	518	467	406	406
Total cold run time: 59835 ms
Total hot run time: 54509 ms

@hello-stephen

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

query5	4321	593	457	457
query6	447	210	208	208
query7	4871	574	340	340
query8	318	163	140	140
query9	8753	4043	4052	4043
query10	478	357	314	314
query11	5855	2190	1994	1994
query12	153	100	102	100
query13	1251	624	431	431
query14	6090	4261	3985	3985
query14_1	3763	3775	3766	3766
query15	196	191	174	174
query16	957	516	484	484
query17	924	684	536	536
query18	2420	460	338	338
query19	203	183	145	145
query20	100	99	101	99
query21	230	157	133	133
query22	13034	13040	12884	12884
query23	16301	15029	14512	14512
query23_1	14592	14643	14601	14601
query24	7504	1700	1243	1243
query24_1	1355	1254	1251	1251
query25	582	437	387	387
query26	1315	342	238	238
query27	2633	597	384	384
query28	4564	2084	2040	2040
query29	1075	636	499	499
query30	343	268	224	224
query31	1183	1123	1054	1054
query32	110	62	62	62
query33	554	331	277	277
query34	1178	1137	629	629
query35	734	738	629	629
query36	783	795	726	726
query37	159	104	98	98
query38	1823	1772	1698	1698
query39	838	818	804	804
query39_1	775	782	799	782
query40	244	164	145	145
query41	67	63	65	63
query42	95	94	93	93
query43	322	314	275	275
query44	1428	781	787	781
query45	187	174	181	174
query46	1058	1167	703	703
query47	1517	1564	1469	1469
query48	421	385	289	289
query49	565	418	303	303
query50	1035	420	322	322
query51	10514	10321	10222	10222
query52	86	89	76	76
query53	248	262	206	206
query54	278	225	226	225
query55	75	72	67	67
query56	293	306	273	273
query57	1008	996	917	917
query58	290	254	254	254
query59	1635	1587	1372	1372
query60	304	273	253	253
query61	157	149	153	149
query62	400	320	266	266
query63	229	197	196	196
query64	2892	1075	859	859
query65	3889	3800	3777	3777
query66	1844	466	359	359
query67	28045	28089	28007	28007
query68	3214	1578	996	996
query69	421	301	267	267
query70	864	785	755	755
query71	369	340	324	324
query72	3054	2630	2373	2373
query73	868	769	445	445
query74	4626	4509	4297	4297
query75	2374	2343	2035	2035
query76	2307	1114	748	748
query77	335	359	280	280
query78	11144	11047	10571	10571
query79	1362	1127	730	730
query80	659	548	476	476
query81	457	331	286	286
query82	628	170	131	131
query83	408	326	308	308
query84	327	166	132	132
query85	918	627	520	520
query86	329	240	226	226
query87	1992	1949	1847	1847
query88	3756	2879	2787	2787
query89	411	336	283	283
query90	1863	194	190	190
query91	206	191	170	170
query92	65	59	56	56
query93	1614	1516	987	987
query94	558	358	326	326
query95	792	592	495	495
query96	1067	778	379	379
query97	2431	2456	2356	2356
query98	195	188	185	185
query99	744	734	612	612
Total cold run time: 252487 ms
Total hot run time: 166066 ms

@hello-stephen

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

query1	0.01	0.01	0.01
query2	0.15	0.08	0.08
query3	0.36	0.25	0.25
query4	1.61	0.24	0.24
query5	0.33	0.31	0.32
query6	1.16	0.66	0.66
query7	0.04	0.01	0.01
query8	0.10	0.07	0.07
query9	0.50	0.37	0.37
query10	0.58	0.57	0.57
query11	0.31	0.18	0.18
query12	0.31	0.18	0.18
query13	0.52	0.53	0.55
query14	0.91	0.90	0.90
query15	0.67	0.58	0.58
query16	0.38	0.39	0.38
query17	1.02	1.00	1.00
query18	0.29	0.28	0.29
query19	1.92	1.75	1.78
query20	0.02	0.01	0.01
query21	15.43	0.37	0.32
query22	4.84	0.13	0.13
query23	15.84	0.49	0.30
query24	2.49	0.63	0.44
query25	0.16	0.11	0.10
query26	0.72	0.28	0.21
query27	0.10	0.10	0.10
query28	3.37	0.77	0.41
query29	12.46	4.22	3.27
query30	0.36	0.25	0.26
query31	2.76	0.57	0.32
query32	3.24	0.59	0.48
query33	3.04	2.96	3.00
query34	15.75	3.83	3.21
query35	3.18	3.18	3.16
query36	0.66	0.51	0.48
query37	0.12	0.09	0.09
query38	0.08	0.07	0.07
query39	0.07	0.06	0.06
query40	0.19	0.18	0.16
query41	0.12	0.09	0.08
query42	0.08	0.06	0.07
query43	0.07	0.06	0.07
Total cold run time: 96.32 s
Total hot run time: 25.08 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 22.09% (19/86) 🎉
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.

3 participants