lance-format / lance-format/lance

`optimize_indices` cannot extend a scalar index created over a nested field of an empty dataset

Open
#9,145 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Rust
Stars
7.1k
Forks
852
Avg merge
3d 18h
Merged PRs (30d)
272

Description

Description

create_scalar_index("st.child", index_type="BTREE") succeeds whether or not the dataset has rows. But if it ran while the dataset was empty, the optimize_indices() that should later extend the index over newly appended fragments fails:

OSError: Invalid user input: No column with name child,
/rust/lance/src/index/scalar.rs:364:20

The same index built once rows exist optimizes fine, then and on every later pass.

Output of the repro script:

index built while empty:  optimize_indices() -> OSError: Invalid user input: No column with name child, .../rust/lance/src/index/scalar.rs:364:20
index built over 8 rows:  optimize_indices() -> OK
Steps to reproduce
import tempfile
from pathlib import Path

import lance
import pyarrow as pa

STRUCT = pa.struct([pa.field("child", pa.int64())])
SCHEMA = pa.schema([pa.field("uid", pa.string()), pa.field("st", STRUCT)])


def rows(n: int, start: int = 0) -> pa.Table:
    return pa.table(
        {
            "uid": pa.array([f"u{i}" for i in range(start, start + n)]),
            "st": pa.array([{"child": i} for i in range(start, start + n)], type=STRUCT),
        },
        schema=SCHEMA,
    )


def run(label: str, rows_before_index: int) -> None:
    uri = str(Path(tempfile.mkdtemp()) / "ds.lance")
    initial = SCHEMA.empty_table() if rows_before_index == 0 else rows(rows_before_index)
    lance.write_dataset(initial, uri, schema=SCHEMA, mode="create", data_storage_version="2.2")

    lance.dataset(uri).create_scalar_index("st.child", index_type="BTREE")
    lance.write_dataset(rows(4, start=100), uri, mode="append")  # a fragment the index misses

    try:
        lance.dataset(uri).optimize.optimize_indices()
    except Exception as exc:
        print(f"{label}  optimize_indices() -> {type(exc).__name__}: {exc}")
    else:
        print(f"{label}  optimize_indices() -> OK")


run("index built while empty:", rows_before_index=0)
run("index built over 8 rows:", rows_before_index=8)
Expected behavior

Both cases reach full coverage: an index over a nested field should be extendable regardless of whether the dataset held rows when the index was created.

Lance version

11.0.0

Language binding

Python

Environment

macOS 26.6.2, arm64, local

Logs / traceback

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the failing nested-field lookup near rust/lance/src/index/scalar.rs:364 and reproduce the issue using the Python script in the report. Compare index extension for an empty dataset with extension after initial rows exist. Done means optimize_indices() succeeds and the index reaches full coverage in both cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, rust
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.