Skip to content

[opt](lance) add prefilter option for filter execution control - #68813

Merged
Gabriel39 merged 8 commits into
apache:branch-4.1from
zhangstar333:prefilter-boolean
Oct 10, 2026
Merged

Gabriel39 merged 8 commits into
apache:branch-4.1from
zhangstar333:prefilter-boolean

Conversation

@zhangstar333

@zhangstar333 zhangstar333 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Problem Summary:
doc: apache/doris-website#4203
add prefilter:bool to control filter exectue;
prefilter default is false, so will do ann search firstly, then execute filter.
prefilter turn true, will execute filter firstly, then do ANN search.

Release note

None

Check List (For Author)

  • Test

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

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

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

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

@zhangstar333
zhangstar333 requested a review from yiguolei as a code owner October 9, 2026 05:37
@hello-stephen

Copy link
Copy Markdown
Contributor

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

Please clearly describe your PR:

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

@zhangstar333

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review complete for PR #68813 at 471b949. I request changes for two P1 correctness/compatibility issues. The seven inline findings are two P1, four P2, and one P3. No existing inline thread or additional user focus was present. All conclusions are from static inspection; builds and tests were prohibited and were not run.

Critical checkpoints:

  • Goal and proof: The change exposes opt-in Lance prefilter behavior, revises search/profile metrics, and rebases the Foyer cache. The added regression outputs illustrate the new postfilter result, but they are inspected expectations rather than executed proof. The stale BE prefilter assertions are M2.
  • Scope: FE TVFs/planning, Thrift, BE scanners/profiles, tests/docs, and the pinned third-party patch were all reviewed. The broad dependency patch is tied to the cache and scanner update; no further distinct scope issue was substantiated.
  • Concurrency and locks: Multiple scanner tasks can report statistics; RuntimeProfile protects counter-map insertion with a mutex and uses atomic counter updates. I found no separate lock-order/deadlock bug or heavy critical section beyond counter registration; cache sharing creates the origin collision in M5.
  • Lifecycle and static initialization: Scanner statistics arrive at EOF/close, and Foyer entries survive BE restarts. Callback allocation can escape the required non-throwing C boundary (M4), and recovered cache entries retain the origin collision (M5). No cross-translation-unit static-initialization dependency was found in the changed paths.
  • Configuration: The new TVF prefilter property defaults false; the BE-wide Foyer cache is already default-enabled. No new dynamic configuration item needs propagation. The default interacts with mixed-version execution as M1 describes.
  • Compatibility: Thrift field 5 is optional but schema_version remains 1. Old BEs ignore false and prefilter while new BEs postfilter old requests, changing query rows during rolling upgrades (M1). The pinned lance-c scanner/patch interfaces were checked statically.
  • Parallel paths and conditions: Vector and full-text, indexed and flat splits, two-phase fetch, prepared requests, TopK/offset, and __isset.prefilter were traced. Delayed fragment scoping reaches planning before TopK; no separate fragment bug was substantiated. Optional counter trees differ between split types and FE aggregation drops later-only groups (M7).
  • Tests and results: Added regression suites and .out rows were checked against the changed semantics; old BE filtered cases still assert prefilter behavior without setting the flag (M2). Mixed-version, distinct-endpoint cache, warm-write, and mixed-split aggregate coverage remain absent. No test or build was executed, so runtime and CI outcomes are unverified.
  • Observability: New Lance groups expose scanner metrics, but M7 can hide them in the aggregate profile, and the prefilter guide misstates the new default (M3). Existing group labels still render with TUnit.NONE; no other logging/metric gap was confirmed.
  • Transactions, persistence, and writes: No Doris table-write, EditLog, or transaction path changed. Foyer persists cache data and metadata: origin identity is missing from recovered keys (M5), and each warm read enqueues a redundant size-key write (M6). Recovery validation rejects corrupt/truncated entries; the separate short-block suspicion remains covered by M5.
  • FE/BE variable passing: The search request copies and forwards the new optional flag on current paths, but lacks a rolling-upgrade capability gate (M1).
  • Performance and remaining issues: M6 is the confirmed write-amplification path. All other suspicious points were either resolved with code evidence or folded into the seven inline findings; no unresolved candidate remains.

Comment thread docs/lance-prefilter-profile.md Resolved
Comment thread be/src/format_v2/table/lance_reader.cpp Resolved
Comment thread thirdparty/patches/lance-c-foyer.patch Resolved
Comment thread thirdparty/patches/lance-c-foyer.patch Resolved
Comment thread be/src/format_v2/table/lance_reader.cpp Resolved
@zhangstar333

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Review recovery stopped: No final review submission was declared
Workflow run: https://github.com/apache/doris/actions/runs/37901705711

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

@zhangstar333

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Static review complete for head 65077a5afe817b0ccd4a2ad9b0045bc1ca9547a2 after two convergence rounds and a final sweep of all 23 changed paths. This was static inspection only; no build or test was run.

Findings and prior threads

  • New [P1] inline: prefilter=false (including omission) filters each search split before Doris merges candidates, so multi-split vector or FTS searches can return a row that snapshot-wide postfiltering would exclude.
  • Existing [P1] inline #4227447649 remains applicable: old and new FE/BE combinations interpret the omitted/new prefilter field differently. The author reply does not change the wire behavior. It is carried here without reposting.
  • Existing [P1] inline #4227447670 remains applicable: the BE-wide Foyer cache keys data and metadata by Lance's S3 bucket prefix and path, omitting per-catalog endpoint identity. It is carried here without reposting.
  • Existing [P2] inline #4227447675 remains applicable: warm cached_get calls still enqueue size-key disk writes. It is already reported. Earlier profile-tree, callback, documentation, and BE test concerns have been addressed at this head.

Critical checkpoints

  1. Goal and correctness: FE parses the boolean and sends Thrift to both vector and FTS BE paths; true and false reach Lance. The new default and explicit false fail snapshot-wide search semantics when multiple physical splits are merged. Fixed snapshot versions and fragment ownership were traced; no separate version or delete-visibility defect was substantiated.
  2. Scope and parallel paths: The search wiring is localized, while the PR also rebases the Foyer cache patch and reorganizes profile counters. Indexed and flat vector, FTS segment, offset, prepared query, and two-phase paths were checked. The split-ordering defect spans both search kinds; phase-two row-ID fetch cannot repair candidates filtered earlier.
  3. Concurrency, memory, and lifecycle: The BE-wide session/cache is shared across queries, dataset handles retain their cache state, per-dataset statistics use atomics, and the wrapper registry uses a short mutex section. Counters are registered before installing Lance's callback; its noexcept wrapper catches failures. No additional lock-order, static-initialization, ownership, or memory-accounting defect was substantiated. The existing origin-key P1 remains a shared-cache correctness failure, including disk recovery.
  4. Configuration and compatibility: No new Doris config is added; the existing data cache is enabled by default and initialized once per BE process. The optional Thrift field reaches both new BE search paths, but mixed-version FE/BE semantics remain incompatible as noted in existing P1 #4227447649. No storage-format or function-symbol change was identified beyond the reviewed lance-c API/dependency rebase.
  5. Conditions, errors, and writes: Cache eligibility and conditional/versioned-read bypasses, BE Status returns, and reader close paths were checked. This PR does not change Doris transactions, EditLog, or table write atomicity. Foyer's persistent disk writes make the already-reported origin collision durable; warm size-key writes remain a performance issue.
  6. Tests, observability, and performance: Changed BE tests, regression suites, and expected-output files cover true, false/default, and invalid boolean values in single-split cases; they do not cover a rejected global winner across two splits or distinct storage origins. The profile metric tree and callback fixes appear complete, and profile counter names were checked. No test result was independently executed. The remaining warm-read write amplification is already tracked by P2 #4227447675.

User focus: no additional focus was supplied. All new substantiated findings are included inline; already-reported issues were deduplicated. Review execution is complete with a blocking P1 verdict.

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

@Gabriel39

Copy link
Copy Markdown
Contributor

run buildall

@zhangstar333 zhangstar333 changed the title Prefilter boolean [opt](lance) add prefilter option for filter execution control Oct 9, 2026
@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 33.33% (3/9) 🎉
Increment coverage report
Complete coverage report

@zhangstar333

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 33.33% (3/9) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 78.52% (1963/2500)
Line Coverage 65.43% (35365/54048)
Region Coverage 53.07% (34022/64102)
Branch Coverage 55.82% (10476/18767)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 33.33% (3/9) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 22.22% (2/9) 🎉
Increment coverage report
Complete coverage report

Issue Number: N/A

Related PR: N/A

Problem Summary: Doris pinned lance-c at 98468344bc9d56aa7ed4a4192e844afa62b34ba1 and missed upstream apache#91 vector post-filtering and scoped FTS prefiltering. Pin the dependency to 8e9e09ecc795c91ce7f97513db94c2d91eec799d, update the archive checksum, and regenerate the local Foyer patch against the new base while keeping its implementation unchanged.

Include Lance-C vector post-filtering and scoped FTS prefiltering improvements.

- Test: Static dependency validation: shell syntax checks, archive checksum validation, strict patch application, isolated downloader idempotence and source-tree comparison. No Doris build or unit/regression tests run.
- Behavior changed: Yes; adopts upstream Lance-C apache#91 search-filter behavior.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: N/A

Related PR: lance-format/lance-c#73

Problem Summary: Doris carried an older local Foyer patch. Refresh it from the current PR head 8a08f171b66a3e01e7ba634bcfc6168a955c7289 and rebase it onto the pinned Lance-C commit 8e9e09ecc795c91ce7f97513db94c2d91eec799d, preserving the newer upstream search and prewarm changes. The PR head includes its revert of the interim cache-origin-isolation change.

### Release note

Update the bundled Lance-C Foyer cache integration to match upstream PR apache#73.

### Check List (For Author)

- Test: Static validation: strict patch application, downloader refresh, isolated repeated downloader run, source-tree comparison, shell syntax, and diff checks. No build or tests run.
- Behavior changed: Yes; the bundled Foyer implementation now matches the current PR head, including its cache-origin-isolation revert.
- Does this need documentation: No
@zhangstar333

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor

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

Static review complete for PR #68813 at d6dcb81. No new inline findings survived the two-round review and final changed-file sweep. Three existing P1 inline findings still apply and remain blockers: 4227447649 (mixed-version FE/BE prefilter default), 4227447670 (Foyer cache keys omit storage-origin identity), and 4228261574 (postfilter runs inside each split before the global TopN). Existing P2 4227447675 still covers repeated size-key disk writes on warm reads; I am not reposting it. No additional user review focus was provided.

Critical checkpoints:

  • Goal, scope, and validation: I traced prefilter parsing through Thrift, BE vector/FTS scanner configuration and global TopN, plus the Foyer rebase and profile regrouping. The changed tests and expected outputs cover true, false, omitted, and invalid prefilter values on their fixtures; they do not resolve the existing multi-split postfilter blocker. The broad Cargo.lock change is tied to the pinned lance-c/Foyer rebase. This was static inspection only: no builds or tests were run.
  • Concurrency and lifecycle: BE scanner tasks share a lazily initialized process-wide Lance session and cache. I checked scanner close/EOF/abort, dataset wrapper ownership, cache locks and asynchronous fills, and the synchronous statistics callback. The callback updates pre-registered counters within a catch boundary, and I found no distinct new lock-order, deadlock, static-initialization, or ownership defect. Two further cache performance patterns considered during review already existed in the base patch, so they are not new PR findings.
  • Configuration and compatibility: Foyer cache configuration is restart-scoped; initialization failures surface as a status. Thrift field 5 is optional and does not reuse an id on the checked release branches, but the changed default still breaks mixed-version search semantics as described in existing P1 4227447649. FE sends the flag to both vector and FTS paths; the changed C API, dependency pin, checksum, and lockfile were checked for coherence.
  • Parallel paths and conditions: I checked vector flat/index splits, FTS segments, offset expansion, two-phase row-ID fetch, prepared search, and Doris residual filters. The per-split postfilter ordering remains the existing P1 4228261574; the offset and fetch paths did not establish a separate regression. The cache's endpoint alias remains existing P1 4227447670.
  • Tests and observability: The BE and regression cases, changed .out files, and profile documentation were inspected against current code. Grouped BE counters are registered before callbacks, including the phase-two timing parent; FE aggregation and printing match those names. Previously reported profile/callback issues appear addressed. No executed test result is claimed.
  • Visibility, persistence, writes, error and performance: Lance reads use the selected dataset version. No Doris transaction, EditLog, MoW, or product write path changes are in this diff; the cache wrapper forwards writes and persists disk entries. Origin identity remains the data-correctness risk above. Cache initialization and callback failures have explicit boundaries; I found no distinct new memory-tracking or silent-error defect. The warm size-key write amplification remains existing P2 4227447675. A short cached-block concern had no supported trigger under immutable Lance data files and Foyer recovery; other cold-fill and minimum-disk-capacity concerns predate this PR.

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

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 33.33% (3/9) 🎉
Increment coverage report
Complete coverage report

