This guide maps the main ownership boundaries in the Lance repository. Use it to find the module that owns a behavior before changing an implementation, its persistent representation, or its language binding.
Lance has three visible surfaces and a layered Rust core:
Python API -> PyO3 bindings --\
Java API -----> JNI bindings ---+-> lance (Dataset and query orchestration)
|
+-> scanner planners -> io::exec physical nodes
| |
| +-> lance-index
| +-> lance-file / lance-io
|
+-> lance-table (manifest and transactions)
+-> lance-index (index algorithms and storage)
+-> lance-file / lance-encoding / lance-io
+-> lance-datafusion* / lance-linalg / lance-select
The dependency direction is downward. Bindings translate Python or Java values
and preserve lifetime semantics; they do not define table behavior. The
lance crate coordinates datasets, queries, writes, and commits. Lower-level
crates own durable metadata, index algorithms, physical file I/O, encoding,
expression evaluation, and row-selection primitives.
The tree below shows the main code entry points. It is intentionally a navigation map, not an exhaustive file inventory.
lance/
|-- rust/
| |-- lance/ # Dataset API and orchestration
| | `-- src/
| | |-- dataset/
| | | |-- scanner.rs # Scanner state and public query entry
| | | |-- scanner/ # builder, plan, source, FTS, vector, ...
| | | |-- optimize.rs # compaction workflow entry
| | | |-- optimize/ # planning, execution, commit
| | | |-- blob/ # Blob v2 codec and batch rewrite
| | | |-- write/merge_insert.rs
| | | |-- write/merge_insert/ # builder, job, merger, fragment update
| | | |-- mem_wal/write.rs # MemWAL shard-writer entry
| | | `-- mem_wal/write/ # coordination, replay, handlers, tests
| | `-- index/
| | |-- api.rs # Dataset index public trait
| | |-- lifecycle.rs # create/drop/merge/commit/optimize entry
| | |-- provenance.rs # source freshness and coverage
| | |-- loading.rs # raw listing, cache keys, opening
| | |-- statistics.rs # descriptions and statistics
| | |-- prewarm.rs # cache residency and diagnostics
| | |-- segment.rs # segment metadata/capability rules
| | `-- remap.rs # streaming row-address remapping
| |-- lance-table/ # manifests, transactions, format metadata
| |-- lance-index/ # index formats, builders, scorers, search
| |-- lance-file/ # Lance file reader/writer
| |-- lance-encoding/ # column encodings and decoders
| |-- lance-io/ # object stores, scheduling, file I/O
| |-- lance-datafusion*/ # DataFusion planning and execution glue
| `-- lance-select/ # row addresses, masks, selection plans
|-- python/
| |-- python/lance/_dataset/ # Python dataset capability modules
| `-- src/dataset/ # PyO3 objects grouped by capability
|-- java/
| |-- src/main/java/org/lance/ # public Java API
| `-- lance-jni/src/blocking_dataset/ # JNI adapters grouped by capability
`-- docs/ # user and format documentation
Shared contracts stay with the layer that can enforce them:
| Contract | Owner |
|---|---|
| Stable logical row IDs, physical row addresses, deleted rows, and system columns | lance-core types plus lance-table metadata; callers pass those types rather than re-encoding the bit layout |
| Manifest, fragment, transaction, and index metadata | rust/lance-table/src/format/ and rust/lance-table/src/transaction/ |
| Row masks and selection algebra | rust/lance-select/src/ |
| Index query, build, persistence, scoring, and posting-list algorithms | rust/lance-index/src/ |
| File layout and codec invariants | lance-file and lance-encoding |
| Dataset-level lifecycle, coordination, and public API behavior | rust/lance/src/dataset/ and rust/lance/src/index/ |
| Binding argument conversion and foreign-language object lifetime | python/src/ and java/lance-jni/src/ |
A type moves with the behavior that constructs, validates, serializes, and
updates its invariant. Avoid introducing generic types, validation, or
utils modules as a destination for otherwise unrelated behavior.
rust/lance/src/dataset/scanner.rs owns the user-visible Scanner state and
entry points. The child modules divide the query pipeline by responsibility:
scanner/builder.rsapplies configuration setters and leaves cross-option validation to planning.scanner/plan.rsvalidates scan state, chooses the source family, and adds final ordering, limit, and projection stages.scanner/source.rsbuilds physical row sources, including filtered reads, indexed takes, overlays, deletion masks, and distributed takes.scanner/fts.rsowns FTS field and granularity resolution, coverage classification, residual search, and indexed/flat fallback planning.scanner/vector.rsandscanner/minhash.rsown their search planning and uncovered-row fallback.scanner/aggregate.rsowns aggregate expression construction and lowering.
The scanner selects and composes a source with the coverage, row identity,
deletion, and overlay handling required by the query. io::exec nodes execute
the selected plan and apply row filters and masks while streaming.
rust/lance/src/io/exec/fts.rs is the execution facade. Its child modules own
physical query shapes:
match_query,phrase,combined_fields, andbooleanown indexed query nodes.flat_matchandflat_combined_fieldsown flat scans over uncovered rows.compound,corpus, andselectionown mixed indexed/flat coordination, shared BM25 corpus state, and exact segment selection.segments,document,matches, andquery_tokensadapt persisted index segments and schema-shaped documents into the execution nodes.
Dataset coverage and fallback decisions remain in dataset/scanner/fts.rs.
Query syntax, posting lists, BM25 preparation, and ranking algorithms remain in
lance-index.
rust/lance/src/dataset/optimize.rs keeps the workflow entry and re-exports the
public compaction contracts. The adjacent modules separate the lifecycle:
optimize/planning.rsowns serializable options and plans, candidate selection, index-aware grouping, and task construction. It does not rewrite data or publish a manifest.optimize/execution.rsowns task execution andRewriteResult. It writes replacement fragments but does not publish them.optimize/commit.rsowns concurrent commit coordination, index remapping, fragment publication, and partial-result semantics.dataset/blob/rewrite.rsowns shared Blob v2 batch classification, descriptor-to-payload conversion, null/offset preservation, and theBlobV2BatchRewritePlantype.
Blob rewrite is a Blob-domain capability shared by compaction, updates, merge-insert, scans, and managed clones. A caller supplies a schema, a batch, and row addresses; it does not duplicate Blob traversal or descriptor logic.
rust/lance/src/index.rs is the module facade and keeps
DatasetIndexExt as one coherent public implementation entry. Its child modules
own the lifecycle stages:
lifecycle.rscreates, drops, merges, commits, lists, and optimizes logical indexes. It coordinates the stages below without taking their private state.provenance.rsdetermines whether committed segment coverage is still fresh under staged replay, overlays, row revival, and fragment-reuse remapping.loading.rsperforms raw manifest listings, FRI bookkeeping, cache-key construction, opening, and the internal loading trait.statistics.rsdescribes indexes and computes statistics, including the existing legacy auto-migration path.prewarm.rsloads selected segments into caches and reports residency.segment.rsvalidates persisted segment metadata and capability predicates.remap.rsstreams row-address translation for data rewrites.
The dependency direction is from lifecycle coordination toward provenance,
loading, statistics, prewarm, segment, and remap. statistics.rs may consume the
raw FRI manifest lookup from loading.rs; loading must not depend on
statistics. lance-index remains the owner of concrete index formats,
algorithms, and readers.
rust/lance/src/dataset/write/merge_insert.rs keeps the public operation types
and the overall entry point. Child modules divide one execution:
builder.rsvalidates keys and operations and builds an immutable job.job.rsplans and executes the source/target join, target-base resolution, retries, and transaction assembly.merger.rsapplies matched and not-matched semantics to joined rows and tracks staged mutations.update_fragments.rsgroups updates, writes replacement fragments, preserves row lineage, and reconciles row addresses.
The merge plan, Blob inventory, stable row IDs, deletion state, and target version are carried explicitly through these stages. Publication stays with the dataset commit path.
rust/lance/src/dataset/mem_wal/write.rs exposes the shard-writer entry.
Adjacent modules own independent parts of one persistent writer lifecycle:
config.rsowns shard-writer settings and validation.writer.rsownsShardWriter, resident memtables, write admission, and lifecycle coordination.backpressure.rsowns memory accounting, durability notifications, and admission control.tasks.rsandhandlers.rsown ordered background dispatch and WAL, index, and memtable-flush handlers.schema.rsderives the storage schema, validates incoming writes, and shapes rows and tombstones for persistence.replay.rsreconstructs memtables and resumes cursors from the WAL.stats.rsexposes snapshots of write, WAL, and memtable state.
WAL ordering, writer epochs, generation boundaries, and durable cursors are lifecycle invariants. They stay in the MemWAL write modules rather than being copied into Python, Java, or benchmark code.
python/python/lance/dataset.py remains the public LanceDataset entry.
python/python/lance/_dataset/ contains capability-oriented Python objects and
helpers such as scanner, index, optimize, cleanup, refs,
transaction, delta, sql, stats, and merge_insert.
python/src/dataset.rs remains the central _Dataset PyO3 object.
python/src/dataset/ contains capability modules for operations that have
distinct conversion or lifetime rules, including writes, merge-insert, SQL,
delta, base paths, cleanup, I/O statistics, compaction, and statistics.
Python modules may validate Python-only calling conventions, coerce Python inputs, and preserve Python object behavior. Dataset semantics, row identity, transactions, and error meaning stay in Rust.
java/src/main/java/org/lance/Dataset.java remains the public Java dataset
object. It owns the native handle, allocator/session lifetime, close behavior,
and the lifecycle and index-build locks shared by all operations.
java/lance-jni/src/blocking_dataset/ divides the native adapter surface:
constructionandconversionsopen datasets and translate handles, versions, storage options, and namespace objects.readandschemaown reads, statistics, schema inspection, and schema evolution.mutation,maintenance,index, andrefsown writes, cleanup/compaction, index operations, and version/tag/branch operations.
JNI modules borrow the Rust dataset through the single Java handle. They must not create a second handle lifecycle or copy Java lock behavior into native state.
Tests stay with the contract they protect:
| Area | Test entry |
|---|---|
| Scanner planning and behavior | Module-local tests in dataset/scanner/ and integration tests in dataset/tests/dataset_scanner_*.rs |
| FTS execution | Inline tests in io/exec/fts.rs and its owner submodules; dataset-level plan and segment behavior in dataset/tests/dataset_scanner_*.rs |
| Compaction | dataset/optimize/tests/ grouped by planning, execution, commit, overlays, lineage, indexes, vectors, and Blob behavior |
| Blob rewrite | Unit tests in dataset/blob/rewrite.rs and compaction-path tests in dataset/optimize/tests/blob.rs |
| Dataset indexes | Private-state tests beside each index owner module; cross-module behavior in index/tests/ grouped by create, lifecycle, loading, merge, nested fields, optimize, and statistics |
| Merge-insert | Job-owned planning, unit-state, indexed, and lineage tests in dataset/write/merge_insert/job/tests/; public operation, concurrency, semantics, deferred-condition, subcolumn, and type tests in dataset/write/merge_insert/tests/ |
| MemWAL writes | Tests beside owner modules plus dataset/mem_wal/write/tests/ for end-to-end writes, WAL replay, schema evolution, and integration |
| Python API and PyO3 | Python tests grouped by user capability, plus Rust binding tests where FFI conversion or lifetime behavior is the contract |
| Java API and JNI | Java suites by capability, plus JNI-local tests for native conversion and error behavior |
Put a test where it can observe the invariant without widening production visibility. Use cross-module integration tests for behavior that spans planner, execution, and commit boundaries.
Start in rust/lance/src/dataset/optimize/planning.rs:
- Read
DefaultCompactionPlanner::candidacy_offor the per-fragment decision between self-compaction, neighbor compaction, and no rewrite. - Follow
CompactionPlanner::planfor index boundaries, grouping, output-size limits, and serializableCompactionPlanconstruction. - Read
optimize/execution.rsonly to understand what the selected task will write; publication remains inoptimize/commit.rs. - Update
dataset/optimize/tests/planning.rsfor selection and grouping contracts.
Start in rust/lance/src/dataset/scanner/fts.rs:
- Follow
fts_search_sourceand the FTS arm ofScanner::create_planinscanner/plan.rsto see how the scanner chooses an indexed, residual, hybrid, or flat source. - Read
has_complete_hybrid_fts_coverageand the segment/overlay helpers in the same module before changing when the index can replace a scan. - Change
io/exec/fts/only when the physical node behavior changes; the dataset planner remains the owner of coverage and fallback selection. - Update
dataset/tests/dataset_scanner_plans.rs,dataset_scanner_segments.rs, and the relevantio/exec/ftsowner tests.
Start in rust/lance/src/dataset/blob/rewrite.rs:
- Read
BlobV2BatchRewritePlan::{try_new, output_schema, transform_batch}andtransform_blob_v2_batch. - Check every caller with Blob v2 data: scan, update, merge-insert, managed clone, and compaction. They share this module instead of carrying a local copy.
- Preserve nulls, nested struct/list offsets, external references, row count,
_rowaddrhandling, and managed descriptor behavior. - Update the module tests and
dataset/optimize/tests/blob.rswhen the transformation or a compaction integration path changes.