lance-format / lance-format/lance

Add support for Dict(K,V) as scalar index (BTree and Bitmap) on nullable and non-nullable columns

Open
#7,193 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

It currently is not possible to create a scalar index for a column of type Dict(u16, Utf8).
On a nullable dictionary column, the BTree index fails to write page_lookup.lance (≥ v2.1 encoder).

The encoder rejects the resulting empty-values dictionary:

create BTree on nullable dict => 
  Err(Arrow { 
    message: "Invalid argument error: Value at position 0 out of bounds: 0 (should be in [0, -1])", 
    location: Location { file: "rust/lance-encoding/src/encodings/logical/primitive.rs", 
    line: 5665, 
    column: 33 
  } })

thread 'dataset::scanner::test::repro_btree_dict_null_crash' (193226154) panicked at rust/lance/src/dataset/scanner.rs:5371:13:

called `Result::unwrap()` on an `Err` value: Arrow { 
  message: "Invalid argument error: Value at position 0 out of bounds: 0 (should be in [0, -1])", 
  location: Location { file: "rust/lance-encoding/src/encodings/logical/primitive.rs", 
  line: 5665, 
  column: 33 
} }

Repro:

#[tokio::test]
async fn repro_btree_dict_null_crash() {
   use arrow_array::{Int16Array, Int16DictionaryArray};
   use lance_index::scalar::BuiltinIndexType;

   // Dictionary(Int16, Utf8) column with a null, small enough for a single
   // btree page.
   let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
      "category",
      DataType::Dictionary(Box::new(DataType::Int16), Box::new(DataType::Utf8)),
      true,
   )]));
   let dict = Arc::new(StringArray::from(vec!["a", "b", "c"]));
   let keys = Int16Array::from(vec![Some(0), Some(1), Some(2), None]); // a,b,c,null
   let arr = Int16DictionaryArray::try_new(keys, dict).unwrap();
   let batch = RecordBatch::try_new(schema.clone(), vec![Arc::new(arr)]).unwrap();
   let reader = RecordBatchIterator::new(vec![Ok(batch)], schema.clone());
   let mut dataset = Dataset::write(reader, "memory://repro_btree_dict", None)
      .await
      .unwrap();

   let params = ScalarIndexParams::for_builtin(BuiltinIndexType::BTree);
   let res = dataset
      .create_index(&["category"], IndexType::Scalar, None, &params, true)
      .await;
   println!("create BTree on nullable dict => {res:?}");
   res.unwrap();
}

General Issues With Dicts

Dicts for the same column are not guaranteed to be equal over multiple RecordBatches, as far as I understand. For example, if they are received via IPC, where a dict can be replaced completely in a second RecordBatch. See https://arrow.apache.org/docs/format/Columnar.html#dictionary-messages

It therefore makes sense to use the logical values of a dict column in the index. This should at least yield the same storage requirement as a BTree or Bitmap index on a Utf8 column (talking about Dictionary(_, Utf8)).

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 repro_btree_dict_null_crash test and the create_index call in the issue, then inspect rust/lance-encoding/src/encodings/logical/primitive.rs around line 5665 and rust/lance/src/dataset/scanner.rs around line 5371. Run the nullable dictionary BTree repro first, then cover nullable and non-nullable Dict(u16, Utf8) cases for BTree and Bitmap. Done means scalar index creation succeeds and uses logical dictionary values.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
databases
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.