@Gabriel39
Gabriel39 merged commit bcea9bb into apache:branch-4.1 Oct 10, 2026
35 of 40 checks passed
@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 10.71% (9/84) 🎉
Increment coverage report
Complete coverage report

yiguolei pushed a commit that referenced this pull request Oct 13, 2026
### What problem does this PR solve?
Problem Summary:
doc: apache/doris-website#4203
add prefilter:bool to control filter exectue;
prefilter default is false, so will do ann search firstly, then execute
filter.
prefilter turn true, will execute filter firstly, then do ANN search.

### Release note

None

### Check List (For Author)

- Test <!-- At least one of them must be included. -->
    - [x] Regression test
    - [ ] Unit Test
    - [ ] Manual test (add detailed scripts or steps below)
    - [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
        - [ ] Previous test can cover this change.
        - [ ] No code files have been changed.
        - [ ] Other reason <!-- Add your reason?  -->

- Behavior changed:
    - [ ] No.
    - [ ] Yes. <!-- Explain the behavior change -->

- Does this need documentation?
    - [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
apache/doris-website#1214 -->

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
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 <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 26, 2026
### What problem does this PR solve?

Issue Number: None

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

Problem Summary:

Port the Lance BE read path of branch-4.1/4.2 (as of branch-4.2
cabcf45) to master. Master links liblance_c but has no Lance reader:
file_scan_operator keeps FORMAT_LANCE off FileScannerV2 and V1 has no
Lance path, so every FORMAT_LANCE scan fails today. This change is BE
only. The FE connector plugin and the regression suites follow
separately, so nothing on master plans a Lance scan yet and the BE unit
tests carry the proof.

- Reader: be/src/format_v2/lance/* and format_v2/table/lance_reader.*
  (plain scans, vector and full-text search, Substrait and runtime
  filter pushdown, scalar index segments, COUNT(*) from metadata, LIMIT,
  profiles), copied from branch-4.2 except one local renamed for
  -Wshadow.
- Hooks: FileFormat::LANCE and its exhaustive switches, FileScannerV2
  dispatch, and fetch_table_schema for FORMAT_LANCE. Lance queries use
  FileScannerV2 whatever enable_file_scanner_v2 says, as ADBC and fluss
  do: there is no V1 Lance reader. The second phase of TopN lazy
  materialization reads Lance rows through dataset take_rows.
- Row locations: GlobalRowLoacationV2 gets a version 1 layout with a
  64-bit Lance row id (the version 0 layout is unchanged),
  PRequestBlockDesc.row_id becomes uint64 (varint, wire compatible), and
  the materialization operator and RowIdStorageReader decode and
  validate by version.
- Thrift: the external search request body, TLanceFileDesc 4-6 and
  TExternalSearchRequest 2-4 with the branch-4.1/4.2 ids.
- Core: PTypeDesc keeps NULL_TYPE for a NULL literal type, and the
  nullable serde reads an Arrow Null array as NULLs.
- Nine lance_* BE configs, a BE-wide Lance session with an optional
  Foyer data cache and 10 lance_session_* metrics; macOS links IOKit.
- Tests: the Lance BE unit tests and their fixtures, including the
  docker fixture all_types.lance that one of them reads. Under master's
  -ffile-prefix-map the session test locates its fixture from the
  repository root, and the reader tests shrink the data cache to 64 MiB
  under the UT temp dir: the default 100 GiB disk tier is 51200 block
  files that stay open for the rest of the test process.

Not included: the MaterializationOperator::push timer fix (apache#69579), the
Lance index metadata scanner, the FE side, regression suites, and the
4.x changes after cabcf45 (apache#68707, apache#68813, apache#68815).

### Release note

Add nine lance_* BE configs and lance_session_* BE metrics. They take
effect once the FE can plan Lance scans.

### Check List (For Author)

- Test: Unit Test
    - BE UT: the Lance reader, nested Null, runtime filter and session
      suites, plus the row-location protocol and FileScannerV2 cases
- Behavior changed: Yes. PRequestBlockDesc.row_id is uint64, and the
  second phase of TopN lazy materialization rejects row locations of an
  unknown version or size.
- Does this need documentation: No (documented with the FE connector)

Co-authored-by: zhangstar333 <[email protected]>
Co-authored-by: Gabriel <[email protected]>
Co-authored-by: zhaobo wang <[email protected]>
Co-authored-by: jay <[email protected]>
Co-authored-by: FANNG <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 26, 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 <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 27, 2026
### What problem does this PR solve?

Issue Number: None

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

Problem Summary:

Port the Lance BE read path of branch-4.1/4.2 (as of branch-4.2
cabcf45) to master. Master links liblance_c but has no Lance reader:
file_scan_operator keeps FORMAT_LANCE off FileScannerV2 and V1 has no
Lance path, so every FORMAT_LANCE scan fails today. This change is BE
only. The FE connector plugin and the regression suites follow
separately, so nothing on master plans a Lance scan yet and the BE unit
tests carry the proof.

- Reader: be/src/format_v2/lance/* and format_v2/table/lance_reader.*
  (plain scans, vector and full-text search, Substrait and runtime
  filter pushdown, scalar index segments, COUNT(*) from metadata, LIMIT,
  profiles), copied from branch-4.2 except one local renamed for
  -Wshadow.
- Hooks: FileFormat::LANCE and its exhaustive switches, FileScannerV2
  dispatch, and fetch_table_schema for FORMAT_LANCE. Lance queries use
  FileScannerV2 whatever enable_file_scanner_v2 says, as ADBC and fluss
  do: there is no V1 Lance reader. The second phase of TopN lazy
  materialization reads Lance rows through dataset take_rows.
- Row locations: GlobalRowLoacationV2 gets a version 1 layout with a
  64-bit Lance row id (the version 0 layout is unchanged),
  PRequestBlockDesc.row_id becomes uint64 (varint, wire compatible), and
  the materialization operator and RowIdStorageReader decode and
  validate by version.
- Thrift: the external search request body, TLanceFileDesc 4-6 and
  TExternalSearchRequest 2-4 with the branch-4.1/4.2 ids.
- Core: PTypeDesc keeps NULL_TYPE for a NULL literal type, and the
  nullable serde reads an Arrow Null array as NULLs.
- Nine lance_* BE configs, a BE-wide Lance session with an optional
  Foyer data cache and 10 lance_session_* metrics; macOS links IOKit.
- Tests: the Lance BE unit tests and their fixtures, including the
  docker fixture all_types.lance that one of them reads. Under master's
  -ffile-prefix-map the session test locates its fixture from the
  repository root, and the reader tests shrink the data cache to 64 MiB
  under the UT temp dir: the default 100 GiB disk tier is 51200 block
  files that stay open for the rest of the test process.

Not included: the MaterializationOperator::push timer fix (apache#69579), the
Lance index metadata scanner, the FE side, regression suites, and the
4.x changes after cabcf45 (apache#68707, apache#68813, apache#68815).

### Release note

Add nine lance_* BE configs and lance_session_* BE metrics. They take
effect once the FE can plan Lance scans.

### Check List (For Author)

- Test: Unit Test
    - BE UT: the Lance reader, nested Null, runtime filter and session
      suites, plus the row-location protocol and FileScannerV2 cases
- Behavior changed: Yes. PRequestBlockDesc.row_id is uint64, and the
  second phase of TopN lazy materialization rejects row locations of an
  unknown version or size.
- Does this need documentation: No (documented with the FE connector)

Co-authored-by: zhangstar333 <[email protected]>
Co-authored-by: Gabriel <[email protected]>
Co-authored-by: zhaobo wang <[email protected]>
Co-authored-by: jay <[email protected]>
Co-authored-by: FANNG <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 27, 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 <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 28, 2026
### What problem does this PR solve?

Issue Number: None

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

Problem Summary:

Port the Lance BE read path of branch-4.1/4.2 (as of branch-4.2
cabcf45) to master. Master links liblance_c but has no Lance reader:
file_scan_operator keeps FORMAT_LANCE off FileScannerV2 and V1 has no
Lance path, so every FORMAT_LANCE scan fails today. This change is BE
only. The FE connector plugin and the regression suites follow
separately, so nothing on master plans a Lance scan yet and the BE unit
tests carry the proof.

- Reader: be/src/format_v2/lance/* and format_v2/table/lance_reader.*
  (plain scans, vector and full-text search, Substrait and runtime
  filter pushdown, scalar index segments, COUNT(*) from metadata, LIMIT,
  profiles), copied from branch-4.2 except one local renamed for
  -Wshadow.
- Hooks: FileFormat::LANCE and its exhaustive switches, FileScannerV2
  dispatch, and fetch_table_schema for FORMAT_LANCE. Lance queries use
  FileScannerV2 whatever enable_file_scanner_v2 says, as ADBC and fluss
  do: there is no V1 Lance reader. The second phase of TopN lazy
  materialization reads Lance rows through dataset take_rows.
- Row locations: GlobalRowLoacationV2 gets a version 1 layout with a
  64-bit Lance row id (the version 0 layout is unchanged),
  PRequestBlockDesc.row_id becomes uint64 (varint, wire compatible), and
  the materialization operator and RowIdStorageReader decode and
  validate by version.
- Thrift: the external search request body, TLanceFileDesc 4-6 and
  TExternalSearchRequest 2-4 with the branch-4.1/4.2 ids.
- Core: PTypeDesc keeps NULL_TYPE for a NULL literal type, and the
  nullable serde reads an Arrow Null array as NULLs.
- Nine lance_* BE configs, a BE-wide Lance session with an optional
  Foyer data cache and 10 lance_session_* metrics; macOS links IOKit.
- Tests: the Lance BE unit tests and their fixtures, including the
  docker fixture all_types.lance that one of them reads. Under master's
  -ffile-prefix-map the session test locates its fixture from the
  repository root, and the reader tests shrink the data cache to 64 MiB
  under the UT temp dir: the default 100 GiB disk tier is 51200 block
  files that stay open for the rest of the test process.

Not included: the MaterializationOperator::push timer fix (apache#69579), the
Lance index metadata scanner, the FE side, regression suites, and the
4.x changes after cabcf45 (apache#68707, apache#68813, apache#68815).

### Release note

Add nine lance_* BE configs and lance_session_* BE metrics. They take
effect once the FE can plan Lance scans.

### Check List (For Author)

- Test: Unit Test
    - BE UT: the Lance reader, nested Null, runtime filter and session
      suites, plus the row-location protocol and FileScannerV2 cases
- Behavior changed: Yes. PRequestBlockDesc.row_id is uint64, and the
  second phase of TopN lazy materialization rejects row locations of an
  unknown version or size.
- Does this need documentation: No (documented with the FE connector)

Co-authored-by: zhangstar333 <[email protected]>
Co-authored-by: Gabriel <[email protected]>
Co-authored-by: zhaobo wang <[email protected]>
Co-authored-by: jay <[email protected]>
Co-authored-by: FANNG <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 28, 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 <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 29, 2026
### What problem does this PR solve?

Issue Number: None

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

Problem Summary:

Port the Lance BE read path of branch-4.1/4.2 (as of branch-4.2
cabcf45) to master. Master links liblance_c but has no Lance reader:
file_scan_operator keeps FORMAT_LANCE off FileScannerV2 and V1 has no
Lance path, so every FORMAT_LANCE scan fails today. This change is BE
only. The FE connector plugin and the regression suites follow
separately, so nothing on master plans a Lance scan yet and the BE unit
tests carry the proof.

- Reader: be/src/format_v2/lance/* and format_v2/table/lance_reader.*
  (plain scans, vector and full-text search, Substrait and runtime
  filter pushdown, scalar index segments, COUNT(*) from metadata, LIMIT,
  profiles), copied from branch-4.2 except one local renamed for
  -Wshadow.
- Hooks: FileFormat::LANCE and its exhaustive switches, FileScannerV2
  dispatch, and fetch_table_schema for FORMAT_LANCE. Lance queries use
  FileScannerV2 whatever enable_file_scanner_v2 says, as ADBC and fluss
  do: there is no V1 Lance reader. The second phase of TopN lazy
  materialization reads Lance rows through dataset take_rows.
- Row locations: GlobalRowLoacationV2 gets a version 1 layout with a
  64-bit Lance row id (the version 0 layout is unchanged),
  PRequestBlockDesc.row_id becomes uint64 (varint, wire compatible), and
  the materialization operator and RowIdStorageReader decode and
  validate by version.
- Thrift: the external search request body, TLanceFileDesc 4-6 and
  TExternalSearchRequest 2-4 with the branch-4.1/4.2 ids.
- Core: PTypeDesc keeps NULL_TYPE for a NULL literal type, and the
  nullable serde reads an Arrow Null array as NULLs.
- Nine lance_* BE configs, a BE-wide Lance session with an optional
  Foyer data cache and 10 lance_session_* metrics; macOS links IOKit.
- Tests: the Lance BE unit tests and their fixtures, including the
  docker fixture all_types.lance that one of them reads. Under master's
  -ffile-prefix-map the session test locates its fixture from the
  repository root, and the reader tests shrink the data cache to 64 MiB
  under the UT temp dir: the default 100 GiB disk tier is 51200 block
  files that stay open for the rest of the test process.

Not included: the MaterializationOperator::push timer fix (apache#69579), the
Lance index metadata scanner, the FE side, regression suites, and the
4.x changes after cabcf45 (apache#68707, apache#68813, apache#68815).

### Release note

Add nine lance_* BE configs and lance_session_* BE metrics. They take
effect once the FE can plan Lance scans.

### Check List (For Author)

- Test: Unit Test
    - BE UT: the Lance reader, nested Null, runtime filter and session
      suites, plus the row-location protocol and FileScannerV2 cases
- Behavior changed: Yes. PRequestBlockDesc.row_id is uint64, and the
  second phase of TopN lazy materialization rejects row locations of an
  unknown version or size.
- Does this need documentation: No (documented with the FE connector)

Co-authored-by: zhangstar333 <[email protected]>
Co-authored-by: Gabriel <[email protected]>
Co-authored-by: zhaobo wang <[email protected]>
Co-authored-by: jay <[email protected]>
Co-authored-by: FANNG <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 29, 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 <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 31, 2026
### What problem does this PR solve?

Issue Number: None

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

Problem Summary:

Port the Lance BE read path of branch-4.1/4.2 (as of branch-4.2
cabcf45) to master. Master links liblance_c but has no Lance reader:
file_scan_operator keeps FORMAT_LANCE off FileScannerV2 and V1 has no
Lance path, so every FORMAT_LANCE scan fails today. This change is BE
only. The FE connector plugin and the regression suites follow
separately, so nothing on master plans a Lance scan yet and the BE unit
tests carry the proof.

- Reader: be/src/format_v2/lance/* and format_v2/table/lance_reader.*
  (plain scans, vector and full-text search, Substrait and runtime
  filter pushdown, scalar index segments, COUNT(*) from metadata, LIMIT,
  profiles), copied from branch-4.2 except one local renamed for
  -Wshadow.
- Hooks: FileFormat::LANCE and its exhaustive switches, FileScannerV2
  dispatch, and fetch_table_schema for FORMAT_LANCE. Lance queries use
  FileScannerV2 whatever enable_file_scanner_v2 says, as ADBC and fluss
  do: there is no V1 Lance reader. The second phase of TopN lazy
  materialization reads Lance rows through dataset take_rows.
- Row locations: GlobalRowLoacationV2 gets a version 1 layout with a
  64-bit Lance row id (the version 0 layout is unchanged),
  PRequestBlockDesc.row_id becomes uint64 (varint, wire compatible), and
  the materialization operator and RowIdStorageReader decode and
  validate by version.
- Thrift: the external search request body, TLanceFileDesc 4-6 and
  TExternalSearchRequest 2-4 with the branch-4.1/4.2 ids.
- Core: PTypeDesc keeps NULL_TYPE for a NULL literal type, and the
  nullable serde reads an Arrow Null array as NULLs.
- Nine lance_* BE configs, a BE-wide Lance session with an optional
  Foyer data cache and 10 lance_session_* metrics; macOS links IOKit.
- Tests: the Lance BE unit tests and their fixtures, including the
  docker fixture all_types.lance that one of them reads. Under master's
  -ffile-prefix-map the session test locates its fixture from the
  repository root, and the reader tests shrink the data cache to 64 MiB
  under the UT temp dir: the default 100 GiB disk tier is 51200 block
  files that stay open for the rest of the test process.

Not included: the MaterializationOperator::push timer fix (apache#69579), the
Lance index metadata scanner, the FE side, regression suites, and the
4.x changes after cabcf45 (apache#68707, apache#68813, apache#68815).

### Release note

Add nine lance_* BE configs and lance_session_* BE metrics. They take
effect once the FE can plan Lance scans.

### Check List (For Author)

- Test: Unit Test
    - BE UT: the Lance reader, nested Null, runtime filter and session
      suites, plus the row-location protocol and FileScannerV2 cases
- Behavior changed: Yes. PRequestBlockDesc.row_id is uint64, and the
  second phase of TopN lazy materialization rejects row locations of an
  unknown version or size.
- Does this need documentation: No (documented with the FE connector)

Co-authored-by: zhangstar333 <[email protected]>
Co-authored-by: Gabriel <[email protected]>
Co-authored-by: zhaobo wang <[email protected]>
Co-authored-by: jay <[email protected]>
Co-authored-by: FANNG <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 31, 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 <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Nov 2, 2026
### What problem does this PR solve?

Issue Number: None

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

Problem Summary:

Port the Lance BE read path of branch-4.1/4.2 (as of branch-4.2
cabcf45) to master. Master links liblance_c but has no Lance reader:
file_scan_operator keeps FORMAT_LANCE off FileScannerV2 and V1 has no
Lance path, so every FORMAT_LANCE scan fails today. This change is BE
only. The FE connector plugin and the regression suites follow
separately, so nothing on master plans a Lance scan yet and the BE unit
tests carry the proof.

- Reader: be/src/format_v2/lance/* and format_v2/table/lance_reader.*
  (plain scans, vector and full-text search, Substrait and runtime
  filter pushdown, scalar index segments, COUNT(*) from metadata, LIMIT,
  profiles), copied from branch-4.2 except one local renamed for
  -Wshadow.
- Hooks: FileFormat::LANCE and its exhaustive switches, FileScannerV2
  dispatch, and fetch_table_schema for FORMAT_LANCE. Lance queries use
  FileScannerV2 whatever enable_file_scanner_v2 says, as ADBC and fluss
  do: there is no V1 Lance reader. The second phase of TopN lazy
  materialization reads Lance rows through dataset take_rows.
- Row locations: GlobalRowLoacationV2 gets a version 1 layout with a
  64-bit Lance row id (the version 0 layout is unchanged),
  PRequestBlockDesc.row_id becomes uint64 (varint, wire compatible), and
  the materialization operator and RowIdStorageReader decode and
  validate by version.
- Thrift: the external search request body, TLanceFileDesc 4-6 and
  TExternalSearchRequest 2-4 with the branch-4.1/4.2 ids.
- Core: PTypeDesc keeps NULL_TYPE for a NULL literal type, and the
  nullable serde reads an Arrow Null array as NULLs.
- Nine lance_* BE configs, a BE-wide Lance session with an optional
  Foyer data cache and 10 lance_session_* metrics; macOS links IOKit.
- Tests: the Lance BE unit tests and their fixtures, including the
  docker fixture all_types.lance that one of them reads. Under master's
  -ffile-prefix-map the session test locates its fixture from the
  repository root, and the reader tests shrink the data cache to 64 MiB
  under the UT temp dir: the default 100 GiB disk tier is 51200 block
  files that stay open for the rest of the test process.

Not included: the MaterializationOperator::push timer fix (apache#69579), the
Lance index metadata scanner, the FE side, regression suites, and the
4.x changes after cabcf45 (apache#68707, apache#68813, apache#68815).

### Release note

Add nine lance_* BE configs and lance_session_* BE metrics. They take
effect once the FE can plan Lance scans.

### Check List (For Author)

- Test: Unit Test
    - BE UT: the Lance reader, nested Null, runtime filter and session
      suites, plus the row-location protocol and FileScannerV2 cases
- Behavior changed: Yes. PRequestBlockDesc.row_id is uint64, and the
  second phase of TopN lazy materialization rejects row locations of an
  unknown version or size.
- Does this need documentation: No (documented with the FE connector)

Co-authored-by: zhangstar333 <[email protected]>
Co-authored-by: Gabriel <[email protected]>
Co-authored-by: zhaobo wang <[email protected]>
Co-authored-by: jay <[email protected]>
Co-authored-by: FANNG <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Nov 2, 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 <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Nov 5, 2026
### What problem does this PR solve?

Issue Number: None

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

Problem Summary:

Port the Lance BE read path of branch-4.1/4.2 (as of branch-4.2
cabcf45) to master. Master links liblance_c but has no Lance reader:
file_scan_operator keeps FORMAT_LANCE off FileScannerV2 and V1 has no
Lance path, so every FORMAT_LANCE scan fails today. This change is BE
only. The FE connector plugin and the regression suites follow
separately, so nothing on master plans a Lance scan yet and the BE unit
tests carry the proof.

- Reader: be/src/format_v2/lance/* and format_v2/table/lance_reader.*
  (plain scans, vector and full-text search, Substrait and runtime
  filter pushdown, scalar index segments, COUNT(*) from metadata, LIMIT,
  profiles), copied from branch-4.2 except one local renamed for
  -Wshadow.
- Hooks: FileFormat::LANCE and its exhaustive switches, FileScannerV2
  dispatch, and fetch_table_schema for FORMAT_LANCE. Lance queries use
  FileScannerV2 whatever enable_file_scanner_v2 says, as ADBC and fluss
  do: there is no V1 Lance reader. The second phase of TopN lazy
  materialization reads Lance rows through dataset take_rows.
- Row locations: GlobalRowLoacationV2 gets a version 1 layout with a
  64-bit Lance row id (the version 0 layout is unchanged),
  PRequestBlockDesc.row_id becomes uint64 (varint, wire compatible), and
  the materialization operator and RowIdStorageReader decode and
  validate by version.
- Thrift: the external search request body, TLanceFileDesc 4-6 and
  TExternalSearchRequest 2-4 with the branch-4.1/4.2 ids, in place of the
  comments that reserve them on master (apache#70075); field 6 stays master's
  paimon_options.
  TVectorSearchOptions 6 is branch-4.2's approx_mode (apache#69852), reserved
  in a comment.
- Core: PTypeDesc keeps NULL_TYPE for a NULL literal type, and the
  nullable serde reads an Arrow Null array as NULLs.
- Nine lance_* BE configs, a BE-wide Lance session with an optional
  Foyer data cache and 10 lance_session_* metrics; macOS links IOKit.
- Tests: the Lance BE unit tests and their fixtures, including the
  docker fixture all_types.lance that one of them reads. Under master's
  -ffile-prefix-map the session test locates its fixture from the
  repository root, and the reader tests shrink the data cache to 64 MiB
  under the UT temp dir: the default 100 GiB disk tier is 51200 block
  files that stay open for the rest of the test process.

Not included: the MaterializationOperator::push timer fix (apache#69579), the
Lance index metadata scanner, the FE side, regression suites, and the
4.x changes after cabcf45 (apache#68707, apache#68813, apache#68815).

### Release note

Add nine lance_* BE configs and lance_session_* BE metrics. They take
effect once the FE can plan Lance scans.

### Check List (For Author)

- Test: Unit Test
    - BE UT: the Lance reader, nested Null, runtime filter and session
      suites, plus the row-location protocol and FileScannerV2 cases
- Behavior changed: Yes. PRequestBlockDesc.row_id is uint64, and the
  second phase of TopN lazy materialization rejects row locations of an
  unknown version or size.
- Does this need documentation: No (documented with the FE connector)

Co-authored-by: zhangstar333 <[email protected]>
Co-authored-by: Gabriel <[email protected]>
Co-authored-by: zhaobo wang <[email protected]>
Co-authored-by: jay <[email protected]>
Co-authored-by: FANNG <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Nov 5, 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. On master the field replaces the comment that reserves it
     (apache#70075).

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 <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
yiguolei pushed a commit that referenced this pull request Nov 6, 2026
### 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 one exception is `TVectorSearchOptions` 6, branch-4.2's
`approx_mode` (#69852, merged into branch-4.2 after the snapshot and not
ported): it is reserved in a comment, and a master BE ignores it, so
during a rolling upgrade a 4.2 FE that asks for `fast` or `accurate`
gets Lance's default (`normal`).

**The problem, and what it cost.**

| | master today | with this PR |
|---|---|---|
| A `FORMAT_LANCE` scan | FileScannerV2 has no Lance reader, so the scan
fails with "FileScannerV2 does not support file format" | 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: the reader dispatch and `_to_file_format` handle
`FORMAT_LANCE` / `"lance"`; `FileFormat::LANCE` is added to its two
exhaustive switches. Since #69723 every file scan uses FileScannerV2, so
nothing else routes Lance scans (on branch-4.2 a Lance query fails with
NotSupported when `enable_file_scanner_v2` is off).
- 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. On master, #70075 reserved `TExternalSearchRequest` 2-5 in
comments and added 6 (`paimon_options`); this PR replaces those comments
with the fields and leaves 6 as it is. `TVectorSearchOptions` 6 is
reserved in a comment for branch-4.2's `approx_mode` (#69852).
- After the snapshot (one commit each):
- #68813: `prefilter` decides whether the TVF search filter runs before
(true) or after (unset / false) candidate selection, for vector and
full-text search. The profile groups the Lance counters under
`LanceTiming`, `LanceIO`, `LanceIndex`, `LanceVectorIndex`,
`LanceScalarIndex`, `LanceExecutionPlan`, `LanceScan`, `LanceSearchPlan`
and `LanceDataCache`, uses the 4.x counter names, and registers every
scan metric before the statistics callback is installed, so the
callback, which Lance invokes from its execution streams, never adds
counters. #68813's FE tweak that hides `TUnit.NONE` labels in
`AggCounter` belongs to the FE port.
- #68707 (part): a vector split searches exactly the index segments FE
planned for it, and a split without segments is searched flat whatever
`use_index` says; a split with segments under `use_index = false` is
rejected. 4.x FEs plan segments only when `use_index` allows them.
- #68815: `distance_lower_bound` (inclusive) and `distance_upper_bound`
(exclusive) are validated (finite FLOAT values, lower < upper at FLOAT
precision, single-vector only) and installed with
`lance_scanner_set_distance_range()`, which master's pinned lance-c
provides.
- 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.
- Runtime filters that mention INT64_MIN stay residual-only. The pinned
Lance SQL parser reads `-9223372036854775808` as the negation of a
magnitude that it parses as i64 first; the overflow turns the literal
into a Float64, which the planner cannot coerce to Int64 or Decimal, so
a pushed IN, `>=` or `<=` filter on an INT64 or scale-zero decimal
column failed planning and the query failed. Every other i64 is still
pushed.
- 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 #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. Since #69697 the same masks keep hidden DATETIMEV2 /
TIMESTAMPTZ values out of its range checks. A mask costs at most one
pass per batch: a NULL row that owns no child slots, or only NULL ones,
allocates nothing, and Arrow stream load masks each retained IPC batch
once (`mask_arrow_descendants_not_visible()`) before it hands row ranges
to the SerDes, so its per-block reads allocate nothing. The one
per-block mask left is for map keys under a NULL map row that still owns
entries, since a MapArray cannot hold NULL keys; Arrow's map builder
writes NULL map rows without entries.
- 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. A negative cache size (and, while the data cache
is enabled, a negative disk capacity or read block size) fails the first
Lance scan with a message that names the setting, as does any data-cache
value lance-c rejects; the rest of the BE keeps serving. 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.

A Lance scan's native working set, that is the I/O buffered up to
`lance_io_buffer_size_bytes` per scanner, the decode readahead and the
operators of a search plan such as the top-k of a flat vector search, is
allocated inside lance-c and is charged to neither the query nor its
workload group. It counts toward the BE process memory only, so memory
GC, which ranks queries by tracked memory, does not attribute it to the
Lance query. lance-c accepts no memory budget, so on BEs with little
memory headroom size `lance_io_buffer_size_bytes`,
`lance_batch_readahead` and `lance_fragment_readahead` for the number of
concurrent Lance scanners. As on branch-4.1 / 4.2; tracked as a
follow-up.
- 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. With this PR `util/rle_encoding.h` reaches 231 TUs and
`util/threadpool.h` 589, the same as master `315c2e00331` alone (limits
275 and 660 since #69717), and `gen_cpp/internal_service.pb.h` 562
(master alone 559, limit 660); the reach budgets are unchanged.

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), on
the head rebased onto master `378404eeb12`: 1029 cases in 54 suites run,
1027 pass and 2 Parquet microbenchmarks skip by design. They cover the
Lance suites (107: `LanceTableReader*` 83, `LanceNestedNullTest` 10,
`LanceRuntimeFilterHelperTest` 10, `LanceSessionManagerTest` 4); 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` (28); the ORC and
Parquet reader suites (`NewOrcReaderTest`, `NewParquetReaderTest`,
`ParquetScanTest` and the other `Parquet*` suites), whose row ids now go
through `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. The
ninth review round adds
`LanceRuntimeFilterHelperTest.KeepsInt64MinFiltersResidualOnly` and
`LanceTableReaderFilterTest.KeepsInt64MinRuntimeFiltersOutOfLanceScanner`:
both pass with the 106 Lance cases (`Lance*`) on the branch rebased onto
`339e087b11f`, and restoring `parse_int64()` in either check makes
exactly these two fail. On the head rebased onto master `315c2e00331`,
with the per-block mask fix (`42cf4e3e5d1`), 328 cases in 24 suites
pass: the Arrow SerDe and validation suites, `ArrowParentNullsTest`
(15), `LoadArrowPipeTest` (47), the DATETIMEV2 / TIMESTAMPTZ SerDe
suites, `ArrowColumnToDorisColumnTest`, `ArrowBlockConvertorTest`,
`AdbcReaderTest`, `RemoteDorisV2ReaderTest`, `PaimonRustTableReaderTest`
and the Lance suites. Allocating at the first NULL row of each call
again fails the new `ArrowParentNullsTest` case and the four new
`LoadArrowPipeTest` block cases, dropping the per-batch mask fails the
LIST and STRUCT block cases, and returning the child unmasked fails the
new timestamp case, four `ArrowParentNullsTest` cases and
`LanceTableReaderTypeTest.IgnoresDate32PayloadHiddenByNullParents`.

### 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 <!-- At least one of them must be included. -->
    - [ ] Regression test
    - [x] Unit Test
- `run-be-ut.sh --run --filter=...` with the suites listed under
Results: 420 of 420 pass on macOS arm64 (ASAN) on the content before the
rebases, and the Lance and Arrow SerDe suites pass again with the
nested-offset, zero-field struct, hidden-child, hidden-Decimal256 and
compaction-mask fixes (the ADBC SQLite cases skip without a local SQLite
driver). Rebased onto master `43f9ceb48d7`: #69668 conflicted in the
scanner selection and in `fetch_table_schema`, which now hands every
file format to `format::fetch_file_schema()`, so the Lance branch runs
ahead of that call; #69646 added a TopN row-group test that reads the
version 0 row location fields, which now go through `file_local` (the
earlier rebase onto `5caaeb3e3d4` resolved #69614 and #69601 in the
scanner selection, the `FileFormat` enum, its test and the reach budget
table). The 95 TUs that this PR changes or that master changed since
`5caaeb3e3d4` were syntax-checked against the rebased tree with the UT
build flags, and CI runs the suites again. Rebased again onto master
`8d88f1cc6f7`: the only conflict was the budget table, where #65858
moved the `util/rle_encoding.h` baseline to 828, and the 163 TUs that
this PR changes or that master changed since `43f9ceb48d7` pass the same
syntax check. Rebased again onto master `339e087b11f`: the only conflict
was the budget table again, where #67292 moved the `util/rle_encoding.h`
baseline to 834; #69872 added Arrow type checks ahead of the NULL-parent
masking in the map and struct readers and merged cleanly. Of the 114
sources that this PR changes or that master changed since `8d88f1cc6f7`,
111 pass the same syntax check. The other three are master's: two new
#67292 files under `storage/segment` fail the same way on master alone,
since they compile only inside their unity batch, and this PR adds no
source to that target; the #69818 benchmark is not in the UT build and
includes only `util/simd` headers that this PR does not touch. Reverting
any of the review fixes, the #68813 flag, the #68707 split rule or the
#68815 bounds makes its new tests fail. Rebased again onto master
`378404eeb12`, which removed the legacy file scanner (#69723): the
conflicts were in the scanner-selection code and tests that #69723
deleted, so this PR's Lance lines there went with them (including its
`LanceQueriesAlwaysUseScannerV2` test), in the profile keys of
`materialization_opertor.cpp`, and in the row fetch of
`rowid_fetcher.cpp`, whose non-Lance path is now only
`FileScannerV2::read_by_rows`; the deleted V1 ORC / Parquet row id tests
took this PR's `file_local` rename with them, and the
`util/rle_encoding.h` budget commit is dropped because master is no
longer near that limit. Of the 100 sources that this PR changes or that
master changed since `339e087b11f`, 99 pass the same syntax check; the
other, `format_v2/orc/orc_reader.cpp`, fails the same way on master
alone. On the rebased head the suites listed under Results run 1029
cases: 1027 pass and 2 microbenchmarks skip. The test-utility fix that
`DataTypeDateTimeV2SerDeTest` needed on macOS (#69695) is now on master.
Rebased again onto master `315c2e00331`: the only conflict was
`TExternalSearchRequest` in `PlanNodes.thrift`, where #70075 had
reserved fields 2-5 in comments; the two commits that add the fields
replace those comments, and field 6 stays #70075's `paimon_options`.
#69717 cut transitive includes from shared headers: of the 160 sources
that this PR changes or that master changed since `378404eeb12`, 156
pass the same syntax check; the two `jemalloc_hook` tests have no
compile command on macOS, `runtime/memory/jemalloc_hook.cpp` uses glibc
aliases that macOS cannot build (#69819), and
`format_v2/table_reader.cpp` alone fails the same way on master alone
while its unity batch compiles. The full `doris_be_test` builds and
links on the rebased head; `ArrowParentNulls*` and `Lance*` (121 cases)
pass before the mask fix, and the 328 cases above with it.
- `build-support/check-build-hygiene.sh` (on this branch rebased onto
master `315c2e00331`), clang-format 16 and
`build-support/run-clang-tidy.sh` on the changed files: all pass; on the
changed lines clang-tidy (llvm 22) reports only
`readability-inconsistent-ifelse-braces` on the `DORIS_CHECK` lines of
the masking functions, the macro's expansion that master's existing
`DORIS_CHECK`s report too.
    - [ ] Manual test (add detailed scripts or steps below)
    - [ ] No need to test or manual test. Explain why:
- Behavior changed:
    - [ ] No.
- [x] 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?
    - [x] No. Lance is documented with the FE connector.
- [ ] Yes. <!-- Add document PR link here. eg:
apache/doris-website#1214 -->

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->

---------

Co-authored-by: zhangstar333 <[email protected]>
Co-authored-by: Gabriel <[email protected]>
Co-authored-by: zhaobo wang <[email protected]>
Co-authored-by: jay <[email protected]>
Co-authored-by: FANNG <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Co-authored-by: zy-kkk <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants