Skip to content

[feature](be) Read Lance datasets with FileScannerV2 - #69581

Open
morningman wants to merge 14 commits into
apache:masterfrom
morningman:b2m-t37-lance-be-reader
Open

morningman wants to merge 14 commits into
apache:masterfrom
morningman:b2m-t37-lance-be-reader

Conversation

@morningman

@morningman morningman commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: #65730 #66608 #66805 #66841 #66999 #67005 #67011 #67110 #67268 #67289 #67325 #67650 #67921 #68028 #68335 #68375 #68615 #68690 #68813 #68815 #68707 #68649

Problem Summary:

In short. This ports the Lance BE read path of branch-4.1 / branch-4.2 to master, in its branch-4.2 form as of cabcf45c1af, plus the BE side of three 4.x changes merged after that snapshot: the search filter prefilter flag and profile layout (#68813), distance bounds for vector_search (#68815), and the split-level index rule of #68707 that #68815 relies on. It gathers the BE parts of the PRs listed above. Master links liblance_c but has no Lance reader, so every FORMAT_LANCE scan fails today. With this PR the BE reads Lance datasets through FileScannerV2: plain scans, vector search and full-text search, predicate and runtime-filter pushdown, the schema fetch of a local Lance TVF, and the second phase of TopN lazy materialization. The PR is BE only. The FE connector plugin and the regression suites come in separate PRs, so until then nothing on master plans a Lance scan, and the BE unit tests carry the proof.

Original authors: @zhangstar333 @Gabriel39 @wangzhaobo957-cloud @Jay-ju @FANNG1

Background. Lance shipped in 4.1.4: the Lance catalog, local() / s3() with format=lance, and the vector_search / full_text_search TVFs. On master the FE side is being rebuilt as a connector plugin (fe-connector-lance), and the BE needs the reader from 4.x. The thrift ids are the branch-4.1 / 4.2 ones, including TExternalSearchRequest.5 (prefilter) and TVectorSearchParams.6/7 (distance bounds) added after the snapshot, so a BE built from master also reads the Lance scan ranges and search requests that a 4.x FE sends during a rolling upgrade.

The problem, and what it cost.

master today with this PR
A FORMAT_LANCE scan file_scan_operator keeps it off FileScannerV2 and V1 has no Lance path, so the query fails read by LanceTableReader in FileScannerV2
fetch_table_schema for a local Lance TVF not handled returns the Arrow schema mapped to Doris types; Null leaves keep NULL_TYPE
The row location of a Lance row in TopN lazy materialization GlobalRowLoacationV2 only has a 32-bit row id, but a Lance row id is a 64-bit id within the snapshot a version 1 layout carries the 64-bit row id; phase two reads the rows with the dataset's take_rows

How this PR fixes it.

  • Reader: be/src/format_v2/lance/* and be/src/format_v2/table/lance_reader.*, as on branch-4.2 except one local renamed for master's -Wshadow. The unit tests construct RuntimeState(TQueryOptions(), globals), because master removed the globals-only constructor.

  • FileScannerV2: is_supported, the reader dispatch and _to_file_format handle FORMAT_LANCE / "lance"; FileFormat::LANCE is added to its two exhaustive switches. Lance queries use FileScannerV2 whatever enable_file_scanner_v2 says, as ADBC and fluss do: there is no V1 Lance reader, and the variable is fuzzy in the regression harness. On branch-4.2 a Lance query fails with NotSupported when the variable is off. Loads are unchanged.

  • Row locations: ROW_VERSION {FILE_LOCAL_ROW_ID = 0, LANCE_DATASET_ROW_ID = 1} and a union layout for GlobalRowLoacationV2. Version 0 keeps its byte layout, which static asserts pin. PRequestBlockDesc.row_id becomes uint64 (a proto2 varint, so the wire format is compatible). The materialization operator decodes row locations by version, rejects an unknown version or a wrong size, and stamps row_location_version. RowIdStorageReader validates the counts and the version, range-checks internal row ids against uint32, and groups Lance rows by plan node, snapshot version and dataset URI, so each dataset is read with one take_rows. Master's segment batching, scan-slot dedup and fetch concurrency stay as they are.

  • Thrift (PlanNodes.thrift): the external search request body (TVectorElementType, TVectorMetric, TSearchVector, TVectorSearchParams 1-7, TFtsCoverageMode, TFtsQueryType, TFtsMatchOperator, TFullTextSearchParams, TSearchFilterFormat, TSearchFilter, TVectorSearchOptions, TExternalSearchQuery), TExternalSearchRequest 2-5 and TLanceFileDesc 4-6, all with the branch-4.1 / 4.2 ids. The enums spell out their 4.x values. No id is reused.

  • After the snapshot (one commit each):

  • Review fixes:

    • A nested array goes to the Doris SerDes without a copy only when it and every descendant start at offset 0 and reference exactly their child ranges. Lance's legacy (v1) decoder can return a struct or list that is tight at its own level over a Boolean child with a bit offset (a batch that starts mid-byte in the bitmap), and the SerDes reject that under Arrow input validation.
    • A zero-field STRUCT<>, which Lance decodes, is reported as an unsupported column at any nesting level, like the other unsupported Lance types. It used to be typed as a STRUCT that ColumnStruct cannot hold (its row count comes from its first child).
    • A Lance Decimal256 field with precision <= 38 is narrowed to Decimal128 by a safe Arrow cast before decoding. Doris types it DECIMAL32/64/128I by precision, and those SerDes read only Decimal128 arrays; the BE used to crash on the first value, also for nested leaves. Only values a reader can see are cast: before a nested leaf is narrowed, every child slot that no visible row references (under a NULL list, fixed-size-list, map or struct row, or outside a sliced parent) is marked NULL, so a hidden value does not fail the cast and a visible value outside the declared precision still does; map keys, which cannot be NULL, stay checked.
    • FLOAT / DOUBLE >= runtime filters stay residual-only. Lance compares floats by totalOrder, so a pushed x >= c dropped negative-NaN rows that Doris keeps.
    • The shared Arrow DATE32 decoding (DataTypeDateV2SerDe::read_column_from_arrow) rejects days outside 0000-01-01..9999-12-31 instead of returning a non-null 0000-00-00, skips null slots and adds the epoch in 64 bits: the DATE32 range part of branch-4.1 / 4.2 [fix](arrow) Correct year-zero DATE conversion on branch-4.1 #68649. It also changes ADBC, Arrow stream load and paimon-rust reads.
    • Child values hidden by a NULL list, fixed-size-list, map or struct row are not decoded: the array, map and struct SerDes give the element SerDe a validity bitmap that also clears those slots (mask_arrow_children_hidden_by_null_parents()). Arrow leaves them unspecified, and Lance's v2.1 miniblock encoding keeps them, so a hidden 10000-01-01 under a NULL fixed-size list used to fail the DATE32 range check above. Visible values are unchanged; this applies to ADBC, Arrow stream load and paimon-rust as well. The masks come from ExecEnv's tracked Arrow pool. A nested Lance array that is compacted before decoding is masked the same way, at every level, before its ValidateFull(), so a hidden invalid UTF-8 string, out-of-precision decimal, Date64 or time no longer fails the batch while a visible one still does.
  • Core: to_protobuf keeps NULL_TYPE for a NULL literal type, so a Null leaf in ARRAY / MAP / STRUCT no longer comes back as BOOLEAN. The nullable serde reads an Arrow Null array as NULLs without touching buffers that the array does not have.

  • Configs, caches and metrics, with the 4.x names and defaults:

    Config Default
    lance_index_cache_size_bytes 10 GiB
    lance_metadata_cache_size_bytes 1 GiB
    enable_lance_data_cache true
    lance_data_cache_path ${DORIS_HOME}/lance_data_cache
    lance_data_cache_disk_capacity_bytes 100 GiB
    lance_data_cache_read_block_size_bytes 1 MiB
    lance_io_buffer_size_bytes (mutable) 2 GiB
    lance_batch_readahead (mutable) 5
    lance_fragment_readahead (mutable) 5

    The BE-wide Lance session (index cache, metadata cache and the optional Foyer disk cache) is created by the first Lance scan, so a BE that never reads Lance creates no cache directory. The first six configs take effect after a restart. Ten lance_session_* metrics report the two in-memory caches. With the defaults, the disk tier keeps 51,200 sparse 2 MiB block files open from the first Lance scan on (100 GiB in blocks of twice the read block size), the same as on branch-4.1 / 4.2. Size lance_data_cache_disk_capacity_bytes and lance_data_cache_read_block_size_bytes against the BE's open-file limit, or set enable_lance_data_cache = false.

    The index and metadata caches live in lance-c memory: they are not charged to a MemTracker or registered with CacheManager, so memory GC, the cache capacity adjustment under memory pressure and /api/clear_cache/all do not shrink them, and enable_lance_data_cache = false does not turn them off. lance-c has no call to clear, evict or resize them, so size them with the two configs. The disk tier keys blocks by Lance's store prefix (scheme$bucket for S3, GCS and OSS, without the endpoint) and the object path (thirdparty/patches/lance-c-foyer.patch). Several endpoints that serve the same bucket name and data-file path with different bytes (data files registered under chosen names or replaced in place; Lance's writers use random file names) share entries, also across a BE restart; such a deployment should set enable_lance_data_cache = false. Both are as on branch-4.1 / 4.2 and tracked as follow-ups.

  • Build: macOS links -framework IOKit. This is the first time objects of liblance_c.a are pulled into doris_be, so the binary and the link time grow. The build-hygiene reverse reach budget of util/rle_encoding.h is rebaselined from 825 to 911: master 5caaeb3e3d4 alone is at 907 (limit 908), and the reader's four TUs reach it through the existing exprs/vexpr_context.h -> storage/segment/column_reader.h path, which no include narrowing in the Lance files can avoid. util/threadpool.h reaches 1096 TUs with this PR, within master's baseline of 1200.

Not included: the Lance index metadata scanner (LANCE_INDEX_ENTRIES); the FE side, including the rolling-upgrade guard that keeps Lance lazy materialization off while an old BE could decode a version 1 row location as version 0; the regression suites; the rest of #68707 (searching historical versions, tags and branches, rejecting fragment ids missing from the snapshot, naming the snapshot in errors and the profile). The InitReaderAvgTime / GetBlockAvgTime profile fix that #67005 carried goes in a separate PR.

Results.

master with this PR
A FORMAT_LANCE query fails before reading anything read by LanceTableReader in FileScannerV2
A Lance row in the second phase of TopN lazy materialization cannot be represented a version 1 row location, read back with take_rows
A row location of an unknown version or size in phase two decoded as version 0 rejected with an error
An Arrow DATE32 day outside 0000-01-01..9999-12-31 (ADBC, Arrow stream load, paimon-rust, Lance) read as a non-null 0000-00-00 the read fails with InvalidArgument

BE unit tests (ASAN, macOS arm64, against master's pinned lance-c): 428 cases, all pass. They cover the Lance suites (101: LanceTableReader* 79, LanceNestedNullTest 10, LanceRuntimeFilterHelperTest 9, LanceSessionManagerTest 3); the row-location protocol (MaterializationSharedStateTest, RowIdStorageReaderTest, and the new RowIdFetchRequestValidationTest: count mismatch, unknown version, a Lance version on an internal mapping, an internal row id of 2^32); FileScannerV2Test with the new LanceQueriesAlwaysUseScannerV2, FileScanOperatorAdbcTest and FileScanOperatorFlussTest; the ORC / Parquet global row id tests that now read .file_local; and the Arrow / protobuf / DATE serde suites with the new ArrowParentNullsTest, plus the Iceberg and Parquet reader suites that decode Arrow dates. Reverting any of the review fixes, the #68813 flag, the #68707 split rule or the #68815 bounds makes its new tests fail. The sixth review round adds 13 cases (ArrowParentNullsTest 10, LanceTableReaderTypeTest 3) and extends one; with them the Arrow SerDe, Lance, ADBC and IcebergV2ReaderTest suites run 317 cases: 310 pass and the 7 ADBC SQLite cases skip without a local SQLite driver.

Release note

Add nine lance_* BE configs and lance_session_* BE metrics. They take effect once the FE can plan Lance scans. Reading an Arrow DATE32 value outside 0000-01-01..9999-12-31 through ADBC, Arrow stream load or paimon-rust now fails instead of returning 0000-00-00.

Check List (For Author)

  • Test
  • Behavior changed:
    • No.
    • Yes. PRequestBlockDesc.row_id is uint64; phase two of TopN lazy materialization rejects row locations of an unknown version or size; FORMAT_LANCE scans run on FileScannerV2; an Arrow DATE32 day outside the Doris DATE range fails the read instead of becoming 0000-00-00.
  • Does this need documentation?
    • No. Lance is documented with the FE connector.
    • 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?

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

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. Static review of #69581 at 6d43ec5 found five verified defects: a Decimal256 conversion crash, two search-request compatibility failures, a floating runtime-filter false negative, and silent DATE32 corruption. Every finding is inline; no existing inline threads needed carrying forward.

Critical checkpoint conclusions:

  1. Goal and proof: the BE Lance read/search/schema-fetch/lazy-fetch integration is present, but the five defects prevent a correctness-complete port. Added unit tests provide source coverage; no tests were executed in this static-only review.
  2. Scope and clarity: reader helpers, dispatch, protocol, core NULL handling and row-layout changes are related to the port; no unrelated production edit identified.
  3. Concurrency: Scanner serializes reader operations; phase-two workers retain task tracking and completion/semaphore cleanup; call_once publishes the session; entity locking serializes metric hooks and teardown. No additional verified lock-order defect.
  4. Lifecycle and static initialization: partial-creation guards and Arrow import ownership were checked; scanners close before dataset/FTS state, metrics unregister before session release, and the manager uses a function-local static. No additional verified ownership or initialization defect.
  5. Configuration: six session/cache settings require restart; three mutable scan settings are validated and sampled when each native scanner is created.
  6. Compatibility: version-zero row locations retain their 24-byte layout; uint64 varint widening preserves old ordinals, and unknown versions/sizes fail. Existing 4.x search-filter timing and distance bounds are lost. Linux glibc compatibility source/link handling was inspected; no final ELF artifact exists to audit.
  7. Thrift/protobuf: complete touched definitions were compared with live-matching branch-3.1/4.0/4.1/4.2 refs. No ID reuse, changed enum meaning or new required field found; the two omitted request options still break query semantics across upgrades.
  8. Parallel paths: normal/vector/FTS/schema/take_rows and internal/Parquet/ORC row fetching were inspected. Existing FE producer guarantees pin search snapshots, normalize split offsets, and exclude nested lazy slots. The decimal and temporal conversion defects affect the shared new Lance boundary.
  9. Conditions and errors: new structural/version/count checks have concrete failure paths. The reported decimal crash and ignored DATE32 conversion failure need explicit handling.
  10. Test coverage: substantial BE tests cover types, extensions, nulls, searches, fragments, uint64 IDs and malformed requests. Missing cases include low-precision Decimal256, out-of-range DATE32, negative-NaN probe rows, and old-FE search-option serialization. FE connector and regression suites are separate PR work.
  11. Expected results: source assertions and all 109 fixture files (99 binary files and 10 JSON hints) were inspected; no new textual regression outputs were added. Fixture generation and executable results were not independently validated.
  12. Observability: reader/search/fetch timing and cache counters plus process-wide cache metrics are present. Native/cache statistics are finalized at stream/dataset close; live execution attribution was not measured.
  13. Transactions, persistence and failover: no table-write, EditLog, transaction or MoW visibility path changes; supported search fetching uses fixed dataset snapshots.
  14. Writes and crash behavior: persistent writes are disposable cache data, not table commits. The Decimal256 finding can crash a BE while reading valid data.
  15. FE/BE variables: request producers/consumers and row-version transport were traced; prefilter and distance-bound inputs are dropped despite accepting schema_version=1.
  16. Performance and memory: schema plans and dataset-grouped takes reuse work; Arrow normalization uses its supplied pool. Native Rust/cache accounting, throughput, cancellation stress and final Linux ABI remain execution-validation limits; no build/runtime claim is made.
  17. Other concerns and user focus: no additional user focus was supplied. Remaining suspected mechanisms were independently resolved or excluded using producer/native evidence; all assigned static coverage and candidate dispositions are recorded in the review ledger.

Review converged after 3 round(s).

const PrimitiveType doris_decimal_type = precision <= 9 ? TYPE_DECIMAL32
: precision <= 18 ? TYPE_DECIMAL64
: precision <= 38 ? TYPE_DECIMAL128I
: TYPE_DECIMAL256;

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] Handle Decimal256 carriers before selecting a narrower SerDe

A valid Lance Decimal256(18,2) is mapped to Doris DECIMAL64 here, but normalization leaves its Decimal256Array unchanged. DataTypeDecimalSerDe<TYPE_DECIMAL64>::read_column_from_arrow casts it to arrow::DecimalArray (Decimal128Array) and then calls Value on the null result, so reading a nonempty column crashes the BE. The pinned Lance datatype encoding preserves the physical 256-bit width even when precision is <=38; current fixtures only use Decimal256 at precision 39/76. Normalize and checked-narrow that carrier, or support both physical widths in the SerDe, including nested leaves.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed. Lance keeps the 256-bit carrier of decimal256(18, 2), while this mapping and the FE type the column DECIMAL64 by precision, so DataTypeDecimalSerDe<TYPE_DECIMAL64>::read_column_from_arrow dereferenced the null result of dynamic_cast<const arrow::DecimalArray*>; the same held for DECIMAL32/128I targets and nested leaves. Fixed in 05437fd: LanceArrowArrayNormalizer narrows a Decimal256 field with precision <= 38 to decimal128(p, s) through a safe Arrow cast, so a value outside the declared precision fails with an error naming the field, and list/struct/map parents are rebuilt around the narrowed leaf. Unit tests cover top-level, sliced nested and overflow cases and a decimal256(18, 2) Lance round trip.

Comment thread be/src/format_v2/table/lance_reader.cpp Outdated
}
// Fragment-scoped nearest queries require prefiltering before installing the query. The same
// path applies the TVF search filter, when present.
if (lance_scanner_set_prefilter(scanner, true) != 0) {

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 the 4.x search-filter timing flag

The branch-4.1/4.2 FE serializes TExternalSearchRequest.prefilter=false by default, but this head omits wire field 5 and unconditionally enables prefiltering here. With top_k=1, use_index=false, and filter='keep = true', a nearest row with keep=false followed by a farther keep=true row should yield an empty postfilter result; this BE returns the farther row. The FE keeps that TVF filter only in the request, so a Doris residual cannot restore the requested behavior during an FE-4.x/BE-master upgrade. Retain field 5 with its established default and honor it when configuring the scanner.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, fixed in 60aebf8: this PR now ports the BE side of #68813. TExternalSearchRequest.5: optional bool prefilter has the 4.x id and comment, and both vector and full-text search pass __isset.prefilter && prefilter to lance_scanner_set_prefilter(), so unset or false means postfilter as on branch-4.1/4.2. SearchFilterDefaultsToPostfilter (from #68813) and a new full-text case cover both settings.

2: optional TSearchVector query_vector
3: optional i64 top_k
4: optional i64 offset
5: optional TVectorMetric metric

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] Retain the existing vector distance-bound fields

Branch-4.1/4.2 already define fields 6/7 as distance_lower_bound/distance_upper_bound. Their FE writes these from vector_search properties without synthesizing a Doris residual. This definition discards both fields during Thrift deserialization, and the new reader never applies a native distance range while still accepting schema_version=1. During an FE-4.x/BE-master upgrade, an exact top-1 query whose nearest squared L2 distance is 1 and distance_upper_bound=0.5 therefore returns that row instead of an empty result. Preserve the IDs and enforce the range, or explicitly reject unsupported bounded requests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, fixed in 7edd9cc: TVectorSearchParams 6/7 (distance_lower_bound / distance_upper_bound) have the 4.x ids and types, the request is validated as in #68815 (finite FLOAT bounds, lower < upper at FLOAT precision, no multi-vector), and lance_scanner_set_distance_range() is installed after nearest(), using master's pinned lance-c. #68815's fallback for quantized indexes relies on the BE searching a split without index segments flat, which came from #68707, so a66c2a6 ports that rule (use_index follows the split's segments; a split with segments under use_index=false is rejected). #68815's four tests and fixtures come along unchanged.

return std::nullopt;
}
const auto* sql_operator = predicate.op() == TExprOpcode::GE ? ">=" : "<=";
return "(" + quote_sql_identifier(slot->column_name()) + " " + sql_operator + " " +

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 negative NaNs when pushing floating range filters

For a nested-loop join L.x > B.y with finite B.y=1, FE can generate the MIN runtime filter L.x >= 1. A Lance FLOAT containing negative quiet-NaN bits 0xffc00000 satisfies both predicates in Doris, whose Compare treats every NaN as greatest. The pinned Arrow floating comparison uses total ordering, placing negative NaNs below finite values, and DataFusion delegates GE directly to that kernel; this generated SQL therefore drops a matching probe row. Rejecting nonfinite bound literals does not protect against NaN input values. Keep floating range filters residual-only or widen them to preserve every Doris survivor, with differential negative-NaN coverage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed. Doris keeps every NaN for x >= c (GreaterThanEqualsFloat treats NaN as greatest regardless of sign), while the pinned Arrow/DataFusion kernels use totalOrder, so a MIN-only filter could drop a negative-NaN row that Doris returns. <= and IN are safe: Lance only returns extra negative NaNs for <=, which the residual removes, and IN sets with NaN members are not pushed. Fixed in bf5dcbd: FLOAT/DOUBLE >= runtime filters stay residual-only, the policy PaimonRustPredicateConverter already applies, with a helper test and a reader test on 0xffc00000 / 0xfff8000000000000 rows. branch-4.1 and branch-4.2 carry the same helper.

RETURN_IF_ERROR(
_array_normalizer_for_arrow_column[static_cast<size_t>(arrow_index)]
.normalize_for_doris(arrow_column, _memory_pool, &normalized_column));
RETURN_IF_ERROR(output.serde->read_column_from_arrow(

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] Check DATE32 conversion failures at the Lance boundary

The new adapter advertises Arrow DATE32 as supported DATEV2, including valid external dates above Doris's maximum year 9999. For example, day count 2932897 is 10000-01-01. The shared DATE32 Arrow SerDe ignores get_date_from_daynr(false) and appends its zero-initialized value, while the nullable wrapper copies the source's non-null validity bit. This call therefore reports success with a non-null 0000-00-00 value. DATE64 and native Parquet conversion already reject or explicitly null failed conversions. Add equivalent checked range handling to this path, including nested date leaves, and cover the first date above 9999-12-31.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed, though the defect is in the shared DataTypeDateV2SerDe::read_column_from_arrow DATE32 branch: master has ignored the get_date_from_daynr() result there since #48944, so ADBC, Arrow stream load and paimon-rust return the same non-null 0000-00-00. branch-4.1/4.2 reject these values in the SerDe (#68649), and the 4.2 Lance code relies on that. Fixed in 6714c7f with the DATE32 range part of #68649: null slots are skipped, the day number is computed in 64 bits, and an out-of-range day returns InvalidArgument like the DATE64 branch next to it, which also covers ARRAY/STRUCT/MAP leaves.

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 78.02% (2080/2666)
Line Coverage 66.27% (38352/57868)
Region Coverage 53.81% (36079/67054)
Branch Coverage 57.31% (11621/20276)

morningman added a commit to morningman/doris that referenced this pull request Oct 25, 2026
…er.h

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581

Problem Summary: BE configure fails the build-hygiene gate on this PR
(COMPILE and BE UT): util/threadpool.h now reaches 1088 TUs, one over
its limit of 1087 (baseline 988 +10%). Master reaches 1083. Five of the
new Lance TUs reach the header through exprs/vexpr_context.h ->
storage/segment/column_reader.h -> io/cache/cached_remote_file_reader.h
-> io/cache/block_file_cache.h, an existing chain this PR does not
touch.

One of them did not need to. lance_record_batch_converter.h included
the whole format_v2/table_reader.h for two structs, ColumnDefinition and
GlobalRowIdContext, which are defined in format_v2/column_data.h, and
that header reaches nothing on the chain. The converter header now
includes column_data.h instead, so lance_record_batch_converter.cpp no
longer reaches util/threadpool.h and the reach is 1087, within the
limit. No gate table changes. The other four TUs (lance_reader.cpp,
lance_runtime_filter_helper.cpp and their unit tests) evaluate conjuncts
and runtime filters, so they include vexpr_context.h on purpose;
lance_reader.h still gets table_reader.h directly.

### Release note

None

### Check List (For Author)

- Test: Manual test
    - build-support/check-build-hygiene.sh passes (threadpool.h reach
      1087 of 1087).
    - With the BE UT compile flags, clang++ -fsyntax-only passes on
      lance_record_batch_converter.cpp, on a TU that includes only its
      header, and on the unity batch that holds it.
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
morningman added a commit to morningman/doris that referenced this pull request Oct 25, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581, apache#68649

Problem Summary:

Reproduction: read an Arrow Date32 array holding a day outside
0000-01-01..9999-12-31 into a Doris DATE column, for example 2932897
(10000-01-01) written by PyArrow into a Lance dataset, or the same value
returned by an ADBC driver, an Arrow stream load or the paimon-rust reader.
The row comes back as a non-null `0000-00-00`, also inside ARRAY, STRUCT and
MAP values.

Root cause: the DATE32 branch of the shared
`DataTypeDateV2SerDe::read_column_from_arrow` ignores the bool returned by
`get_date_from_daynr()` (dropped since apache#48944) and appends the
default-constructed value. It also adds the epoch offset in 32 bits, so
INT32_MAX overflows, and it decodes the payload of NULL slots. The DATE64
branch next to it already rejects out-of-range days.

Fix: port the DATE32 range part of the branch-4.1/4.2 fix apache#68649. NULL slots
append the default value without being decoded, the day number is computed
in 64 bits, and a day number <= 0, above DATE_MAX_DAYNR or rejected by
`get_date_from_daynr()` returns `InvalidArgument("Arrow Date32 value is
outside the Doris DATE range: row=..., days=...")` with the 4.x wording.
ARRAY/STRUCT/MAP leaves get the same check through their element SerDes.
`write_column_to_arrow` and the year-zero calendar mapping (the other part of
apache#68649, and master draft apache#67449) are unchanged; the new tests avoid the days
whose meaning that mapping changes.

Before: out-of-range days read as `0000-00-00`. After: the read fails with
the error above; in-range values and NULLs read as before.

The Lance reader ported in apache#69581 (from the branch-4.2 snapshot, which
already contains apache#68649) relies on this SerDe check.

### Release note

Reading Arrow DATE32 values outside 0000-01-01..9999-12-31 (ADBC, Arrow
stream load, paimon-rust, Lance) now fails instead of returning 0000-00-00.

### Check List (For Author)

- Test: Unit Test
    - New: `DataTypeDateTimeV2SerDeTest.RejectsInvalidDate32Values`,
      `LanceTableReaderTypeTest.RejectsDate32OutsideDorisDateRange`,
      `LanceTableReaderTypeTest.ReadsLastDorisDateAndIgnoresNullDate32Payload`.
    - All pass on macOS arm64 (ASAN), together with `DataTypeDateTimeV2SerDeTest.*`,
      `DataTypeSerDeArrowTest.*`, `DataTypeSerDeArrowValidationTest.*`,
      `ArrowColumnToDorisColumnTest.*`, `ParquetExprTest.*`,
      `IcebergV2ReaderTest.*` and the Lance suites. With master's DATE32 branch
      restored, the first two fail.
    - `DataTypeDateTimeV2SerDeTest` aborts in its setup under macOS ASAN on
      master: `load_data_from_csv()` in `be/test/testutil/test_util.cpp`
      dereferences `std::set::end()`. It ran here with that assertion removed
      locally; that fix goes in a separate PR.
- Behavior changed: Yes. An Arrow DATE32 day outside 0000-01-01..9999-12-31
  used to be returned as a non-null 0000-00-00; the scan now fails with an
  InvalidArgument error, as Arrow DATE64 values already did.
- Does this need documentation: No

Co-authored-by: Gabriel <liwenqiang@selectdb.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
morningman added a commit to morningman/doris that referenced this pull request Oct 25, 2026
…decoding

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581

Problem Summary:

Reproduction: write a Lance dataset with a `pa.decimal256(18, 2)` column and
scan it with the Lance reader added in apache#69581. Lance keeps the 256-bit width
on disk, so the scanner returns an Arrow `Decimal256Array`, and the BE crashes
on the first non-empty batch. The same happens for any `decimal256(p, s)` with
p <= 38, also as a list, large_list, fixed_size_list, struct or map leaf. The
existing fixture only has decimal256 columns with precision 39 and 76, which
is why the tests did not catch it.

Root cause: `arrow_field_to_doris_type()` and the FE pick the Doris type from
the precision alone (DECIMAL32 up to 9, DECIMAL64 up to 18, DECIMAL128I up to
38), while `LanceArrowArrayNormalizer` passes the `Decimal256Array` through
unchanged. `DataTypeDecimalSerDe<TYPE_DECIMAL32/64/128I>::read_column_from_arrow`
casts the array with `dynamic_cast<const arrow::DecimalArray*>`, gets null for
a `Decimal256Array`, and dereferences it in `Value()`.

Fix: at schema binding, `LanceArrowArrayNormalizer::create()` records
`decimal128(p, s)` for a Decimal256 field with p <= 38 and marks it as needing
special handling, which parents inherit through the existing child loop.
`normalize_for_doris()` then narrows the array with
`arrow::compute::Cast(..., CastOptions::Safe())` on the query's Arrow memory
pool, the cast master's ADBC normalizer already uses
(`format/arrow/arrow_array_normalizer.cpp`). Nested parents rebuild their type
around the narrowed child through `set_lance_nested_type()`, as for BFloat16
leaves, before compaction. A value outside the declared precision, which is
invalid Arrow data (for example unscaled 10^18 in decimal(18, 2)), fails with
`narrow Lance Decimal256 field '<name>' to decimal128(18, 2) failed: Decimal
value does not fit in precision 18` instead of being truncated. Precision
39..76 keeps Decimal256 and DECIMAL256 as before.

Before: the BE dereferences a null pointer and crashes. After: the values are
read unchanged, and an out-of-precision value fails with an error that names
the field.

branch-4.1 and branch-4.2 have the same normalizer, so the same change applies
there.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New: `LanceTableReaderTypeTest.NarrowsDecimal256WithDecimal128PrecisionDuringNormalization`,
      `LanceTableReaderTypeTest.KeepsWideDecimal256UnchangedDuringNormalization`,
      `LanceTableReaderTypeTest.ConvertsDecimal256WithDecimal128PrecisionToDorisDecimals`,
      `LanceTableReaderTypeTest.NarrowsDecimal256LeavesInSlicedNestedArrays`,
      `LanceTableReaderTypeTest.RejectsDecimal256ValuesOutsideDeclaredPrecision`,
      `LanceTableReaderTypeTest.ReadsDecimal256WithDecimal128PrecisionFromLanceDataset`
      (writes a decimal256(18, 2) dataset, checks that Lance stores Decimal256 and
      that the reader returns DECIMAL64 values).
    - All pass on macOS arm64 (ASAN) with the Lance suites. With the narrowing
      disabled, the normalization cases fail and the sliced nested case then
      crashes in `DataTypeDecimalSerDe::read_column_from_arrow`, as reported.
- Behavior changed: No (fixes a crash in the reader this PR adds)
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
morningman added a commit to morningman/doris that referenced this pull request Oct 25, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581

Problem Summary:

Reproduction: scan a Lance FLOAT or DOUBLE column that holds a negative NaN
(float bits 0xffc00000, double bits 0xfff8000000000000) under a runtime filter
`x >= c`, for example a MIN-only filter from a nested-loop join on `x >= y`.
Doris treats every NaN as the greatest value regardless of its sign
(`GreaterThanEqualsFloat` in `common/compare.h`), so the row satisfies the
filter, but it is missing from the result.

Root cause: `build_range_filter_sql()` translates the filter into the Lance
SQL `(x >= c)`. Lance evaluates it with Arrow/DataFusion comparison kernels
that order floating values by IEEE 754 totalOrder, where a negative NaN sorts
below every finite value, so Lance drops the row before the scanner evaluates
the Doris residual. The residual can only remove rows, not bring them back.

Fix: `build_range_filter_sql()` returns `std::nullopt` for a `>=` filter whose
slot is FLOAT or DOUBLE, so the filter stays residual-only and is reported
under `LanceRuntimeFilterSkippedIds`, the policy `PaimonRustPredicateConverter`
already applies to floating predicates. `<=` filters are still pushed: Lance
then only returns extra negative-NaN rows, which the residual removes. IN
filters are still pushed: a NaN row never matches a finite member, and sets
with NaN members were already rejected. A MIN_MAX filter now pushes only its
`<=` half.

Before: rows holding a negative NaN were dropped under a floating `>=` runtime
filter. After: Lance returns them and the Doris residual decides, so the
result matches a scan without the pushed filter.

branch-4.1 and branch-4.2 carry the identical helper, so the same change
applies there.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New: `LanceRuntimeFilterHelperTest.KeepsFloatingMinFiltersResidualOnly`
      (`>=` skipped, `<=` and IN still pushed, SQL of a MIN_MAX pair),
      `LanceTableReaderFilterTest.PreservesNegativeNaNUnderFloatingMinRuntimeFilter`
      (FLOAT and DOUBLE rows 0.5, 1, 2, +NaN, -NaN under `>= 1`; every row Doris
      keeps must come back from Lance).
    - Both pass on macOS arm64 (ASAN) with the Lance suites; with
      `build_range_filter_sql()` as before this change, both fail.
- Behavior changed: No (the Lance reader is added by this PR; floating `>=`
  runtime filters are no longer pushed into Lance, so such scans may read more
  rows)
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
morningman added a commit to morningman/doris that referenced this pull request Oct 25, 2026
…file as in branch-4.x

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581, apache#68813

Problem Summary:

apache#69581 ports the Lance reader from the branch-4.2 snapshot cabcf45. After
that snapshot, apache#68813 changed two things in the 4.x BE, both ported here.

1. Search filter timing. apache#68813 added `TExternalSearchRequest.prefilter`
   (field 5): unset or false means the TVF search filter refines the top-k
   candidates (postfilter), true means it constrains them before top-k
   (prefilter). The 4.x FE sends it for `vector_search()` and
   `full_text_search()`. Without the field, a master BE always prefiltered
   vector search and never prefiltered full-text search, so a 4.x FE paired
   with it got different rows: on the all_types.lance fixture, top_k 1 with
   search filter `row_id >= 3` returns row 4 with prefilter and no row with
   postfilter.
   - `gensrc/thrift/PlanNodes.thrift`: `5: optional bool prefilter` with the
     4.x comment; the stale `TSearchFilter` comment is replaced.
   - `_configure_vector_search()` and `_configure_full_text_search()` pass
     `request.__isset.prefilter && request.prefilter` to
     `lance_scanner_set_prefilter()`, replacing the comment that
     fragment-scoped nearest queries need prefiltering (master's lance-c pin
     4fafcd64525 no longer does).
   - Compatibility (gensrc/AGENTS.md): field 5 is `optional bool prefilter` on
     the branch-4.2 (a7ae6f4) and branch-4.1 (3f911d0) heads;
     branch-4.0 and branch-3.1 have no `TExternalSearchRequest`. No id is
     reused.

2. Profile layout. The snapshot hangs about 45 counters directly under
   `LanceReader`, looks Lance's scan metrics up in two maps, and creates two
   scalar-index timers from Lance's statistics callback the first time they
   are reported, that is from Lance's execution threads while the scan runs.
   As in apache#68813:
   - The counters are grouped under `LanceTiming`, `LanceIO`, `LanceIndex`,
     `LanceVectorIndex`, `LanceScalarIndex`, `LanceExecutionPlan`,
     `LanceScan`, `LanceSearchPlan` and `LanceDataCache` (label counters,
     `TUnit::NONE`, like `RuntimeFilterInfo`).
   - One immutable table, `LANCE_SCAN_METRICS` in `lance_reader_helper.h`,
     drives both registration and the callback lookup. Every metric is
     registered in `_init_scanner_profile()` before the callback is installed,
     so the callback only updates existing counters and every split has the
     same counter tree.
   - The callback is `noexcept` and swallows exceptions, so nothing unwinds
     through the lance-c ABI.
   - `LanceIndexObjectLoads` (`indices_loaded`), `LanceIndexCacheHits` and
     `LanceIndexCacheMisses` are new; the info strings `LanceTopKPlusOffset`
     and `LanceSubstraitFilterBytes` are dropped.
   - Renames: `LanceScannerReadTime` -> `LanceScannerNextTime`,
     `LanceRuntimeFilterSqlTime` -> `LanceRuntimeFilterSqlTranslationTime`,
     `LanceExecutionIOOps` / `IORequests` / `IOBytesRead` -> `LanceIOReadOps` /
     `LanceIOReadRequests` / `LanceIOReadBytes`,
     `LanceIndexPartitionCacheMissLoads` -> `LanceIndexComponentsLoaded`,
     `LancePrefilter*` -> `RowIdPrefilter*` (`LancePrefilterRowIds` ->
     `RowIdPrefilterIds`), `LanceIVFPartitionsRanked` ->
     `LanceIVFPartitionsConsidered`, `LanceIVFPartitionsSearched` ->
     `LanceIndexPartitionsSearched`, `LanceVectorIndexSegmentsSearched` ->
     `LanceVectorIndexDeltasSearched`, `LanceTaskWaitTime` ->
     `LanceScanTaskWaitTime`, `LanceIVFPartitionRankingTime` ->
     `LanceIVFPartitionSelectionTime`, `LanceScalarIndexSegmentPrepareTime` ->
     `LanceScalarIndexCandidateEvaluationTime`.
   - Master differs from 4.x in one line: the callback asserts with
     `DCHECK_LT` that a found metric has a registered counter, where 4.x
     checks it in the `if`; both come from the same array.
   - Not ported: apache#68813's FE change that makes `AggCounter` print nothing for
     `TUnit.NONE`. It belongs to the FE port; until then master's merged
     profile prints the group labels like the existing `RuntimeFilterInfo`.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New: `LanceTableReaderVectorSearchTest.SearchFilterDefaultsToPostfilter`
      (from apache#68813) and `LanceTableReaderFullTextSearchTest.SearchFilterHonorsPrefilterFlag`
      (top_k 1 with filter `row_id <= 2`: no row with postfilter, row 2 with
      prefilter; 4.x has no BE test for the full-text path).
    - Changed as in apache#68813: `AppliesSearchFilterBeforeTopK` becomes
      `AppliesSearchFilterBeforeTopKWhenPrefilterEnabled`;
      `MultiVectorScoresFiltersOffsetsAndIndexedCoverage` and
      `MultiVectorSegmentTopKMatchesIndependentGlobalOracle` set `prefilter`
      for their filtered cases, and the former checks the `RowIdPrefilter*`
      counters and that a flat split exposes `LanceIndexPartitionLoadTime`.
    - All pass on macOS arm64 (ASAN) against master's pinned lance-c,
      with the Lance suites. With vector search always prefiltering and
      full-text search never prefiltering, the two new cases fail.
- Behavior changed: Yes, on the Lance path added by apache#69581. A search filter
  without `prefilter` now postfilters vector search, and full-text search
  honors `prefilter = true`, as on branch-4.1/4.2. Lance profile counters are
  grouped and renamed. Master has no FE that plans Lance scans yet.
- Does this need documentation: No

Co-authored-by: zhangstar333 <zhangsida@selectdb.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
morningman added a commit to morningman/doris that referenced this pull request Oct 25, 2026
… planned

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581, apache#68707, apache#68815

Problem Summary:

apache#69581 ports the Lance reader from the branch-4.2 snapshot cabcf45, where
`_configure_vector_search()` passes the request's `use_index` to
`lance_scanner_set_use_index()`. FE decides per split whether to use an index:
a split it plans with index segments carries their UUIDs, and a split without
them is meant to be searched flat. With `use_index = true` and no segments,
Lance instead searches the split with the first index on the column in
manifest order. That index need not be the one FE chose: when FE finds no
index whose metric matches the query, it plans every split flat, and the
snapshot BE still searched them with the mismatched index. On branch-4.2,
apache#68815 also plans distance-range searches over quantized indexes (IVF_SQ) as
flat fallback splits, because quantized distances can drop rows inside the
range; the snapshot BE searches those with the index and returns too few rows.

Fix: port the split-level rule of apache#68707.
- `lance_scanner_set_use_index(scanner, segment_count > 0)`: a split with
  segments searches exactly those, a split without segments searches flat,
  and `LancePlannedFlatSearchFragmentCount` now matches what Lance does.
- A split that carries index segments while the request sets
  `use_index = false` is rejected with InvalidArgument. FE plans segments only
  when `use_index` allows them, on branch-4.1 (4.1.4 onward) and branch-4.2
  alike, so a 4.x FE never sends this combination.
- Not ported: the rest of apache#68707 (searching historical versions, tags and
  branches; rejecting fragment ids missing from the snapshot; naming the
  snapshot in errors and the profile). That comes with the next 4.x catch-up.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New, from apache#68707 (only master's `RuntimeState(TQueryOptions(), globals)`
      constructor differs): `LanceTableReaderVectorSearchTest.SplitWithoutIndexSegmentsSearchesFlat`,
      `LanceTableReaderVectorSearchTest.RejectsIndexSegmentsWhenUseIndexIsFalse`.
    - Changed as in apache#68707: `MultiVectorScoresFiltersOffsetsAndIndexedCoverage`,
      `MultiVectorTopOnePreservesPrecisionAndBatchIndependence` and
      `MultiVectorCosineMasksUndefinedRows` pass the index segments for their
      indexed cases (new helper `make_indexed_lance_range`) and check
      `LanceIndexPartitionsSearched`; `MultiVectorDefaultMetricIsConsistentAcrossFragments`
      checks that every split is searched flat.
    - All pass on macOS arm64 (ASAN) against master's pinned lance-c,
      with the Lance suites. With `use_index` taken from the request and the
      rejection removed, the two new cases fail.
- Behavior changed: Yes, on the Lance path added by apache#69581: a vector split
  without index segments is searched flat even when `use_index` is true, and a
  split with segments under `use_index = false` is rejected. Master has no FE
  that plans Lance scans yet.
- Does this need documentation: No

Co-authored-by: zy-kkk <zhongyk10@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
morningman added a commit to morningman/doris that referenced this pull request Oct 25, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581, apache#68815

Problem Summary:

apache#69581 ports the Lance reader from the branch-4.2 snapshot cabcf45. After
that snapshot, apache#68815 added `TVectorSearchParams.distance_lower_bound` (6) and
`distance_upper_bound` (7) to branch-4.1 and branch-4.2: FLOAT bounds,
inclusive lower and exclusive upper, which the 4.x FE fills for a
single-vector `vector_search()` with distance bounds. apache#68815 kept the
external-search `schema_version` at 1, so a BE cannot tell a bounded request
from an unbounded one by version. A master BE without the fields ignored them
and returned the unbounded top-k rows, for example rows with `_distance`
below the lower bound.

Fix: port the BE side of apache#68815.
- `gensrc/thrift/PlanNodes.thrift`: add `6: optional double
  distance_lower_bound` and `7: optional double distance_upper_bound` to
  `TVectorSearchParams` with the 4.x comment.
- `LanceTableReader::_validate_external_search_request()` rejects bounds on a
  multi-vector query (NotSupported), a bound that is not a finite FLOAT value
  (NaN, infinity, or beyond FLT_MAX), and a lower bound that is not below the
  upper bound at FLOAT precision (InvalidArgument), before the dataset is
  opened.
- `_configure_vector_search()` narrows the bounds to FLOAT and installs them
  with `lance_scanner_set_distance_range()` after `lance_scanner_nearest()`,
  which clears any earlier range. A missing bound is passed as NULL
  (unbounded on that side). Requests without bounds are unchanged.

Before: distance bounds sent by a 4.x FE were silently ignored. After: the
reader returns only rows with lower <= _distance < upper, still capped by
top_k and offset, and rejects invalid bounds.

Compatibility (gensrc/AGENTS.md): fields 6 and 7 of `TVectorSearchParams` are
`optional double distance_lower_bound` and `optional double
distance_upper_bound` on the branch-4.2 (a7ae6f4) and branch-4.1
(3f911d0) heads, the same ids and types as here. branch-4.0 and
branch-3.1 have no `TVectorSearchParams`, and on master the struct is new in
apache#69581 with fields 1-5. No id is reused, and both fields are optional.

`lance_scanner_set_distance_range()` is in master's lance-c pin
(4fafcd64525), so only the BE changes.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New (copied unchanged from apache#68815 except for master's
      `RuntimeState(TQueryOptions(), globals)` constructor):
      `LanceTableReaderVectorSearchTest.ValidatesDistanceRangeBeforeDatasetAccess`,
      `LanceTableReaderVectorSearchTest.AppliesDistanceBoundsAndOffset`,
      `LanceTableReaderVectorSearchTest.DistanceRangeFallbackPreservesExactScores`,
      `LanceTableReaderVectorSearchTest.DistanceRangeCoversIndexedAndAppendedFragments`.
    - Fixtures copied from d306323 (identical on the branch-4.2 head):
      `be/test/format_v2/table/lance/data/distance_range.lance` (11 files),
      `distance_range_dot.lance` (8 files), `distance_range_sq.lance` (8
      files) and their description
      `be/test/format_v2/table/lance/distance_range_fixture.md`.
    - All pass on macOS arm64 (ASAN) against master's pinned lance-c, with the
      Lance suites. `DistanceRangeFallbackPreservesExactScores` needs the
      previous commit: without it the IVF_SQ fallback split is searched with
      the quantized index and loses its only row. With the range not installed,
      the three cases that read rows fail.
- Behavior changed: Yes, on the Lance external-search path added by apache#69581:
  distance bounds are applied instead of ignored, and invalid bounds are
  rejected. Master has no FE that sends Lance search requests yet.
- Does this need documentation: No

Co-authored-by: Gabriel <liwenqiang@selectdb.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
morningman added a commit to morningman/doris that referenced this pull request Oct 25, 2026
…Lance reader

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581

Problem Summary:

BE configure runs `build-support/check-build-hygiene.sh`. Its reverse reach
budget caps how many TUs may transitively include `util/threadpool.h` at the
baseline 988 (master 9a48f81, 2026-08-18) plus 10%, that is 1087. Master
76fb880 is at 1084, and the Lance reader adds four TUs that reach it
(`format_v2/table/lance_reader.cpp`,
`format_v2/lance/lance_runtime_filter_helper.cpp` and their unit tests), so
configure on master plus this PR fails with "util/threadpool.h now reaches
1088 TUs (baseline 988 +10% = 1087)".

The four TUs add no new edge. They need `VExprContext`, directly or through
`format_v2/table_reader.h`, and `exprs/vexpr_context.h` holds
`segment_v2::ColumnIteratorOptions` by value, so it includes
`storage/segment/column_reader.h` -> `io/cache/cached_remote_file_reader.h` ->
`io/cache/block_file_cache.h` -> `util/threadpool.h`, the path the other
format_v2 readers already take. Narrowing includes inside the Lance files
cannot remove it; the record batch converter header was already narrowed to
`format_v2/column_data.h` in this PR.

As the gate's message prescribes, this rebaselines `util/threadpool.h` to the
measured 1088 (limit 1197) and records the reason next to the entry. Cutting
the `vexpr_context.h` -> `column_reader.h` edge, by moving
`ColumnIteratorOptions` into a light header, would bring the reach down to
about 846; that is a separate build cleanup.

### Release note

None

### Check List (For Author)

- Test: No need to test: build configuration only.
  `bash build-support/check-build-hygiene.sh` passes on this branch and on
  master 76fb880 plus this PR.
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

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.

Two new P2 findings remain: out-of-range Lance timestamps can silently wrap their year, and persistent native session caches have no Doris tracking/reclamation bridge. Both are reported inline.

The five existing findings were independently rechecked at a1c3636: Decimal256 narrowing (4369909216), search prefilter timing (4369909218), vector distance bounds (4369909220), negative-NaN GE filters (4369909223), and DATE32 range checking (4369909226) are fixed. No existing P0/P1 finding remains applicable, so none is reposted or carried as blocking. The new timestamp finding follows a separate TIMESTAMP/DATETIMEV2 conversion path from the repaired DATE32 issue. There was no additional user-provided focus.

Critical checkpoint conclusions:

  1. Purpose and proof: the native Lance BE scan/schema/search/fetch port is connected through FileScannerV2. Extensive unit-test code is present; timestamp boundaries and persistent-cache reclamation still need coverage for the inline defects. Tests were inspected, not executed.
  2. Scope and reuse: the reader, conversion, filter and session helpers form a coherent BE port; shared Null/DATE32 SerDes and row-ID width changes have corresponding callers and tests.
  3. Concurrency and locks: parallel scanners share the call_once-initialized session; callbacks update pre-registered counters. Phase-two tasks retain per-task outputs and completion ownership. No additional concrete race or lock inversion was found.
  4. Lifecycle/static initialization: scanner cleanup precedes FTS context/dataset cleanup; partial-open guards and schema resets preserve ownership. The function-local session and synchronized metric-hook teardown avoid an established static-lifetime defect; cache retention is covered inline.
  5. Configuration: six cache settings are process-scoped; the three mutable buffer/readahead settings are sampled for newly created scanners. Existing scanners are not promised immediate updates.
  6. Compatibility: the 24-byte version-0 layout and original uint32 row-ID values are preserved; version 1 carries native uint64 Lance IDs, and current receivers reject unknown versions. Old receivers cannot decode version 1: the explicitly deferred FE rollout gate is still needed before enabling mixed-version Lance lazy materialization.
  7. Thrift/protobuf: every touched definition was compared with live-verified branch-3.1/4.0/4.1/4.2 snapshots. Existing IDs, enum meanings and optional search fields match 4.1/4.2; older branches lack the new search definitions. Legacy protobuf row IDs retain their wire meaning and range.
  8. Parallel paths: normal/vector/FTS, schema-only, nested types, row-ID take/scatter and legacy Parquet/ORC paths were checked. FE split requests reset search offset after increasing candidate top_k, explaining the different split-level offset handling.
  9. Conditions and errors: FFI failures, malformed versions/counts and unsupported filter/type cases return errors or retain residual evaluation. Lance's pinned planner repairs signed-zero comparisons; raw DataFusion behavior alone is not a defect here.
  10. Test coverage: positive/negative tests cover types, NULLs, slicing, indexes, search bounds, prefilter and row-ID validation. Full scanner/metadata-count/RPC integration, cancellation, broader writer interoperability and live resource behavior were not established by execution.
  11. Expected results: no regression .out files changed. Binary fixture inventories and their test expectations were inspected; their runtime output was not independently reproduced.
  12. Observability: scan, index, cache, search and fetch metrics/timers are present; metric gauges do not provide the missing cache reclamation hook.
  13. Transactions/persistence: no Doris EditLog or transaction-state mutation is introduced; snapshot identity is carried into phase-two fetch.
  14. Writes/atomicity: the production change is a read path. No new Doris write/commit protocol or committed-row visibility defect was identified.
  15. FE/BE passing: search fields and split contracts match current 4.x senders; the master FE connector/rollout policy is outside this BE-only port.
  16. Performance/memory: session/schema reuse, zero-copy common arrays and deduplicated snapshot-level take_rows avoid redundant work. Persistent cache tracking/reclamation remains an inline issue. No benchmark or memory-pressure experiment was run.
  17. Further sweep: adjacent timestamp conversion, complete upstream floating-filter handling, real Rust Arrow export layouts, RF cache scope, recursive fragment rebuilds, native take ordering and cleanup were rechecked. No other verified finding remains.

Static review only: no builds, tests, source modifications or submodule initialization were performed. Existing glibc compatibility adapters/link ordering were inspected, but no completed Linux ELF artifacts were available to verify the GLIBC_2.17 release baseline. These execution/artifact and deferred-FE limits are not claimed as validated behavior. All code-review candidates and risk focuses have a recorded disposition; no unresolved candidate is being submitted as a finding.

Review converged after 2 round(s).

RETURN_IF_ERROR(
_array_normalizer_for_arrow_column[static_cast<size_t>(arrow_index)]
.normalize_for_doris(arrow_column, _memory_pool, &normalized_column));
RETURN_IF_ERROR(output.serde->read_column_from_arrow(

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] Reject out-of-range timestamps before narrowing the year

A valid Lance timestamp(us) value 2005980768000000000 represents 65537-01-01 00:00:00, but this call returns non-null 0001-01-01. The schema mapper selects DATETIMEV2(6), and the shared timestamp SerDe calls from_unixtime(), which passes the civil year to unchecked_set_time(uint16_t year, ...), narrowing 65537 to 1. transform_date_scale(6, 6) only copies, and NullableSerDe retains the source's non-null bit. The repaired DATE32 check is on another conversion path and cannot catch this, including nested timestamp leaves. Validate the calendar range before narrowing and add first-out-of-range and year-wrap timestamp cases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed, and it is not specific to Lance: DataTypeDateTimeV2SerDe::read_column_from_arrow packs the civil year from from_unixtime() into unchecked_set_time(uint16_t year, ...) without a range check, so ADBC, Arrow stream load, paimon-rust and remote Doris reads return the same 0001-01-01 for 2005980768000000000 us, and years 10000..65535 come back as non-null values outside the DATETIME range. The TIMESTAMPTZ Arrow path and the UTC-adjusted Parquet paths call is_valid_date() only after packing, which a wrapped year passes. branch-4.1 and branch-4.2 have the same code. Unlike the DATE32 check in this PR, which ports a fix branch-4.1 / 4.2 already have (#68649), this one is missing on every branch, and the change belongs in the shared SerDe used by all of those readers. It goes in a separate PR that checks the civil year before narrowing, as the ORC path does since #68516, for the Arrow and Parquet timestamp paths, with first-out-of-range, year-wrap, pre-0000 and nested cases. This PR does not touch that SerDe.

.read_block_size_bytes =
static_cast<uint64_t>(_config.lance_data_cache_read_block_size_bytes),
};
_session = lance_session_new_with_data_cache(

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] Make persistent session caches trackable and reclaimable

This singleton gives the native index and metadata caches 10 GiB and 1 GiB by default, but neither cache is charged to a Doris MemTracker or registered with CacheManager. The pinned native session retains these caches after dataset/query teardown; publishing size gauges does not add a reclaim path. Once indexed reads populate them, Doris's memory-pressure capacity adjustment and /api/clear_cache/all only visit registered CachePolicy objects, leaving these idle entries live even while other queries encounter process memory pressure. Disabling the Foyer data cache does not disable these two caches. Add process-cache accounting and a CacheManager eviction/capacity bridge, and cover reclamation after the owning queries finish.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed; this is the branch-4.1 / 4.2 design, ported unchanged. The index and metadata caches belong to the lance-c session, which a BE creates on its first Lance scan with the configured capacities (lance_index_cache_size_bytes, lance_metadata_cache_size_bytes, applied at restart); enable_lance_data_cache = false does not affect them, and the lance_session_* metrics report their usage. The pinned lance-c exposes only lance_session_get_cache_stats(). It has no call to clear, evict or resize either cache, so a CachePolicy could neither prune them nor follow the memory-pressure capacity adjustment. Making them reclaimable needs that API in lance-c first and should land on all branches together, so it is tracked as a follow-up. The PR description now says that these caches are not charged to a MemTracker or managed by CacheManager, and how to size them.

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 78.02% (2080/2666)
Line Coverage 66.26% (38345/57868)
Region Coverage 53.65% (35972/67054)
Branch Coverage 57.23% (11603/20276)

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17375	5186	5073	5073
q2	2154	302	246	246
q3	10216	1401	823	823
q4	4683	463	338	338
q5	7456	802	536	536
q6	192	186	152	152
q7	751	800	595	595
q8	9271	1351	1134	1134
q9	5628	4218	4223	4218
q10	6812	1297	1043	1043
q11	435	242	229	229
q12	638	409	289	289
q13	18110	3537	2994	2994
q14	279	277	255	255
q15	q16	739	712	662	662
q17	1179	746	595	595
q18	6974	6189	6142	6142
q19	1126	976	615	615
q20	371	337	223	223
q21	4698	2143	2306	2143
q22	352	304	258	258
Total cold run time: 99439 ms
Total hot run time: 28563 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5635	5563	5560	5560
q2	678	579	549	549
q3	5239	5395	5026	5026
q4	1999	2154	1539	1539
q5	5333	5253	5653	5253
q6	269	208	157	157
q7	2287	2015	1869	1869
q8	3055	2716	2750	2716
q9	8148	7954	7932	7932
q10	3722	3691	3216	3216
q11	614	586	417	417
q12	721	681	544	544
q13	3537	3784	3241	3241
q14	328	343	325	325
q15	q16	708	732	641	641
q17	8312	7346	6957	6957
q18	13024	12236	12916	12236
q19	923	787	777	777
q20	2140	2181	1903	1903
q21	5627	4593	4683	4593
q22	505	441	427	427
Total cold run time: 72804 ms
Total hot run time: 65878 ms

@hello-stephen

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

query5	4294	573	437	437
query6	434	223	185	185
query7	4798	441	245	245
query8	312	172	141	141
query9	8774	3087	3043	3043
query10	462	304	254	254
query11	5960	3846	3559	3559
query12	150	92	86	86
query13	1272	413	316	316
query14	6135	3948	3632	3632
query14_1	3405	3378	3417	3378
query15	208	196	180	180
query16	966	423	414	414
query17	860	667	513	513
query18	2436	426	310	310
query19	208	168	146	146
query20	86	84	81	81
query21	210	124	104	104
query22	13007	12892	12728	12728
query23	13484	12864	12516	12516
query23_1	12628	12603	12610	12603
query24	7341	810	466	466
query24_1	460	486	467	467
query25	504	381	336	336
query26	1248	255	141	141
query27	2763	441	272	272
query28	4647	1939	1946	1939
query29	1533	584	436	436
query30	296	226	186	186
query31	1011	930	815	815
query32	138	91	92	91
query33	513	296	222	222
query34	883	850	473	473
query35	728	741	638	638
query36	883	804	739	739
query37	117	96	81	81
query38	1767	1729	1659	1659
query39	808	771	750	750
query39_1	718	724	742	724
query40	215	113	93	93
query41	68	66	61	61
query42	88	83	80	80
query43	336	359	319	319
query44	1299	696	692	692
query45	179	175	164	164
query46	842	964	573	573
query47	2897	2873	2707	2707
query48	305	316	223	223
query49	580	430	306	306
query50	689	281	206	206
query51	10516	10264	10384	10264
query52	80	82	71	71
query53	191	208	166	166
query54	258	213	186	186
query55	72	70	67	67
query56	243	246	214	214
query57	1674	1629	1570	1570
query58	294	270	260	260
query59	2136	2202	2052	2052
query60	302	260	234	234
query61	174	167	159	159
query62	396	347	306	306
query63	194	161	165	161
query64	2854	1048	870	870
query65	3382	3290	3278	3278
query66	1785	432	318	318
query67	20152	20137	20081	20081
query68	2966	999	617	617
query69	402	295	261	261
query70	840	770	820	770
query71	290	237	209	209
query72	2708	2573	2308	2308
query73	511	517	297	297
query74	4632	4540	4330	4330
query75	2317	2294	1912	1912
query76	2297	1002	595	595
query77	340	365	262	262
query78	9809	9961	9175	9175
query79	1023	862	537	537
query80	777	414	361	361
query81	575	323	264	264
query82	566	127	111	111
query83	282	199	178	178
query84	314	113	105	105
query85	953	408	368	368
query86	353	239	231	231
query87	2047	1932	1791	1791
query88	3636	2680	2673	2673
query89	334	292	240	240
query90	1705	165	163	163
query91	160	147	124	124
query92	98	89	83	83
query93	1032	999	582	582
query94	506	327	278	278
query95	574	341	354	341
query96	663	480	232	232
query97	2464	2444	2341	2341
query98	161	150	155	150
query99	761	770	657	657
Total cold run time: 234414 ms
Total hot run time: 150868 ms

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage `` 🎉
Increment coverage report
Complete coverage report

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.13	0.08	0.08
query3	0.35	0.13	0.12
query4	1.60	0.13	0.14
query5	0.24	0.24	0.25
query6	1.15	0.66	0.65
query7	0.03	0.01	0.01
query8	0.10	0.07	0.06
query9	0.44	0.36	0.36
query10	0.60	0.58	0.61
query11	0.32	0.18	0.17
query12	0.32	0.19	0.18
query13	0.50	0.51	0.52
query14	0.93	0.93	0.93
query15	0.66	0.59	0.60
query16	0.40	0.35	0.36
query17	1.03	1.02	0.99
query18	0.29	0.27	0.28
query19	2.01	1.86	1.88
query20	0.02	0.02	0.01
query21	15.38	0.36	0.31
query22	4.92	0.13	0.14
query23	15.84	0.48	0.29
query24	2.38	0.54	0.38
query25	0.16	0.10	0.10
query26	0.77	0.28	0.21
query27	0.11	0.10	0.10
query28	3.48	0.79	0.39
query29	12.59	4.29	3.34
query30	0.35	0.17	0.16
query31	2.75	0.57	0.31
query32	3.23	0.58	0.47
query33	3.13	3.05	3.09
query34	15.97	4.11	3.43
query35	3.38	3.38	3.39
query36	0.59	0.49	0.49
query37	0.12	0.09	0.09
query38	0.07	0.07	0.06
query39	0.06	0.06	0.05
query40	0.19	0.17	0.16
query41	0.10	0.06	0.05
query42	0.06	0.05	0.06
query43	0.07	0.06	0.06
Total cold run time: 96.82 s
Total hot run time: 25.15 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 0.00% (0/7) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 31.25% (15/48) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 66.72% (32083/48083)
Line Coverage 51.56% (339735/658953)
Region Coverage 46.97% (275370/586268)
Branch Coverage 48.70% (130056/267043)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 29.79% (14/47) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.41% (36063/46588)
Line Coverage 62.66% (408482/651916)
Region Coverage 59.17% (345621/584147)
Branch Coverage 60.10% (160099/266390)

@morningman
morningman force-pushed the b2m-t37-lance-be-reader branch from a1c3636 to a19d360 Compare October 26, 2026 03:25
morningman added a commit to morningman/doris that referenced this pull request Oct 26, 2026
…er.h

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581

Problem Summary: BE configure fails the build-hygiene gate on this PR
(COMPILE and BE UT): util/threadpool.h now reaches 1088 TUs, one over
its limit of 1087 (baseline 988 +10%). Master reaches 1083. Five of the
new Lance TUs reach the header through exprs/vexpr_context.h ->
storage/segment/column_reader.h -> io/cache/cached_remote_file_reader.h
-> io/cache/block_file_cache.h, an existing chain this PR does not
touch.

One of them did not need to. lance_record_batch_converter.h included
the whole format_v2/table_reader.h for two structs, ColumnDefinition and
GlobalRowIdContext, which are defined in format_v2/column_data.h, and
that header reaches nothing on the chain. The converter header now
includes column_data.h instead, so lance_record_batch_converter.cpp no
longer reaches util/threadpool.h and the reach is 1087, within the
limit. No gate table changes. The other four TUs (lance_reader.cpp,
lance_runtime_filter_helper.cpp and their unit tests) evaluate conjuncts
and runtime filters, so they include vexpr_context.h on purpose;
lance_reader.h still gets table_reader.h directly.

### Release note

None

### Check List (For Author)

- Test: Manual test
    - build-support/check-build-hygiene.sh passes (threadpool.h reach
      1087 of 1087).
    - With the BE UT compile flags, clang++ -fsyntax-only passes on
      lance_record_batch_converter.cpp, on a TU that includes only its
      header, and on the unity batch that holds it.
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@hello-stephen

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

query5	4313	593	435	435
query6	426	211	191	191
query7	4815	469	228	228
query8	319	166	158	158
query9	8843	3200	3174	3174
query10	480	331	252	252
query11	5926	3859	3599	3599
query12	151	92	87	87
query13	1256	446	309	309
query14	6051	3996	3662	3662
query14_1	3466	3477	3467	3467
query15	215	200	185	185
query16	952	459	457	457
query17	842	663	508	508
query18	2409	406	293	293
query19	178	167	133	133
query20	84	81	80	80
query21	206	126	106	106
query22	13007	13086	12773	12773
query23	13519	12951	12494	12494
query23_1	12476	12497	12481	12481
query24	7322	825	478	478
query24_1	479	498	485	485
query25	513	394	340	340
query26	1260	270	137	137
query27	2760	430	269	269
query28	4643	2013	2015	2013
query29	1548	572	456	456
query30	295	217	183	183
query31	1015	930	827	827
query32	134	91	95	91
query33	529	287	239	239
query34	897	846	514	514
query35	726	762	666	666
query36	830	828	719	719
query37	126	100	83	83
query38	1768	1741	1682	1682
query39	787	779	737	737
query39_1	725	724	738	724
query40	224	123	112	112
query41	86	82	68	68
query42	91	87	89	87
query43	356	374	312	312
query44	1335	707	710	707
query45	180	176	166	166
query46	834	982	576	576
query47	2876	2899	2950	2899
query48	294	303	222	222
query49	571	425	286	286
query50	676	272	207	207
query51	10371	10325	10430	10325
query52	77	79	71	71
query53	191	211	159	159
query54	238	186	177	177
query55	71	67	62	62
query56	252	226	206	206
query57	1695	1619	1572	1572
query58	293	262	258	258
query59	2148	2232	1986	1986
query60	293	244	225	225
query61	161	146	137	137
query62	391	330	279	279
query63	187	156	158	156
query64	2726	903	762	762
query65	3324	3279	3297	3279
query66	1767	413	296	296
query67	20412	20246	20088	20088
query68	3045	1015	618	618
query69	372	293	249	249
query70	833	794	783	783
query71	310	240	217	217
query72	2757	2698	2283	2283
query73	524	519	297	297
query74	4661	4500	4380	4380
query75	2334	2287	1905	1905
query76	2298	1029	600	600
query77	360	380	276	276
query78	9897	9711	9116	9116
query79	879	870	533	533
query80	465	408	352	352
query81	523	320	279	279
query82	199	136	105	105
query83	204	201	184	184
query84	285	120	103	103
query85	757	413	372	372
query86	259	235	231	231
query87	1993	1961	1788	1788
query88	3698	2774	2769	2769
query89	311	283	243	243
query90	2096	177	175	175
query91	157	152	125	125
query92	98	91	88	88
query93	976	1013	571	571
query94	445	300	235	235
query95	586	397	316	316
query96	667	489	249	249
query97	2452	2437	2324	2324
query98	181	153	146	146
query99	761	763	648	648
Total cold run time: 233576 ms
Total hot run time: 151354 ms

@hello-stephen

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

query1	0.01	0.00	0.01
query2	0.14	0.09	0.09
query3	0.34	0.13	0.14
query4	1.60	0.14	0.14
query5	0.28	0.26	0.26
query6	1.16	0.65	0.65
query7	0.05	0.01	0.01
query8	0.10	0.07	0.08
query9	0.45	0.37	0.37
query10	0.59	0.58	0.59
query11	0.32	0.18	0.18
query12	0.31	0.19	0.18
query13	0.52	0.54	0.53
query14	0.92	0.94	0.93
query15	0.66	0.59	0.59
query16	0.34	0.36	0.37
query17	1.05	0.99	1.03
query18	0.29	0.29	0.28
query19	2.00	1.88	1.84
query20	0.02	0.02	0.02
query21	15.42	0.35	0.31
query22	4.94	0.13	0.13
query23	15.85	0.48	0.29
query24	2.20	0.52	0.39
query25	0.14	0.10	0.10
query26	0.76	0.28	0.21
query27	0.09	0.09	0.10
query28	3.42	0.79	0.40
query29	12.61	4.41	3.41
query30	0.36	0.19	0.15
query31	2.76	0.61	0.32
query32	3.23	0.62	0.48
query33	2.95	3.00	2.90
query34	15.89	4.12	3.45
query35	3.38	3.38	3.40
query36	0.60	0.50	0.47
query37	0.12	0.09	0.10
query38	0.07	0.07	0.07
query39	0.06	0.06	0.05
query40	0.20	0.16	0.16
query41	0.11	0.05	0.06
query42	0.06	0.07	0.06
query43	0.07	0.06	0.06
Total cold run time: 96.44 s
Total hot run time: 25.22 s

@morningman

Copy link
Copy Markdown
Contributor Author

Addressed the fourth review round in f5af56058e7. Per finding:

  • 4389385861 (P2, zero-field structs): fixed. STRUCT<> is now an unsupported column at any nesting level, with two new schema tests.
  • 4373822200 (P1, endpoint identity in the data cache key): unchanged, as answered in the thread. The key is built in the shared thirdparty/patches/lance-c-foyer.patch; the fix is tracked for all branches, and the PR description states the exposure and the enable_lance_data_cache = false workaround.

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

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. One new P2 finding is inline: DATE32 conversion rejects a child value hidden beneath a NULL fixed-size-list parent, making a supported valid Lance scan fail. This is a different input and failure from the repaired visible-DATE32 range finding.

The existing P1 shared data-cache identity finding still applies to f5af56058e79a80e163627bcc9b58642db3e602a. I independently traced the default-enabled singleton session into the Foyer patch: keys include the Lance store prefix and object path, but omit the physical endpoint. Two origins with the same bucket/path and different registered immutable data-file bytes can return the other origin's cached rows. Random writer filenames reduce this trigger; they do not exclude it. Documentation and a deferred fix do not remove the defect. Retain comment 4373822200; no duplicate inline is posted.

One new independent finding was accepted. Existing timestamp year-wrap P2 4370665038 and native cache accounting/reclamation P2 4370665039 also remain applicable and were deduplicated. The previous Decimal256-carrier, search-prefilter, distance-bound, negative-NaN, nested-offset and zero-field-struct findings are repaired on this head; their focused test assertions were inspected.

Critical checkpoint conclusions:

Checkpoint Conclusion
Goal and proof The BE scan, schema-fetch, ordinary/vector/FTS and lazy row-fetch paths are connected. Focused BE tests cover their main contracts. The FE connector and regression suites are separate work, so full master FE-to-BE integration is not established here.
Scope and clarity The substantial port is organized into reader, schema/normalization, conversion, runtime-filter and session helpers. Shared Arrow and row-location changes were reviewed beyond Lance.
Concurrency and locks Session initialization uses call_once; metric hooks/deregistration share entity locking. Runtime-filter SQL snapshots are immutable and native callback counters are registered before use. No additional concrete lock inversion or race was identified by static inspection.
Lifetime and static initialization Scanner/stream callbacks retire before dataset/FTS context teardown; native and Arrow handles have cleanup paths. No new cross-TU initializer dependency was identified. Native cache lifetime/accounting remains the known P2.
Configuration Six session/cache settings are restart scoped; I/O buffer and two readahead settings are mutable, positive-validated and sampled when a new scanner opens.
ABI and rolling upgrades Version-zero row locations retain their 24-byte layout and uint32 domain. Version-one IDs retain uint64 through materialization/RPC/take_rows; legacy readers retain their adapters. Enabling Lance lazy materialization with old BEs still requires the separately scoped FE rollout guard.
Thrift/protobuf IDs Touched definitions were compared with current branch-3.1/4.0/4.1/4.2 refs. Search IDs, optional fields and enum values match the releases that define them; uint32-to-uint64 row_id retains varint encoding and existing values. No new collision or required field was found.
Parallel paths Ordinary/scalar-index/uncovered-fragment, flat/indexed vector, multi-vector, FTS MATCH/PHRASE, pre/postfilter, schema discovery and phase-two paths were checked, including legacy Parquet/ORC/internal row IDs.
Special conditions Concrete cases support the unsupported-type, NaN, slice, precision, vector-shape, version and count guards. Signed-zero pushdown is corrected by the pinned Lance optimizer. No additional verified predicate-equivalence defect was found.
Test coverage Inspected negative/boundary, null/nested, split/batch, EOF, search-oracle, type/carrier, invalid-request and uint64/order/duplicate tests. NULL fixed-size-list DATE32 payloads lack a regression test for the new finding. Direct Lance count/cancellation integration and local signed-zero/subnormal differential cases also remain coverage gaps.
Expected results and fixtures Assertions and fixture references/provenance were inspected; no .out result changes. Binary fixture decoding and the author's reported passes were not independently executed.
Observability Reader/search/native counters, pushed/skipped RF IDs, cache metrics and phase-two timings are present. Native execution summaries are EOF-based; live per-batch native metrics were not established.
Transactions and persistence No Doris transaction, EditLog, MoW or persistence-write changes. Dataset snapshot identity is preserved in the examined fixed-version read/fetch paths.
Data writes and failure atomicity Production changes are read paths; native handle cleanup and RPC error propagation were inspected. No new Doris write/commit atomicity path applies.
FE-to-BE values Search kind, vector byte layout/options, timing flag, distance bounds, split segments, snapshot and row-location version were traced. Full FE rollout behavior remains outside this BE-only port.
Performance and build Schema binding, SQL caching and ordinary-array zero-copy paths avoid repeated setup. Cache resource risks are already reported. The IOKit addition and documented reach-budget rebaseline were inspected. No benchmark or completed ELF GLIBC_2.17 audit was performed.
Other issues and user focus No additional user focus was supplied. All assigned candidates and risk questions have explicit ledger dispositions; the NULL fixed-size-list DATE32 error is the only accepted new finding.

Validation was static only: no builds, tests, benchmarks, source edits or submodule initialization. Runtime races, remote-I/O cancellation latency, complete binary-fixture decoding and final Linux artifact ABI are not validated by this review. Review convergence concerns coverage and candidate disposition; the existing P1 still requires changes.

Review converged after 3 round(s).

Existing P0/P1 findings confirmed for this head: #69581 (comment)

const int64_t daynr = static_cast<int64_t>(date_value) + date_threshold;
DateV2Value<DateV2ValueType> v;
v.get_date_from_daynr(date_value + date_threshold);
if (daynr <= 0 || daynr > DATE_MAX_DAYNR ||

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] Ignore DATE32 payloads hidden by null fixed-size lists

A valid Lance v2.1 FIXED_SIZE_LIST<DATE32,1> using lance-encoding:structural-encoding=miniblock can have parent validity [true,false,true] and non-null child days [0,2932897,1]. Its visible result is [1970-01-01], NULL, [1970-01-02]. The pinned writer retains the child data, and the decoder restores only outer validity. This compact array passes normalization unchanged; NullableSerDe records the parent NULL, but ArraySerDe still decodes all three children. The middle child itself is non-null, so this new check rejects its hidden 10000-01-01 payload and fails the scan. Propagate parent nulls into child conversion or skip hidden spans, while retaining rejection for visible invalid dates; add a native mixed-null fixed-list regression. This is distinct from resolved comment 4369909226, which concerned visible out-of-range dates.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed, and fixed in a5e1ead1de1. Arrow leaves the child values under a null list, fixed-size-list, map or struct row unspecified, and the array SerDe passed the whole child range to the element SerDe, which only skipped the child's own nulls. The DATE32 range check that this PR adds therefore rejected a hidden day such as 2932897 and failed a batch whose visible values are all valid. The array, map and struct SerDes now hand their children a validity bitmap that also clears the slots hidden by a null parent (mask_arrow_children_hidden_by_null_parents()), so the element SerDe neither validates nor decodes them; the parent's null map hides them as before. This covers DATE32, DATE64 and any other element check; only the validity buffer is replaced, and nothing is allocated when no parent row in the range is null. LanceTableReaderTypeTest.IgnoresDate32PayloadHiddenByNullParents reads your FIXED_SIZE_LIST<DATE32, 1> case, a LIST with a non-empty null segment, a STRUCT, a MAP and a LIST, and fails without the change; ArrowParentNullsTest covers the helper. branch-4.1 and branch-4.2 have the same check and SerDes.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	16601	5123	5036	5036
q2	2101	318	262	262
q3	9884	1367	818	818
q4	4609	465	336	336
q5	7420	798	524	524
q6	176	183	151	151
q7	760	828	605	605
q8	9368	1338	1081	1081
q9	5579	4187	4160	4160
q10	6839	1291	1017	1017
q11	441	243	235	235
q12	641	398	273	273
q13	18095	3528	3064	3064
q14	271	273	245	245
q15	q16	735	702	654	654
q17	1194	744	561	561
q18	7030	6182	6118	6118
q19	1124	967	618	618
q20	399	340	227	227
q21	5015	2132	2274	2132
q22	352	297	247	247
Total cold run time: 98634 ms
Total hot run time: 28364 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5679	5582	5615	5582
q2	670	589	526	526
q3	5196	5426	4903	4903
q4	1988	2134	1539	1539
q5	5370	5159	5399	5159
q6	260	210	160	160
q7	2409	2106	1896	1896
q8	3073	2707	2887	2707
q9	8164	8096	8025	8025
q10	3749	3657	3224	3224
q11	599	436	419	419
q12	709	677	514	514
q13	3556	4016	3221	3221
q14	304	319	302	302
q15	q16	685	725	649	649
q17	8341	7535	6962	6962
q18	13030	12208	12873	12208
q19	904	769	768	768
q20	2130	2169	1916	1916
q21	5561	4567	4690	4567
q22	489	462	401	401
Total cold run time: 72866 ms
Total hot run time: 65648 ms

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.95% (2100/2729)
Line Coverage 66.09% (38703/58557)
Region Coverage 53.79% (36364/67603)
Branch Coverage 57.33% (11830/20634)

@hello-stephen

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

query5	4318	574	436	436
query6	425	210	202	202
query7	4819	434	237	237
query8	315	162	142	142
query9	8796	3186	3212	3186
query10	476	308	251	251
query11	5942	3835	3594	3594
query12	169	93	86	86
query13	1246	462	316	316
query14	6090	3962	3610	3610
query14_1	3467	3444	3410	3410
query15	220	193	182	182
query16	944	425	427	425
query17	821	655	512	512
query18	2401	412	304	304
query19	198	152	131	131
query20	87	83	82	82
query21	209	125	106	106
query22	13033	13060	12844	12844
query23	13478	12840	12413	12413
query23_1	12510	12449	12549	12449
query24	7162	837	488	488
query24_1	481	493	504	493
query25	539	427	373	373
query26	1267	256	143	143
query27	2788	434	271	271
query28	4497	2024	2021	2021
query29	1524	613	467	467
query30	304	220	191	191
query31	1015	930	817	817
query32	142	98	99	98
query33	535	306	254	254
query34	893	862	500	500
query35	730	760	652	652
query36	815	805	732	732
query37	122	98	82	82
query38	1765	1774	1666	1666
query39	799	795	746	746
query39_1	738	735	741	735
query40	220	116	97	97
query41	71	67	67	67
query42	92	93	80	80
query43	348	369	326	326
query44	1322	718	706	706
query45	181	183	165	165
query46	832	971	575	575
query47	2952	3016	2635	2635
query48	317	323	226	226
query49	580	405	303	303
query50	677	285	214	214
query51	10265	10383	10075	10075
query52	80	82	73	73
query53	194	211	153	153
query54	245	215	205	205
query55	73	68	69	68
query56	282	239	243	239
query57	1639	1617	1544	1544
query58	292	272	264	264
query59	2176	2193	2034	2034
query60	303	253	241	241
query61	171	169	171	169
query62	400	335	287	287
query63	190	155	162	155
query64	2749	960	751	751
query65	3315	3254	3253	3253
query66	1742	416	309	309
query67	20367	20113	19959	19959
query68	3315	1021	626	626
query69	405	313	244	244
query70	857	802	776	776
query71	295	224	213	213
query72	2836	2627	2271	2271
query73	525	535	303	303
query74	4653	4489	4378	4378
query75	2369	2305	1929	1929
query76	2232	1020	585	585
query77	345	381	273	273
query78	9790	9857	9240	9240
query79	988	847	522	522
query80	1411	420	346	346
query81	593	330	272	272
query82	558	135	105	105
query83	273	200	190	190
query84	313	127	99	99
query85	1116	436	362	362
query86	403	244	238	238
query87	2012	1924	1792	1792
query88	3788	2776	2719	2719
query89	352	282	234	234
query90	1903	175	172	172
query91	160	188	128	128
query92	113	95	89	89
query93	1001	986	575	575
query94	636	315	267	267
query95	574	406	311	311
query96	675	492	237	237
query97	2467	2450	2339	2339
query98	162	152	153	152
query99	778	770	647	647
Total cold run time: 235912 ms
Total hot run time: 150842 ms

@hello-stephen

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

query1	0.01	0.00	0.01
query2	0.09	0.05	0.05
query3	0.26	0.09	0.08
query4	1.60	0.10	0.10
query5	0.24	0.25	0.25
query6	1.17	0.69	0.67
query7	0.04	0.01	0.01
query8	0.05	0.04	0.04
query9	0.41	0.35	0.33
query10	0.58	0.58	0.57
query11	0.20	0.14	0.14
query12	0.18	0.16	0.15
query13	0.49	0.49	0.48
query14	1.02	1.03	1.00
query15	0.63	0.59	0.60
query16	0.36	0.35	0.36
query17	1.15	1.11	1.14
query18	0.20	0.20	0.20
query19	2.14	2.01	1.99
query20	0.02	0.01	0.01
query21	15.52	0.19	0.12
query22	4.80	0.06	0.05
query23	16.29	0.33	0.13
query24	3.06	0.52	0.36
query25	0.12	0.07	0.05
query26	0.88	0.22	0.16
query27	0.04	0.04	0.03
query28	2.42	0.79	0.36
query29	12.65	4.07	3.26
query30	0.27	0.13	0.10
query31	2.77	0.56	0.32
query32	3.22	0.60	0.49
query33	3.17	3.27	3.32
query34	15.33	3.98	3.37
query35	3.32	3.32	3.34
query36	0.57	0.44	0.40
query37	0.10	0.06	0.06
query38	0.05	0.04	0.03
query39	0.04	0.03	0.03
query40	0.18	0.14	0.14
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 95.81 s
Total hot run time: 24.26 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 31.25% (15/48) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 67.09% (32411/48313)
Line Coverage 51.94% (343943/662147)
Region Coverage 47.34% (279280/589890)
Branch Coverage 49.06% (132017/269120)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 29.79% (14/47) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.54% (36300/46814)
Line Coverage 62.82% (411512/655107)
Region Coverage 59.32% (348626/587749)
Branch Coverage 60.22% (161676/268463)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

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

… rows

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581

Problem Summary:

Arrow leaves the child values under a NULL list, fixed-size-list, map or
struct row unspecified. A FIXED_SIZE_LIST<DATE32, 1> with validity [1, 0, 1]
may hold the days [0, 2932897, 1]; its rows are [1970-01-01], NULL,
[1970-01-02]. Lance's v2.1 miniblock encoding keeps such child data, and
other Arrow producers (ADBC drivers, Arrow stream load, paimon-rust) may as
well.

Root cause: the array, map and struct SerDes pass the whole child range to
the element SerDe, which skips only the child's own NULLs. The DATE32 range
check added by this PR therefore rejected 2932897 (10000-01-01) and failed
the batch, although no visible value is out of range.

Fix: before reading a child, the nested SerDes hand it a validity bitmap
that also clears the slots hidden by a NULL parent row
(`mask_arrow_children_hidden_by_null_parents()`), so the element SerDe
neither validates nor decodes them; the parent's null map hides them as
before. Only the validity buffer is replaced, and nothing is allocated when
no parent row in the range is NULL. This covers DATE32, DATE64 and any other
check an element SerDe makes.

Before: such a batch failed with "Arrow Date32 value is outside the Doris
DATE range". After: it reads as [1970-01-01], NULL, [1970-01-02].

branch-4.1 and branch-4.2 have the same DATE32 check and nested SerDes.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New: `ArrowParentNullsTest` (without a NULL parent the child itself is
      returned; only the slots of NULL parents are cleared and the child
      keeps its own NULLs; the field of a sliced struct is addressed with its
      offset) and `LanceTableReaderTypeTest.IgnoresDate32PayloadHiddenByNullParents`
      (FIXED_SIZE_LIST, LIST with a non-empty NULL segment, STRUCT, MAP and
      LIST<STRUCT> over a hidden 10000-01-01).
    - `ArrowParentNullsTest.*`, `DataTypeSerDeArrowTest.*`,
      `DataTypeSerDeArrowValidationTest.*`, `ArrowColumnToDorisColumnTest.*`,
      the Lance and ADBC suites and `IcebergV2ReaderTest.*` on macOS arm64
      (ASAN): 297 pass, 7 ADBC SQLite cases skip (no SQLite driver here).
      With the three SerDe changes reverted, the Lance test fails with
      "Arrow Date32 value is outside the Doris DATE range: row=1,
      days=2932897".
- Behavior changed: No (visible values are unchanged; the check that hidden
  values tripped is added by this PR)
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@morningman

Copy link
Copy Markdown
Contributor Author

Addressed the fifth review round in a5e1ead1de1. Per finding:

  • 4390935904 (P2, DATE32 payloads hidden by null parents): fixed. The array, map and struct SerDes no longer decode child values hidden by a null parent row, so element checks such as the DATE32 range only see visible values. New tests: ArrowParentNullsTest and LanceTableReaderTypeTest.IgnoresDate32PayloadHiddenByNullParents.
  • 4373822200 (P1, endpoint identity in the data cache key): unchanged, as answered in the thread.

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

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. Two new P2 findings concern NULL-parent Arrow conversion and query memory accounting. Existing P1 4373822200 still applies: the default shared data-cache key omits physical endpoint identity, so independent origins with different bytes at the same bucket/object path can share data. It is retained without a duplicate inline comment.

Applicable critical checkpoints:

  • Goal and correctness: the BE port connects FORMAT_LANCE to FileScannerV2, schema discovery, normal/vector/FTS scans and phase-two row fetch. Hidden Decimal256 children can still fail before the new NULL-parent mask; the shared validity-copy allocation also bypasses query accounting.
  • Scope and clarity: the changes form a coherent BE reader/protocol port plus shared Arrow fixes. FE integration is explicitly deferred.
  • Concurrency and locking: reviewed parallel readers, shared session initialization, metrics hooks, native statistics callbacks and fetch-task completion. Hook deregistration shares the metrics mutex; scanner counters are registered before callback installation. No additional locking defect substantiated.
  • Lifecycle and static initialization: readers close scanner/FTS context/dataset in order; shared initialization is lazy and call_once retains initialization failures. Persistent native index/metadata caches remain outside Doris reclamation, as already reported in P2 4370665039.
  • Configuration: six session/data-cache settings apply at restart; the three mutable I/O/readahead settings are read when new scanners are configured. Their distinction is documented in code.
  • ABI and rolling upgrades: version-zero row locations retain their 24-byte layout, version/size dispatch rejects unknown encodings, and uint32 file-local IDs remain representable in the widened varint field. The FE rolling-upgrade guard is outside this BE-only PR; complete rolling-upgrade behavior was not executed.
  • Thrift/protobuf compatibility: compared touched definitions against branch-3.1, 4.0, 4.1 and 4.2. New search fields are optional; IDs, enum meanings and types agree where present. No new collision found.
  • Parallel paths: traced shared ARRAY/MAP/STRUCT/DATE/nullable SerDes and their other Arrow consumers, normal versus search scans, vector versus FTS, and existing internal/Parquet/ORC row locations. Both new findings have effects beyond their local helper.
  • Conditions and error handling: malformed requests and unsupported types fail explicitly. The hidden-child Decimal256 check runs before ancestor visibility is propagated; it needs the same visibility discipline as downstream SerDes.
  • Functional and negative tests: inspected broad unit coverage for types, slices, NULLs, unsupported fields, malformed requests, native search options and row IDs. The two new cases need focused regressions. Full FE integration/regression execution is deferred by the PR and was not validated here.
  • Expected test results: inspected fixture documentation, JSON version hints, assertions and expected conversions/search outcomes; binary fixtures were not decoded or executed in this static review.
  • Observability: scan/search/cache counters and process session metrics are present; phase-two counters preserve units. Metrics do not provide native-cache reclamation or account for the new Arrow mask allocation.
  • Transactions and persistence: no Doris transaction/EditLog/write protocol changes; snapshot selection and fixed-version row fetch were traced. Persistent cache identity remains the existing blocker.
  • Writes, crashes and cleanup: reviewed query cleanup, cancellation, errors and fetch-task completion; no new database-write atomicity change. Native cache files are process resources with the known cache limits above.
  • FE/BE propagation: checked release FE producers for search fields, fixed snapshots, offset ownership and lazy-fetch eligibility. Current master FE Lance support is not part of this PR.
  • Performance and memory: ordinary compact Arrow arrays retain their no-copy path, and per-scanner I/O/readahead is bounded by configuration. Large child-validity copies must use tracked allocation; native persistent-cache accounting remains an existing issue. No benchmarks were run.
  • Other concerns and user focus: no additional user focus was supplied. Signed-zero filtering and native JSON representation suspicions were dismissed through pinned native planner/conversion guarantees. Distributed provider postfiltering matches the published per-split execution contract and is not reported.

Static-only review: no builds, tests, benchmarks, source modifications or submodule initialization. Final Linux ELF/glibc compatibility, binary fixture execution and the deferred FE integration were not validated. Existing Decimal256 carrier, prefilter flag, distance-bound, negative-NaN, DATE32, descendant-offset and empty-struct fixes were rechecked and are not reposted.

Review converged after 2 round(s).

Existing P0/P1 findings confirmed for this head: #69581 (comment)

auto child_array = arrow::MakeArray(child_data[child_idx]);
std::shared_ptr<arrow::Array> normalized_child;
RETURN_IF_ERROR(
child_normalizer.normalize_for_doris(child_array, memory_pool, &normalized_child));

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] Mask NULL ancestors before narrowing Decimal256 children

A Lance v2.1 miniblock FIXED_SIZE_LIST<decimal256(18,2),1> can retain non-null child payloads [1, 10^18, 2] under parent validity [true,false,true]; its visible rows are [0.01], NULL, [0.02]. The pinned native encoder preserves those child bytes and restores only outer validity. This recursion passes the raw Decimal256 child to the safe Decimal128 cast before ArraySerDe applies the new parent mask, so the hidden middle payload fails with "Decimal value does not fit in precision 18" and aborts a scan whose visible values are valid. Propagate ancestor visibility before validating special-child conversions, and add a native mixed-null miniblock regression. This is distinct from the repaired DATE32 thread: the failure occurs in the Lance normalizer before that SerDe fix runs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed, and fixed in 1c74dbab92c. The Lance normalizer narrows a nested Decimal256 leaf by casting the whole raw child array before any SerDe runs, so the safe cast checked the values under a NULL list, fixed-size-list, map or struct row and, for a sliced parent, the values of rows outside the slice; the SerDe-level masking from the previous round only applies after normalization. Before narrowing, the normalizer now marks NULL every child slot that no non-NULL row of the parent references, level by level down to the leaf, so only values a reader can see are checked, and a visible value outside the declared precision is still rejected with the field name. A map is masked through its values: Arrow allows no NULL in a map's entries struct or keys (MapArray::SetData() aborts on one), so a Decimal256 key under a NULL map row is still checked. BFloat16 widening and extension unwrapping never inspect a value, so only the subtrees that narrow a decimal are masked. Covered by LanceTableReaderTypeTest.NarrowsDecimal256PayloadsHiddenByNullParents (NULL fixed-size-list, list, struct, map and list-of-struct rows, and a sliced list) and by a nested visible value added to LanceTableReaderTypeTest.RejectsDecimal256ValuesOutsideDeclaredPrecision; the masking helper has its own cases in ArrowParentNullsTest (MasksTheSlotsOfNullListRows, MasksTheSlotsOfNullFixedSizeListAndStructRows, MasksTheItemsOfNullMapRows, MasksTheChildSlotsOutsideASlicedParent, ReturnsTheChildUnchangedWhenEverySlotIsVisible, RejectsSlotsOutsideTheChildAndUnknownParents). Building such a payload needs hand-made buffers (pyarrow's converters reject a value outside the declared precision), so the test builds the batch shape directly through the converter, like the DATE32 test of the previous round; the Lance writer's pass-through of fixed-size-list child data was checked for that thread.

continue;
}
if (validity == nullptr) {
auto allocated = arrow::AllocateBitmap(bit_length);

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] Charge copied validity bitmaps to the query memory tracker

arrow::AllocateBitmap(bit_length) uses Arrow's default pool, whereas Doris's tracked ArrowMemoryPool is a separate pool selected explicitly by the Lance converter. This helper therefore allocates its owned validity copy outside query/workload memory accounting and allocation-limit checks. Child cardinality is independent of the parent batch size: a NULL list retaining 100 million byte children needs a 12.5 MB bitmap here, multiplied by nested columns and parallel readers. Route these copies through a Doris-tracked pool or buffer and cover accounting/allocation failure for this SerDe stage; the normalizer's memory-pool test does not reach it. This affects the shared ARRAY/MAP/STRUCT Arrow consumers as well as Lance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 1c74dbab92c: the masked validity bitmap is now allocated from ExecEnv::arrow_memory_pool(), the tracked doris::ArrowMemoryPool that the Lance converter and the ADBC reader already use, so it is charged to the query and passes its limit check; unit tests that never initialize ExecEnv fall back to Arrow's default pool, as LanceRecordBatchConverter does. ArrowParentNullsTest.AllocatesTheMaskFromTheDorisArrowPool puts a proxy pool into ExecEnv and checks that the bitmap is charged to it and released with the masked array, and ArrowParentNullsTest.ReportsMaskAllocationFailure checks that a pool refusing the bitmap fails the read with a Status. The scale is smaller than described, though. The bitmap is one bit per child slot and lives only until the child SerDe returns, while the same call appends at least two tracked bytes per child slot to the Doris column (value plus null map; the segment of a NULL list row is read as NULL elements), so the 100-million-child example already charges about 200 MB to the query in that call, against 12.5 MB here. Columns are converted one after another and each mask is released once its child is read, so at most one bitmap per nesting level (two for a map) is alive at a time, not one per nested column.

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.95% (2100/2729)
Line Coverage 66.08% (38696/58557)
Region Coverage 53.83% (36389/67603)
Branch Coverage 57.31% (11825/20634)

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17598	5128	5016	5016
q2	2139	303	252	252
q3	10253	1392	817	817
q4	4687	466	336	336
q5	7438	800	526	526
q6	182	189	151	151
q7	769	802	601	601
q8	9651	1368	1043	1043
q9	5867	4231	4163	4163
q10	6890	1302	1033	1033
q11	436	252	236	236
q12	637	405	276	276
q13	18102	3561	3053	3053
q14	275	271	247	247
q15	q16	739	720	656	656
q17	1201	733	567	567
q18	7039	6177	6108	6108
q19	1106	962	597	597
q20	389	338	216	216
q21	5462	2160	2326	2160
q22	347	290	263	263
Total cold run time: 101207 ms
Total hot run time: 28317 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5623	5595	5606	5595
q2	686	560	556	556
q3	5240	5391	4972	4972
q4	1996	2148	1565	1565
q5	5282	5159	5682	5159
q6	271	209	157	157
q7	2315	2122	1856	1856
q8	3063	2709	2640	2640
q9	8091	7982	8113	7982
q10	3781	3661	3317	3317
q11	639	451	425	425
q12	743	695	517	517
q13	3583	3893	3276	3276
q14	323	312	288	288
q15	q16	711	707	658	658
q17	8114	7086	6837	6837
q18	13029	12191	12814	12191
q19	949	788	801	788
q20	2146	2147	1915	1915
q21	5535	4601	4690	4601
q22	508	447	417	417
Total cold run time: 72628 ms
Total hot run time: 65712 ms

@hello-stephen

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

query5	4321	598	450	450
query6	427	219	194	194
query7	4806	439	244	244
query8	316	168	149	149
query9	8784	3215	3236	3215
query10	458	315	258	258
query11	5994	3841	3583	3583
query12	151	92	86	86
query13	1276	462	330	330
query14	6146	3949	3670	3670
query14_1	3478	3429	3426	3426
query15	206	196	183	183
query16	955	457	450	450
query17	873	673	550	550
query18	2419	424	312	312
query19	203	168	139	139
query20	93	87	86	86
query21	215	130	110	110
query22	12896	12985	12766	12766
query23	13490	12885	12358	12358
query23_1	12474	12550	12407	12407
query24	7351	844	460	460
query24_1	483	471	482	471
query25	510	399	335	335
query26	1264	252	149	149
query27	2765	435	273	273
query28	4631	2016	2023	2016
query29	1506	580	439	439
query30	295	212	186	186
query31	1005	923	816	816
query32	132	87	93	87
query33	524	301	240	240
query34	883	850	494	494
query35	756	758	672	672
query36	798	813	745	745
query37	128	103	94	94
query38	1755	1759	1652	1652
query39	795	799	726	726
query39_1	742	729	727	727
query40	221	119	94	94
query41	67	64	63	63
query42	88	88	82	82
query43	344	363	310	310
query44	1330	708	721	708
query45	176	183	170	170
query46	820	964	559	559
query47	2853	2921	2725	2725
query48	302	320	218	218
query49	571	401	301	301
query50	697	286	213	213
query51	10416	10297	11283	10297
query52	81	82	68	68
query53	190	212	157	157
query54	274	190	180	180
query55	74	71	70	70
query56	246	225	215	215
query57	1658	1619	1565	1565
query58	283	267	261	261
query59	2151	2176	1964	1964
query60	286	247	227	227
query61	146	150	141	141
query62	378	324	282	282
query63	189	161	156	156
query64	2728	958	755	755
query65	3333	3262	3245	3245
query66	1771	422	304	304
query67	20454	20539	20293	20293
query68	2983	1019	628	628
query69	388	295	256	256
query70	832	816	787	787
query71	310	243	217	217
query72	2858	2640	2331	2331
query73	512	531	299	299
query74	4631	4512	4342	4342
query75	2330	2290	1934	1934
query76	2322	1017	611	611
query77	363	381	276	276
query78	9737	9658	9122	9122
query79	913	853	532	532
query80	511	419	362	362
query81	517	313	265	265
query82	580	136	114	114
query83	327	195	190	190
query84	312	122	104	104
query85	866	411	385	385
query86	329	237	233	233
query87	1988	1932	1786	1786
query88	3738	2785	2761	2761
query89	320	279	246	246
query90	1876	177	171	171
query91	169	156	124	124
query92	103	90	92	90
query93	990	982	583	583
query94	457	309	284	284
query95	593	341	312	312
query96	686	483	243	243
query97	2461	2416	2332	2332
query98	159	154	149	149
query99	765	763	658	658
Total cold run time: 234084 ms
Total hot run time: 151298 ms

@hello-stephen

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

query1	0.01	0.01	0.01
query2	0.13	0.08	0.08
query3	0.33	0.13	0.13
query4	1.62	0.14	0.14
query5	0.28	0.24	0.23
query6	1.15	0.66	0.64
query7	0.05	0.01	0.01
query8	0.09	0.07	0.07
query9	0.43	0.36	0.37
query10	0.57	0.57	0.60
query11	0.32	0.18	0.17
query12	0.31	0.18	0.18
query13	0.51	0.54	0.52
query14	0.95	0.94	0.93
query15	0.66	0.58	0.58
query16	0.34	0.36	0.34
query17	1.01	1.01	1.05
query18	0.30	0.27	0.27
query19	1.89	1.89	1.83
query20	0.02	0.02	0.01
query21	15.39	0.34	0.31
query22	4.92	0.12	0.14
query23	15.88	0.49	0.28
query24	2.27	0.50	0.37
query25	0.15	0.11	0.10
query26	0.76	0.27	0.21
query27	0.10	0.11	0.10
query28	3.34	0.84	0.40
query29	12.58	4.43	3.40
query30	0.34	0.16	0.14
query31	2.75	0.60	0.33
query32	3.22	0.60	0.47
query33	3.01	3.01	2.95
query34	15.91	4.07	3.44
query35	3.37	3.39	3.38
query36	0.61	0.51	0.50
query37	0.11	0.09	0.09
query38	0.08	0.06	0.07
query39	0.06	0.05	0.06
query40	0.18	0.17	0.16
query41	0.11	0.05	0.06
query42	0.06	0.06	0.06
query43	0.07	0.06	0.06
Total cold run time: 96.24 s
Total hot run time: 25.11 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 31.25% (15/48) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 67.08% (32412/48315)
Line Coverage 51.95% (344030/662233)
Region Coverage 47.39% (279595/589975)
Branch Coverage 49.07% (132090/269166)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 29.79% (14/47) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.61% (36336/46816)
Line Coverage 62.86% (411826/655193)
Region Coverage 59.32% (348676/587834)
Branch Coverage 60.23% (161730/268509)

morningman and others added 2 commits October 28, 2026 12:49
…sks to the query

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581

Problem Summary:

1. The Lance normalizer narrows a Decimal256 leaf of precision <= 38 to
   decimal128 with Arrow's safe cast, and for a nested leaf it cast the whole
   raw child array. The values under a NULL list, fixed-size-list, map or
   struct row, which Arrow leaves unspecified, and for a sliced parent the
   values of the rows outside the slice were precision-checked too. A
   FIXED_SIZE_LIST<decimal256(18,2),1> with validity [1, 0, 1] may hold the
   unscaled children [1, 10^18, 2]; its rows are [0.01], NULL, [0.02], yet
   the hidden 10^18 failed the batch with "Decimal value does not fit in
   precision 18". The SerDe-level masking added for the hidden DATE32 values
   runs only after normalization.

   Fix: before a child goes to a child normalizer that narrows a decimal,
   every child slot that no visible non-NULL parent row references is marked
   NULL (`mask_arrow_child_slots_not_visible()`). Each level masks its own
   children, so the descendants of a hidden slot are hidden too. A visible
   value outside the declared precision is still rejected. A map key cannot
   be NULL, so a Decimal256 map key under a NULL map row is still checked.
   Children that narrow nothing skip the mask.

2. The validity bitmaps of these masks came from Arrow's default pool, which
   the query's memory tracker does not see. They now come from ExecEnv's
   `doris::ArrowMemoryPool`, the pool the Lance converter already uses; unit
   tests that never initialize ExecEnv fall back to the default pool, as the
   converter does.

Before: the batch above failed. After: it reads as [0.01], NULL, [0.02].

branch-4.1 and branch-4.2 do not narrow Lance Decimal256 values and have no
such masks.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New: `LanceTableReaderTypeTest.NarrowsDecimal256PayloadsHiddenByNullParents`
      (FIXED_SIZE_LIST, LIST, STRUCT, MAP and LIST<STRUCT> over a hidden
      10^18, and a sliced LIST); `RejectsDecimal256ValuesOutsideDeclaredPrecision`
      gains a nested visible out-of-range value; `ArrowParentNullsTest`
      gains 8 cases (each parent type, a sliced parent, no allocation when
      every slot is visible, offsets outside the child, the mask allocated
      from the Doris Arrow pool, allocation failure).
    - `ArrowParentNullsTest.*`, `DataTypeSerDeArrowTest.*`,
      `DataTypeSerDeArrowValidationTest.*`, `ArrowColumnToDorisColumnTest.*`,
      the Lance and ADBC suites and `IcebergV2ReaderTest.*` on macOS arm64
      (ASAN): 306 pass, 7 ADBC SQLite cases skip (no SQLite driver here).
      Mutations: without the normalizer mask the Lance test fails; with the
      mask allocated from the default pool the two pool tests fail; without
      masking the slots outside a sliced parent the slice test and the Lance
      test fail.
    - clang-tidy (llvm 22) reports only `readability-inconsistent-ifelse-braces`
      on the three `DORIS_CHECK` lines of the new function, the macro's
      expansion that existing `DORIS_CHECK`s report too.
- Behavior changed: No (visible values are unchanged; the narrowing that
  hidden values tripped is added by this PR)
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
### What problem does this PR solve?

Issue Number: None

Related PR: apache#69581

Problem Summary:

A Lance nested array whose descendants start at an offset is copied into an
offset-zero array before decoding, and the copy runs `ValidateFull()` first,
for the offset and length checks the builder needs. `ValidateFull()` also
checks every non-NULL value of every descendant over its whole length:
UTF-8 for strings, the precision of decimals, whole days for Date64 and the
day range for Time32 / Time64, also in slots that no visible row reaches.
The slice [1, 4) of a FIXED_SIZE_LIST<STRING, 1> with rows ["z"], ["a"],
NULL, ["c"] may hold the child strings ["z", "a", "\xff", "c"]; its rows are
["a"], NULL, ["c"], yet the hidden "\xff" failed the batch with "Invalid
UTF8 sequence". The previous fixes mask such slots only for the element
SerDes and for the Decimal256 leaves the normalizer narrows, so a hidden
value in any other subtree of a compacted array still failed: a string, a
decimal256 of precision above 38, a Date64 or a time.

Fix: `compact_lance_array()` masks the array before validating it:
`mask_arrow_descendants_not_visible()` marks NULL, at every nesting level,
every slot that no visible row reaches, through the per-level
`mask_arrow_child_slots_not_visible()`. `ValidateFull()` and the copy then
run on the masked array, so the offset and length checks stay and an
invalid visible value still fails. The builders copy no child slot of a NULL
list, map or fixed-size-list row, and the masked fields of a NULL struct
row are read as NULL anyway, so every visible value is unchanged. Map keys
cannot be NULL and stay unmasked. A slot that is NULL already needs no
hiding, so the subtrees the normalizer masked before narrowing get no
second bitmap, and an array with nothing hidden allocates nothing.

Before: the slice above failed to read. After: it reads as ["a"], NULL,
["c"].

branch-4.1 and branch-4.2 do not compact Lance arrays this way.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New: `LanceTableReaderTypeTest.CompactsArraysWithInvalidPayloadsHiddenByNullParents`
      (FIXED_SIZE_LIST<STRING> with invalid UTF-8, LIST<DECIMAL256(40,2)>
      out of precision and LIST<STRUCT<s, d>> under NULL rows and outside
      the slice), `RejectsVisibleInvalidPayloadsInCompactedArrays` (the same
      values visible), `ArrowParentNullsTest.MasksHiddenSlotsAtEveryNestingLevel`
      and `LeavesVisibleAndAlreadyMaskedArraysUnchanged`.
    - `ArrowParentNullsTest.*`, `DataTypeSerDeArrowTest.*`,
      `DataTypeSerDeArrowValidationTest.*`, `ArrowColumnToDorisColumnTest.*`,
      the Lance and ADBC suites and `IcebergV2ReaderTest.*` on macOS arm64
      (ASAN): 310 pass, 7 ADBC SQLite cases skip. Mutations: without the
      mask before validation the compaction test fails; without recursion
      the nesting test and the compaction test fail; masking already-NULL
      slots again fails the no-second-bitmap test.
    - clang-tidy (llvm 22) reports only `readability-inconsistent-ifelse-braces`
      on the three `DORIS_CHECK` lines of the new function.
- Behavior changed: No (visible values are unchanged)
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@morningman

Copy link
Copy Markdown
Contributor Author

Addressed the sixth review round in 1c74dbab92c, and in d320776955b the same problem in the compaction path, which the review did not raise. Per finding:

  • 4395648413 (P2, Decimal256 payloads hidden by NULL parents): fixed in 1c74dbab92c. Before a nested Decimal256 leaf is narrowed, every child slot that no visible row references is marked NULL, level by level, so only values a reader can see are cast; a visible value outside the declared precision is still rejected. Map keys cannot be NULL and stay checked.
  • 4395648419 (P2, mask bitmaps outside the query memory tracker): fixed in 1c74dbab92c; the bitmaps now come from ExecEnv's tracked Arrow pool. The scale is smaller than described, see the thread.
  • Compaction (not raised by the review): While fixing the hidden Decimal256 values, we found the same problem one step later, in compaction. A nested Lance array that needs a compacted copy (a sliced batch, or a level whose offsets or lengths do not line up with its child) goes through Arrow's ValidateFull() before it is copied, and full validation checks every non-NULL value of each descendant over the descendant's whole length: UTF-8 for strings, the precision of decimals, whole days for Date64 and the day range for Time32/Time64. That includes the slots under a NULL list, fixed-size-list, map or struct row and those of rows outside the slice, so a batch failed with validate sliced Lance array failed on a value that no row shows, for example invalid UTF-8 under a NULL fixed-size-list row or an out-of-precision decimal256(40, 2) value under a NULL list row. In d320776955b, compact_lance_array() first marks NULL every slot that no visible row reaches, at every nesting level, with the same mask the normalizer applies before narrowing; map keys stay unmasked, because Arrow does not allow a NULL key. ValidateFull() still runs on the masked array, so a visible invalid value is still rejected and its offset and length checks still guard the copy. The copied rows are unchanged, slots that are NULL already, such as those the narrowing masked, get no second bitmap, nothing is allocated when no slot is hidden, and the bitmaps come from the converter's tracked Arrow pool. Covered by LanceTableReaderTypeTest.CompactsArraysWithInvalidPayloadsHiddenByNullParents, LanceTableReaderTypeTest.RejectsVisibleInvalidPayloadsInCompactedArrays, ArrowParentNullsTest.MasksHiddenSlotsAtEveryNestingLevel and ArrowParentNullsTest.LeavesVisibleAndAlreadyMaskedArraysUnchanged.

BE unit tests on macOS arm64 (ASAN): ArrowParentNullsTest, the Arrow SerDe suites, the Lance and ADBC suites and IcebergV2ReaderTest, 310 pass, 7 ADBC SQLite cases skip (no local SQLite driver). Each new test fails under a mutation of the code it guards.

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: workspace routing discovery unauthorized (401)
Workflow run: https://github.com/apache/doris/actions/runs/40641426210

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

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants