lance-format / lance-format/lance

Appending an all-null DictionaryArray fails with an out-of-bounds error

Open
#7,906 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

Appending a valid record batch containing an all-null Dictionary<Int32, Utf8> column fails with an Arrow out-of-bounds error. Non-dictionary nullable columns work as expected.

The affected component is rust/lance-encoding, in PrimitiveStructuralEncoder::extract_validity_buf. The encoder removes the dictionary array's null buffer and rebuilds its ArrayData. For an all-null dictionary, the values dictionary is empty. Removing validity makes key 0 appear valid, and Arrow rejects it as an index into an empty dictionary.

This is related to #3962/#3974, but appears distinct: that issue concerned nullable input reaching dictionary encoding, while this reproducer fails while rebuilding an explicitly dictionary-typed all-null array.

Steps to reproduce

Use these dependencies:

[dependencies]
arrow = "58"
lance = { git = "https://github.com/lance-format/lance", tag = "v9.1.0-beta.8", default-features = false }
tokio = { version = "1.23", features = ["rt", "macros"] }

Run this program with cargo run:

use std::sync::Arc;

use arrow::array::{new_null_array, ArrayRef, RecordBatch, RecordBatchIterator, UInt64Array};
use arrow::datatypes::{DataType, Field, Schema};
use lance::dataset::{WriteMode, WriteParams};
use lance::Dataset;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let dictionary_type =
        DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8));

    let schema = Arc::new(Schema::new(vec![
        Field::new("id", DataType::UInt64, false),
        Field::new("value", dictionary_type.clone(), true),
    ]));

    let empty = RecordBatch::new_empty(schema.clone());
    let mut dataset = Dataset::write(
        RecordBatchIterator::new([Ok(empty)], schema.clone()),
        "memory://all_null_dictionary",
        Some(WriteParams {
            mode: WriteMode::Overwrite,
            ..Default::default()
        }),
    )
    .await
    .unwrap();

    let arrays: Vec<ArrayRef> = vec![
        Arc::new(UInt64Array::from(vec![1])),
        new_null_array(&dictionary_type, 1),
    ];
    let batch = RecordBatch::try_new(schema.clone(), arrays).unwrap();

    dataset
        .append(
            Box::new(RecordBatchIterator::new([Ok(batch)], schema)),
            None,
        )
        .await
        .unwrap();
}
Expected behavior

The append succeeds and reading the dataset returns one row whose dictionary column is null.

Lance version

v9.1.0-beta.8 (5107a99e3f3912851c8cbb3822bd5b4cbf7fda2f)

Language binding

Rust

Environment

Fedora Linux, x86_64, memory:// storage, Rust 1.96.0

Logs / traceback
LanceError(Arrow): Invalid argument error: Value at position 0 out of bounds:
0 (should be in [0, -1])

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

Run the provided Rust reproducer with the listed Lance and Arrow dependencies, then inspect rust/lance-encoding at PrimitiveStructuralEncoder::extract_validity_buf. Trace how the dictionary array's validity buffer and empty values dictionary are rebuilt, and verify the fix by confirming append succeeds and reading returns one row with a null dictionary column.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
data-engineering, databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.