Skip to content

Bug: Persistent FTS index on an empty node table causes hash_index.cpp assertion after reopen #464

@sify21

Description

@sify21

Ladybug version

Rust crate: lbug = 0.16.1

What operating system are you using?

Ubuntu 24.04

What happened?

Creating a persistent FTS index on an empty node table succeeds, but after closing the process and reopening the
database from a new process, even a normal MATCH query fails with an internal assertion:

Assertion failed in file ".../lbug-src/src/storage/index/hash_index.cpp" on line 483:
hashIndexStorageInfo.overflowHeaderPage == INVALID_PAGE_IDX

This is different from #438: LOAD EXTENSION FTS succeeds when the Rust host binary is built with -rdynamic. The
failure happens after reopening a database that contains an FTS index created on an empty node table.

Are there known steps to reproduce?

Minimal Rust Reproduction

Cargo.toml:

[package]
name = "lbug_empty_fts_repro"
version = "0.1.0"
edition = "2021"
build = "build.rs"

[dependencies]
lbug = "0.16.1"

build.rs:

fn main() {
    // Needed for LadybugDB dynamic extensions to resolve symbols from the Rust host binary.
    println!("cargo:rustc-link-arg=-rdynamic");
}

src/main.rs:

use std::{env, process};

fn main() {
    if let Err(err) = run() {
        eprintln!("{err}");
        process::exit(1);
    }
}

fn run() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = env::args().skip(1);
    let db_path = args.next().unwrap_or_else(|| "/tmp/lbug-empty-fts.db".to_string());
    let mode = args.next().unwrap_or_else(|| "create-empty-index".to_string());

    let db = lbug::Database::new(&db_path, lbug::SystemConfig::default())?;
    let conn = lbug::Connection::new(&db)?;

    match mode.as_str() {
        "create-empty-index" => {
            conn.query(
                "CREATE NODE TABLE IF NOT EXISTS TestNode (
                    id STRING,
                    name STRING,
                    body STRING,
                    PRIMARY KEY(id)
                );",
            )?;
            conn.query("INSTALL FTS;")?;
            conn.query("LOAD EXTENSION FTS;")?;
            conn.query(
                "CALL CREATE_FTS_INDEX(
                    'TestNode',
                    'test_node_fts',
                    ['name', 'body'],
                    stemmer := 'porter'
                );",
            )?;
            conn.query("CHECKPOINT;")?;
            println!("created FTS index on empty table");
        }

        "reopen-query" => {
            conn.query("LOAD EXTENSION FTS;")?;
            let result = conn.query("MATCH (n:TestNode) RETURN n.id LIMIT 5;")?;
            println!("{result}");
        }

        "create-nonempty-index" => {
            conn.query(
                "CREATE NODE TABLE IF NOT EXISTS TestNode (
                    id STRING,
                    name STRING,
                    body STRING,
                    PRIMARY KEY(id)
                );",
            )?;
            conn.query("CREATE (:TestNode {id: 'n1', name: 'hello', body: 'hello world'});")?;
            conn.query("INSTALL FTS;")?;
            conn.query("LOAD EXTENSION FTS;")?;
            conn.query(
                "CALL CREATE_FTS_INDEX(
                    'TestNode',
                    'test_node_fts',
                    ['name', 'body'],
                    stemmer := 'porter'
                );",
            )?;
            conn.query("CHECKPOINT;")?;
            println!("created FTS index on non-empty table");
        }

        other => {
            return Err(format!("unknown mode: {other}").into());
        }
    }

    Ok(())
}

Steps to Reproduce

rm -f /tmp/lbug-empty-fts.db

cargo run -- /tmp/lbug-empty-fts.db create-empty-index
cargo run -- /tmp/lbug-empty-fts.db reopen-query

Actual Result

The second command fails with:

Assertion failed in file ".../lbug-src/src/storage/index/hash_index.cpp" on line 483:
hashIndexStorageInfo.overflowHeaderPage == INVALID_PAGE_IDX

Expected Result

Creating an FTS index on an empty table should either:

  • be supported and reopen cleanly,
  • fail gracefully when the index is created, or
  • be ignored/deferred until rows exist.

It should not leave the database in a state that later triggers an internal assertion after reopen.

Control Case

If the table contains at least one row before creating the FTS index, reopening succeeds:

rm -f /tmp/lbug-empty-fts.db

cargo run -- /tmp/lbug-empty-fts.db create-nonempty-index
cargo run -- /tmp/lbug-empty-fts.db reopen-query

This suggests the issue is specific to persistent FTS indexes created on empty node tables.

Relation to #438

This is related to Rust embedding and FTS, but it is not the same failure mode:

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions