tursodatabase / tursodatabase/libsql

Partial index causes rows with NULL to be invisible in unrelated multi-column WHERE queries

Open
#2,227 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
17.2k
Forks
531
Avg merge
1h 12m
Merged PRs (30d)
1

Description

Description

When a partial index with WHERE column IS NOT NULL exists on a table, rows where that column is NULL become invisible to multi-column WHERE queries — even when the query does not reference the NULL column at all.

Reproduction

CREATE TABLE test_table (
    id         TEXT PRIMARY KEY,
    task_id    TEXT NOT NULL,
    status     TEXT NOT NULL,
    deadline   INTEGER          -- nullable
);

-- This partial index triggers the bug
CREATE INDEX idx_status_deadline
    ON test_table(status, deadline)
    WHERE deadline IS NOT NULL;

CREATE INDEX idx_task_id
    ON test_table(task_id);

-- Insert a row with deadline = NULL
INSERT INTO test_table (id, task_id, status, deadline)
VALUES ('row-1', 'task-A', 'active', NULL);
Expected
SELECT count(*) FROM test_table WHERE task_id = 'task-A';              -- 1 ✓
SELECT count(*) FROM test_table WHERE status = 'active';               -- 1 ✓
SELECT count(*) FROM test_table WHERE task_id = 'task-A' AND status = 'active';  -- 1 (expected)
Actual
SELECT count(*) FROM test_table WHERE task_id = 'task-A';              -- 1 ✓
SELECT count(*) FROM test_table WHERE status = 'active';               -- 1 ✓
SELECT count(*) FROM test_table WHERE task_id = 'task-A' AND status = 'active';  -- 0 ✗ BUG

Each column matches individually (count = 1), but the combined AND returns 0. The UPDATE ... WHERE task_id = ? AND status = ? also affects 0 rows.

Additional observations
  • Rows inserted without NULL in the indexed column are not affected
  • The bug occurs with raw SQL literals (no parameter binding involved)
  • Named parameters and positional parameters both reproduce the same behavior
  • UPDATE ... SET status = 'completed' (unconditional, no WHERE) succeeds and affects 1 row — confirming the row exists
  • Dropping the partial index immediately fixes the issue; replacing with a regular composite index (no WHERE clause) also works

Workaround

Replace the partial index with a regular composite index:

DROP INDEX IF EXISTS idx_status_deadline;
CREATE INDEX idx_status_deadline ON test_table(status, deadline);  -- no WHERE clause

Environment

  • turso crate: 0.6.0-pre.15 (Rust SDK)
  • Platform: macOS (Darwin 25.4.0), aarch64
  • Rust: 1.94.0
  • Storage: in-memory (:memory:) — reproducible without file-based DB

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 by running the issue's SQL reproduction with the partial index, then compare query behavior after dropping it or replacing it with a regular composite index. Trace the database query-planning path responsible for multi-column WHERE evaluation and add regression coverage showing that SELECT and UPDATE return or affect the NULL-deadline row.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, rust, sql, sqlite
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.